937 lines
34 KiB
Bash
937 lines
34 KiB
Bash
#!/bin/bash
|
|
{ #prevents errors if script was modified while in use
|
|
|
|
function error {
|
|
echo -e "\e[91m$1\e[39m" 1>&2
|
|
exit 1
|
|
}
|
|
|
|
if [[ $(id -u) == 0 ]]; then
|
|
error "Artian-Apps is not designed to be run as root! Please try again as a regular user."
|
|
fi
|
|
|
|
[ -z "$DIRECTORY" ] && DIRECTORY="$(readlink -f "$(dirname "$0")")"
|
|
|
|
#if being sourced, ensure DIRECTORY variable set
|
|
if [ -z "$DIRECTORY" ] || [ "$DIRECTORY" == "$HOME" ] || [ ! -d "$DIRECTORY" ] || [ ! -f "${DIRECTORY}/api" ] || [ ! -f "${DIRECTORY}/gui" ];then
|
|
echo "DIRECTORY variable must be set to a valid Artian-apps folder. Default folder: $HOME/Artian-apps"
|
|
return 1
|
|
fi
|
|
|
|
# runonce-entries is run in the updater, runonce requires that all api functions be available to subprocess (like is done in the gui script)
|
|
#for the will_reinstall() and list_intersect() functions
|
|
set -a #make all functions in the api available to subprocesses
|
|
source "${DIRECTORY}/api" || error "failed to source ${DIRECTORY}/api"
|
|
|
|
get_date() {
|
|
#number of days since 1/1/1970
|
|
echo $(($(date --utc +%s)/86400))
|
|
}
|
|
|
|
check_repo() { #download Artian-apps repository to the update/Artian-apps folder
|
|
if [ "$speed" == fast ];then
|
|
return 0
|
|
fi
|
|
echo -n "Checking for online changes... " 1>&2
|
|
|
|
#if the updater script exists in update folder, then just git pull to save time
|
|
if [ -s "${DIRECTORY}/update/Artian-apps/updater" ];then
|
|
cd "${DIRECTORY}/update/Artian-apps"
|
|
git pull -q 1>&2 || rm -rf "${DIRECTORY}/update"
|
|
fi | grep -v "Already up to date." 1>&2
|
|
|
|
git_url="$(cat "${DIRECTORY}/etc/git_url" || echo 'https://git.fdc.abrish.ir/a.kamyar/PublicFiles/Artian-Apps')"
|
|
|
|
#if updater script still does not exist then do a git clone
|
|
if [ ! -s "${DIRECTORY}/update/Artian-apps/updater" ];then
|
|
#keep trying until success
|
|
while true;do
|
|
rm -rf "${DIRECTORY}/update"
|
|
mkdir -p "${DIRECTORY}/update" && cd "${DIRECTORY}/update" || error "failed to create and enter the update directory!"
|
|
|
|
#clone the repository, and display an error message if it fails
|
|
if git clone --depth=1 "$git_url" 1>&2 ;then
|
|
echo "Done" 1>&2
|
|
break #exit the loop
|
|
else
|
|
echo -e "\n${DIRECTORY}/updater: failed to download Artian-Apps repository!\nTrying again in 60 seconds." 1>&2
|
|
sleep 60
|
|
fi
|
|
done
|
|
else
|
|
echo "Done" 1>&2
|
|
fi
|
|
cd #don't exit the function with the current directory being "${DIRECTORY}/update"
|
|
}
|
|
|
|
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
|
|
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
|
|
warning "Something isn't right. Does '${DIRECTORY}/data/settings/Check for updates' exist?" 1>&2
|
|
else
|
|
echo -e "\e[91mUnrecognized update interval! \e[0m" 1>&2
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
list_files() { #list all files on Artian-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/Artian-apps" || error "Failed to enter update directory!"
|
|
local updatefiles="$(find . -type f | cut -c 3- | grep -v '^\.git/\|^\.github/\|^apps/\|^data/')"
|
|
|
|
#list all files in main folder
|
|
cd "${DIRECTORY}"
|
|
local localfiles="$(find . -type f | cut -c 3- | grep -v '^\.git/\|^\.github/\|^apps/\|^update/\|^data/\|^logs/\|^xlunch/')"
|
|
|
|
echo -e "${localfiles}\n${updatefiles}" | sort | uniq
|
|
cd $HOME
|
|
}
|
|
|
|
get_updatable_files() { #sets the updatable_files variable
|
|
local updatable_files
|
|
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
|
|
#Use rsync for faster speed, if available
|
|
if command -v rsync >/dev/null ;then
|
|
updatable_files=$(rsync --exclude="/apps/" --exclude="/.git/" --exclude="/.github/" --exclude="/data/" -ric --dry-run --out-format="%n" "${DIRECTORY}/update/Artian-apps/" "${DIRECTORY}/")
|
|
fi
|
|
if [ "$?" != "0" ] || ! command -v rsync >/dev/null ;then
|
|
#get list of Artian-apps files without absolute paths. Example line: 'etc/terminal-run'
|
|
local file_list="$(list_files)" || exit 1
|
|
|
|
updatable_files='' #the variable to be returned
|
|
local IFS=$'\n'
|
|
for file in $file_list ;do
|
|
|
|
echo -en "Scanning files... $file\e[0K\r" 1>&2
|
|
if [ ! -f "${DIRECTORY}/${file}" ];then
|
|
#file is missing locally - add to updatable_files list
|
|
updatable_files+=$'\n'"${file}"
|
|
|
|
elif [ ! -f "${DIRECTORY}/update/Artian-apps/${file}" ];then
|
|
#file is missing in the update folder - local
|
|
true #do not add to updatable_apps list
|
|
|
|
elif ! files_match "${DIRECTORY}/update/Artian-apps/${file}" "${DIRECTORY}/${file}" ;then
|
|
#files don't match - add to updatable_files list
|
|
updatable_files+=$'\n'"${file}"
|
|
fi
|
|
done
|
|
#remove initial newline character
|
|
updatable_files="${updatable_files:1}"
|
|
fi
|
|
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+="\n'$file' won't be updated - it's listed in data/update-exclusion."
|
|
done
|
|
|
|
echo -e "Scanning files... Done\e[0K" 1>&2
|
|
echo "$updatable_files"
|
|
|
|
#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() { #return a list of updatable apps
|
|
local updatable_apps
|
|
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
|
|
updatable_apps="$(cat "${DIRECTORY}/data/update-status/updatable-apps")"
|
|
else #compare all apps to the ones in the update folder
|
|
|
|
#Use rsync for faster speed, if available
|
|
if command -v rsync >/dev/null ;then
|
|
# requires english locale to be properly enabled and for diff naming convention not to change
|
|
#diff -rq "${DIRECTORY}/apps" "${DIRECTORY}/update/Artian-apps/apps" | sed "\|^Only in ${DIRECTORY}/apps|d" | sed "s;Only in ${DIRECTORY}/update/Artian-apps/apps: ;;g" | sed "s;Files ${DIRECTORY}/apps/;;g" | awk -F '/' '{print $1}' | uniq
|
|
# same output, but with rsync which is much simpler. slightly slower
|
|
updatable_apps=$(rsync -ric --dry-run --out-format="%n" "${DIRECTORY}/update/Artian-apps/apps/" "${DIRECTORY}/apps/" | awk -F '/' '{print $1}' | sort | uniq; exit ${PIPESTATUS[0]})
|
|
fi
|
|
|
|
if [ "$?" != "0" ] || ! command -v rsync >/dev/null; then
|
|
updatable_apps=''
|
|
local IFS=$'\n'
|
|
for app in $(list_apps online)
|
|
do
|
|
echo -en "Scanning apps... $app\e[0K\r" 1>&2
|
|
|
|
if [ ! -d "${DIRECTORY}/apps/${app}" ];then
|
|
#if app is missing locally, add to updatable list
|
|
updatable_apps+=$'\n'"${app}"
|
|
|
|
elif ! diff -r "${DIRECTORY}/apps/${app}" "${DIRECTORY}/update/Artian-apps/apps/${app}" -q >/dev/null ;then
|
|
#if app-folder contents don't match, add to updatable list
|
|
updatable_apps+=$'\n'"${app}"
|
|
fi
|
|
done
|
|
updatable_apps="${updatable_apps:1}" #remove initial newline character
|
|
fi
|
|
fi
|
|
echo -e "Scanning apps... Done\e[0K" 1>&2
|
|
echo "$updatable_apps"
|
|
}
|
|
|
|
list_updates_cli () { #from https://unix.stackexchange.com/a/673436
|
|
# little helpers for terminal print control and key input
|
|
ESC=$( printf "\033")
|
|
cursor_blink_on() { printf "${ESC}[?25h"; }
|
|
cursor_blink_off() { printf "${ESC}[?25l"; }
|
|
cursor_to() { printf "${ESC}[$1;${2:-1}H"; }
|
|
print_inactive() { printf "$2 $1 "; }
|
|
print_active() { printf "$2 ${ESC}[7m $1 ${ESC}[27m"; }
|
|
get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
|
|
|
|
local return_value=$1 &>/dev/null
|
|
local -n options=$2 &>/dev/null
|
|
local -n defaults=$3 &>/dev/null
|
|
|
|
local selected=()
|
|
for ((i=0; i<${#options[@]}; i++)); do
|
|
selected+=("true")
|
|
printf "\n"
|
|
done
|
|
|
|
# determine current screen position for overwriting the options
|
|
local lastrow=$(get_cursor_row)
|
|
local startrow=$(($lastrow - ${#options[@]}))
|
|
|
|
# ensure cursor and input echoing back on upon a ctrl+c during read -s
|
|
trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
|
|
cursor_blink_off
|
|
|
|
key_input() {
|
|
local key
|
|
IFS= read -rsn1 key 2>/dev/null >&2
|
|
if [[ $key = "" ]]; then echo enter; fi;
|
|
if [[ $key = $'\x20' ]]; then echo space; fi;
|
|
if [[ $key = "k" ]]; then echo up; fi;
|
|
if [[ $key = "j" ]]; then echo down; fi;
|
|
if [[ $key = $'\x1b' ]]; then
|
|
read -rsn2 key
|
|
if [[ $key = [A || $key = k ]]; then echo up; fi;
|
|
if [[ $key = [B || $key = j ]]; then echo down; fi;
|
|
fi
|
|
}
|
|
|
|
toggle_option() {
|
|
local option=$1
|
|
if [[ ${selected[option]} == true ]]; then
|
|
selected[option]=false
|
|
else
|
|
selected[option]=true
|
|
fi
|
|
}
|
|
|
|
print_options() {
|
|
# print options by overwriting the last lines
|
|
local idx=0
|
|
for option in "${options[@]}"; do
|
|
local prefix="[ ]"
|
|
if [[ ${selected[idx]} == true ]]; then
|
|
prefix="[\e[38;5;46m✔\e[0m]"
|
|
fi
|
|
|
|
cursor_to $(($startrow + $idx))
|
|
if [ $idx -eq $1 ]; then
|
|
print_active "$option" "$prefix"
|
|
else
|
|
print_inactive "$option" "$prefix"
|
|
fi
|
|
((idx++))
|
|
done
|
|
}
|
|
|
|
local active=0
|
|
while true; do
|
|
print_options $active
|
|
|
|
# user key control
|
|
case $(key_input) in
|
|
space) toggle_option $active;;
|
|
enter) print_options -1; break;;
|
|
up) ((active--));
|
|
if [ $active -lt 0 ]; then active=$((${#options[@]} - 1)); fi;;
|
|
down) ((active++));
|
|
if [ $active -ge ${#options[@]} ]; then active=0; fi;;
|
|
esac
|
|
done
|
|
|
|
# cursor position back to normal
|
|
cursor_to $lastrow
|
|
printf "\n"
|
|
cursor_blink_on
|
|
|
|
eval $return_value='("${selected[@]}")'
|
|
}
|
|
|
|
|
|
list_updates_gui() { #input: updatable_apps and updatable_files variables, output: updatable_apps and updatable_files, with refreshable_apps being a subset of updatable_apps
|
|
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
|
|
|
|
local LIST
|
|
local app
|
|
local compressed_update
|
|
for app in $updatable_apps ;do #generate a yad list for every updatable app
|
|
# get app tooltip
|
|
read -r 2>/dev/null line <"${DIRECTORY}/update/Artian-apps/apps/${app}/description" || line="Description unavailable"
|
|
# if ${app} folder did not exist before and install-${arch}, install, or packages file is in the new update, then this is a "new app"
|
|
# results in applications that only have support for a different architecture to show under the compressed update
|
|
if [ ! -d "${DIRECTORY}/apps/${app}" ] && ( [ -f "${DIRECTORY}/update/Artian-apps/apps/${app}/install-${arch}" ] || [ -f "${DIRECTORY}/update/Artian-apps/apps/${app}/install" ] || [ -f "${DIRECTORY}/update/Artian-apps/apps/${app}/packages" ] ); then
|
|
LIST+="TRUE
|
|
${DIRECTORY}/update/Artian-apps/apps/${app}/icon-24.png
|
|
$app (new app)
|
|
refresh-app:$app
|
|
$line"$'\n'
|
|
# if install-${arch} is in the new update and it was not installable on this system before, then this is a "new app".
|
|
elif [ -f "${DIRECTORY}/update/Artian-apps/apps/${app}/install-${arch}" ] && [ ! -f "${DIRECTORY}/apps/${app}/install-${arch}" ] && [ ! -f "${DIRECTORY}/apps/${app}/install" ] && [ ! -f "${DIRECTORY}/apps/${app}/packages" ]; then
|
|
LIST+="TRUE
|
|
${DIRECTORY}/update/Artian-apps/apps/${app}/icon-24.png
|
|
$app (new app)
|
|
refresh-app:$app
|
|
$line"$'\n'
|
|
# similar to the above. if install script is in the new update and it was not installable on this system before, then this is a "new app".
|
|
elif [ -f "${DIRECTORY}/update/Artian-apps/apps/${app}/install" ] && [ ! -f "${DIRECTORY}/apps/${app}/install-${arch}" ] && [ ! -f "${DIRECTORY}/apps/${app}/install" ] && [ ! -f "${DIRECTORY}/apps/${app}/packages" ]; then
|
|
LIST+="TRUE
|
|
${DIRECTORY}/update/Artian-apps/apps/${app}/icon-24.png
|
|
$app (new app)
|
|
refresh-app:$app
|
|
$line"$'\n'
|
|
# if app will be reinstalled then include it on the list and notify the user that it will be reinstalled
|
|
elif will_reinstall "$app"; then
|
|
LIST+="TRUE
|
|
${DIRECTORY}/update/Artian-apps/apps/${app}/icon-24.png
|
|
$app (<b>new update</b>)
|
|
reinstall-app:$app
|
|
$line"$'\n'
|
|
# if app has been installed in the past then show it on the list. Useful to notify users that their app, which failed to install in the past, may now work.
|
|
elif [ "$(app_status "${app}")" == 'corrupted' ];then
|
|
LIST+="TRUE
|
|
${DIRECTORY}/update/Artian-apps/apps/${app}/icon-24.png
|
|
$app (Try installing it again!)
|
|
refresh-app:$app
|
|
This app failed to install last time. Maybe it will work now! After updating, try installing this app again."$'\n'
|
|
else # app is not new or getting reinstalled, add to compressed update list
|
|
compressed_update+=$'\n'"refresh-app:${app}"
|
|
fi
|
|
done
|
|
|
|
local file
|
|
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
|
|
|
|
LIST+="TRUE
|
|
${mimeicon}
|
|
$file ($mimetype)
|
|
file:$file
|
|
"$'\n'
|
|
|
|
done
|
|
|
|
# add special entry to list for compressed app updates
|
|
if [ ! -z "$compressed_update" ];then
|
|
# display all apps to be updated if less than 11 in number (-lt 12 lines due to the trailing newline)
|
|
if [[ $(wc -l <<< "$compressed_update") -lt 12 ]]; then
|
|
LIST+="TRUE
|
|
${DIRECTORY}/icons/categories/All Apps.png
|
|
Inactive updates
|
|
compressed_apps
|
|
Refreshes the Artian-Apps details for these apps$(echo "${compressed_update}" | tr -d '\n' | sed 's/refresh-app:/, /g' | sed 's/^,/:/g')"$'\n'
|
|
else
|
|
LIST+="TRUE
|
|
${DIRECTORY}/icons/categories/All Apps.png
|
|
Inactive updates
|
|
compressed_apps
|
|
Refreshes the Artian-Apps details for apps that do not need to be reinstalled for this update. (Too many to list here)"$'\n'
|
|
fi
|
|
fi
|
|
|
|
# remove trailing newline
|
|
LIST="${LIST%$'\n'}"
|
|
|
|
if [ -z "$LIST" ];then
|
|
status_green "Nothing to update. Nothing to do!"
|
|
exit 0
|
|
fi
|
|
|
|
#Display a list of everything updatable
|
|
output="$(echo "$LIST" | yad --class Artian-Apps --name "Artian-Apps" --center --title='Artian-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 --column=tip:HD --tooltip-column=5 \
|
|
--button='Cancel'!"${DIRECTORY}/icons/exit.png"!"Close without updating anything":1 \
|
|
--button='Update now'!"${DIRECTORY}/icons/download.png":0 \
|
|
"${args[@]}")" || exit 1
|
|
|
|
# add compressed_update apps to output
|
|
if echo "$output" | grep -q '^compressed_apps' ;then
|
|
output+=$'\n'"${compressed_update}"
|
|
fi
|
|
|
|
#remove empty newlines from output
|
|
output="$(echo "$output" | grep .)"
|
|
|
|
#regenerate list of updatable apps and files, based on what the user selected
|
|
refreshable_apps="$(echo "$output" | grep '^refresh-app:' | sed 's/^refresh-app://g')"
|
|
updatable_apps="$(echo "$output" | grep '^refresh-app:\|^reinstall-app:' | sed 's/^refresh-app://g ; s/^reinstall-app://g')"
|
|
updatable_files="$(echo "$output" | grep '^file:' | sed 's/^file://g')"
|
|
|
|
}
|
|
|
|
update_app() { #first arg is app name
|
|
local app="$1"
|
|
[ -z "$app" ] && error "update_app(): no app specified!"
|
|
status "Updating \e[1m${app}\e[0m\e[96m..."
|
|
echo
|
|
|
|
#Set terminal title
|
|
echo -ne "\e]0;Updating ${app}\a"
|
|
|
|
# check if update app folder exists before doing anything
|
|
# it can happen that executing the updater from the Artian-apps GUI the update folder is missing
|
|
# if the user has no internet or internet issues the update folder will be removed due to the failed git pull and then wait in the loop for a new git clone
|
|
# the GUI updater executes check_repo with the "fast" option so it skips updating the Artian-apps update folder and obtains any previously determined updatable apps and files
|
|
# if we do not check for this then apps will be removed, the older version moved to trash, and then the new version will fail to copy over since it does not exist
|
|
if [ -d "${DIRECTORY}/update/Artian-apps/apps/${app}" ]; then
|
|
local installback=no
|
|
if will_reinstall "$app";then
|
|
installback=yes
|
|
status "$app's install script has been updated. Reinstalling $app..."
|
|
#uninstall it
|
|
"${DIRECTORY}/manage" uninstall "$app" update #report to the app uninstall script that this is an uninstall for the purpose of updating by passing "update"
|
|
|
|
#fix edge case: if app is installed but uninstall script doesn't exist somehow, then pretend app was uninstalled so that the reinstall later will happen noninteractively
|
|
if [ "$(app_status "$app")" == installed ];then
|
|
echo 'uninstalled' > "${DIRECTORY}/data/status/${app}"
|
|
fi
|
|
fi
|
|
|
|
no_status=true refresh_app "$app"
|
|
|
|
failed=false
|
|
if [ "$installback" == 'yes' ];then
|
|
#install the app again
|
|
"${DIRECTORY}/manage" install "$app" update #report to the app install script that this is an install for the purpose of updating by passing "update"
|
|
if [ $? != 0 ]; then
|
|
failed=true
|
|
else
|
|
# click update link only if app is already installed and the update succeeded
|
|
shlink_link "$app" update &
|
|
fi
|
|
fi
|
|
else
|
|
failed=true
|
|
fi
|
|
|
|
if [ "$failed" == 'true' ]; then
|
|
echo -e "\e[91mFailed to update ${app}.\e[0m"
|
|
return 1
|
|
else
|
|
status_green "${app} was updated successfully."
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
refresh_app() { #first arg is app name
|
|
local app="$1"
|
|
[ -z "$app" ] && error "refresh_app(): no app specified!"
|
|
[ "$no_status" != true ] && status -n "Refreshing \e[1m${app}\e[0m\e[96m... "
|
|
#Set terminal title
|
|
[ "$no_status" != true ] && echo -ne "\e]0;Refreshing ${app}\a"
|
|
|
|
#move old program to trash - this allows app-developers to recover their work if update was accidental
|
|
gio trash "${DIRECTORY}/apps/${app}" 2>/dev/null
|
|
#failsafe
|
|
rm -rf "${DIRECTORY}/apps/${app}"
|
|
|
|
#copy new version from update/ to apps/
|
|
cp -rf "${DIRECTORY}/update/Artian-apps/apps/${app}" "${DIRECTORY}/apps/${app}"
|
|
if [ "$?" == "0" ]; then
|
|
[ "$no_status" != true ] && status_green Done
|
|
return 0
|
|
else
|
|
[ "$no_status" != true ] && warning "Failed to refresh app '$app'!"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
update_file() { #first arg is file name
|
|
local file="$1"
|
|
[ -z "$file" ] && error "update_file(): no file specified!"
|
|
mkdir -p "$(dirname "${DIRECTORY}/${file}")"
|
|
|
|
#copy new version
|
|
if cp -f "${DIRECTORY}/update/Artian-apps/${file}" "${DIRECTORY}/${file}" ;then
|
|
[ "$no_status" != true ] && status_green "${file} file was copied successfully."
|
|
return 0
|
|
else
|
|
[ "$no_status" != true ] && echo -e "\e[91mFailed to copy ${DIRECTORY}/update/Artian-apps/${file}! \e[0m"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
update_git() {
|
|
#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/Artian-apps/.git" "${DIRECTORY}" || error "Failed to copy new .git folder!"
|
|
}
|
|
|
|
update_now_cli() { #input: updatable_files and updatable_apps variables
|
|
local IFS=$'\n'
|
|
for file in $updatable_files ;do
|
|
update_file "$file"
|
|
done
|
|
|
|
for app in $updatable_apps ;do
|
|
update_app "$app"
|
|
done
|
|
|
|
update_git
|
|
|
|
[ "$no_status" != true ] && status_green "\nArtian-Apps updates complete."
|
|
true
|
|
}
|
|
|
|
update_now_gui_apps() { # deprecated function that is only here so old updater scripts can call it.
|
|
# this function exists to avoid having to write commands in terminal-run formatting for update_now_gui
|
|
local failed_apps=""
|
|
local IFS=$'\n'
|
|
for app in $updatable_apps ;do
|
|
update_app "$app" || failed_apps+="$app"$'\n'
|
|
done
|
|
|
|
#Set terminal title
|
|
echo -ne "\e]0;Updates complete\a"
|
|
|
|
#load the app list now to reduce launch time
|
|
refresh_app_list &
|
|
|
|
action=update diagnose_apps "$failed_apps"
|
|
}
|
|
|
|
update_now_gui() { #input: updatable_files and updatable_apps variables
|
|
local IFS=$'\n'
|
|
local queue=''
|
|
|
|
#contruct a queue to send to terminal_manage_multi.
|
|
if [ ! -z "$updatable_files" ]; then
|
|
queue+="$(echo "$updatable_files" | sed 's/^/update-file /g')"$'\n'
|
|
fi
|
|
|
|
if [ ! -z "$refreshable_apps" ];then
|
|
queue+="$(echo "$refreshable_apps" | sed 's/^/refresh /g')"$'\n'
|
|
fi
|
|
|
|
if [ ! -z "$updatable_apps" ];then
|
|
# for future reference, the for loop should be used here as opposed to using a function like list_subract
|
|
# list_subtract requires input to be sorted which would be undesirable as it would change update order compared to what is shown to the user in the update GUI
|
|
for app in $updatable_apps ;do
|
|
# if app is not in refreshable_apps, do a regular update
|
|
if ! echo "${refreshable_apps}" | grep -q "^$app"; then
|
|
queue+="update $app"$'\n'
|
|
fi
|
|
done
|
|
fi
|
|
queue="${queue::-1}" #remove final newline character
|
|
queue="$(tac <<<"$queue")" # reverse order because manage daemon reverses it again; affects alphabetization, not priority
|
|
#echo "update_now_gui: queue is '$queue'"
|
|
|
|
#update any files with terminal_manage. If terminal-run fails it will update the files in the background. This is a safety measure to update Artian-apps scripts if terminal fails to launch.
|
|
if [ "$no_update" != true ];then
|
|
terminal_manage_multi "$queue" &
|
|
else
|
|
echo "$queue"
|
|
fi
|
|
}
|
|
|
|
runmode="$1"
|
|
speed="$2"
|
|
|
|
if [ ! -z "$speed" ] && [ "$speed" != 'fast' ];then
|
|
#error "Unknown value for speed: "\""$speed"\"". Allowed value: fast"
|
|
true
|
|
#speed is $2, which may be a yadflag if it's not 'fast'.
|
|
else
|
|
shift #$2 is setting speed, so move everything over so $3 starts yad flags
|
|
fi
|
|
if [ "$runmode" == onboot ];then
|
|
#older Artian-apps installations used an autostarted script with the 'onboot' flag.
|
|
runmode=autostarted
|
|
elif [ "$runmode" == source ];then
|
|
#this script can be sourced to get functions: source "${DIRECTORY}/updater" source
|
|
return 0
|
|
elif [ -z "$runmode" ];then
|
|
#if no runmode was specified, default to gui.
|
|
runmode=gui
|
|
fi
|
|
|
|
#get remaining arguments to pass them to yad
|
|
shift;args=("$@")
|
|
if [ ! -z "$(echo "${args[@]}")" ];then
|
|
echo "Flags to be passed to yad: ${args[*]}"
|
|
fi
|
|
|
|
#runmode values: autostarted, get-status, set-status, gui, gui-yes, cli, cli-yes
|
|
|
|
mkdir -p "${DIRECTORY}/data/update-status"
|
|
|
|
status "\nUpdater mode: $runmode\n"
|
|
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
|
|
status "Won't check for updates today, because 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
|
|
status "No apps have been installed yet, so exiting now."
|
|
exit 0
|
|
fi
|
|
|
|
#wait until internet works
|
|
iter=1
|
|
while ! command wget --spider https://git.fdc.abrish.ir/a.kamyar/PublicFiles/ &>/dev/null ;do
|
|
echo -n "Artian-Apps updater script: no internet connection yet. "
|
|
echo " Waiting 10 seconds..."
|
|
sleep 10
|
|
iter=$(( $iter + 1 ))
|
|
if [[ "$iter" -gt 18 ]]; then
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
check_repo
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files="$(get_updatable_files)"
|
|
|
|
#write to updatable-apps file only if something was changed
|
|
if [ "$updatable_apps" != "$(cat "${DIRECTORY}/data/update-status/updatable-apps" 2>/dev/null)" ];then
|
|
echo "$updatable_apps" | grep . > "${DIRECTORY}/data/update-status/updatable-apps"
|
|
fi
|
|
|
|
#write to updatable-files file only if something was changed
|
|
if [ "$updatable_files" != "$(cat "${DIRECTORY}/data/update-status/updatable-files" 2>/dev/null)" ];then
|
|
echo "$updatable_files" | grep . > "${DIRECTORY}/data/update-status/updatable-files"
|
|
fi
|
|
|
|
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
|
|
status "Nothing is updatable."
|
|
exit 0
|
|
elif [ -z "$updatable_files" ] && [ -z "$(echo "$updatable_apps" | list_intersect "$(list_apps installed)")" ];then
|
|
status "No installed apps are updatable."
|
|
exit 0
|
|
fi
|
|
|
|
#get dimensions of primary screen
|
|
screen_dimensions="$(xrandr --nograb --current | awk -F 'connected |\\+|\\(' '/ connected.*[0-9]+x[0-9]+\+/ && $2 {printf $2 ", "}' | sed -n -e 's/^.*primary //p' | tr 'x+' ' ' | tr ',+' ' ')"
|
|
if [ -z "$screen_dimensions" ];then
|
|
# if screen_dimensions is empty, this could be a single monitor wayland display which does not have the word "primary" in the output
|
|
# workaround is to get the first output returned for the connected display
|
|
screen_dimensions="$(xrandr --nograb --current | awk -F 'connected |\\+|\\(' '/ connected.*[0-9]+x[0-9]+\+/ && $2 {printf $2 ", "}' | tr 'x+' ' ' | tr ',+' ' ')"
|
|
fi
|
|
screen_width="$(awk '{print $1}' <<<"$screen_dimensions")"
|
|
screen_height="$(awk '{print $2}' <<<"$screen_dimensions")"
|
|
|
|
status "Displaying notification in lower-right of screen..."
|
|
{ #display notification in lower-right
|
|
output="$(yad --class Artian-Apps --name "Artian-Apps" --form --text='Artian-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="Skip!${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 --class Artian-Apps --name "Artian-Apps" --center --title='Change Artian-Apps update interval' --width=440 \
|
|
--form --separator='\n' --window-icon="${DIRECTORY}/icons/logo.png" \
|
|
--text="You just requested for Artian-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
|
|
status "User did not allow anything to be updated."
|
|
exit 0
|
|
fi
|
|
|
|
update_now_gui
|
|
|
|
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"
|
|
check_repo
|
|
|
|
#runonce entries
|
|
"${DIRECTORY}/etc/runonce-entries"
|
|
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files="$(get_updatable_files)"
|
|
|
|
#write to updatable-apps file only if something was changed
|
|
if [ "$updatable_apps" != "$(cat "${DIRECTORY}/data/update-status/updatable-apps")" ];then
|
|
echo "$updatable_apps" | grep . > "${DIRECTORY}/data/update-status/updatable-apps"
|
|
fi
|
|
|
|
#write to updatable-files file only if something was changed
|
|
if [ "$updatable_files" != "$(cat "${DIRECTORY}/data/update-status/updatable-files")" ];then
|
|
echo "$updatable_files" | grep . > "${DIRECTORY}/data/update-status/updatable-files"
|
|
fi
|
|
|
|
# this is the only part of any script that is called after running an update using the new files
|
|
# since the new gui merge requires the gui script to be restarted, use this opportunity to kill the old GUI and start a new one
|
|
# only done once with a runonce if current gui format version is not 2
|
|
runonce <<"EOF"
|
|
if [[ "$GUI_FORMAT_VERSION" != 2 ]]; then
|
|
pkill gui && ( "${DIRECTORY}/gui" & )
|
|
echo "stopped the GUI"
|
|
else
|
|
echo "skipped stopping the gui"
|
|
fi
|
|
EOF
|
|
|
|
"$0" get-status &>/dev/null
|
|
exit $?
|
|
|
|
elif [ "$runmode" == gui ];then #dialog-list of updatable apps, with checkboxes and an Update button
|
|
check_repo
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files="$(get_updatable_files)"
|
|
|
|
|
|
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
|
|
status "Nothing is updatable."
|
|
exit 0
|
|
fi
|
|
|
|
list_updates_gui
|
|
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
|
|
status "User did not allow anything to be updated."
|
|
echo
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$use_terminal" == 0 ];then
|
|
update_now_cli
|
|
"${DIRECTORY}/updater" set-status
|
|
else
|
|
update_now_gui
|
|
fi
|
|
|
|
elif [ "$runmode" == gui-yes ];then #update now without asking for confirmation
|
|
check_repo
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files="$(get_updatable_files)"
|
|
|
|
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
|
|
status "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
|
|
check_repo
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files="$(get_updatable_files)"
|
|
echo
|
|
|
|
#if no updatable files and updatebla apps
|
|
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
|
|
status "Everything is up to date."
|
|
exit 0
|
|
fi
|
|
|
|
#if no updatable apps
|
|
if [ -z "$updatable_apps" ];then
|
|
status "All apps are up to date."
|
|
else
|
|
IFS=$'\n'
|
|
updatable_apps_options=($(echo -e "$updatable_apps"))
|
|
status "App updates available.\nUncheck an app to skip updating it."
|
|
list_updates_cli result updatable_apps_options
|
|
|
|
i=0
|
|
IFS=$'\n'
|
|
for app in $updatable_apps; do
|
|
if [ "${result[$i]}" == "true" ]; then
|
|
apps_to_update+="$app\n"
|
|
fi
|
|
i="$(($i+1))"
|
|
done
|
|
|
|
updatable_apps="$(echo -e "$apps_to_update")"
|
|
fi
|
|
|
|
echo
|
|
|
|
#if no updatable files
|
|
if [ -z "$updatable_files" ];then
|
|
status "All files are up to date."
|
|
else
|
|
IFS=$'\n'
|
|
updatable_files_options=($(echo -e "$updatable_files"))
|
|
status "File updates available.\nUncheck a file to skip updating it."
|
|
list_updates_cli result updatable_files_options
|
|
|
|
i=0
|
|
IFS=$'\n'
|
|
for file in $updatable_files; do
|
|
if [ "${result[$i]}" == "true" ]; then
|
|
files_to_update+="$file\n"
|
|
fi
|
|
i="$(($i+1))"
|
|
done
|
|
|
|
updatable_files="$(echo -e "$files_to_update")"
|
|
fi
|
|
|
|
echo
|
|
|
|
if [ ! -z "$updatable_files" ] || [ ! -z "$updatable_apps" ];then
|
|
printf "${ESC}[?25l"
|
|
for i in {6..1}; do
|
|
echo -ne "Updating in: $i\r (Use Ctrl-C to abort) " && sleep 1
|
|
done
|
|
printf "${ESC}[?25h"
|
|
echo -e "\n"
|
|
|
|
update_now_cli
|
|
"${DIRECTORY}/updater" set-status
|
|
else
|
|
status "User did not allow anything to be updated."
|
|
exit 0
|
|
fi
|
|
|
|
|
|
elif [ "$runmode" == cli-yes ];then #update now without asking for confirmation
|
|
check_repo
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files="$(get_updatable_files)"
|
|
echo
|
|
|
|
if [ -z "$updatable_files" ] && [ -z "$updatable_apps" ];then
|
|
status "Nothing is updatable."
|
|
exit 0
|
|
fi
|
|
|
|
if [ -z "$updatable_apps" ];then
|
|
status "No apps can be updated."
|
|
echo
|
|
else
|
|
status "These apps can be updated:"
|
|
echo
|
|
echo "$updatable_apps" | sed 's/^/ - /g'
|
|
echo
|
|
fi
|
|
|
|
if [ -z "$updatable_files" ];then
|
|
status "No files can be updated."
|
|
echo
|
|
else
|
|
status "These files can be updated:"
|
|
echo
|
|
echo "$updatable_files" | sed 's/^/ - /g'
|
|
echo
|
|
fi
|
|
|
|
echo
|
|
update_now_cli
|
|
|
|
else
|
|
error "updater: unknown run mode. Exiting now.\n"
|
|
fi
|
|
|
|
echo
|
|
exit 0
|
|
} #prevents errors if script was modified while in use
|