You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Botspot-Pi-Apps/updater

468 lines
16 KiB
Bash

#!/bin/bash
{ #this curly brace at start and end of script prevents issues when this script edits itself. See: https://stackoverflow.com/a/19430939
DIRECTORY="$(readlink -f "$(dirname "$0")")"
function error {
echo -e "\e[91m$1\e[39m"
exit 1
}
#for the will_reinstall() and list_intersect() functions
source "${DIRECTORY}/api" || error "failed to source ${DIRECTORY}/api"
get_date() {
#number of days since 1/1/1970
echo $(($(date --utc +%s)/86400))
}
check_update_interval() { #return 0 if update-interval allows update-checking today, exit 1 otherwise
local lastupdatecheck="$(cat "${DIRECTORY}/data/last-update-check" 2>/dev/null)"
if [ -z $lastupdatecheck ];then
echo "Warning: ${DIRECTORY}/data/last-update-check does not exist!" 1>&2
lastupdatecheck=0
fi
#write today's date to file. Format is "number of days since 1/1/1970"
get_date > "${DIRECTORY}/data/last-update-check"
local updateinterval="$(cat "${DIRECTORY}/data/settings/Check for updates")"
#allowed values: Always, Daily, Weekly, Never
if [ "$updateinterval" == 'Never' ];then
return 1
elif [ "$updateinterval" == 'Daily' ];then
#if updates checked today, don't check
if [ "$(get_date)" == "$lastupdatecheck" ];then
return 1
fi
elif [ "$updateinterval" == 'Weekly' ];then
#if updates checked less than 7 days ago, don't check
if [ "$(get_date)" -le "$((lastupdatecheck + 7))" ];then
return 1
fi
elif [ "$updateinterval" == 'Always' ];then
return 0
elif [ -z "$updateinterval" ];then
echo "Something isn't right. Does '${DIRECTORY}/data/settings/Check for updates' exist?" 1>&2
else
echo "Warning: Unrecognized update interval!" 1>&2
fi
return 0
}
list_files() { #list all files on pi-apps with relative paths - both on main directory and update directory
if [ ! -d "${DIRECTORY}/update" ];then
error "${DIRECTORY}/update does not exist. Most likely there is no Internet connection."
fi
#list all files in update folder
cd "${DIRECTORY}/update/pi-apps" || error "Failed to enter update directory!"
local updatefiles="$(find . -type f | cut -c 3- | grep -v '.git/' | grep -v 'apps/' | grep -v 'data/')"
#list all files in main folder
cd "${DIRECTORY}"
local localfiles="$(find . -type f | cut -c 3- | grep -v '.git/' | grep -v 'apps/' | grep -v 'data/' | grep -v 'logs/' | grep -v 'xlunch/')"
echo -e "${localfiles}\n${updatefiles}" | sort | uniq
cd $HOME
}
get_updatable_files() { #sets the updatable_files variable
echo -n "Scanning files... " 1>&2
#get list of pi-apps files without absolute paths. Example line: 'etc/terminal-run'
local file_list="$(list_files)" || exit 1
updatable_files='' #the variable to be returned
#exclude files mentioned in data/update-exclusion file, ignoring any commented lines
local IFS=$'\n'
if [ "$speed" == fast ] && [ -f "${DIRECTORY}/data/update-status/updatable-files" ];then
#speed is set to 'fast' - don't hash anything but rely on past results
updatable_files="$(cat "${DIRECTORY}/data/update-status/updatable-files")"
else #speed was not set to 'fast', so compare each file to the one in the update folder
for file in $file_list ;do
echo -en "Scanning files... $file\033[0K\r" 1>&2
if [ ! -f "${DIRECTORY}/${file}" ];then
#file is missing locally - add to updatable_files list
updatable_files="${updatable_files}
${file}"
elif [ ! -f "${DIRECTORY}/update/pi-apps/${file}" ];then
#file is missing in the update folder - local
true #do not add to updatable_apps list
elif ! diff "${DIRECTORY}/update/pi-apps/${file}" "${DIRECTORY}/${file}" -q >/dev/null ;then
#files don't match - add to updatable_files list
updatable_files="${updatable_files}
${file}"
fi
done
#remove initial newline character
updatable_files="${updatable_files:1}"
fi
#If an updatable file is listed in update-exclusion, remove it from list and save notification text for later.
for file in $(cat "${DIRECTORY}/data/update-exclusion" | grep "^[^#;]") ;do
updatable_files="$(echo "$updatable_files" | grep -v "$file")"
local exclusion_msg="$exclusion_msg\n'$file' won't be updated - it's listed in data/update-exclusion."
done
echo "$updatable_files"
echo -e "Scanning files... Done\033[0K" 1>&2
#if any files were excluded by update-exclusion, list them now, after echoing "Done"
[ ! -z "$exclusion_msg" ] && echo -e "$exclusion_msg\n" 1>&2
return 0
}
get_updatable_apps() { #sets the updatable_apps variable
if [ "$speed" == fast ] && [ -f "${DIRECTORY}/data/update-status/updatable-apps" ];then
#speed is set to 'fast' - don't hash anything but rely on past results
cat "${DIRECTORY}/data/update-status/updatable-apps"
else #speed was not set to 'fast', so compare each file to the one in the update folder
updatable_apps="$("${DIRECTORY}/manage" check-all)"
local exitcode=$?
#if manage output is '.', then clear the variable
[ "$updatable_apps" == '.' ] && updatable_apps=''
echo "$updatable_apps"
exit $exitcode #return the same code that the manage script exited with
fi
}
generate_yad_list() { #input: updatable_apps and updatable_files variables
local IFS=$'\n'
#If updatable_apps and updatable_files variables empty, set them to the results of the last update-check
if [ -z "$updatable_apps" ] && [ -z "$updatable_files" ];then
updatable_apps="$(cat "${DIRECTORY}/data/update-status/updatable-apps")"
updatable_files="$(cat "${DIRECTORY}/data/update-status/updatable-files")"
fi
for app in $updatable_apps ;do #generate a yad list for every updatable app
echo "TRUE
${DIRECTORY}/update/pi-apps/apps/${app}/icon-24.png
$app ($([ ! -d "${DIRECTORY}/apps/${app}" ] && echo 'new ')app$(will_reinstall "$app" && echo ', <b>will be reinstalled</b>'))
app:$app"
done
for file in $updatable_files ;do #generate a yad list for every updatable file
#determine mimetype of updatable_apps file to display an informative icon in the list
if [ "$(file -b --mime-type "${DIRECTORY}/${file}")" == 'text/x-shellscript' ];then
#if updatable_apps file in question is a shell script, then display shellscript icon.
mimeicon="${DIRECTORY}/icons/shellscript.png"
mimetype='script'
elif [[ "${DIRECTORY}/${file}" == *.png ]] || [[ "${DIRECTORY}/${file}" == *.svg ]];then
mimeicon="${DIRECTORY}/icons/image.png"
mimetype='image'
else
#otherwise display txt icon.
mimeicon="${DIRECTORY}/icons/txt.png"
mimetype='file'
fi
echo "TRUE
${mimeicon}
$file ($mimetype)
file:$file"
done
}
list_updates_gui() { #input: updatable_apps and updatable_files variables
LIST="$(generate_yad_list)"
#echo "List: ${LIST}EOL"
if [ -z "$LIST" ];then
echo -e '\e[92mNothing to update. Nothing to do!\e[39m'
exit 0
fi
#Display a list of everything updatable
output="$(echo -e "$LIST" | yad --center --title='Pi-Apps' \
--window-icon="${DIRECTORY}/icons/logo.png" --width=310 --height=300 \
--list --checklist --separator='\n' --print-column=4 --no-headers \
--text="Updates available:"$'\n'"Uncheck an item to skip updating it." \
--column=:CHK --column=:IMG --column=Name --column=ID:HD \
--button='Later'!"${DIRECTORY}/icons/exit.png"!"Remind me later":1 \
--button='Update now'!"${DIRECTORY}/icons/download.png":0)" || exit 0
#remove empty newlines from output
output="$(echo "$output" | grep .)"
#regenerate list of updatable apps and files, based on what the user selected
updatable_apps="$(echo "$output" | grep '^app:' | sed 's/^app://g')"
updatable_files="$(echo "$output" | grep '^file:' | sed 's/^file://g')"
}
update_now_gui() { #input: updatable_files and updatable_apps variables
local IFS=$'\n'
for file in $updatable_files ;do
mkdir -p "$(dirname "${DIRECTORY}/${file}")"
#copy new version to apps/
cp -f "${DIRECTORY}/update/pi-apps/${file}" "${DIRECTORY}/${file}" || echo -e "\e[91mFailed to copy ${DIRECTORY}/update/pi-apps/${file}\e[39m!"
echo -e "\e[92m${file} file was copied successfully.\e[39m"
done
IFS="$PREIFS"
if [ ! -z "$updatable_apps" ];then
"${DIRECTORY}/etc/terminal-run" '
DIRECTORY="'"$DIRECTORY"'"
updatable_apps="'"$updatable_apps"'"
trap "sleep 10" EXIT
PREIFS="$IFS"
IFS=$'\''\n'\''
for i in $updatable_apps
do
"${DIRECTORY}/manage" update "$i" nofetch
done
IFS="$PREIFS"
echo -e "\e[92mAll updates complete. Closing in 10 seconds.\e[39m"
' "Updating $(echo "$updatable_apps" | wc -l) app$([ $(echo "$updatable_apps" | wc -l) != 1 ] && echo s)..."
fi
#delete .git folder, then copy the new one
rm -rf "${DIRECTORY}/.git" || sudo rm -rf "${DIRECTORY}/.git" || error "Failed to delete old ${DIRECTORY}/.git folder!"
cp -a "${DIRECTORY}/update/pi-apps/.git" "${DIRECTORY}" || error "Failed to copy new .git folder!"
echo -e "\e[92mPi-Apps updates complete.\e[39m"
}
update_now_cli() { #input: updatable_files and updatable_apps variables
local IFS=$'\n'
for file in $updatable_files ;do
mkdir -p "$(dirname "${DIRECTORY}/${file}")"
#copy new version to apps/
cp -f "${DIRECTORY}/update/pi-apps/${file}" "${DIRECTORY}/${file}" || echo -e "\e[91mFailed to copy ${DIRECTORY}/update/pi-apps/${file}\e[39m!"
echo -e "\e[92m${file} file was copied successfully.\e[39m"
done
for app in $updatable_apps ;do
"${DIRECTORY}/manage" update "$app" nofetch
done
#delete .git folder, then copy the new one
rm -rf "${DIRECTORY}/.git" || sudo rm -rf "${DIRECTORY}/.git" || error "Failed to delete old ${DIRECTORY}/.git folder!"
cp -a "${DIRECTORY}/update/pi-apps/.git" "${DIRECTORY}" || error "Failed to copy new .git folder!"
echo -e "\e[92mPi-Apps updates complete.\e[39m"
}
screen_width="$(xrandr | grep "HDMI-1" | awk '{print $4}' | tr 'x+' ' ' | awk '{print $1}')"
screen_height="$(xrandr | grep "HDMI-1" | awk '{print $4}' | tr 'x+' ' ' | awk '{print $2}')"
runmode="$1"
speed="$2"
if [ ! -z "$speed" ] && [ "$speed" != 'fast' ];then
error "Unknown value for speed: "\""$speed"\"". Allowed value: fast"
fi
if [ "$runmode" == onboot ];then
runmode=autostarted
elif [ -z "$runmode" ];then
runmode=gui
fi
#runmode values: autostarted, get-status, set-status, gui, gui-yes, cli, cli-yes
echo "Updater mode: $runmode"
if [ "$runmode" == autostarted ];then #if update-interval allows, and one app installed, display notification on boot
#check if update interval allows update-checks, otherwise exit
check_update_interval
if [ $? != 0 ];then
echo "Won't check for updates today, because of the update interval is set to '$(cat "${DIRECTORY}/data/settings/Check for updates")' in Settings."
exit 0
fi
#check that at least one app has been installed by the user
if [ "$(ls "${DIRECTORY}/data/status" | wc -l)" == 0 ];then
echo "No apps have been installed yet, so exiting now."
exit 0
fi
updatable_apps="$(get_updatable_apps)"
updatable_files="$(get_updatable_files)"
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "Nothing is updatable."
exit 0
fi
echo "Displaying notification in lower-right of screen..."
{ #display notification in lower-right
output="$(yad --form --text='Pi-Apps updates available.' --separator='\n' \
--on-top --skip-taskbar --undecorated --close-on-unfocus \
--geometry=260+$((screen_width-262))+$((screen_height-150)) \
--image="${DIRECTORY}/icons/logo-64.png" \
--field='Never show again':CHK FALSE \
--button="Details!${DIRECTORY}/icons/info.png":0 --button="Close!${DIRECTORY}/icons/exit.png":2)"
button=$?
#if Details not clicked, and checkbox clicked, launch a dialog to change the update interval
if [ $button != 0 ];then
if [ "$(echo "$output" | grep . )" == TRUE ];then
#User checked the 'Never show again' box, so ask to change update interval
curval="$(cat "${DIRECTORY}/data/settings/Check for updates")"
[ -z "$curval" ] && curval="$(cat "${DIRECTORY}/etc/setting-params/Check for updates" | grep -v '#' | head -n1)"
params="$(cat "${DIRECTORY}/etc/setting-params/Check for updates" | grep -v '#')"
params="$(echo "$params" | grep -x "$curval" | tr '\n' '!')!$(echo "$params" | grep -vx "$curval" | tr '\n' '!')"
params="$(echo -e "$params" | sed 's/!!/!/g' | sed 's/!$//g' | sed 's/^!//g')"
echo "Params: '$params'"
output="$(yad --center --title='Change Pi-Apps update interval' --width=440 \
--form --separator='\n' --window-icon="${DIRECTORY}/icons/logo.png" \
--text="You just requested for Pi-Apps to <i>never check for updates</i> on boot."$'\n'"Are you sure? If so, change the update interval to "\""<b>Never</b>"\"" below." \
--field='Update interval: ':CB "$params" \
--button=Cancel!"${DIRECTORY}/icons/exit.png":1 \
--button=Save!"${DIRECTORY}/icons/check.png":0)"
button=$?
output="$(echo "$output" | grep .)"
if [ $button == 0 ];then #save button clicked
echo "$output" > "${DIRECTORY}/data/settings/Check for updates"
fi
fi
#since Details was not clicked, exit now
exit 0
fi
}
list_updates_gui
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "User did not allow anything to be updated."
exit 0
fi
update_now_gui
#display notification saying that pi-apps updates are complete
yad --form --text='Pi-Apps updates complete.' \
--on-top --skip-taskbar --undecorated --close-on-unfocus \
--geometry=260+$((screen_width-262))+$((screen_height-150)) \
--image="${DIRECTORY}/icons/logo-64.png" \
--button="Close!${DIRECTORY}/icons/exit.png":1
elif [ "$runmode" == 'get-status' ];then #Check if anything was deemed updatable the last time updates were checked for.
if [ -s "${DIRECTORY}/data/update-status/updatable-files" ] || [ -s "${DIRECTORY}/data/update-status/updatable-apps" ];then
exit 0
else
exit 1
fi
elif [ "$runmode" == 'set-status' ];then #check for updates and write updatable apps/files to "${DIRECTORY}/data/update-status"
updatable_apps="$(get_updatable_apps)"
updatable_files="$(get_updatable_files)"
echo "$updatable_apps" | grep . > "${DIRECTORY}/data/update-status/updatable-apps"
echo "$updatable_files" | grep . > "${DIRECTORY}/data/update-status/updatable-files"
"$0" get-status &>/dev/null
exit $?
elif [ "$runmode" == gui ];then #dialog-list of updatable apps, with checkboxes and an Update button
updatable_apps="$(get_updatable_apps)"
updatable_files="$(get_updatable_files)"
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "Nothing is updatable."
exit 0
fi
list_updates_gui
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "User did not allow anything to be updated."
exit 0
fi
update_now_gui
elif [ "$runmode" == gui-yes ];then #update now without asking for confirmation
updatable_apps="$(get_updatable_apps)"
updatable_files="$(get_updatable_files)"
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "Nothing is updatable."
exit 0
fi
update_now_gui
elif [ "$runmode" == cli ];then #return list of updatable apps, and ask the user permission to update
updatable_apps="$(get_updatable_apps)"
updatable_files="$(get_updatable_files)"
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "Nothing is updatable."
exit 0
fi
echo "These apps can be updated:"
echo -n "$updatable_apps" | sed 's/^/ - /g'
echo
echo "These files can be updated:"
echo -n "$updatable_files" | sed 's/^/ - /g'
echo
read -p "Update now? [Y/n] " answer
if [ "$answer" == n ] || [ "$answer" == N ];then
exit 0
fi
update_now_cli
elif [ "$runmode" == cli-yes ];then #update now without asking for confirmation
updatable_apps="$(get_updatable_apps)"
updatable_files="$(get_updatable_files)"
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
echo "Nothing is updatable."
exit 0
fi
echo "These apps can be updated:"
echo -n "$updatable_apps" | sed 's/^/ - /g'
echo "These files can be updated:"
echo -n "$updatable_files" | sed 's/^/ - /g'
echo
update_now_cli
else
error "updater: unknown run mode."
fi
exit 0
} #this curly brace at start and end of script prevents issues when this script updates itself. See: https://stackoverflow.com/a/19430939