#!/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 (new update) 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 never check for updates on boot."$'\n'"Are you sure? If so, change the update interval to "\""Never"\"" 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