From d7768f7af69e5b3107adc00a4fa6851263c0405a Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Sun, 15 Oct 2017 12:15:13 +0300 Subject: [PATCH 01/17] Improvement to the next version * Extendable code * No requires root permissions * Can get icon name from CSV file by application name * Better determining new icon name (from application name) * Fixes icons on a user desktop and hides backup files * Does not break shebang in launchers * Works with files that have spaces in the name --- fix.sh | 682 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 506 insertions(+), 176 deletions(-) diff --git a/fix.sh b/fix.sh index 6b69892..cd861f7 100755 --- a/fix.sh +++ b/fix.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Script for fixing hardcoded icons. Written and maintained on GitHub # at https://github.com/Foggalong/hardcode-fixer - addtions welcome! @@ -10,185 +10,515 @@ # a copy of the GNU General Public License along with this program. # If not, see . -# Version info -date=201709040 # [year][month][date][extra] - -# Locations -git_locate="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master" -username=${SUDO_USER:-$USER} -userhome="/home/$username" -app_dirs=("$userhome/.local/share/applications/" - "$userhome/.local/share/applications/kde4/" - "$(sudo -u $username xdg-user-dir DESKTOP)/" - "/usr/share/applications/" - "/usr/share/applications/kde4/" - "/usr/local/share/applications/" - "/usr/local/share/applications/kde4/") -local_apps="$userhome/.local/share/applications/" -local_icon="$userhome/.local/share/icons/hicolor/48x48/apps/" -steam_icon="/usr/share/icons/hicolor/48x48/apps/steam.png" - - -# Allows timeout when launched via 'Run in Terminal' -function gerror() { sleep 3; exit 1; } - - -# Fix Launcher -function fix_launch() { - launcher=$1 - name=$2 - icon=$3 - type=$4 - - # Check if already fixed, if not create marked launcher copy - local_version="$local_apps$name.desktop" - if [ -f "$local_version" ]; then - line=$(head -n 1 $local_version) - if [[ $line == "# HC"* ]]; then - return - else - cp "$local_version" "$local_version.old" - sed -i '1i# HC:Local' "$local_version" - fi - else - cp "$launcher" "$local_version" - sed -i '1i# HC:Global' "$local_version" - fi - - echo -n "$type: Fixing $name..." - - # Making copy of needed icon and determining new icon name - if [[ $type == "H" ]]; then - new_icon=$(echo ${name} | sed -e 's/\ /_/' ) # | sed -e 's/\./_/' ) - ext=$(echo ${icon} | sed -e 's/.*\.//' ) - cp $icon $local_icon$new_icon.$ext - elif [[ $type == "S" ]]; then - exec=$(grep '^Exec=' ${file} | sed -e 's/.*Exec=//' ) - new_icon=steam_icon_$(echo $exec | sed -e 's/steam\ steam:\/\/rungameid\/*//' ) - cp $steam_icon "$local_icon$new_icon.png" - fi - - sed -i "s|Icon=${icon}.*|Icon=${new_icon}|g" $local_version - echo " done" -} - - -# Deals with the flags -if [ -z "$1" ]; then - mode="fix" -else - case $1 in - -r|--revert) - echo "This will undo all changes previously made." - while true; do - read -r -p "Are you sure you want to continue? " answer - case $answer in - [Yy]* ) mode="revert"; break;; - [Nn]* ) exit;; - * ) echo "Please answer [Y/y]es or [N/n]o.";; - esac - done;; - -h|--help) - echo -e \ - "Usage: ./$(basename -- $0) [OPTION]\n" \ - "\rFixes hardcoded icons of installed applications.\n\n" \ - "\rCurrently supported options:\n" \ - "\r -r, --revert \t\t Reverts any changes made.\n" \ - "\r -h, --help \t\t Displays this help menu.\n" \ - "\r -v, --version \t Displays program version.\n" - exit 0 ;; - -v|--version) - echo -e "$(basename -- $0) $date\n" - exit 0 ;; - *) - echo -e "$(basename -- $0): invalid option -- '$1'" - echo -e "Try '$(basename -- $0) --help' for more information." - gerror - esac +if test -z "$BASH_VERSION"; then + printf "Error: this script only works in bash.\n" >&2 + exit 1 fi +# set -x # Uncomment to debug this shell script +set -o errexit \ + -o noclobber \ + -o pipefail -# TODO this entire 40+ LOC is just for checking for updates. should this still be here? -# Verifies if 'curl' is installed -if ! type "curl" >> /dev/null 2>&1; then - echo -e \ - "$0: This script requires 'curl' to be installed\n" \ - "\rto fetch the required files and check for updates.\n" \ - "\rPlease install it and rerun this script." - gerror -fi +unset GREP_OPTIONS # avoid mess up -# Checks for having internet access -if eval "curl -sk https://github.com/" >> /dev/null 2>&1; then - : # pass -else - echo -e \ - "No internet connection available. This script\n" \ - "\rrequires internet access to connect to GitHub\n" \ - "\rto check for updates and download 'to-fix' info." - gerror -fi +readonly SCRIPT_NAME="$(basename -- "$0")" +readonly SCRIPT_DIR="$(dirname -- "$0")" +readonly -a ARGS=("$@") -# Check for newer version of fix.sh -new_date=$(curl -sk "${git_locate}"/fix.sh | grep "date=[0-9]\{9\}" | sed "s/[^0-9]//g") -if [ "$date" -lt "$new_date" ]; then - echo -e \ - "You're running an out of date version of\n" \ - "\rthe script. Please download the latest\n" \ - "\rverison from the GitHub page or update\n" \ - "\rvia your package manager. If you continue\n" \ - "\rwithout updating you may run into problems." - while true; do - read -r -p "Would you like to [e]xit, or [c]ontinue?" answer - case $answer in - [Ee]* ) exit;; - [Cc]* ) break;; - * ) echo "Please answer [e]xit or [c]ontinue";; - esac - done -fi +readonly PROGNAME="hardcode-fixer" +declare -i VERSION=201710140 # [year][month][date][extra] +# date=999999990 # deprecate the previous version + +message() { + printf "%s: %b\n" "$PROGNAME" "$*" >&2 +} +verbose() { + [ "$VERBOSE" = 1 ] || return 0 + message "INFO:" "$@" +} -if [ "$mode" == "fix" ]; then - # Iterate over all the launcher locations - for location in ${app_dirs[@]}; do - # Iterate over the files in those locations - for file in ${location}*; do - # Check if the file is a launcher - if [[ ${file} == *.desktop ]]; then - name=$(echo ${file} | sed -e 's/.*\///' | sed -e 's/\.desktop//' ) - icon=$(grep '^Icon=' ${file} | sed -e 's/.*Icon=//' ) - # Check for signs of hardcoding - if [[ $icon == *"/"* ]]; then - # What to do if the icon line is standard hardcoded - fix_launch ${file} ${name} ${icon} "H" - elif [[ $icon == "steam" ]] && [[ ${name} != "steam" ]]; then - # What to do if it's using the generic Steam icon - fix_launch ${file} ${name} ${icon} "S" - # elif [[ $icon = *"."* ]]; then - # # What to do if it's extension hardcoded - # fix_launch ${file} ${name} ${icon} "E" - fi - fi - done - done -elif [ "$mode" == "revert" ]; then - for file in ${local_apps}*; do - if [[ ${file} == *.desktop ]]; then - # Check if launcher is product of hc-fix - line=$(head -n 1 $file) - name=$(echo ${file} | sed -e 's/.*\///' | sed -e 's/\.desktop//' ) - if [[ $line == "# HC:"* ]]; then - echo -n "Reverting $name..." - icon=$(grep '^Icon=' ${file} | sed -e 's/.*Icon=//' ) - if [[ $line == "# HC:Global" ]]; then - rm $file - elif [[ $line == "# HC:Local" ]]; then - mv -f "$file.old" $file - fi - rm $local_icon$icon.* - echo " done" - fi - fi - done -fi +warning() { + message "WARN:" "$@" +} + +fail() { + message "ERR:" "$@" + exit 1 +} + +_is_hardcoded() { + local desktop_file="$1" + LANG=C grep -q '^Icon=.*/.*' -- "$desktop_file" +} + +_is_hardcoded_steam_app() { + local desktop_file="$1" + local icon_name + + if LANG=C grep -q 'steam://run' -- "$desktop_file"; then + icon_name="$(get_icon_name "$desktop_file")" + if [ "$icon_name" = "steam" ]; then + return 0 + fi + fi + + return 1 +} + +_is_local() { + # checks if file or dir in LOCAL_APPS_DIRS + local dir="$1" + + [ -d "$dir" ] || dir="$(dirname "$dir")" + + for i in "${LOCAL_APPS_DIRS[@]}"; do + [ "$i" == "$dir" ] || continue + return 0 + done + + return 1 +} + +get_app_name() { + local desktop_file="$1" + awk -F= '/^Name/ { print $2; exit }' "$desktop_file" +} + +get_icon_name() { + local desktop_file="$1" + awk -F= '/^Icon/ { print $2; exit }' "$desktop_file" +} + +set_icon_name() { + # changes an icon for the desktop entry + local desktop_file="$1" + local icon_name="$2" + + sed -i -e "/^Icon=/c \ + Icon=${icon_name} + " "$desktop_file" +} + +get_marker_value() { + local desktop_file="$1" + awk -F= '/^X-Hardcode-Fixer-Marker/ { print $2; exit }' "$desktop_file" +} + +set_marker_value() { + local desktop_file="$1" + local marker_value="${2:-local}" + + # append after Icon + sed -i -e "/^Icon=/a \ + X-Hardcode-Fixer-Marker=${marker_value} + " "$desktop_file" +} + +icon_lookup() { + # returns path to an icon (icon lookup for poor man) + local icon_name="$1" + local -a icons_dirs=( + "/usr/share/icons/hicolor/48x48/apps" + "/usr/share/icons/hicolor" + "/usr/local/share/icons/hicolor/48x48/apps" + "/usr/local/share/icons/hicolor" + "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/48x48/apps" + "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor" + "/usr/share/pixmaps" + "/usr/local/share/pixmaps" + ) + + for icons_dir in "${icons_dirs[@]}"; do + for icon_path in "$icons_dir/$icon_name".*; do + [ -f "$icon_path" ] || continue + printf '%s' "$icon_path" + done + done +} + +get_icon_path() { + local desktop_file="$1" + local icon_value icon_path + + icon_value="$(get_icon_name "$desktop_file")" + + if [ "${icon_value:0:1}" = "/" ]; then + # it's absolute path + icon_path="$icon_value" + else + icon_path="$(icon_lookup "$icon_value")" + fi + + printf '%s' "$icon_path" +} + +copy_icon_file() { + local icon_path="$1" + local icon_name="$2" + local icon_ext="${icon_path##*.}" + + if [ -f "$icon_path" ]; then + mkdir -p "$LOCAL_ICONS_DIR" + cp -f "$icon_path" "$LOCAL_ICONS_DIR/${icon_name}.${icon_ext}" + else + warning "Cannot find an icon for '$icon_name'." + return 1 + fi +} + +get_local_path() { + local desktop_file="$1" + local base_name + + base_name="$(basename "$desktop_file")" + + printf "%s/%s" "$LOCAL_APPS_DIR" "$base_name" +} + +download_file() { + local url="$1" + local file="${2:--}" # is not a typo, output to stdout by default + + verbose "Downloading '$url' ..." + + if command -v wget > /dev/null 2>&1; then + wget --no-check-certificate -q -O "$file" "$url" \ + || fail "Fail to download '$url'." + elif command -v curl > /dev/null 2>&1; then + curl -sk -o "$file" "$url" \ + || fail "Fail to download '$url'." + else + fail "\n" \ + "\r This script requires 'wget' to be installed\n" \ + "\r to fetch the required files and check for updates.\n" \ + "\r Please install it and rerun this script." + fi +} + +get_from_db() { + # returns icon name if find it + local desktop_file="$1" + local app_name + + app_name="$(get_app_name "$desktop_file")" + + awk -F, -v app_name="$app_name" ' + BEGIN { IGNORECASE = 1; } + $1 == app_name { + print $4 + exit + } + ' "$DB_FILE" +} + +translate_from_app_name() { + local desktop_file="$1" + + # 1. to lowercase + # 2. delete invalid characters + # 3. replace spaces with minus + get_app_name "$desktop_file" \ + | tr '[:upper:]' '[:lower:]' \ + | tr -cd '[:alnum:]-_. ' \ + | sed -e 's/[ ]/-/g' +} + +backup_desktop_file() { + local desktop_file="$1" + local dir_name base_name new_file_path + + dir_name="$(dirname "$desktop_file")" + base_name="$(basename "$desktop_file")" + new_file_path="${dir_name}/.${base_name}.orig" + + if [ -f "$new_file_path" ]; then + versbose "Backup file already exists" + return 1 + fi + + cp -a "$desktop_file" "$new_file_path" +} + +restore_desktop_file() { + local desktop_file="$1" + local dir_name base_name file_path + + dir_name="$(dirname "$desktop_file")" + base_name="$(basename "$desktop_file")" + file_path="${dir_name}/.${base_name}.orig" + + [ -f "$file_path" ] || return 1 + mv -f "$file_path" "$desktop_file" +} + +fix_hardcoded_steam_app() { + local desktop_file="$1" + local app_name app_id icon_path new_icon_name + + app_name="$(get_app_name "$desktop_file")" + icon_path="$(get_icon_path "$desktop_file")" + app_id="$(sed -n '/^Exec/ s/.*\/\([0-9]\+\)/\1/p' "$desktop_file")" + new_icon_name="steam_icon_${app_id}" + + message "Fixing '$app_name' ..." + + backup_desktop_file "$desktop_file" + + set_marker_value "$desktop_file" + set_icon_name "$desktop_file" "$new_icon_name" + copy_icon_file "$icon_path" "$new_icon_name" +} + +fix_hardcoded_app() { + local desktop_file="$1" + local app_name new_icon_name new_file_path marker_value + + app_name="$(get_app_name "$desktop_file")" + icon_path="$(get_icon_name "$desktop_file")" + new_icon_name="$(get_from_db "$desktop_file")" + + if [ -z "$new_icon_name" ]; then + new_icon_name="$(translate_from_app_name "$desktop_file")" + fi + + if _is_local "$desktop_file"; then + backup_desktop_file "$desktop_file" + marker_value="local" + else + new_file_path="$(get_local_path "$desktop_file")" + + if [ -e "$new_file_path" ]; then + verbose "'$app_name' already in local apps" + return 1 + fi + + mkdir -p "$(dirname "$new_file_path")" + cp -a "$desktop_file" "$new_file_path" + + desktop_file="$new_file_path" + marker_value="global" + fi + + message "Fixing '$app_name'..." + + set_icon_name "$desktop_file" "$new_icon_name" + set_marker_value "$desktop_file" "$marker_value" + copy_icon_file "$icon_path" "$new_icon_name" +} + +apply() { + local app_dir file + + if [ "$FORCE_DOWNLOAD" = 0 ] && [ -f "$SCRIPT_DIR/tofix.csv" ]; then + DB_FILE="$SCRIPT_DIR/tofix.csv" + else + DB_FILE="$(mktemp -u -t hardcode_db_XXXXX.csv)" + + message "Downloading DB into '$DB_FILE' file ..." + download_file "$DB_URL" "$DB_FILE" + + # remove csv file when exit + cleanup() { + verbose "Removing '$DB_FILE' ..." + rm -f "$DB_FILE" + unset DB_FILE + } + + trap cleanup EXIT HUP INT TERM + fi + + for app_dir in "${GLOBAL_APPS_DIRS[@]}" "${LOCAL_APPS_DIRS[@]}"; do + for desktop_file in "$app_dir"/*.desktop; do + [ -f "$desktop_file" ] || continue + if _is_hardcoded "$desktop_file"; then + fix_hardcoded_app "$desktop_file" || continue + elif _is_hardcoded_steam_app "$desktop_file"; then + fix_hardcoded_steam_app "$desktop_file" || continue + else + continue + fi + done + done +} + +revert() { + local app_name app_dir file marker_value + + for app_dir in "${LOCAL_APPS_DIRS[@]}"; do + for desktop_file in "$app_dir"/*.desktop; do + [ -f "$desktop_file" ] || continue + + app_name="$(get_app_name "$desktop_file")" + marker_value="$(get_marker_value "$desktop_file")" + + if [ -n "$marker_value" ]; then + message "Reverting '$app_name' ..." + + if [ "$marker_value" = "local" ]; then + restore_desktop_file "$desktop_file" + else + rm -f "$desktop_file" + fi + fi + done + done +} + +cmdline() { + local arg action + + show_usage() { + cat >&2 <<- EOF + usage: + $SCRIPT_NAME {-a --apply} [options] + $SCRIPT_NAME {-r --revert} [options] + + ACTIONS: + -a, --apply fixes hardcoded icons of installed applications + -r, --revert reverts any changes made + + OPTIONS: + -d --force-download download the new database (ignore the local DB) + -V --version print $PROGNAME version and exit + -v --verbose be verbose + -h --help show this help + EOF + } + + for arg in "${ARGS[@]}"; do + case "$arg" in + -a|--apply) + action="apply" + ;; + -r|--revert) + action="revert" + ;; + -d|--force-download) + FORCE_DOWNLOAD=1 + ;; + -v|--verbose) + VERBOSE=1 + ;; + -V|--version) + printf "%s (version: %s)\n" "$PROGNAME" "$VERSION" + exit 0 + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + message "illegal option -- '$arg'" + show_usage + exit 2 + ;; + esac + done + + case "$action" in + apply) + apply + ;; + revert) + revert + ;; + *) + fail "You must choose an action.\n" \ + "\rType '$SCRIPT_NAME --help' to display help." + ;; + esac + + message "Done!" +} + +show_menu() { + local -a menu_items=( "apply" "revert" "verbose" "help" "quit" ) + local num_items="${#menu_items[@]}" + local PS3="[1-${num_items}]> " # set custom prompt + local COLUMNS=1 # force listing to be vertical + + cat >&2 <<- EOF + Welcome to $PROGNAME ($VERSION)! + Type 'help' to view a list of commands or enter + the number of the action you want to execute. + + EOF + + select menu_item in "${menu_items[@]}"; do + case "${menu_item:-$REPLY}" in + apply|[aA]*) + apply + break + ;; + revert|[rR]*) + revert + break + ;; + verbose|verb*) + VERBOSE=1 + message "Verbose mode is enabled." + ;; + help|[hH]*) + cat >&2 <<- EOF + + apply — Fixes hardcoded icons of installed applications + revert — Reverts any changes made + verbose - Be verbose + help — Displays this help menu + quit - Quit "$PROGNAME" + + EOF + ;; + quit|[qQ]*) + exit 0 + ;; + *) + echo "$REPLY -- invalid command" + ;; + esac + done < /dev/tty # don't read from stdin + + message "Done!" + + # Allows pause when launched via 'Run in Terminal' + read -r -p 'Press [Enter] to close' < /dev/tty # don't read from stdin +} + +main() { + if [ "$(id -u)" -eq 0 ]; then + fail "This script must be run as normal user." + fi + + declare DB_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master/tofix.csv" + declare LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" + declare LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" + + declare -a GLOBAL_APPS_DIRS=( + "/usr/share/applications" + "/usr/share/applications/kde4" + "/usr/local/share/applications" + "/usr/local/share/applications/kde4" + ) + + declare -a LOCAL_APPS_DIRS=( + "${XDG_DATA_HOME:-$HOME/.local/share}/applications" + "${XDG_DATA_HOME:-$HOME/.local/share}/applications/kde4" + "$(xdg-user-dir DESKTOP)" + ) + + # default values of options + declare -i FORCE_DOWNLOAD=0 + declare -i VERBOSE=0 + + if [ "${#ARGS[@]}" -gt 0 ]; then + cmdline + else + show_menu + fi +} + +main + +exit 0 From 1164016f41a40f84363ce7e59ed646974f5421a4 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Sun, 15 Oct 2017 20:22:39 +0300 Subject: [PATCH 02/17] Improve translate_from_app_name * remove text between parentheses --- fix.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/fix.sh b/fix.sh index cd861f7..2c147a7 100755 --- a/fix.sh +++ b/fix.sh @@ -216,13 +216,18 @@ get_from_db() { translate_from_app_name() { local desktop_file="$1" - # 1. to lowercase - # 2. delete invalid characters - # 3. replace spaces with minus + # 1. remove text between parentheses + # 2. replace spaces with hyphens + # 3. replace two or more hyphens by one + # 4. to lowercase + # 5. delete invalid characters get_app_name "$desktop_file" \ + | sed \ + -e 's/[ ]([^)]\+)//g' \ + -e 's/[ ]/-/g' \ + -e 's/-\+/-/g' \ | tr '[:upper:]' '[:lower:]' \ - | tr -cd '[:alnum:]-_. ' \ - | sed -e 's/[ ]/-/g' + | tr -cd '[:alnum:]-_' } backup_desktop_file() { From 4ac3e8ab058458e629fc426e7752587e0d0165b0 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Mon, 16 Oct 2017 21:22:34 +0300 Subject: [PATCH 03/17] Remove copied icons when reverting --- fix.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fix.sh b/fix.sh index 2c147a7..17cad9b 100755 --- a/fix.sh +++ b/fix.sh @@ -356,6 +356,7 @@ revert() { [ -f "$desktop_file" ] || continue app_name="$(get_app_name "$desktop_file")" + icon_name="$(get_icon_name "$desktop_file")" marker_value="$(get_marker_value "$desktop_file")" if [ -n "$marker_value" ]; then @@ -364,8 +365,15 @@ revert() { if [ "$marker_value" = "local" ]; then restore_desktop_file "$desktop_file" else - rm -f "$desktop_file" + rm -f -- "$desktop_file" fi + + verbose "Removing '$icon_name' icon ..." + for ext in png svg xpm; do + [ -f "$LOCAL_ICONS_DIR/$icon_name.$ext" ] || continue + rm -- "$LOCAL_ICONS_DIR/$icon_name.$ext" + break + done fi done done From 294f99b80d24298a75c3966d9372bd6cca67f5ad Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Tue, 17 Oct 2017 08:53:13 +0300 Subject: [PATCH 04/17] Combine functions fix_hardcoded* into one * icon_lookup: change local icons path * icon_lookup: return only the first match --- fix.sh | 132 +++++++++++++++++++++++++++------------------------------ 1 file changed, 63 insertions(+), 69 deletions(-) diff --git a/fix.sh b/fix.sh index 17cad9b..cc2f28c 100755 --- a/fix.sh +++ b/fix.sh @@ -67,20 +67,6 @@ _is_hardcoded_steam_app() { return 1 } -_is_local() { - # checks if file or dir in LOCAL_APPS_DIRS - local dir="$1" - - [ -d "$dir" ] || dir="$(dirname "$dir")" - - for i in "${LOCAL_APPS_DIRS[@]}"; do - [ "$i" == "$dir" ] || continue - return 0 - done - - return 1 -} - get_app_name() { local desktop_file="$1" awk -F= '/^Name/ { print $2; exit }' "$desktop_file" @@ -91,6 +77,18 @@ get_icon_name() { awk -F= '/^Icon/ { print $2; exit }' "$desktop_file" } +get_steam_app_id() { + # returns id of the Steam app + local desktop_file="$1" + sed -n '/^Exec/ s/.*\/\([0-9]\+\)/\1/p' "$desktop_file" +} + +get_steam_icon_name() { + # returns name of the Steam icon (steam_icon_ID) + local desktop_file="$1" + printf 'steam_icon_%s' "$(get_steam_app_id "$desktop_file")" +} + set_icon_name() { # changes an icon for the desktop entry local desktop_file="$1" @@ -125,7 +123,7 @@ icon_lookup() { "/usr/local/share/icons/hicolor/48x48/apps" "/usr/local/share/icons/hicolor" "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/48x48/apps" - "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor" + "${XDG_DATA_HOME:-$HOME/.local/share}/icons" "/usr/share/pixmaps" "/usr/local/share/pixmaps" ) @@ -134,6 +132,7 @@ icon_lookup() { for icon_path in "$icons_dir/$icon_name".*; do [ -f "$icon_path" ] || continue printf '%s' "$icon_path" + return 0 # only the first match done done } @@ -168,15 +167,6 @@ copy_icon_file() { fi } -get_local_path() { - local desktop_file="$1" - local base_name - - base_name="$(basename "$desktop_file")" - - printf "%s/%s" "$LOCAL_APPS_DIR" "$base_name" -} - download_file() { local url="$1" local file="${2:--}" # is not a typo, output to stdout by default @@ -258,58 +248,49 @@ restore_desktop_file() { mv -f "$file_path" "$desktop_file" } -fix_hardcoded_steam_app() { +fix_hardcoded_app() { local desktop_file="$1" - local app_name app_id icon_path new_icon_name + local method="$2" + local app_name icon_path new_icon_name local_desktop_file app_name="$(get_app_name "$desktop_file")" icon_path="$(get_icon_path "$desktop_file")" - app_id="$(sed -n '/^Exec/ s/.*\/\([0-9]\+\)/\1/p' "$desktop_file")" - new_icon_name="steam_icon_${app_id}" + new_icon_name="$(get_from_db "$desktop_file")" - message "Fixing '$app_name' ..." + case "$method" in + global) + local_desktop_file="$LOCAL_APPS_DIR/$(basename "$desktop_file")" - backup_desktop_file "$desktop_file" + if [ -e "$local_desktop_file" ]; then + verbose "'$app_name' already exists in local apps. Skipping." + return 1 + fi - set_marker_value "$desktop_file" - set_icon_name "$desktop_file" "$new_icon_name" - copy_icon_file "$icon_path" "$new_icon_name" -} + mkdir -p "$(dirname "$local_desktop_file")" + cp -a "$desktop_file" "$local_desktop_file" -fix_hardcoded_app() { - local desktop_file="$1" - local app_name new_icon_name new_file_path marker_value - - app_name="$(get_app_name "$desktop_file")" - icon_path="$(get_icon_name "$desktop_file")" - new_icon_name="$(get_from_db "$desktop_file")" + desktop_file="$local_desktop_file" + ;; + local) + backup_desktop_file "$desktop_file" + ;; + steam) + backup_desktop_file "$desktop_file" + new_icon_name="$(get_steam_icon_name "$desktop_file")" + ;; + *) + warning "illegal method -- $method" + return 1 + esac if [ -z "$new_icon_name" ]; then new_icon_name="$(translate_from_app_name "$desktop_file")" fi - if _is_local "$desktop_file"; then - backup_desktop_file "$desktop_file" - marker_value="local" - else - new_file_path="$(get_local_path "$desktop_file")" - - if [ -e "$new_file_path" ]; then - verbose "'$app_name' already in local apps" - return 1 - fi - - mkdir -p "$(dirname "$new_file_path")" - cp -a "$desktop_file" "$new_file_path" - - desktop_file="$new_file_path" - marker_value="global" - fi - - message "Fixing '$app_name'..." + message "Fixing '$app_name' [$method] ..." set_icon_name "$desktop_file" "$new_icon_name" - set_marker_value "$desktop_file" "$marker_value" + set_marker_value "$desktop_file" "$method" copy_icon_file "$icon_path" "$new_icon_name" } @@ -334,13 +315,23 @@ apply() { trap cleanup EXIT HUP INT TERM fi - for app_dir in "${GLOBAL_APPS_DIRS[@]}" "${LOCAL_APPS_DIRS[@]}"; do + for app_dir in "${GLOBAL_APPS_DIRS[@]}"; do + for desktop_file in "$app_dir"/*.desktop; do + [ -f "$desktop_file" ] || continue + if _is_hardcoded "$desktop_file"; then + fix_hardcoded_app "$desktop_file" "global" || continue + continue + fi + done + done + + for app_dir in "${LOCAL_APPS_DIRS[@]}"; do for desktop_file in "$app_dir"/*.desktop; do [ -f "$desktop_file" ] || continue if _is_hardcoded "$desktop_file"; then - fix_hardcoded_app "$desktop_file" || continue + fix_hardcoded_app "$desktop_file" "local" || continue elif _is_hardcoded_steam_app "$desktop_file"; then - fix_hardcoded_steam_app "$desktop_file" || continue + fix_hardcoded_app "$desktop_file" "steam" || continue else continue fi @@ -362,11 +353,14 @@ revert() { if [ -n "$marker_value" ]; then message "Reverting '$app_name' ..." - if [ "$marker_value" = "local" ]; then - restore_desktop_file "$desktop_file" - else - rm -f -- "$desktop_file" - fi + case "$marker_value" in + global) + rm -f -- "$desktop_file" + ;; + local|steam) + restore_desktop_file "$desktop_file" + ;; + esac verbose "Removing '$icon_name' icon ..." for ext in png svg xpm; do From 9097d8bd9fa9b20f5f09ad84e459b208529fe8dc Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Tue, 17 Oct 2017 09:17:40 +0300 Subject: [PATCH 05/17] revert: skip restore a desktop file if backup file not found --- fix.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fix.sh b/fix.sh index cc2f28c..abe729f 100755 --- a/fix.sh +++ b/fix.sh @@ -27,7 +27,7 @@ readonly SCRIPT_DIR="$(dirname -- "$0")" readonly -a ARGS=("$@") readonly PROGNAME="hardcode-fixer" -declare -i VERSION=201710140 # [year][month][date][extra] +declare -i VERSION=201710170 # [year][month][date][extra] # date=999999990 # deprecate the previous version message() { @@ -244,8 +244,12 @@ restore_desktop_file() { base_name="$(basename "$desktop_file")" file_path="${dir_name}/.${base_name}.orig" - [ -f "$file_path" ] || return 1 - mv -f "$file_path" "$desktop_file" + if [ -f "$file_path" ]; then + mv -f "$file_path" "$desktop_file" + else + warning "Can't find the backup file. Skipping." + return 1 + fi } fix_hardcoded_app() { @@ -358,7 +362,7 @@ revert() { rm -f -- "$desktop_file" ;; local|steam) - restore_desktop_file "$desktop_file" + restore_desktop_file "$desktop_file" || continue ;; esac From 4a410bc90a110d48e77156c9d6d90ce08e89d901 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Wed, 18 Oct 2017 07:44:29 +0300 Subject: [PATCH 06/17] Fix exit code http://www.tldp.org/LDP/abs/html/exitcodes.html --- fix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fix.sh b/fix.sh index abe729f..329ed30 100755 --- a/fix.sh +++ b/fix.sh @@ -423,7 +423,7 @@ cmdline() { *) message "illegal option -- '$arg'" show_usage - exit 2 + exit 128 ;; esac done From 91a00fe9e8f460015cf311cc09b682b8bf170402 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Wed, 18 Oct 2017 20:17:17 +0300 Subject: [PATCH 07/17] Move global variables to top --- fix.sh | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/fix.sh b/fix.sh index 329ed30..91a9ed1 100755 --- a/fix.sh +++ b/fix.sh @@ -30,6 +30,27 @@ readonly PROGNAME="hardcode-fixer" declare -i VERSION=201710170 # [year][month][date][extra] # date=999999990 # deprecate the previous version +declare DB_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master/tofix.csv" +declare LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" +declare LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" + +declare -a GLOBAL_APPS_DIRS=( + "/usr/share/applications" + "/usr/share/applications/kde4" + "/usr/local/share/applications" + "/usr/local/share/applications/kde4" +) + +declare -a LOCAL_APPS_DIRS=( + "${XDG_DATA_HOME:-$HOME/.local/share}/applications" + "${XDG_DATA_HOME:-$HOME/.local/share}/applications/kde4" + "$(xdg-user-dir DESKTOP)" +) + +# default values of options +declare -i FORCE_DOWNLOAD=0 +declare -i VERBOSE=0 + message() { printf "%s: %b\n" "$PROGNAME" "$*" >&2 } @@ -502,27 +523,6 @@ main() { fail "This script must be run as normal user." fi - declare DB_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master/tofix.csv" - declare LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" - declare LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" - - declare -a GLOBAL_APPS_DIRS=( - "/usr/share/applications" - "/usr/share/applications/kde4" - "/usr/local/share/applications" - "/usr/local/share/applications/kde4" - ) - - declare -a LOCAL_APPS_DIRS=( - "${XDG_DATA_HOME:-$HOME/.local/share}/applications" - "${XDG_DATA_HOME:-$HOME/.local/share}/applications/kde4" - "$(xdg-user-dir DESKTOP)" - ) - - # default values of options - declare -i FORCE_DOWNLOAD=0 - declare -i VERBOSE=0 - if [ "${#ARGS[@]}" -gt 0 ]; then cmdline else From 2f194611fc12fe0655934483df3236aad2048f25 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Sat, 21 Oct 2017 07:58:14 +0300 Subject: [PATCH 08/17] Allow overwriting options with global variables * simplify show_menu * apply: clean up --- fix.sh | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/fix.sh b/fix.sh index 91a9ed1..ff9cc24 100755 --- a/fix.sh +++ b/fix.sh @@ -48,8 +48,8 @@ declare -a LOCAL_APPS_DIRS=( ) # default values of options -declare -i FORCE_DOWNLOAD=0 -declare -i VERBOSE=0 +declare -i FORCE_DOWNLOAD="${FORCE_DOWNLOAD:-0}" +declare -i VERBOSE="${VERBOSE:-0}" message() { printf "%s: %b\n" "$PROGNAME" "$*" >&2 @@ -345,7 +345,6 @@ apply() { [ -f "$desktop_file" ] || continue if _is_hardcoded "$desktop_file"; then fix_hardcoded_app "$desktop_file" "global" || continue - continue fi done done @@ -357,8 +356,6 @@ apply() { fix_hardcoded_app "$desktop_file" "local" || continue elif _is_hardcoded_steam_app "$desktop_file"; then fix_hardcoded_app "$desktop_file" "steam" || continue - else - continue fi done done @@ -466,10 +463,8 @@ cmdline() { } show_menu() { - local -a menu_items=( "apply" "revert" "verbose" "help" "quit" ) - local num_items="${#menu_items[@]}" - local PS3="[1-${num_items}]> " # set custom prompt - local COLUMNS=1 # force listing to be vertical + local -a menu_items=( "apply" "revert" "help" "quit" ) + local PS3="($PROGNAME)> " # set custom prompt cat >&2 <<- EOF Welcome to $PROGNAME ($VERSION)! @@ -488,22 +483,15 @@ show_menu() { revert break ;; - verbose|verb*) - VERBOSE=1 - message "Verbose mode is enabled." - ;; help|[hH]*) cat >&2 <<- EOF - apply — Fixes hardcoded icons of installed applications revert — Reverts any changes made - verbose - Be verbose help — Displays this help menu quit - Quit "$PROGNAME" - EOF ;; - quit|[qQ]*) + quit|[qQ]*|[eE]*) exit 0 ;; *) From 9b9727b24baff473db5160571bf2d89677159a8c Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Thu, 26 Oct 2017 19:39:16 +0300 Subject: [PATCH 09/17] Add update checker --- fix.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/fix.sh b/fix.sh index ff9cc24..be32bd3 100755 --- a/fix.sh +++ b/fix.sh @@ -30,7 +30,7 @@ readonly PROGNAME="hardcode-fixer" declare -i VERSION=201710170 # [year][month][date][extra] # date=999999990 # deprecate the previous version -declare DB_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master/tofix.csv" +declare UPSTREAM_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master" declare LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" declare LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" @@ -88,6 +88,19 @@ _is_hardcoded_steam_app() { return 1 } +_is_update_available() { + # returns true if the upstream version bigger than current + local -i upstream_version + + upstream_version="$(get_upstream_version)" + + if [ "$VERSION" -lt "$upstream_version" ]; then + return 0 + fi + + return 1 +} + get_app_name() { local desktop_file="$1" awk -F= '/^Name/ { print $2; exit }' "$desktop_file" @@ -208,6 +221,15 @@ download_file() { fi } +get_upstream_version() { + local upstream_file="$UPSTREAM_URL/fix.sh" + + download_file "$upstream_file" \ + | LANG=C grep -o 'date=[0-9]\+' \ + | head -1 \ + | tr -dc '[0-9]' +} + get_from_db() { # returns icon name if find it local desktop_file="$1" @@ -325,10 +347,25 @@ apply() { if [ "$FORCE_DOWNLOAD" = 0 ] && [ -f "$SCRIPT_DIR/tofix.csv" ]; then DB_FILE="$SCRIPT_DIR/tofix.csv" else + if _is_update_available; then + cat >&2 <<- EOF + + You're running an out of date version of the script. + Please download the latest verison from the GitHub page + or update via your package manager. If you continue + without updating you may run into problems. + + Press [ENTER] to continue or Ctrl-c to exit + EOF + + # Wait for user to read the message + read -r < /dev/tty # don't read from stdin + fi + DB_FILE="$(mktemp -u -t hardcode_db_XXXXX.csv)" message "Downloading DB into '$DB_FILE' file ..." - download_file "$DB_URL" "$DB_FILE" + download_file "$UPSTREAM_URL/tofix.csv" "$DB_FILE" # remove csv file when exit cleanup() { @@ -431,7 +468,10 @@ cmdline() { VERBOSE=1 ;; -V|--version) - printf "%s (version: %s)\n" "$PROGNAME" "$VERSION" + printf "%s (version %s)\n" "$PROGNAME" "$VERSION" + if _is_update_available; then + message "update is available." + fi exit 0 ;; -h|--help) @@ -503,7 +543,7 @@ show_menu() { message "Done!" # Allows pause when launched via 'Run in Terminal' - read -r -p 'Press [Enter] to close' < /dev/tty # don't read from stdin + read -r -p 'Press [ENTER] to close' < /dev/tty # don't read from stdin } main() { From ba6ec50984b9d37520982f3e3b118c817c41c889 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Thu, 26 Oct 2017 21:29:45 +0300 Subject: [PATCH 10/17] Add comments to several functions --- fix.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fix.sh b/fix.sh index be32bd3..6952850 100755 --- a/fix.sh +++ b/fix.sh @@ -70,11 +70,13 @@ fail() { } _is_hardcoded() { + # returns true if Icon contains a path local desktop_file="$1" LANG=C grep -q '^Icon=.*/.*' -- "$desktop_file" } _is_hardcoded_steam_app() { + # returns true if is a Steam launcher and Icon equals 'steam' local desktop_file="$1" local icon_name @@ -149,7 +151,7 @@ set_marker_value() { } icon_lookup() { - # returns path to an icon (icon lookup for poor man) + # looks for icon in the list of dirs and returns absolute path to the icon local icon_name="$1" local -a icons_dirs=( "/usr/share/icons/hicolor/48x48/apps" @@ -172,6 +174,7 @@ icon_lookup() { } get_icon_path() { + # returns absolute path to the icon local desktop_file="$1" local icon_value icon_path From fdc736d50036d919c85f34d72ee3fc73e7b0513a Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Thu, 26 Oct 2017 22:13:29 +0300 Subject: [PATCH 11/17] Fix warning from shellcheck SC2021: Don't use [] around ranges in tr, it replaces literal square brackets. --- fix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fix.sh b/fix.sh index 6952850..a20a526 100755 --- a/fix.sh +++ b/fix.sh @@ -230,7 +230,7 @@ get_upstream_version() { download_file "$upstream_file" \ | LANG=C grep -o 'date=[0-9]\+' \ | head -1 \ - | tr -dc '[0-9]' + | tr -cd '[:digit:]' } get_from_db() { From bed01cb9a4f7c72121d2b87a5a7789b36b97c439 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Fri, 27 Oct 2017 22:22:16 +0300 Subject: [PATCH 12/17] Convert invalid icon formats #306 --- fix.sh | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/fix.sh b/fix.sh index a20a526..9f8a7c3 100755 --- a/fix.sh +++ b/fix.sh @@ -195,13 +195,34 @@ copy_icon_file() { local icon_name="$2" local icon_ext="${icon_path##*.}" - if [ -f "$icon_path" ]; then - mkdir -p "$LOCAL_ICONS_DIR" - cp -f "$icon_path" "$LOCAL_ICONS_DIR/${icon_name}.${icon_ext}" - else + if [ ! -f "$icon_path" ]; then warning "Cannot find an icon for '$icon_name'." return 1 fi + + mkdir -p "$LOCAL_ICONS_DIR" + + case "$icon_ext" in + png|svg|svgz|xpm) + cp "$icon_path" "$LOCAL_ICONS_DIR/${icon_name}.${icon_ext}" + ;; + gif|ico|jpg) + if ! command -v convert > /dev/null 2>&1; then + warning "imagemagick is not installed." \ + "Icon '${icon_name}.${icon_ext}' cannot be converted." + return 1 + fi + + verbose "Converting '${icon_name}.${icon_ext}' to" \ + "'${icon_name}.png' ..." + convert "$icon_path" -alpha on -background none -thumbnail 48x48 \ + -flatten "$LOCAL_ICONS_DIR/${icon_name}.png" + ;; + *) + warning "'${icon_name}.${icon_ext}' has invalid icon format." + return 1 + ;; + esac } download_file() { From 115de5eafbe7fe302c6f35d07868356a2ba02646 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Tue, 31 Oct 2017 19:57:36 +0200 Subject: [PATCH 13/17] Improvements to args parsing --- fix.sh | 126 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/fix.sh b/fix.sh index 9f8a7c3..e23b9b4 100755 --- a/fix.sh +++ b/fix.sh @@ -420,6 +420,8 @@ apply() { fi done done + + message "${FUNCNAME[0]}: Done!" } revert() { @@ -454,76 +456,87 @@ revert() { fi done done + + message "${FUNCNAME[0]}: Done!" } -cmdline() { - local arg action +parse_opts() { + local opt command + local -a opts=() + local -a commands=() + + usage() { + local exit_code="$1" - show_usage() { cat >&2 <<- EOF - usage: - $SCRIPT_NAME {-a --apply} [options] - $SCRIPT_NAME {-r --revert} [options] - - ACTIONS: - -a, --apply fixes hardcoded icons of installed applications - -r, --revert reverts any changes made - - OPTIONS: - -d --force-download download the new database (ignore the local DB) - -V --version print $PROGNAME version and exit - -v --verbose be verbose - -h --help show this help + usage: $SCRIPT_NAME [command] [options] + + commands: + -A, --apply fixes hardcoded icons of installed applications + -R, --revert reverts any changes made + -V, --version print $PROGNAME version and exit + -h, --help show this help + + options: + -d, --force-download download the new database (ignore the local DB) + -v, --verbose be verbose + + Long commands without double '--' are also allowed. EOF + + exit "$exit_code" } - for arg in "${ARGS[@]}"; do - case "$arg" in - -a|--apply) - action="apply" - ;; - -r|--revert) - action="revert" + # Translate --gnu-long-options and commands to -g (short options) + for opt; do + case "$opt" in + --apply|apply) opts+=( -A ) ;; + --revert|revert) opts+=( -R ) ;; + --version|version) opts+=( -V ) ;; + --force-download) opts+=( -d ) ;; + --help|help) opts+=( -h ) ;; + --verbose) opts+=( -v ) ;; + --[0-9a-Z]*) + message "illegal option -- '$opt'" + usage 128 ;; - -d|--force-download) - FORCE_DOWNLOAD=1 - ;; - -v|--verbose) - VERBOSE=1 + *) opts+=( "$opt" ) ;; + esac + done + + while getopts ":ARVdhv" opt "${opts[@]}"; do + case "$opt" in + A ) commands+=( "apply" ) ;; + R ) commands+=( "revert" ) ;; + V ) commands+=( "version" ) ;; + d ) FORCE_DOWNLOAD=1 ;; + h ) usage 0 ;; + v ) VERBOSE=1 ;; + \?) + message "illegal option -- '-$OPTARG'" + usage 128 ;; - -V|--version) + esac + done + + for command in "${commands[@]}"; do + case "$command" in + apply) apply ;; + revert) revert ;; + version) printf "%s (version %s)\n" "$PROGNAME" "$VERSION" if _is_update_available; then message "update is available." fi exit 0 ;; - -h|--help) - show_usage - exit 0 - ;; - *) - message "illegal option -- '$arg'" - show_usage - exit 128 - ;; esac done - case "$action" in - apply) - apply - ;; - revert) - revert - ;; - *) - fail "You must choose an action.\n" \ - "\rType '$SCRIPT_NAME --help' to display help." - ;; - esac - - message "Done!" + # display interactive menu, if no commands were passed + if [ "${#commands[@]}" -eq 0 ]; then + show_menu + fi } show_menu() { @@ -541,11 +554,9 @@ show_menu() { case "${menu_item:-$REPLY}" in apply|[aA]*) apply - break ;; revert|[rR]*) revert - break ;; help|[hH]*) cat >&2 <<- EOF @@ -563,11 +574,6 @@ show_menu() { ;; esac done < /dev/tty # don't read from stdin - - message "Done!" - - # Allows pause when launched via 'Run in Terminal' - read -r -p 'Press [ENTER] to close' < /dev/tty # don't read from stdin } main() { @@ -576,7 +582,7 @@ main() { fi if [ "${#ARGS[@]}" -gt 0 ]; then - cmdline + parse_opts "${ARGS[@]}" else show_menu fi From bd1c0814dd6334bd6b70fe542e864b3cc3f9e371 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Thu, 2 Nov 2017 19:18:12 +0200 Subject: [PATCH 14/17] Minor changes * added and changed several messages and comments * some functions and variable have been renamed * minor changes in code style and code refactoring --- fix.sh | 242 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 125 insertions(+), 117 deletions(-) diff --git a/fix.sh b/fix.sh index e23b9b4..cd2a2f2 100755 --- a/fix.sh +++ b/fix.sh @@ -15,7 +15,6 @@ if test -z "$BASH_VERSION"; then exit 1 fi -# set -x # Uncomment to debug this shell script set -o errexit \ -o noclobber \ -o pipefail @@ -24,15 +23,15 @@ unset GREP_OPTIONS # avoid mess up readonly SCRIPT_NAME="$(basename -- "$0")" readonly SCRIPT_DIR="$(dirname -- "$0")" -readonly -a ARGS=("$@") +readonly -a ARGS=( "$@" ) readonly PROGNAME="hardcode-fixer" declare -i VERSION=201710170 # [year][month][date][extra] # date=999999990 # deprecate the previous version -declare UPSTREAM_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master" -declare LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" -declare LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" +UPSTREAM_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master" +LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" +LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" declare -a GLOBAL_APPS_DIRS=( "/usr/share/applications" @@ -51,21 +50,25 @@ declare -a LOCAL_APPS_DIRS=( declare -i FORCE_DOWNLOAD="${FORCE_DOWNLOAD:-0}" declare -i VERBOSE="${VERBOSE:-0}" -message() { +msg() { printf "%s: %b\n" "$PROGNAME" "$*" >&2 } -verbose() { - [ "$VERBOSE" = 1 ] || return 0 - message "INFO:" "$@" +verb() { + [ "$VERBOSE" -eq 1 ] || return 0 + msg "INFO:" "$*" } -warning() { - message "WARN:" "$@" +warn() { + msg "WARNING:" "$*" } -fail() { - message "ERR:" "$@" +err() { + msg "ERROR:" "$*" +} + +fatal() { + err "$*" exit 1 } @@ -76,7 +79,7 @@ _is_hardcoded() { } _is_hardcoded_steam_app() { - # returns true if is a Steam launcher and Icon equals 'steam' + # returns true if it's a Steam launcher and Icon equals 'steam' local desktop_file="$1" local icon_name @@ -91,7 +94,7 @@ _is_hardcoded_steam_app() { } _is_update_available() { - # returns true if the upstream version bigger than current + # returns true if the upstream version is greater than current local -i upstream_version upstream_version="$(get_upstream_version)" @@ -103,6 +106,12 @@ _is_update_available() { return 1 } +_has_marker() { + # returns true if desktop file has a marker + local desktop_file="$1" + LANG=C grep -q '^X-Hardcode-Fixer-Marker=' -- "$desktop_file" +} + get_app_name() { local desktop_file="$1" awk -F= '/^Name/ { print $2; exit }' "$desktop_file" @@ -142,26 +151,27 @@ get_marker_value() { set_marker_value() { local desktop_file="$1" - local marker_value="${2:-local}" + local marker_value="$2" - # append after Icon + # add after Icon sed -i -e "/^Icon=/a \ X-Hardcode-Fixer-Marker=${marker_value} " "$desktop_file" } icon_lookup() { - # looks for icon in the list of dirs and returns absolute path to the icon + # looks for icon in dirs in the list and returns absolute path to the icon local icon_name="$1" + local icons_dir icon_path local -a icons_dirs=( "/usr/share/icons/hicolor/48x48/apps" "/usr/share/icons/hicolor" + "/usr/share/pixmaps" "/usr/local/share/icons/hicolor/48x48/apps" "/usr/local/share/icons/hicolor" + "/usr/local/share/pixmaps" "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/48x48/apps" "${XDG_DATA_HOME:-$HOME/.local/share}/icons" - "/usr/share/pixmaps" - "/usr/local/share/pixmaps" ) for icons_dir in "${icons_dirs[@]}"; do @@ -190,13 +200,37 @@ get_icon_path() { printf '%s' "$icon_path" } +get_upstream_version() { + download_file "$UPSTREAM_URL/fix.sh" 2> /dev/null \ + | LANG=C grep -o 'date=[0-9]\+' \ + | head -1 \ + | tr -cd '[:digit:]' +} + +get_from_db() { + # returns icon name if found it + local desktop_file="$1" + local app_name + + app_name="$(get_app_name "$desktop_file")" + + awk -F, -v app_name="$app_name" ' + BEGIN { IGNORECASE = 1; } + $1 == app_name { + print $4 + exit + } + ' "$DB_FILE" +} + copy_icon_file() { local icon_path="$1" local icon_name="$2" local icon_ext="${icon_path##*.}" if [ ! -f "$icon_path" ]; then - warning "Cannot find an icon for '$icon_name'." + warn "Failed to copy '$icon_name' icon." \ + "File '$icon_path' does not exist." return 1 fi @@ -204,73 +238,46 @@ copy_icon_file() { case "$icon_ext" in png|svg|svgz|xpm) - cp "$icon_path" "$LOCAL_ICONS_DIR/${icon_name}.${icon_ext}" + cp -f "$icon_path" "$LOCAL_ICONS_DIR/${icon_name}.${icon_ext}" ;; - gif|ico|jpg) + gif|ico|jpg|jpeg) if ! command -v convert > /dev/null 2>&1; then - warning "imagemagick is not installed." \ + warn "imagemagick is not installed." \ "Icon '${icon_name}.${icon_ext}' cannot be converted." return 1 fi - verbose "Converting '${icon_name}.${icon_ext}' to" \ + verb "Converting '${icon_name}.${icon_ext}' to" \ "'${icon_name}.png' ..." convert "$icon_path" -alpha on -background none -thumbnail 48x48 \ -flatten "$LOCAL_ICONS_DIR/${icon_name}.png" ;; *) - warning "'${icon_name}.${icon_ext}' has invalid icon format." + warn "'${icon_name}.${icon_ext}' has invalid icon format." return 1 - ;; esac } download_file() { local url="$1" - local file="${2:--}" # is not a typo, output to stdout by default - - verbose "Downloading '$url' ..." + local file="${2:--}" # it's not a typo, output to stdout by default if command -v wget > /dev/null 2>&1; then wget --no-check-certificate -q -O "$file" "$url" \ - || fail "Fail to download '$url'." + || fatal "Fail to download '$url' (wget exit code: $?)." elif command -v curl > /dev/null 2>&1; then curl -sk -o "$file" "$url" \ - || fail "Fail to download '$url'." + || fatal "Fail to download '$url' (curl exit code: $?)." else - fail "\n" \ - "\r This script requires 'wget' to be installed\n" \ - "\r to fetch the required files and check for updates.\n" \ - "\r Please install it and rerun this script." + fatal "Fail to download '$url'.\n" \ + "\r This script requires 'wget' to be installed\n" \ + "\r to fetch the required files and check for updates.\n" \ + "\r Please install it and rerun this script." fi } -get_upstream_version() { - local upstream_file="$UPSTREAM_URL/fix.sh" - - download_file "$upstream_file" \ - | LANG=C grep -o 'date=[0-9]\+' \ - | head -1 \ - | tr -cd '[:digit:]' -} - -get_from_db() { - # returns icon name if find it - local desktop_file="$1" - local app_name - - app_name="$(get_app_name "$desktop_file")" - - awk -F, -v app_name="$app_name" ' - BEGIN { IGNORECASE = 1; } - $1 == app_name { - print $4 - exit - } - ' "$DB_FILE" -} - translate_from_app_name() { + # converts app name to icon name local desktop_file="$1" # 1. remove text between parentheses @@ -289,32 +296,32 @@ translate_from_app_name() { backup_desktop_file() { local desktop_file="$1" - local dir_name base_name new_file_path + local dir_name base_name backup_file dir_name="$(dirname "$desktop_file")" base_name="$(basename "$desktop_file")" - new_file_path="${dir_name}/.${base_name}.orig" + backup_file="${dir_name}/.${base_name}.orig" - if [ -f "$new_file_path" ]; then - versbose "Backup file already exists" + if [ -f "$backup_file" ]; then + err "Backup file already exists." return 1 fi - cp -a "$desktop_file" "$new_file_path" + cp "$desktop_file" "$backup_file" } restore_desktop_file() { local desktop_file="$1" - local dir_name base_name file_path + local dir_name base_name backup_file dir_name="$(dirname "$desktop_file")" base_name="$(basename "$desktop_file")" - file_path="${dir_name}/.${base_name}.orig" + backup_file="${dir_name}/.${base_name}.orig" - if [ -f "$file_path" ]; then - mv -f "$file_path" "$desktop_file" + if [ -f "$backup_file" ]; then + mv -f "$backup_file" "$desktop_file" else - warning "Can't find the backup file. Skipping." + err "Fail to find the backup file." return 1 fi } @@ -322,7 +329,7 @@ restore_desktop_file() { fix_hardcoded_app() { local desktop_file="$1" local method="$2" - local app_name icon_path new_icon_name local_desktop_file + local app_name icon_path new_icon_name desktop_file_name local_desktop_file app_name="$(get_app_name "$desktop_file")" icon_path="$(get_icon_path "$desktop_file")" @@ -330,15 +337,16 @@ fix_hardcoded_app() { case "$method" in global) - local_desktop_file="$LOCAL_APPS_DIR/$(basename "$desktop_file")" + desktop_file_name="$(basename "$desktop_file")" + local_desktop_file="$LOCAL_APPS_DIR/$desktop_file_name" if [ -e "$local_desktop_file" ]; then - verbose "'$app_name' already exists in local apps. Skipping." + verb "'$app_name' already exists in local apps. Skipping." return 1 fi mkdir -p "$(dirname "$local_desktop_file")" - cp -a "$desktop_file" "$local_desktop_file" + cp "$desktop_file" "$local_desktop_file" desktop_file="$local_desktop_file" ;; @@ -350,7 +358,7 @@ fix_hardcoded_app() { new_icon_name="$(get_steam_icon_name "$desktop_file")" ;; *) - warning "illegal method -- $method" + err "illegal method -- '$method'" return 1 esac @@ -358,7 +366,7 @@ fix_hardcoded_app() { new_icon_name="$(translate_from_app_name "$desktop_file")" fi - message "Fixing '$app_name' [$method] ..." + msg "Fixing '$app_name' [$method] ..." set_icon_name "$desktop_file" "$new_icon_name" set_marker_value "$desktop_file" "$method" @@ -366,11 +374,12 @@ fix_hardcoded_app() { } apply() { - local app_dir file + local app_dir desktop_file - if [ "$FORCE_DOWNLOAD" = 0 ] && [ -f "$SCRIPT_DIR/tofix.csv" ]; then + if [ "$FORCE_DOWNLOAD" -eq 0 ] && [ -f "$SCRIPT_DIR/tofix.csv" ]; then DB_FILE="$SCRIPT_DIR/tofix.csv" else + verb "Checking for update ..." if _is_update_available; then cat >&2 <<- EOF @@ -388,12 +397,12 @@ apply() { DB_FILE="$(mktemp -u -t hardcode_db_XXXXX.csv)" - message "Downloading DB into '$DB_FILE' file ..." + msg "Downloading DB into '$DB_FILE' file ..." download_file "$UPSTREAM_URL/tofix.csv" "$DB_FILE" - # remove csv file when exit + # delete CSV file when exiting cleanup() { - verbose "Removing '$DB_FILE' ..." + verb "Removing '$DB_FILE' ..." rm -f "$DB_FILE" unset DB_FILE } @@ -404,6 +413,7 @@ apply() { for app_dir in "${GLOBAL_APPS_DIRS[@]}"; do for desktop_file in "$app_dir"/*.desktop; do [ -f "$desktop_file" ] || continue + if _is_hardcoded "$desktop_file"; then fix_hardcoded_app "$desktop_file" "global" || continue fi @@ -413,6 +423,7 @@ apply() { for app_dir in "${LOCAL_APPS_DIRS[@]}"; do for desktop_file in "$app_dir"/*.desktop; do [ -f "$desktop_file" ] || continue + if _is_hardcoded "$desktop_file"; then fix_hardcoded_app "$desktop_file" "local" || continue elif _is_hardcoded_steam_app "$desktop_file"; then @@ -421,22 +432,22 @@ apply() { done done - message "${FUNCNAME[0]}: Done!" + msg "${FUNCNAME[0]}: Done!" } revert() { - local app_name app_dir file marker_value + local app_dir app_name desktop_file icon_ext icon_name marker_value for app_dir in "${LOCAL_APPS_DIRS[@]}"; do for desktop_file in "$app_dir"/*.desktop; do [ -f "$desktop_file" ] || continue - app_name="$(get_app_name "$desktop_file")" - icon_name="$(get_icon_name "$desktop_file")" - marker_value="$(get_marker_value "$desktop_file")" + if _has_marker "$desktop_file"; then + app_name="$(get_app_name "$desktop_file")" + icon_name="$(get_icon_name "$desktop_file")" + marker_value="$(get_marker_value "$desktop_file")" - if [ -n "$marker_value" ]; then - message "Reverting '$app_name' ..." + msg "Reverting '$app_name' ..." case "$marker_value" in global) @@ -445,19 +456,23 @@ revert() { local|steam) restore_desktop_file "$desktop_file" || continue ;; + *) + err "invalid marker value -- '$marker_value'" + continue esac - verbose "Removing '$icon_name' icon ..." - for ext in png svg xpm; do - [ -f "$LOCAL_ICONS_DIR/$icon_name.$ext" ] || continue - rm -- "$LOCAL_ICONS_DIR/$icon_name.$ext" - break + for icon_ext in png svg svgz xpm; do + if [ -f "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" ]; then + verb "Removing '$icon_name.$icon_ext' ..." + rm -- "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" + break + fi done fi done done - message "${FUNCNAME[0]}: Done!" + msg "${FUNCNAME[0]}: Done!" } parse_opts() { @@ -469,7 +484,7 @@ parse_opts() { local exit_code="$1" cat >&2 <<- EOF - usage: $SCRIPT_NAME [command] [options] + usage: $SCRIPT_NAME [command ...] [options] commands: -A, --apply fixes hardcoded icons of installed applications @@ -497,10 +512,10 @@ parse_opts() { --help|help) opts+=( -h ) ;; --verbose) opts+=( -v ) ;; --[0-9a-Z]*) - message "illegal option -- '$opt'" + err "illegal option -- '$opt'" usage 128 ;; - *) opts+=( "$opt" ) ;; + *) opts+=( "$opt" ) esac done @@ -513,7 +528,7 @@ parse_opts() { h ) usage 0 ;; v ) VERBOSE=1 ;; \?) - message "illegal option -- '-$OPTARG'" + err "illegal option -- '-$OPTARG'" usage 128 ;; esac @@ -526,7 +541,7 @@ parse_opts() { version) printf "%s (version %s)\n" "$PROGNAME" "$VERSION" if _is_update_available; then - message "update is available." + msg "update is available." fi exit 0 ;; @@ -540,6 +555,7 @@ parse_opts() { } show_menu() { + local menu_item local -a menu_items=( "apply" "revert" "help" "quit" ) local PS3="($PROGNAME)> " # set custom prompt @@ -552,33 +568,25 @@ show_menu() { select menu_item in "${menu_items[@]}"; do case "${menu_item:-$REPLY}" in - apply|[aA]*) - apply - ;; - revert|[rR]*) - revert - ;; + apply|[aA]*) apply ;; + revert|[rR]*) revert ;; help|[hH]*) cat >&2 <<- EOF - apply — Fixes hardcoded icons of installed applications - revert — Reverts any changes made - help — Displays this help menu - quit - Quit "$PROGNAME" + apply - fixes hardcoded icons of installed applications + revert - reverts any changes made + help - displays this help menu + quit - quit $PROGNAME EOF ;; - quit|[qQ]*|[eE]*) - exit 0 - ;; - *) - echo "$REPLY -- invalid command" - ;; + quit|[qQeE]*) exit 0 ;; + *) err "invalid command -- '$REPLY'" esac done < /dev/tty # don't read from stdin } main() { if [ "$(id -u)" -eq 0 ]; then - fail "This script must be run as normal user." + fatal "This script must be run as normal user." fi if [ "${#ARGS[@]}" -gt 0 ]; then From 9eec0e7aa311321bfaaaf49ed6795c199bfedf67 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Sat, 4 Nov 2017 20:40:17 +0200 Subject: [PATCH 15/17] Add XDG Base Directory support - get applications directories from XDG_DATA_DIR variable - recursive search for desktop files --- fix.sh | 158 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/fix.sh b/fix.sh index cd2a2f2..5c2167f 100755 --- a/fix.sh +++ b/fix.sh @@ -15,10 +15,16 @@ if test -z "$BASH_VERSION"; then exit 1 fi +if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then + printf "Error: this script requires bash version >= 4\n" >&2 + exit 1 +fi + set -o errexit \ -o noclobber \ -o pipefail +shopt -s globstar # enable **/*.deskop path expansion unset GREP_OPTIONS # avoid mess up readonly SCRIPT_NAME="$(basename -- "$0")" @@ -29,23 +35,21 @@ readonly PROGNAME="hardcode-fixer" declare -i VERSION=201710170 # [year][month][date][extra] # date=999999990 # deprecate the previous version +# Import XDG user dirs variables +if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" ]; then + # shellcheck disable=SC1090 + source "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" +fi + +# Get data directories from XDG_DATA_DIRS variable and +# convert colon-separated list into bash array +IFS=: read -ra DATA_DIRS \ + <<< "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" + UPSTREAM_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master" LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" -declare -a GLOBAL_APPS_DIRS=( - "/usr/share/applications" - "/usr/share/applications/kde4" - "/usr/local/share/applications" - "/usr/local/share/applications/kde4" -) - -declare -a LOCAL_APPS_DIRS=( - "${XDG_DATA_HOME:-$HOME/.local/share}/applications" - "${XDG_DATA_HOME:-$HOME/.local/share}/applications/kde4" - "$(xdg-user-dir DESKTOP)" -) - # default values of options declare -i FORCE_DOWNLOAD="${FORCE_DOWNLOAD:-0}" declare -i VERBOSE="${VERBOSE:-0}" @@ -162,23 +166,23 @@ set_marker_value() { icon_lookup() { # looks for icon in dirs in the list and returns absolute path to the icon local icon_name="$1" - local icons_dir icon_path - local -a icons_dirs=( - "/usr/share/icons/hicolor/48x48/apps" - "/usr/share/icons/hicolor" - "/usr/share/pixmaps" - "/usr/local/share/icons/hicolor/48x48/apps" - "/usr/local/share/icons/hicolor" - "/usr/local/share/pixmaps" - "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/48x48/apps" - "${XDG_DATA_HOME:-$HOME/.local/share}/icons" - ) - - for icons_dir in "${icons_dirs[@]}"; do - for icon_path in "$icons_dir/$icon_name".*; do - [ -f "$icon_path" ] || continue - printf '%s' "$icon_path" - return 0 # only the first match + local data_dir icons_dir icon_path + + for data_dir in \ + "${DATA_DIRS[@]}" \ + "${XDG_DATA_HOME:-$HOME/.local/share}" + do + for icons_dir in \ + "$data_dir/icons/hicolor/48x48/apps" \ + "$data_dir/icons/hicolor" \ + "$data_dir/icons" \ + "$data_dir/pixmaps" + do + for icon_path in "$icons_dir/$icon_name".*; do + [ -f "$icon_path" ] || continue + printf '%s' "$icon_path" + return 0 # only the first match + done done done } @@ -374,7 +378,7 @@ fix_hardcoded_app() { } apply() { - local app_dir desktop_file + local data_dir desktop_file if [ "$FORCE_DOWNLOAD" -eq 0 ] && [ -f "$SCRIPT_DIR/tofix.csv" ]; then DB_FILE="$SCRIPT_DIR/tofix.csv" @@ -410,8 +414,8 @@ apply() { trap cleanup EXIT HUP INT TERM fi - for app_dir in "${GLOBAL_APPS_DIRS[@]}"; do - for desktop_file in "$app_dir"/*.desktop; do + for data_dir in "${DATA_DIRS[@]}"; do + for desktop_file in "$data_dir"/applications/**/*.desktop; do [ -f "$desktop_file" ] || continue if _is_hardcoded "$desktop_file"; then @@ -420,56 +424,58 @@ apply() { done done - for app_dir in "${LOCAL_APPS_DIRS[@]}"; do - for desktop_file in "$app_dir"/*.desktop; do - [ -f "$desktop_file" ] || continue + for desktop_file in \ + "${XDG_DATA_HOME:-$HOME/.local/share}"/applications/*.desktop \ + "${XDG_DESKTOP_DIR:-$HOME/Desktop}"/**/*.desktop + do + [ -f "$desktop_file" ] || continue - if _is_hardcoded "$desktop_file"; then - fix_hardcoded_app "$desktop_file" "local" || continue - elif _is_hardcoded_steam_app "$desktop_file"; then - fix_hardcoded_app "$desktop_file" "steam" || continue - fi - done + if _is_hardcoded "$desktop_file"; then + fix_hardcoded_app "$desktop_file" "local" || continue + elif _is_hardcoded_steam_app "$desktop_file"; then + fix_hardcoded_app "$desktop_file" "steam" || continue + fi done msg "${FUNCNAME[0]}: Done!" } revert() { - local app_dir app_name desktop_file icon_ext icon_name marker_value - - for app_dir in "${LOCAL_APPS_DIRS[@]}"; do - for desktop_file in "$app_dir"/*.desktop; do - [ -f "$desktop_file" ] || continue - - if _has_marker "$desktop_file"; then - app_name="$(get_app_name "$desktop_file")" - icon_name="$(get_icon_name "$desktop_file")" - marker_value="$(get_marker_value "$desktop_file")" - - msg "Reverting '$app_name' ..." - - case "$marker_value" in - global) - rm -f -- "$desktop_file" - ;; - local|steam) - restore_desktop_file "$desktop_file" || continue - ;; - *) - err "invalid marker value -- '$marker_value'" - continue - esac - - for icon_ext in png svg svgz xpm; do - if [ -f "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" ]; then - verb "Removing '$icon_name.$icon_ext' ..." - rm -- "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" - break - fi - done - fi - done + local app_name desktop_file icon_ext icon_name marker_value + + for desktop_file in \ + "$LOCAL_APPS_DIR"/*.desktop \ + "${XDG_DESKTOP_DIR:-$HOME/Desktop}"/**/*.desktop + do + [ -f "$desktop_file" ] || continue + + if _has_marker "$desktop_file"; then + app_name="$(get_app_name "$desktop_file")" + icon_name="$(get_icon_name "$desktop_file")" + marker_value="$(get_marker_value "$desktop_file")" + + msg "Reverting '$app_name' ..." + + case "$marker_value" in + global) + rm -f -- "$desktop_file" + ;; + local|steam) + restore_desktop_file "$desktop_file" || continue + ;; + *) + err "invalid marker value -- '$marker_value'" + continue + esac + + for icon_ext in png svg svgz xpm; do + if [ -f "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" ]; then + verb "Removing '$icon_name.$icon_ext' ..." + rm -- "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" + break + fi + done + fi done msg "${FUNCNAME[0]}: Done!" From 99f20ce81ec285d52a598c0e23dd288a11e46a88 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Sun, 12 Nov 2017 23:51:16 +0200 Subject: [PATCH 16/17] Store the CSV file in $XDG_CACHE_HOME * added --csv-file option * added --no-download option * deleted --force-download option * revert verb func name to verbose * minor visual changes --- fix.sh | 109 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/fix.sh b/fix.sh index 5c2167f..7e2781a 100755 --- a/fix.sh +++ b/fix.sh @@ -51,14 +51,14 @@ LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" LOCAL_ICONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons" # default values of options -declare -i FORCE_DOWNLOAD="${FORCE_DOWNLOAD:-0}" +declare -i NO_DOWNLOAD="${NO_DOWNLOAD:-0}" declare -i VERBOSE="${VERBOSE:-0}" msg() { printf "%s: %b\n" "$PROGNAME" "$*" >&2 } -verb() { +verbose() { [ "$VERBOSE" -eq 1 ] || return 0 msg "INFO:" "$*" } @@ -72,7 +72,7 @@ err() { } fatal() { - err "$*" + msg "FATAL:" "$*" exit 1 } @@ -216,6 +216,8 @@ get_from_db() { local desktop_file="$1" local app_name + [ -f "$DB_FILE" ] || return 1 + app_name="$(get_app_name "$desktop_file")" awk -F, -v app_name="$app_name" ' @@ -251,7 +253,7 @@ copy_icon_file() { return 1 fi - verb "Converting '${icon_name}.${icon_ext}' to" \ + verbose "Converting '${icon_name}.${icon_ext}' to" \ "'${icon_name}.png' ..." convert "$icon_path" -alpha on -background none -thumbnail 48x48 \ -flatten "$LOCAL_ICONS_DIR/${icon_name}.png" @@ -265,19 +267,27 @@ copy_icon_file() { download_file() { local url="$1" local file="${2:--}" # it's not a typo, output to stdout by default + local cmd + local -i exit_code=0 if command -v wget > /dev/null 2>&1; then wget --no-check-certificate -q -O "$file" "$url" \ - || fatal "Fail to download '$url' (wget exit code: $?)." + || cmd="wget" exit_code="$?" elif command -v curl > /dev/null 2>&1; then curl -sk -o "$file" "$url" \ - || fatal "Fail to download '$url' (curl exit code: $?)." + || cmd="curl" exit_code="$?" else fatal "Fail to download '$url'.\n" \ "\r This script requires 'wget' to be installed\n" \ "\r to fetch the required files and check for updates.\n" \ "\r Please install it and rerun this script." fi + + if [ "$exit_code" -ne 0 ]; then + err "Fail to download '$url' ($cmd returns exit code $exit_code)." + fi + + return "$exit_code" } translate_from_app_name() { @@ -345,7 +355,7 @@ fix_hardcoded_app() { local_desktop_file="$LOCAL_APPS_DIR/$desktop_file_name" if [ -e "$local_desktop_file" ]; then - verb "'$app_name' already exists in local apps. Skipping." + verbose "'$app_name' already exists in local apps. Skipping." return 1 fi @@ -380,10 +390,16 @@ fix_hardcoded_app() { apply() { local data_dir desktop_file - if [ "$FORCE_DOWNLOAD" -eq 0 ] && [ -f "$SCRIPT_DIR/tofix.csv" ]; then - DB_FILE="$SCRIPT_DIR/tofix.csv" + # do not download if $DB_FILE already set + if [ -z "$DB_FILE" ]; then + DB_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/${PROGNAME}_db.csv" else - verb "Checking for update ..." + verbose "Using '$DB_FILE' file." + NO_DOWNLOAD=1 + fi + + if [ "$NO_DOWNLOAD" -eq 0 ]; then + verbose "Checking for $PROGNAME update ..." if _is_update_available; then cat >&2 <<- EOF @@ -399,19 +415,21 @@ apply() { read -r < /dev/tty # don't read from stdin fi - DB_FILE="$(mktemp -u -t hardcode_db_XXXXX.csv)" + msg "Downloading DB ..." + download_file "$UPSTREAM_URL/tofix.csv" "${DB_FILE}.new" \ + || warn "Will use the last saved database." - msg "Downloading DB into '$DB_FILE' file ..." - download_file "$UPSTREAM_URL/tofix.csv" "$DB_FILE" - - # delete CSV file when exiting - cleanup() { - verb "Removing '$DB_FILE' ..." - rm -f "$DB_FILE" - unset DB_FILE - } + # Rename the file if download is successful + if [ -f "${DB_FILE}.new" ] && [ -s "${DB_FILE}.new" ]; then + mv -f "${DB_FILE}.new" "$DB_FILE" + else + rm -f "${DB_FILE}.new" + fi + fi - trap cleanup EXIT HUP INT TERM + # exit if DB_FILE not exists or empty + if [ ! -f "$DB_FILE" ] || [ ! -s "$DB_FILE" ]; then + fatal "Database '$DB_FILE' not exists or empty." fi for data_dir in "${DATA_DIRS[@]}"; do @@ -470,7 +488,7 @@ revert() { for icon_ext in png svg svgz xpm; do if [ -f "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" ]; then - verb "Removing '$icon_name.$icon_ext' ..." + verbose "Removing '$icon_name.$icon_ext' ..." rm -- "$LOCAL_ICONS_DIR/$icon_name.$icon_ext" break fi @@ -482,9 +500,9 @@ revert() { } parse_opts() { - local opt command + local opt cmd local -a opts=() - local -a commands=() + local -a cmds=() usage() { local exit_code="$1" @@ -493,14 +511,15 @@ parse_opts() { usage: $SCRIPT_NAME [command ...] [options] commands: - -A, --apply fixes hardcoded icons of installed applications - -R, --revert reverts any changes made - -V, --version print $PROGNAME version and exit - -h, --help show this help + -A, --apply fixes hardcoded icons of installed applications + -R, --revert reverts any changes made + -V, --version print $PROGNAME version and exit + -h, --help show this help options: - -d, --force-download download the new database (ignore the local DB) - -v, --verbose be verbose + -f, --csv-file read from the instead of downloading + -n, --no-download do not check for any updates + -v, --verbose be verbose Long commands without double '--' are also allowed. EOF @@ -514,8 +533,9 @@ parse_opts() { --apply|apply) opts+=( -A ) ;; --revert|revert) opts+=( -R ) ;; --version|version) opts+=( -V ) ;; - --force-download) opts+=( -d ) ;; + --csv-file) opts+=( -f ) ;; --help|help) opts+=( -h ) ;; + --no-download) opts+=( -n ) ;; --verbose) opts+=( -v ) ;; --[0-9a-Z]*) err "illegal option -- '$opt'" @@ -525,23 +545,26 @@ parse_opts() { esac done - while getopts ":ARVdhv" opt "${opts[@]}"; do + while getopts ":ARVf:hnv" opt "${opts[@]}"; do case "$opt" in - A ) commands+=( "apply" ) ;; - R ) commands+=( "revert" ) ;; - V ) commands+=( "version" ) ;; - d ) FORCE_DOWNLOAD=1 ;; - h ) usage 0 ;; - v ) VERBOSE=1 ;; - \?) - err "illegal option -- '-$OPTARG'" + A ) cmds+=( "apply" ) ;; + R ) cmds+=( "revert" ) ;; + V ) cmds+=( "version" ) ;; + f ) DB_FILE="$OPTARG" ;; + h ) usage 0 ;; + n ) NO_DOWNLOAD=1 ;; + v ) VERBOSE=1 ;; + : ) err "option requires an argument -- '-$OPTARG'" + usage 128 + ;; + \?) err "illegal option -- '-$OPTARG'" usage 128 ;; esac done - for command in "${commands[@]}"; do - case "$command" in + for cmd in "${cmds[@]}"; do + case "$cmd" in apply) apply ;; revert) revert ;; version) @@ -555,7 +578,7 @@ parse_opts() { done # display interactive menu, if no commands were passed - if [ "${#commands[@]}" -eq 0 ]; then + if [ "${#cmds[@]}" -eq 0 ]; then show_menu fi } From 2b9168e511e89479dd0d4038f84654cd78c4d4d8 Mon Sep 17 00:00:00 2001 From: Sergei Eremenko Date: Mon, 13 Nov 2017 18:15:25 +0200 Subject: [PATCH 17/17] Minor fixes - fix the local at first (don't read the same file twice) - rename marker key - fix get_upstream_version - add clear command to show_menu --- fix.sh | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/fix.sh b/fix.sh index 7e2781a..2b4e8a0 100755 --- a/fix.sh +++ b/fix.sh @@ -20,9 +20,9 @@ if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then exit 1 fi -set -o errexit \ - -o noclobber \ - -o pipefail +set -o errexit +set -o noclobber +set -o pipefail shopt -s globstar # enable **/*.deskop path expansion unset GREP_OPTIONS # avoid mess up @@ -32,7 +32,7 @@ readonly SCRIPT_DIR="$(dirname -- "$0")" readonly -a ARGS=( "$@" ) readonly PROGNAME="hardcode-fixer" -declare -i VERSION=201710170 # [year][month][date][extra] +declare -i VERSION=201711130 # [year][month][date][extra] # date=999999990 # deprecate the previous version # Import XDG user dirs variables @@ -43,8 +43,7 @@ fi # Get data directories from XDG_DATA_DIRS variable and # convert colon-separated list into bash array -IFS=: read -ra DATA_DIRS \ - <<< "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" +IFS=: read -ra DATA_DIRS <<< "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" UPSTREAM_URL="https://raw.githubusercontent.com/Foggalong/hardcode-fixer/master" LOCAL_APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications" @@ -113,7 +112,7 @@ _is_update_available() { _has_marker() { # returns true if desktop file has a marker local desktop_file="$1" - LANG=C grep -q '^X-Hardcode-Fixer-Marker=' -- "$desktop_file" + LANG=C grep -q '^X-HardcodeFixer-Marker=' -- "$desktop_file" } get_app_name() { @@ -150,7 +149,7 @@ set_icon_name() { get_marker_value() { local desktop_file="$1" - awk -F= '/^X-Hardcode-Fixer-Marker/ { print $2; exit }' "$desktop_file" + awk -F= '/^X-HardcodeFixer-Marker/ { print $2; exit }' "$desktop_file" } set_marker_value() { @@ -159,7 +158,7 @@ set_marker_value() { # add after Icon sed -i -e "/^Icon=/a \ - X-Hardcode-Fixer-Marker=${marker_value} + X-HardcodeFixer-Marker=${marker_value} " "$desktop_file" } @@ -206,7 +205,7 @@ get_icon_path() { get_upstream_version() { download_file "$UPSTREAM_URL/fix.sh" 2> /dev/null \ - | LANG=C grep -o 'date=[0-9]\+' \ + | LANG=C grep -o 'VERSION=[0-9]\+' \ | head -1 \ | tr -cd '[:digit:]' } @@ -359,7 +358,7 @@ fix_hardcoded_app() { return 1 fi - mkdir -p "$(dirname "$local_desktop_file")" + mkdir -p "$LOCAL_APPS_DIR" cp "$desktop_file" "$local_desktop_file" desktop_file="$local_desktop_file" @@ -432,16 +431,6 @@ apply() { fatal "Database '$DB_FILE' not exists or empty." fi - for data_dir in "${DATA_DIRS[@]}"; do - for desktop_file in "$data_dir"/applications/**/*.desktop; do - [ -f "$desktop_file" ] || continue - - if _is_hardcoded "$desktop_file"; then - fix_hardcoded_app "$desktop_file" "global" || continue - fi - done - done - for desktop_file in \ "${XDG_DATA_HOME:-$HOME/.local/share}"/applications/*.desktop \ "${XDG_DESKTOP_DIR:-$HOME/Desktop}"/**/*.desktop @@ -455,6 +444,16 @@ apply() { fi done + for data_dir in "${DATA_DIRS[@]}"; do + for desktop_file in "$data_dir"/applications/**/*.desktop; do + [ -f "$desktop_file" ] || continue + + if _is_hardcoded "$desktop_file"; then + fix_hardcoded_app "$desktop_file" "global" || continue + fi + done + done + msg "${FUNCNAME[0]}: Done!" } @@ -505,7 +504,7 @@ parse_opts() { local -a cmds=() usage() { - local exit_code="$1" + local exit_code="${1:-0}" cat >&2 <<- EOF usage: $SCRIPT_NAME [command ...] [options] @@ -604,9 +603,11 @@ show_menu() { apply - fixes hardcoded icons of installed applications revert - reverts any changes made help - displays this help menu + clear - clear the screen quit - quit $PROGNAME EOF ;; + clear|[cC]*) clear ;; quit|[qQeE]*) exit 0 ;; *) err "invalid command -- '$REPLY'" esac