#!/bin/bash

#This script is the backbone of Artian-Apps. It hosts functions that all other scripts depend upon.
#By default, this file is hard to read. But there's an easy way to fix that. In your code editor, find the option to "Fold All".
#In Geany, this option can be found in the Document toolbar.
#Botspot has found it helpful to create a keyboard shortcut to do this quickly. You may find it helpful too.

#output functions below
error() { #red text and exit 1
  echo -e "\e[91m$1\e[0m" 1>&2
  exit 1
}

warning() { #yellow text
  echo -e "\e[93m\e[5m◢◣\e[25m WARNING: $1\e[0m" 1>&2
}

status() { #cyan text to indicate what is happening
  
  #detect if a flag was passed, and if so, pass it on to the echo command
  if [[ "$1" == '-'* ]] && [ ! -z "$2" ];then
    echo -e $1 "\e[96m$2\e[0m" 1>&2
  else
    echo -e "\e[96m$1\e[0m" 1>&2
  fi
}

status_green() { #announce the success of a major action
  echo -e "\e[92m$1\e[0m" 1>&2
}

generate_logo() { #display colorized Artian-Apps logo in terminal
  #ANSI color codes: https://misc.flogisoft.com/bash/tip_colors_and_formatting
  #Search for unicode characters: https://unicode-search.net - search for "block" and "quadrant"
  
  # foreground colors
  blue1='\e[38;5;75m'
  blue2='\e[38;5;26m'
  blue3='\e[38;5;21m'
  blue4='\e[38;5;93m'
  
  green='\e[38;5;46m'
  darkgreen='\e[38;5;34m'
  red='\e[38;5;197m'
  white='\e[97m'
  black='\e[30m'
  default='\e[39m'
  # background colors
  bg_default='\e[49m'
  bg_black='\e[40m'
  bg_white='\e[107m'
  
  #use simpler logo if OS is Buster or lower - to fix issue https://github.com/Botspot/pi-apps/issues/1441
  local version_id="$(grep 'VERSION_ID=' /etc/os-release | tr -cd '0123456789.')"
  if [[ "$version_id" != *\.* ]] && [ "$version_id" -ge 11 ];then
    echo -e "\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                          \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m  \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄▄\e[49m                                                                                           \e[m
\e[49m                    \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m       \e[48;5;31m         \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m                                                                                     \e[m
\e[49m              \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄\e[48;5;38m              \e[48;5;31m               \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m                                                                               \e[m
\e[49m           \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m                   \e[48;5;31m                    \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄\e[49m                                                                            \e[m
\e[49m         \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m                    \e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[48;5;31m                     \e[38;5;31;48;5;31m▄\e[49m                                                                           \e[m
\e[49m         \e[48;5;38m        \e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄\e[49;38;5;38m▀▀▀▀▀▀▀\e[49m         \e[49;38;5;31m▀▀▀▀▀▀▀\e[38;5;31;48;5;31m▄▄▄▄\e[48;5;31m       \e[38;5;31;48;5;32m▄\e[49m                                                                          \e[m
\e[49m        \e[49;38;5;38m▀▀▀▀▀\e[49;38;5;39m▀\e[49m                                     \e[49;38;5;31m▀▀▀▀▀\e[49m           \e[38;5;15;49m▄▄\e[49m        \e[38;5;15;49m▄▄▄▄▄▄\e[49m   \e[38;5;15;49m▄▄▄▄▄▄▄▄▄▄▄▄\e[49m       \e[38;5;15;49m▄▄\e[49m       \e[38;5;15;49m▄\e[49m         \e[38;5;15;49m▄\e[49m     \e[m
\e[49m                    \e[38;5;38;49m▄▄▄▄▄▄▄▄▄▄▄\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄▄\e[38;5;31;49m▄▄▄▄▄▄▄▄▄▄\e[49m                     \e[38;5;15;49m▄\e[48;5;15m \e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m    \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀▀▀▀▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49;38;5;15m▀▀▀▀\e[48;5;15m  \e[49;38;5;15m▀▀▀▀▀\e[48;5;15m  \e[49m    \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m    \e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m      \e[48;5;15m  \e[49m     \e[m
\e[49m          \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄▄\e[48;5;38m                \e[48;5;31m                 \e[38;5;31;48;5;31m▄▄▄\e[38;5;31;49m▄▄\e[49m           \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m  \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m   \e[48;5;15m  \e[49m       \e[38;5;15;48;5;15m▄▄\e[49m    \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m    \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m  \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m   \e[48;5;15m  \e[49m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m    \e[48;5;15m  \e[49m     \e[m
\e[49m         \e[38;5;38;48;5;38m▄\e[48;5;38m                      \e[48;5;31m                      \e[38;5;31;48;5;31m▄\e[38;5;32;49m▄\e[49m        \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m    \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m   \e[48;5;15m  \e[49m      \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄▄\e[49m    \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m   \e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[48;5;15m \e[49m   \e[48;5;15m  \e[49m  \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m   \e[48;5;15m  \e[49m     \e[m
\e[49m        \e[38;5;38;49m▄\e[48;5;38m                       \e[48;5;31m                       \e[38;5;31;48;5;31m▄\e[49m       \e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄\e[48;5;15m \e[49m  \e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m    \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀\e[49m     \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m  \e[48;5;15m  \e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄▄\e[49m  \e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49m \e[48;5;15m  \e[49m     \e[m
\e[49m        \e[49;38;5;38m▀▀▀▀▀▀▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄▄\e[48;5;38m         \e[48;5;31m           \e[38;5;31;48;5;31m▄▄▄▄\e[49;38;5;31m▀▀▀▀▀▀▀▀▀\e[49m      \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[48;5;15m  \e[49m \e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[48;5;15m  \e[38;5;15;49m▄\e[49m     \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m  \e[49m     \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m  \e[49m     \e[m
\e[49m                               \e[49;38;5;38m▀\e[49m                              \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m         \e[38;5;15;48;5;15m▄\e[49m \e[38;5;15;48;5;15m▄▄\e[49m      \e[49;38;5;15m▀\e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[49m     \e[48;5;15m \e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m         \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49;38;5;15m▀\e[49m       \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m     \e[m
\e[49m          \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄▄\e[49m                        \e[38;5;31;49m▄▄▄▄▄▄\e[38;5;31;48;5;31m▄\e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄\e[49m                                                                           \e[m
\e[49m         \e[38;5;38;48;5;38m▄\e[48;5;38m               \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄\e[49m  \e[38;5;31;49m▄▄▄▄▄\e[38;5;31;48;5;32m▄\e[38;5;31;48;5;31m▄▄\e[48;5;31m              \e[38;5;31;49m▄\e[49m                                                                          \e[m
\e[49m        \e[38;5;38;48;5;38m▄\e[48;5;38m                       \e[48;5;31m                        \e[49m                                                                          \e[m
\e[49m          \e[49;38;5;38m▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m                  \e[48;5;31m                   \e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m                                                                           \e[m
\e[49m               \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m            \e[48;5;31m            \e[38;5;31;48;5;31m▄▄\e[49;38;5;31m▀▀▀\e[49m                                                                                 \e[m
\e[49m                      \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m     \e[48;5;31m      \e[38;5;31;48;5;31m▄\e[38;5;25;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m                                                                                       \e[m
\e[49m                            \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m                                                                                              \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
"
  else
    echo -e "\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                          \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m  \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄▄\e[49m                                                                                           \e[m
\e[49m                    \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m       \e[48;5;31m         \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m                                                                                     \e[m
\e[49m              \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄\e[48;5;38m              \e[48;5;31m               \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m                                                                               \e[m
\e[49m           \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m                   \e[48;5;31m                    \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄\e[49m                                                                            \e[m
\e[49m         \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m                    \e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[48;5;31m                     \e[38;5;31;48;5;31m▄\e[49m                                                                           \e[m
\e[49m         \e[48;5;38m        \e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄\e[49;38;5;38m▀▀▀▀▀▀▀\e[49m         \e[49;38;5;31m▀▀▀▀▀▀▀\e[38;5;31;48;5;31m▄▄▄▄\e[48;5;31m       \e[38;5;31;48;5;32m▄\e[49m                                                                          \e[m
\e[49m        \e[49;38;5;38m▀▀▀▀▀\e[49;38;5;39m▀\e[49m                                     \e[49;38;5;31m▀▀▀▀▀\e[49m           \e[38;5;15;49m▄▄\e[49m        \e[38;5;15;49m▄▄▄▄▄▄\e[49m   \e[38;5;15;49m▄▄▄▄▄▄▄▄▄▄▄▄\e[49m       \e[38;5;15;49m▄▄\e[49m       \e[38;5;15;49m▄\e[49m         \e[38;5;15;49m▄\e[49m     \e[m
\e[49m                    \e[38;5;38;49m▄▄▄▄▄▄▄▄▄▄▄\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄▄\e[38;5;31;49m▄▄▄▄▄▄▄▄▄▄\e[49m                     \e[38;5;15;49m▄\e[48;5;15m \e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m    \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀▀▀▀▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49;38;5;15m▀▀▀▀\e[48;5;15m  \e[49;38;5;15m▀▀▀▀▀\e[48;5;15m  \e[49m    \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m    \e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m      \e[48;5;15m  \e[49m     \e[m
\e[49m          \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄▄\e[48;5;38m                \e[48;5;31m                 \e[38;5;31;48;5;31m▄▄▄\e[38;5;31;49m▄▄\e[49m           \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m  \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m   \e[48;5;15m  \e[49m       \e[38;5;15;48;5;15m▄▄\e[49m    \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m    \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m  \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m   \e[48;5;15m  \e[49m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m    \e[48;5;15m  \e[49m     \e[m
\e[49m         \e[38;5;38;48;5;38m▄\e[48;5;38m                      \e[48;5;31m                      \e[38;5;31;48;5;31m▄\e[38;5;32;49m▄\e[49m        \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m    \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m   \e[48;5;15m  \e[49m      \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄▄\e[49m    \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m   \e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[48;5;15m \e[49m   \e[48;5;15m  \e[49m  \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m   \e[48;5;15m  \e[49m     \e[m
\e[49m        \e[38;5;38;49m▄\e[48;5;38m                       \e[48;5;31m                       \e[38;5;31;48;5;31m▄\e[49m       \e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄\e[48;5;15m \e[49m  \e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m    \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀\e[49m     \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m  \e[48;5;15m  \e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄▄\e[49m  \e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49m \e[48;5;15m  \e[49m     \e[m
\e[49m        \e[49;38;5;38m▀▀▀▀▀▀▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄▄\e[48;5;38m         \e[48;5;31m           \e[38;5;31;48;5;31m▄▄▄▄\e[49;38;5;31m▀▀▀▀▀▀▀▀▀\e[49m      \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[48;5;15m  \e[49m \e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[48;5;15m  \e[38;5;15;49m▄\e[49m     \e[48;5;15m  \e[49m     \e[48;5;15m  \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m  \e[49m     \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m  \e[49m     \e[m
\e[49m                               \e[49;38;5;38m▀\e[49m                              \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m         \e[38;5;15;48;5;15m▄\e[49m \e[38;5;15;48;5;15m▄▄\e[49m      \e[49;38;5;15m▀\e[48;5;15m  \e[49m    \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[49m     \e[48;5;15m \e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m         \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49;38;5;15m▀\e[49m       \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m     \e[m
\e[49m          \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄▄\e[49m                        \e[38;5;31;49m▄▄▄▄▄▄\e[38;5;31;48;5;31m▄\e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄\e[49m                                                                           \e[m
\e[49m         \e[38;5;38;48;5;38m▄\e[48;5;38m               \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄\e[49m  \e[38;5;31;49m▄▄▄▄▄\e[38;5;31;48;5;32m▄\e[38;5;31;48;5;31m▄▄\e[48;5;31m              \e[38;5;31;49m▄\e[49m                                                                          \e[m
\e[49m        \e[38;5;38;48;5;38m▄\e[48;5;38m                       \e[48;5;31m                        \e[49m                                                                          \e[m
\e[49m          \e[49;38;5;38m▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m                  \e[48;5;31m                   \e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m                                                                           \e[m
\e[49m               \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m            \e[48;5;31m            \e[38;5;31;48;5;31m▄▄\e[49;38;5;31m▀▀▀\e[49m                                                                                 \e[m
\e[49m                      \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m     \e[48;5;31m      \e[38;5;31;48;5;31m▄\e[38;5;25;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m                                                                                       \e[m
\e[49m                            \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m                                                                                              \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
\e[49m                                                                                                                                  \e[m
"
  fi
}
#end of output functions

add_english() { #add en_US locale for more accurate error
  if [ "$(cat /usr/share/i18n/SUPPORTED | grep -o 'en_US.UTF-8' )" == "en_US.UTF-8" ]; then 
    locale=$(locale -a | grep -oF 'en_US.utf8')
    if [ "$locale" != 'en_US.utf8' ]; then
      status "Adding en_US locale for better logging... "
      sudo sed -i '/en_US.UTF-8/s/^#[ ]//g' /etc/locale.gen
      sudo locale-gen
    fi
  else
    warning "en_US locale is not available on your system. This may cause bad logging experience."
  fi
  export LANG="en_US.UTF-8"
  export LANGUAGE="en_US.UTF-8"
  export LC_ALL="en_US.UTF-8"
}

#package functions
package_info() { #list everything dpkg knows about the $1 package. Note: the package has to be installed for this to show anything.
  local package="$1"
  [ -z "$package" ] && error "package_info(): no package specified!"
  #list lines in /var/lib/dpkg/status between the package name and the next empty line (empty line is then removed)
  sed -n -e '/^Package: '"$package"'$/,/^$/p' /var/lib/dpkg/status | head -n -1
  true #this may exit with code 141 if the pipe was closed early (to be expected with grep -v)
}

package_installed() { #exit 0 if $1 package is installed, otherwise exit 1
  local package="$1"
  [ -z "$package" ] && error "package_installed(): no package specified!"
  #find the package listed in /var/lib/dpkg/status
  #package_info "$package"
  
  #directly search /var/lib/dpkg/status
  grep "^Package: $package$" /var/lib/dpkg/status -A 1 | tail -n 1 | grep -q 'Status: install ok installed'
}

package_available() { #determine if the specified package-name exists in a repository
  local package="$1"
  [ -z "$package" ] && error "package_available(): no package name specified!"
  #using find and grep to do this is nearly instantaneous, rather than apt-cache which takes several seconds
  local IFS=$'\n'
  for file in $(find /var/lib/apt/lists -maxdepth 1 -type f -name "*_Packages") ;do
    grep -q "^Package: $package$" "$file" && break
  done
}

package_dependencies() { #outputs the list of dependencies for the $1 package
  local package="$1"
  [ -z "$package" ] && error "package_dependencies(): no package specified!"
  
  #find the package listed in /var/lib/dpkg/status
  package_info "$package" | grep '^Depends: ' | sed 's/^Depends: //g'
}

package_latest_version() { #returns the latest available versions of the specified package-name. Doesn't matter if it's installed or not.
  local package="$1"
  [ -z "$package" ] && error "package_latest_version(): no package specified!"
  
  # use slower but more accurate apt list command to get package version for current architecture
  apt-cache policy "$package" 2>/dev/null | grep "Candidate: " | awk '{print $2}'
  #grep -rx "Package: $package" /var/lib/apt/lists --exclude="lock" --exclude-dir="partial" --after 4 | grep -o 'Version: .*' | awk '{print $2}' | sort -rV | head -n1
}

package_is_new_enough() { #check if the $1 package has an available version greater than or equal to $2
  local package="$1"
  [ -z "$package" ] && error "package_is_new_enough(): no package specified!"
  
  local compare_version="$2"
  [ -z "$package" ] && error "package_is_new_enough(): no comparison version number specified!"
  
  #determine the latest available version for the specified package
  local package_version="$(package_latest_version "$package")"
  
  #if version value not found, return 1 now
  if [ -z "$package_version" ];then
    return 1
  fi
  
  #given both the package_version and compare_version, see if the greater of the two is the available package's version
  if [ "$(echo "$package_version"$'\n'"$compare_version" | sort -rV | head -n1)" == "$package_version" ];then
    #if so, indicate success
    return 0
  else
    return 1
  fi
}

anything_installed_from_repo() { #Given an apt repository URL, determine if any packages from it are currently installed
  [ -z "$1" ] && error "anything_installed_from_repo: A repository URL must be specified."
  
  #user input repo-url. Remove 'https://', and translate '/' to '_' to conform to apt file-naming standard
  local url="$(echo "$1" | sed 's+.*://++g' | tr '/' '_')"
  
  #find all package-lists pertaining to the url
  local repofiles="$(ls /var/lib/apt/lists/*_Packages | grep "$url")"
  
  #for every repo-file, check if any of them have an installed file
  local found=0
  local IFS=$'\n'
  local repofile
  for repofile in $repofiles ;do
    #search the repo-file for installed packages
    grep '^Package' "$repofile" | awk '{print $2}' | while read -r package ;do
      if package_installed "$package" ;then
        echo "Package installed: $package"
        exit 1
      fi
    done #if exit code is 1, search was successful. If exit code is 0, no packages from the repo were installed.
    
    found=$?
    
    if [ $found == 1 ];then
      break
    fi
  done
  
  #return an exit code
  if [ $found == 1 ];then
    return 0
  else
    return 1
  fi
}

remove_repofile_if_unused() { #Given a sources.list.d file, delete it if nothing from that repository is currently installed. Deletion skipped if $2 is 'test'
  local file="$1"
  local testmode="$2"
  local key="$3"
  [ -z "$file" ] && error "remove_repo_if_unused: no sources.list.d file specified!"
  #return now if the list file does not exist
  [ -f "$file" ] || return 0
  
  #determine what repo-urls are in the file
  local urls="$(cat "$file" | grep -v '^#' | tr ' ' '\n' | grep '://')"
  
  #there could be multiple urls in one file. Check each url and set the in_use variable to 1 if any packages are found
  local IFS=$'\n'
  local in_use=0
  local url
  for url in $urls ;do
    if anything_installed_from_repo "$url" >/dev/null;then
      in_use=1
      break
    fi
  done
  
  if [ "$in_use" == 0 ] && [ "$testmode" == test ];then
    echo "The given repository is not in use and can be deleted:"$'\n'"$file" 1>&2
  elif [ "$in_use" == 0 ];then
    status "Removing the $(basename "$file" | sed 's/.list$//g') repo as it is not being used"
    sudo rm -f "$file"
    [ -f "$key" ] && sudo rm -f "$key" || true
  fi
  
}

#apt functions
apt_lock_wait() { #Wait until other apt processes are finished before proceeding
  #make sure english locale is added first
  add_english

  #check if sudo needs a password currently. this prevents sudo asking for a password in the below fuser command which would have "Waiting until APT locks are released... " written after it after 5 seconds
  #the result would look like something in the terminal to a user "[sudo] password for USER: Waiting until APT locks are released... " which might be confusing
  #this is very often the first command that Artian-apps scripts run that requires sudo privileges from the user
  #passwordless sudo (like on piOS) will always skip the contents of the if
  if ! sudo -n true ; then
    # sudo needs a password so prompt the user to give one before running other commands
    sudo echo > /dev/null
  fi

  #in a background subprocess, after 5 seconds, say "Waiting until APT locks are released... "
  (sleep 5; echo -n "Waiting until APT locks are released... ") &
  local pid=$!
  
  while [ ! -z "$(sudo fuser /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/log/unattended-upgrades/unattended-upgrades.log /var/lib/dpkg/lock-frontend 2>/dev/null)" ];do
    sleep 1
  done
  
  #Try to install a non-existent package to see if apt fails due to a lock-file. Repeat until no errors mention 'Could not get lock'
  while sudo -E apt install lkqecjhxwqekc 2>&1 | grep -q 'Could not get lock' ;do
    sleep 1
  done
  
  #If the background process finished, then that means the "waiting until" message was displayed. This means the kill command will return 1, so echo Done
  kill $pid &>/dev/null || echo "Done"
}

less_apt() { #remove unwanted lines from apt output
  grep --line-buffered -v "apt does not have a stable CLI interface.\|Reading package lists...\|Building dependency tree\|Reading state information...\|Need to get\|Selecting previously unselected package\|Preparing to unpack\|Setting up \|Processing triggers for \|^$"
}

apt_update() { #run an apt update with error-checking and minimal output
  apt_lock_wait
  
  status "Running \e[7msudo apt update\e[27m..."
  output="$(sudo -E apt update --allow-releaseinfo-change "$@" 2>&1 | less_apt | tee /dev/stderr)"
  exitcode=$?
  status "apt update complete."
  
  #inform user about autoremovable packages
  if [ ! -z "$(echo "$output" | grep 'autoremove to remove them' )" ];then
    echo -e "\e[33mSome packages are unnecessary.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt autoremove\e[0m."
  fi
  
  #inform user packages are upgradeable
  if [ ! -z "$(echo "$output" | grep 'packages can be upgraded' )" ];then
    echo -e "\e[33mSome packages can be upgraded.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt full-u\e[0mpg\e[4mrade\e[0m."
  elif [ ! -z "$(echo "$output" | grep 'package can be upgraded' )" ];then
    echo -e "\e[33mOne package can be upgraded.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt full-u\e[0mpg\e[4mrade\e[0m."
  fi
  
  #exit on apt error
  errors="$(echo "$output" | grep '^[(E)|(Err]:')"
  if [ $exitcode != 0 ] || [ ! -z "$errors" ];then
    echo -e "\e[91mFailed to run \e[4msudo apt update\e[0m\e[39m!"
    echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
    
    #run some apt error diagnosis
    echo "$output"
    exit 1
  fi
  return 0
}

repo_add() { #add local packages to the /tmp/Artian-apps-local-packages repository
  #given local deb file(s), make a local apt repository in /tmp/Artian-apps-local-packages
  
  #see: https://unix.stackexchange.com/questions/87130/how-to-quickly-create-a-local-apt-repository-for-random-packages-using-a-debian
  #and: https://serverfault.com/questions/447457/use-apt-get-source-on-a-debian-repo-without-using-etc-apt-source-list
  #and: https://askubuntu.com/questions/382664/use-custom-directory-for-apt-get
  
  #use this flag for apt commands to use the local repository: -o Dir::Etc::SourceList=/tmp/Artian-apps-local-packages/source.list
  
  #ensure the repo-folder exists
  mkdir -p /tmp/Artian-apps-local-packages || error "repo_add(): failed to create folder /tmp/Artian-apps-local-packages"
  
  #move every mentioned deb file to it
  for file in "$@"; do
    mv -f "$file" /tmp/Artian-apps-local-packages || error "repo_add(): failed to move '$file' to the repository: /tmp/Artian-apps-local-packages"
  done
}

repo_refresh() { #index the Artian-apps local apt repository
  
  [ -d /tmp/Artian-apps-local-packages ] || error "repo_update(): cannot index the repository - it's missing! /tmp/Artian-apps-local-packages"
  
  #index the repository by creating a Packages file
  (cd /tmp/Artian-apps-local-packages && apt-ftparchive packages . > Packages) || error "repo_update(): apt-ftparchive failed to index the repository: /tmp/Artian-apps-local-packages
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
  #Make sure the Packages file actually exists
  [ -f /tmp/Artian-apps-local-packages/Packages ] || error "repo_update(): apt-ftparchive failed to index the repository: /tmp/Artian-apps-local-packages
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
  
  #by default, apt-ftparchive will generate lines like "Filename: ./package-name". This seemed to have caused one error-report, so we remove the "./" to hopefully solve the problem.
  sed -i 's+^Filename: \./+Filename: +g' /tmp/Artian-apps-local-packages/Packages
  
  # set repo origin name to Artian-apps-local-packages - see PR #1986
  echo 'APT::FTPArchive::Release {
Origin "Artian-apps-local-packages";
};' > /tmp/Artian-apps-local-packages/aptftp.conf
  
  #hash the repository by creating a Release file
  (cd /tmp/Artian-apps-local-packages && apt-ftparchive -c=/tmp/Artian-apps-local-packages/aptftp.conf release . > Release) || error "repo_update(): apt-ftparchive failed to hash the repository: /tmp/Artian  -apps-local-packages
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
  
  #create a source.list for the repository - add Artian-apps-local-packages to the top, then add lines from /etc/apt/sources.list
  rm -f /tmp/Artian-apps-local-packages/source.list
  echo "deb [trusted=yes] file:/tmp/Artian-apps-local-packages/ ./" > /tmp/Artian-apps-local-packages/source.list
  cat /etc/apt/sources.list >> /tmp/Artian-apps-local-packages/source.list
  
  # add priority to all Artian-apps-local-packages so that they always have higher priority than repo versions - see PR #1986
  # this has no affect when there the Artian-apps-local-packages repo does not exist
  echo 'Package: *
Pin: Release o=Artian-apps-local-packages
Pin-Priority: 990' | sudo tee /etc/apt/preferences.d/Artian-apps-local-packages >/dev/null
}

repo_rm() { #remove the local apt repository
  #wait for other operations to finish before continuing - hopefully this will solve cases when the Artian-apps local repository was removed unexpectedly by a second process
  apt_lock_wait
  
  rm -rf /tmp/Artian-apps-local-packages || sudo rm -rf /tmp/Artian-apps-local-packages || error "repo_rm(): failed to remove the local repository: /tmp/Artian-apps-local-packages"
  
  #Also remove broken symbolic link to /tmp/Artian-apps-local-packages/./Packages
  sudo rm -f /var/lib/apt/lists/_tmp_Artian-apps-local-packages_._Packages
}

app_to_pkgname() { #given an app-name, convert it to a unique, valid package-name that starts with 'Artian-apps-'
  local app="$1"
  [ -z "$app" ] && error "app_to_pkgname(): no app-name specified"
  
  echo "Artian-apps-$(echo "$app" | md5sum | cut -c1-8 | awk '{print $1}')"
}

install_packages() { #Make some packages dependencies of the $app app. Package-names, regex, filenames, and urls are supported.
  
  #convert input array to newline-separated string
  local IFS=' '
  for arg in "$@"; do
    local packages+="$arg
"
  done
  packages="${packages::-1}" #remove final empty newline
  
  #the $app variable must contain something
  [ -z "$app" ] && error 'install_packages function can only be used by apps to install packages. (the $app variable was not set)'
  
  status "Will install these packages: $(tr '\n' ' ' <<<"$packages")"
  
  #array-variable to store custom apt options (for local repositories)
  local apt_flags=()
  
  #variable to remember if the Artian-apps-local-packages repository is being used
  local using_local_packages=0
  repo_rm #remove the local repo, just in case the last operation left it in an unrecoverable state.
  
  #handle regex, urls, local packages
  IFS=$'\n'
  local package
  for package in $packages ;do
    
    #handle local packages (package-name starts with /)
    if [[ "$package" == /* ]];then
      
      #status "Handling local package $package"
      
      [ -f "$package" ] || error "install_packages(): Local package does not exist! ($package)"
      
      #determine the package name from the filename
      packagename="$(dpkg-deb -I "$package" | grep "^ Package:" | awk '{print $2}')"
      packageversion="$(dpkg-deb -I "$package" | grep "^ Version:" | awk '{print $2}')"
      [ -z "$packagename" ] && error "install_packages(): failed to determine a package-name for the file '$package'"
      [ -z "$packageversion" ] && error "install_packages(): failed to determine a package-version for the file '$package'"
      
      #add this local package to the Artian-apps-local-packages repository
      repo_add "$package" || return 1
      using_local_packages=1 #remember that the Artian-apps-local-packages repository is being used
      
      #replace package filename with name of package
      packages="$(echo "$packages" | sed "s|$package|$packagename (>= $packageversion)|")"
      
    #handle urls
    elif [[ "$package" == *://* ]];then
      
      #status "Handling url: $package"
      
      local filename="/tmp/$(basename "$package")"
      
      #add .deb extension if filename doesn't end with it.
      if [ "${filename: -4}" != ".deb" ]; then
        status "$filename is not ending with .deb, renaming it to '${filename}.deb'..." 
        local filename="${filename}.deb"
      fi
      
      wget -O "$filename" "$package" || return 1
      
      #determine the package name from the filename
      packagename="$(dpkg-deb -I "$filename" | grep "^ Package:" | awk '{print $2}')"
      packageversion="$(dpkg-deb -I "$filename" | grep "^ Version:" | awk '{print $2}')"
      [ -z "$packagename" ] && error "install_packages(): failed to determine a package-name for the file '$filename'"
      [ -z "$packageversion" ] && error "install_packages(): failed to determine a package-version for the file '$filename'"
      
      #add this local package to the Artian-apps-local-packages repository
      repo_add "$filename" || return 1
      using_local_packages=1 #remember that the Artian-apps-local-packages repository is being used
      
      #replace package url with name of package
      packages="$(echo "$packages" | sed "s|$package|$packagename (>= $packageversion)|")"
      
    #expand regex (package-name contains *)
    elif echo "$package" | grep -q '*' ;then 
      
      status "Expanding regex in '${package}'..."
      
      list="$(apt-cache search "$package" | awk '{print $1}' | grep "$(echo "$package" | tr -d '*')")"
      
      #replace package with expanded list
      packages="$(echo "$packages" | grep -vF "$package")"$'\n'"$list"
    fi
  done
  #now package list shouldn't contain any '*' characters, urls, local filepaths
  if echo "$packages" | grep -q '*';then 
    error "install_packages(): failed to remove all regex from the package list:\n$packages"
  elif [[ "$packages" == *://* ]];then
    error "install_packages(): failed to remove all urls from the package list:\n$packages"
  elif [[ "$packages" == */* ]];then
    error "install_packages(): failed to remove all filenames from the package list:\n$packages"
  fi #package list contains no '*' characters, urls, local filepaths
  
  #change the $packages list from newline-delimited to comma-delimited
  packages="$(tr '\n' ',' <<<"$packages")"
  
  if [ "$using_local_packages" == 1 ];then
    #Initialize the Artian-apps-local-packages repository
    repo_refresh || return 1
    #add this repository to flags to add to apt
    apt_flags+=(-o 'Dir::Etc::SourceList=/tmp/Artian-apps-local-packages/source.list')
  fi
  
  status "Creating an empty apt-package to install the necessary apt packages..."
  
  #to avoid issues with symbols and spaces in app names, we shasum the app name for use in apt
  local package_name="$(app_to_pkgname "$app")"
  
  echo "It will be named: $package_name"
  
  #If this app's dummy deb is already installed, add its dependencies to the list.
  #this allows install_packages() to be used multiple times in an app's script
  if package_installed "$package_name" ;then
    
    local existing_deps="$(package_dependencies "$package_name")"
    status "The $package_name package is already installed. Inheriting its dependencies: $(echo "$existing_deps")"
    packages+=",""$(echo "$existing_deps" | sed 's/, /,/g')"
  fi
  
  { #create dummy apt package that depends on the packages this app requires
    
    #this stores the comma-separated list of dependency packages. It removes duplicate entries.
    local depends="$(echo "$packages" | sed 's/,|/ |/g' | sed 's/|,/| /g' | tr ',' '\n' | sort -u -t' ' -k1,1 | tr '\n' ',' | sed 's/^,//g' | sed 's/,$//g' | sed 's/,/, /g' ; echo)"
    
    rm -rf ~/$package_name ~/$package_name.deb
    mkdir -p ~/$package_name/DEBIAN
    echo "Maintainer: Artian-Apps team
Name: $app
Description: Dummy package created by Artian-apps to install dependencies for the '$app' app
Version: 1.0
Architecture: all
Priority: optional
Section: custom
Depends: $depends
Package: $package_name" > ~/$package_name/DEBIAN/control
    
    #fix error report "dpkg-deb: error: control directory has bad permissions 700 (must be >=0755 and <=0775)"
    #The two zeros fix error report "dpkg-deb: error: control directory has bad permissions 2755 (must be >=0755 and <=0775)"
    sudo chmod -R '00755' ~/$package_name
    
    #display the finished "Depends: " line to the user
    grep --color=never "^Depends: " ~/$package_name/DEBIAN/control
  }
  
  #Skip installing the dummy deb if it is already installed and has an identical control file
  if package_installed "$package_name" && [ "$(package_info "$package_name" | sort | grep -v '^Status: ')" == "$(cat ~/$package_name/DEBIAN/control | sort)" ];then
    echo "$package_name is already installed and no changes would be made. Skipping..."
  else
    
    #Before apt update, check if local repo still exists
    if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/Artian-apps-local-packages/Packages ];then
      error "User error: Uh-oh, the /tmp/Artian-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
    fi
    
    #run an apt update
    apt_update "${apt_flags[@]}" || exit 1
    
    #After apt update, check again if local repo still exists
    if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/Artian-apps-local-packages/Packages ];then
      error "User error: Uh-oh, the /tmp/Artian-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
    fi
    
    #Build .deb file for the dummy package
    local output="$(dpkg-deb --build ~/$package_name 2>&1)"
    if [ $? != 0 ] || [ ! -f ~/$package_name.deb ];then
      echo ""
      echo "$output"
      error "install_packages(): failed to create dummy deb ${package_name}!"
    fi
    
    #install dummy deb
    status "Installing the $package_name package..."
    
    apt_lock_wait
    local output="$(sudo -E apt install -fy --no-install-recommends --allow-downgrades "${apt_flags[@]}" ~/$package_name.deb 2>&1 | less_apt | tee /dev/stderr)"
    status "Apt finished."
    
    errors="$(echo "$output" | grep '^[(E)|(Err]:')"
    if [ ! -z "$errors" ];then
      echo -e "\e[91mFailed to install the packages!\e[39m"
      echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
      echo "$output"
      
      #some error reports seem to indicate that package URLs aren't being properly downloaded. This output aims to solve the mystery.
      if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/Artian-apps-local-packages/Packages ];then
        echo "User error: Uh-oh, the /tmp/Artian-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
      elif [ "$using_local_packages" == 1 ] && echo "$output" | grep 'but it is not installable' ;then
        echo -e "\e[91mThe Artian-Apps Local Repository was being used, and a package seemed to not be available. Here's the Packages file:\e[39m"
        cat /tmp/Artian-apps-local-packages/Packages
        echo -e "Attempting apt --dry-run installation of the problematic package(s) for debugging purposes:\n"
        grep "but it is not installable" <<<"$output" | awk '{print $4}' | uniq | xargs sudo -E apt install -fy --no-install-recommends --allow-downgrades --dry-run "${apt_flags[@]}"
      fi
      
      exit 1
    fi
  fi
  
  rm -f ~/$package_name.deb
  rm -rf ~/$package_name
  
  #delete the local repository if it was used
  if [ "$using_local_packages" == 1 ];then
    repo_rm
  fi
  
  status_green "Package installation complete."
}

purge_packages() { #Allow dependencies of the $app app to be autoremoved.
  #the $app variable must contain something
  [ -z "$app" ] && error 'purge_packages function can only be used by apps to install packages. (the $app variable was not set)'
  
  status "Allowing packages required by the $app app to be uninstalled"
  
  #to avoid issues with symbols and spaces in app names, we shasum the app name for use in apt
  local package_name="$(app_to_pkgname "$app")"
  
  #if dummy deb found/installed
  if package_installed "$package_name" ;then
    echo "These packages were: $(package_dependencies "$package_name")"
    
    status "Purging the $package_name package..."
    
    apt_lock_wait
    local output="$(sudo -E apt purge -y "$package_name" --autoremove 2>&1 | less_apt | tee /dev/stderr)"
    status "Apt finished."
    
    errors="$(echo "$output" | grep '^[(E)|(Err]:')"
    if [ ! -z "$errors" ];then
      echo -e "\e[91mFailed to uninstall the packages!\e[39m"
      echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
      #run some apt error diagnosis
      echo "$output"
      exit 1
    fi
    
  elif [ -f "${DIRECTORY}/data/installed-packages/${app}" ];then
    #legacy pkg-install implementation
    warning "Using the old implementation - an installed-packages file instead of a dummy deb"
    
    local packages="$(cat "${DIRECTORY}/data/installed-packages/${app}" | tr '\n' ' ' | sed 's/  / /g')"
    
    #normal mode
    local output="$(sudo -E apt purge -y $packages 2>&1)"
    exitcode=$?
    
    errors="$(echo "$output" | grep '^[(E)|(Err]:')"
    if [ $exitcode != 0 ] || [ ! -z "$errors" ];then
      echo -e "\e[91mFailed to uninstall the packages!\e[39m"
      echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
      #run some apt error diagnosis
      echo "$output"
      exit 1
    fi
  else
    status "The $package_name package is not installed so there's nothing to do."
  fi
  
  status_green "All packages have been purged successfully."
  rm -f "${DIRECTORY}/data/installed-packages/${app}"
}

get_icon_from_package() { #given a package-name, find all png files that it installed and print the one with the largest file-size.
  [ -z "$1" ] && error "get_icon_from_package(): requires an apt package name"
  
  #Find dependencies of the listed packages and scan them too
  local package=''
  local extra_packages=''
  for package in "$@" ;do
    #for every package specified, look for dependencies to that package that begin with the same name as the original package
    #Example: given the 'shotwell' package, this will find the 'shotwell-common' package, as well as others
    extra_packages+=" $(package_dependencies "$package" | sed 's/, \||/\n/g' | awk '{print $1}' | grep "^$package" | sort | uniq | tr '\n' ' ')"
  done
  dpkg-query -L "$@" $extra_packages 2>/dev/null | grep '\.png$\|\.svg$' | grep '/icons/\|/pixmaps/' | xargs wc -c | grep -v ' total' | sort -nr | head -n1 | sed 's/  / /g' | sed 's/^ //g' | tr ' ' '\n' | tail -n +2
}

ubuntu_ppa_installer() { #setup a PPA on an Ubuntu distro. Arguments: ppa_name
  local ppa_name="$1"
  [ -z "$1" ] && error "ubuntu_ppa_installer(): This function is used to add a ppa to a ubuntu based install but a required input argument was missing."
  local ppa_grep="$ppa_name"
  [[ "${ppa_name}" != */ ]] && local ppa_grep="${ppa_name}/"
  local ppa_added=$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "$ppa_grep" | wc -l)
  if [[ $ppa_added -eq "1" ]]; then
    status "Skipping $ppa_name PPA, already added"
  else
    status "Adding $ppa_name PPA"
    sudo add-apt-repository "ppa:$ppa_name" -y || exit 1
    apt_update || exit 1
  fi
  # check if ppa .list filename does not exist under the current distro codename
  # on a distro upgrade the .list filename is not updated and add-apt-repository can re-use the old filename
  local ppa_dist="$__os_codename"
  local standard_filename="/etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list" 
  if [[ ! -f "$standard_filename" ]] && ls /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-*.list 1> /dev/null; then
    local original_filename="$(ls /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-*.list | head -1)"
    # change the filename to match the current distro codename
    sudo mv "$original_filename" "$standard_filename"
    sudo rm -f "$original_filename".distUpgrade
    sudo rm -f "$original_filename".save
  fi
}

debian_ppa_installer() { #setup a PPA on a Debian distro. Arguments: ppa_name distribution key
  local ppa_name="$1"
  local ppa_dist="$2"
  local key="$3"
  [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] && error "debian_ppa_installer(): This function is used to add a ppa to a debian based install but a required input argument was missing."
  local ppa_grep="$ppa_name"
  [[ "${ppa_name}" != */ ]] && local ppa_grep="${ppa_name}/ubuntu ${ppa_dist}"
  local ppa_added=$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "$ppa_grep" | wc -l)
  if [[ $ppa_added -eq "1" ]]; then
    status "Skipping $ppa_name PPA, already added"
  else
    status "Adding $ppa_name PPA"
    echo "deb https://ppa.launchpadcontent.net/${ppa_name}/ubuntu ${ppa_dist} main" | sudo tee /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list || error "Failed to add repository to sources.list!"
    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys "$key"
    if [ $? != 0 ];then
      sudo rm -f /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list
      error "Failed to sign the $ppa_name PPA!"
    fi
    apt_update || exit 1
  fi
}

adoptium_installer() {
  status "Adding Adoptium repository:"

  echo "- public key -> keyring"
  rm -f /tmp/adoptium-public-key /tmp/adoptium-archive-keyring.gpg
  wget -O /tmp/adoptium-public-key https://adoptium.jfrog.io/artifactory/api/security/keypair/default-gpg-key/public
  gpg --no-default-keyring --keyring /tmp/adoptium-keyring.gpg --import /tmp/adoptium-public-key
  rm -f /tmp/adoptium-public-key

  echo " - keyring -> GPG key"
  gpg --no-default-keyring --keyring /tmp/adoptium-keyring.gpg --export --output /tmp/adoptium-archive-keyring.gpg
  rm -f /tmp/adoptium-keyring.gpg

  echo " - Moving GPG key to /usr/share/keyrings"
  sudo mv -f /tmp/adoptium-archive-keyring.gpg /usr/share/keyrings

  echo " - Creating /etc/apt/sources.list.d/adoptium.list"
  case "$__os_codename" in
  bionic | focal | jammy | buster | bullseye | bookworm)
    echo "deb [signed-by=/usr/share/keyrings/adoptium-archive-keyring.gpg] https://adoptium.jfrog.io/artifactory/deb $__os_codename main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
    ;;
  *)
    # use bionic target name explicitly for any other OS as its the oldest LTS target adoptium continues to support
    # all supported adoptium OSs use the same debs so the target specified does not actually matter
    echo "deb [signed-by=/usr/share/keyrings/adoptium-archive-keyring.gpg] https://adoptium.jfrog.io/artifactory/deb bionic main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
    ;;
  esac
  apt_update
  if [ $? != 0 ]; then
    anything_installed_from_repo "https://adoptium.jfrog.io/artifactory/deb"
    if [ $? != 0 ]; then
      # nothing installed from repo, this check is to prevent removing repos which other Artian-apps scripts or the user have used successfully
      # safe to remove
      sudo rm -f /etc/apt/sources.list.d/adoptium.list /usr/share/keyrings/adoptium-archive-keyring.gpg
    fi
    error "Failed to perform apt update after adding Adoptium repository."
  fi
}

pipx_install() {
  # install pipx keeping in mind distro issues
  # pipx <= 0.16.1 is compatible with 3.7 <= python3 < 3.9
  # pipx >= 0.16.1 is compatible with python3 >= 3.7
  # some distros lack pipx entirely
  # some distros (raspbian bullseye specifically) have incompatible combinations of pipx (0.12.3) and python3 (3.9) versions, necessitating pipx to be installed/upgraded from pip
  # pipx 1.0.0 is the first stable release and has some features that we would like to assume are available, install it from pip if the distro package is too old
  if package_available pipx && package_is_new_enough pipx 1.0.0 ;then
    install_packages pipx python3-venv || exit 1
  elif package_is_new_enough python3 3.7 ; then
    install_packages python3-venv || exit 1
    sudo -H python3 -m pip install --upgrade pipx || exit 1
  elif package_available python3.8 ;then
    install_packages python3.8 python3.8-venv || exit 1
    sudo -H python3.8 -m pip install --upgrade pipx || exit 1
  else
    error "pipx is not available so cannot install powerline-shell to python venv"
  fi
  sudo PIPX_HOME=/usr/local/pipx PIPX_BIN_DIR=/usr/local/bin pipx install "$@" || error "Failed to install $* with pipx"
}

pipx_uninstall() {
  sudo PIPX_HOME=/usr/local/pipx PIPX_BIN_DIR=/usr/local/bin pipx uninstall "$@" || error "Failed to uninstall $* with pipx"
}

remove_deprecated_app() { # prompts a user to uninstall a deprecated Artian-apps application and then removes the application folder if it exists
  local app="$1" # on app name
  local removal_arch="$2" # 32 or 64 to only remove one architecture if specified
  [ -z "$app" ] && error "remove_deprecated_app(): requires an Artian-apps app name"
  local app_status="$(app_status "${app}")"
  if [ ! -z "$removal_arch" ] && [ "$arch" == "$removal_arch" ] && [ -d "${DIRECTORY}/apps/$app" ] && [ "$app_status" == "installed" ]; then
    local text="Artian-Apps has deprecated $app for ${removal_arch}-bit OSs which you currently have installed.
Would you like to uninstall it now or leave it installed? You will NOT be able to uninstall $app with Artian-apps later."
    userinput_func "$text" "Uninstall now" "Leave installed"
  elif [ ! -z "$removal_arch" ] && [ "$arch" != "$removal_arch" ]; then
    rm -f "${DIRECTORY}/apps/$app/install-$removal_arch"
    return 0
  elif [ -z "$removal_arch" ] && [ -d "${DIRECTORY}/apps/$app" ] && [ "$app_status" == "installed" ]; then
    local text="Artian-Apps has deprecated $app which you currently have installed.
Would you like to uninstall it now or leave it installed? You will NOT be able to uninstall $app with Artian-apps later."
    userinput_func "$text" "Uninstall now" "Leave installed"
  else
    rm -rf "${DIRECTORY}/apps/$app"
    return 0
  fi
  if [ "$output" == "Uninstall now" ]; then
    "${DIRECTORY}/manage" uninstall "$app"
  fi
  rm -rf "${DIRECTORY}/apps/$app"
  return 0
}

terminal_manage() { # wrapper for the original terminal_manage function to terminal_mange_multi
  action="$1"
  app="$2" #one app name
  
  [ -z "$action" ] && error "terminal_manage(): Must specify an action: either 'install' or 'uninstall' or 'update' or 'refresh'"
  
  terminal_manage_multi "$action $app"
}

terminal_manage_multi() { #function to install/uninstall/update/refresh multiple apps (or filelists) - uses a terminal and refreshes the app list
  queue="$1" #one or multiple actions and app names (or filelists of the format 'filelist:path/to/file:path/to/another/file')
  
  #To prevent multiple simultaneous manage instances, use the 'daemon' mode. This will create a queue of actions that are executed concurrently.
  #The first daemon instance is the 'master' process. Subsequent processes will add the action to the queue and then exit.
  #All output is generated by the 'master' daemon process. Subsequent processes shouldn't open a terminal, because the master one is already open.
  if [ -f "${DIRECTORY}/data/manage-daemon/pid" ] && process_exists $(cat "${DIRECTORY}/data/manage-daemon/pid") ;then
    #The 'master' daemon is already running. Avoid launching a second terminal.
    "${DIRECTORY}/manage" daemon "$queue"
    
  else
    #in a terminal, first get the api functions, display the Artian-apps logo, run the manage script, and refresh the app list if the $pipe variable is set
    "${DIRECTORY}/etc/terminal-run" '
      DIRECTORY="'"$DIRECTORY"'"
      export geometry2="'"$geometry2"'"
      source "${DIRECTORY}/api"
      generate_logo
      
      refresh_list() { #Refresh the current list of apps in the event of a change
        if [ ! -z "'"$pipe"'" ] && [ -p "'"$pipe"'" ];then
          echo -e "\f" > "'"$pipe"'"
          "${DIRECTORY}/preload" yad "'"$prefix"'" > "'"$pipe"'" 2>/dev/null
        fi
      }
      
      "${DIRECTORY}/manage" daemon "'"$queue"'"
      
      #refresh app list
      refresh_list
      
      for i in {30..1} ;do
        echo -en "You can close this window now. Auto-closing in $i seconds.\e[0K\r"
        sleep 1
      done
      ' "Terminal Output"
    
    # Check if terminal-run failed to launch. GUI users don't see any terminal output if it fails (since there is no terminal open) so we need to prompt them with a GUI window
    if [ "$?" != 0 ]; then
      echo -e "Unable to open a terminal.\nDebug output below.\n$(DEBUG=1 "${DIRECTORY}/etc/terminal-run" 2>&1)" | yad --center --window-icon="${DIRECTORY}/icons/logo.png" \
      --width=700 --height=300 --text-info --title="Error occured when calling terminal-run" \
      --image="${DIRECTORY}/icons/error.png" --image-on-top --fontname=12 \
      --button='OK'
      if echo "$queue" | grep -q "^update filelist:" ;then #list of files separated by :
        updatable_apps='' updatable_files="$(echo "$queue" | grep "^update filelist:" | sed 's/^update filelist://g' | tr ':' '\n')" no_status=true update_now_cli
      fi
    fi
  fi
}
#end of apt functions

#flatpak functions
flatpak_install() { #install an app using flatpak
  [ -z "$1" ] && error "flatpak_install(): This function is used to install a flatpak app, but nothing was specified."
  
  #make sure flatpak is installed
  if ! command -v flatpak >/dev/null ;then
    error "flatpak_install(): Could not install $1 because flatpak is not installed!"
  fi
  
  if ! package_is_new_enough flatpak 1.14.4 ;then
    case "$__os_codename" in
    buster)
      debian_ppa_installer "theofficialgman/flatpak-no-bwrap" "bionic" "0ACACB5D1E74E484"
      apt_lock_wait
      sudo apt --only-upgrade install flatpak -y | less_apt
      ;;
    bullseye)
      debian_ppa_installer "theofficialgman/flatpak-no-bwrap" "focal" "0ACACB5D1E74E484"
      apt_lock_wait
      sudo apt --only-upgrade install flatpak -y | less_apt
      ;;
    bionic|focal|jammy)
      ubuntu_ppa_installer "theofficialgman/flatpak-no-bwrap"
      apt_lock_wait
      sudo apt --only-upgrade install flatpak -y | less_apt
      ;;
    esac
  fi
  
  status -n "Flatpak: Adding flathub remote... "
  #Add the flathub remote, first as root, if that fails then try installing as user, while removing unwanted output
  ( sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || error "Flatpak failed to add flathub remote!"  ) | grep --line-buffered -v "Note that the directories 
'/var/lib/flatpak/exports/share'
'$HOME/.local/share/flatpak/exports/share'
are not in the search path set by the XDG_DATA_DIRS environment variable, so
applications installed by Flatpak may not appear on your desktop until the
session is restarted."
  #exit 1 if above code failed
  if [ ${PIPESTATUS[0]} != 0 ];then
    exit 1
  fi
  status_green Done
  
  status -n "Flatpak: installing $1... "
  #Install the specified app, first as root, if that fails then try installing as user, while removing unwanted output
  ( sudo flatpak install flathub "$1" -y || flatpak install flathub "$1" -y || error "Flatpak failed to install $1!" ) | grep --line-buffered -v "Note that the directories 

'/var/lib/flatpak/exports/share'
'$HOME/.local/share/flatpak/exports/share'
are not in the search path set by the XDG_DATA_DIRS environment variable, so
applications installed by Flatpak may not appear on your desktop until the
session is restarted."
  #exit 1 if above code failed
  if [ ${PIPESTATUS[0]} != 0 ];then
    exit 1
  fi
  status_green Done
  
  #Artian-Apps tries to avoid unnecessary reboots at all cost. Flatpak places desktop launchers in /var/lib/flatpak/exports/share/applications, which is not searched by default.
  #This path is added to $XDG_DATA_DIRS on the next reboot, but we don't want to wait for that!
  #If there are files in /var/lib/flatpak/exports/share/applications, and XDG_DATA_DIRS is missing flatpak paths, then bind-mount to /usr/share/applications
  if [[ "$XDG_DATA_DIRS" != */var/lib/flatpak/exports/share* ]] && [ ! -z "$(ls /var/lib/flatpak/exports/share/applications)" ] && [ -z "$(ls /usr/share/applications/flatpak-temporary)" ];then
    sudo mkdir -p /usr/share/applications/flatpak-temporary
    sudo mount --bind /var/lib/flatpak/exports/share/applications /usr/share/applications/flatpak-temporary
  elif [[ "$XDG_DATA_DIRS" == */var/lib/flatpak/exports/share* ]] ;then
    sudo rm -rf /usr/share/applications/flatpak-temporary
  fi
  #Additionally, PiOS Buster had a bug where XDG_DATA_DIRS was missing flatpak's entries due to PiOS mods. Artian-Apps fixes this with a runonce.
  
  true
}

flatpak_uninstall() { #uninstall an app using flatpak
  [ -z "$1" ] && error "flatpak_uninstall(): This function is used to uninstall a flatpak app, but nothing was specified."
  
  #if flatpak is not installed, then skip everything with code 0.
  if ! command -v flatpak >/dev/null ;then
    return 0
  fi
  
  #Only try to remove flatpak app if it's installed.
  if flatpak list | grep -qF "$1" ;then
    sudo flatpak uninstall "$1" -y || flatpak uninstall "$1" -y || error "Flatpak failed to uninstall $1!"
  fi
}
#end of flatpak functions

#app functions
list_apps() { # $1 can be: installed, uninstalled, corrupted, cpu_installable, hidden, visible, online, online_only, local, local_only
  if [ -z "$1" ] || [ "$1" == local ];then
    #list all apps
    ls "${DIRECTORY}/apps"
    
  elif [ "$1" == all ];then
    #combined list of apps, both online and local. Removes duplicate apps from the list.
    echo -e "$(list_apps local)\n$(list_apps online)" | sort | uniq
    
  elif [ "$1" == installed ];then
    #list apps      |   only show      (          list of installed apps                | remove match string  |   basename   )
    list_apps local | list_intersect "$(grep -rx 'installed' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
    
  elif [ "$1" == corrupted ];then
    #list apps      |only show         (          list of corrupted apps                | remove match string  |   basename   )
    list_apps local | list_intersect "$(grep -rx 'corrupted' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
    
  elif [ "$1" == disabled ];then
    #list apps      |    only show     (          list of disabled apps                | remove match string  |   basename   )
    list_apps local | list_intersect "$(grep -rx 'disabled' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
    
  elif [ "$1" == uninstalled ];then
    #list apps that have a status file matching "uninstalled"
    list_apps local | list_intersect "$(grep -rx 'uninstalled' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
    #also list apps that don't have a status file
    list_apps local | list_subtract "$(ls "${DIRECTORY}/data/status")"
    
  elif [ "$1" == have_status ];then
    #list apps that have a status file
    list_apps local | list_intersect "$(ls "${DIRECTORY}/data/status")"
    
  elif [ "$1" == missing_status ];then
    #list apps that don't have a status file
    list_apps local | list_subtract "$(ls "${DIRECTORY}/data/status")"
    
  elif [ "$1" == cpu_installable ];then
    #list apps that can be installed on the device's OS architecture (32-bit or 64-bit)
    #find all apps that have install-XX script, install script, or a packages file
    find "${DIRECTORY}/apps" -type f \( -name "install-$arch" -o -name "install" -o -name "packages" \) | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq
    
  elif [ "$1" == package ];then
    #list apps that have a "packages" file
    find "${DIRECTORY}/apps" -type f -name "packages" | sed "s+/packages++g" | sed "s+${DIRECTORY}/apps/++g" | sort | uniq
    
  elif [ "$1" == standard ];then
    #list apps that have scripts
    find "${DIRECTORY}/apps" -type f \( -name "install-32" -o -name "install-64" -o -name "install" -o -name "uninstall" \) | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq
    
  elif [ "$1" == hidden ];then
    #list apps that are hidden
    read_category_files | grep '|hidden' | awk -F'|' '{print $1}'
    
  elif [ "$1" == visible ];then
    #list apps that are in any other category but 'hidden', and aren't disabled
    read_category_files | grep -v '|hidden' | awk -F'|' '{print $1}' # | list_subtract "$(list_apps disabled)"
    
  elif [ "$1" == online ];then
    #list apps that exist on the online git repo
    if [ -d "${DIRECTORY}/update/Artian-apps/apps" ];then
      #if update folder exists, just use that
      ls "${DIRECTORY}/update/Artian-apps/apps" | grep .
    else
      #if update folder doesn't exist, then parse github HTML to get a list of online apps. Horrible idea, but it works!
      wget -qO- "${repo_url}/tree/master/apps" | grep 'title=".*" data-pjax=' -o | sed 's/title="//g' | sed 's/" data-pjax=//g'
    fi
    
  elif [ "$1" == online_only ];then
    #list apps that exist only on the git repo, and not locally
    list_apps online | list_subtract "$(list_apps local)"
    
  elif [ "$1" == local_only ];then
    #list apps that exist only locally, and not on the git repo
    list_apps local | list_subtract "$(list_apps online)"
    
  else
    error "list_apps(): unrecognized filter '$1'!"
  fi
}

list_intersect() { #Outputs only the apps that appear in both stdin and in $1
  # for example, the following two inputs will be a match
  # Audacity
  # Audacity
  # while these two will NOT be a match
  # Multimedia/Audacity
  # .*/Audacity
  comm -12 - <(echo "$1" | sort)
}

list_intersect_partial() { #Outputs only the apps that appear in both stdin and in $1 even with a partial match
  # for example, the following two inputs will be a match
  # Multimedia/Audacity
  # .*/Audacity
  #                      change \n to \|     |   remove last "\|"
  grep -x "$(echo "$1" | sed -z 's/\n/\\|/g' | sed -z 's/\\|$/\n/g')"
}

list_subtract() { #Outputs a list of apps from stdin, minus the ones that appear in $1
  # for example, the following two inputs will be a match
  # Audacity
  # Audacity
  # while these two will NOT be a match
  # Multimedia/Audacity
  # .*/Audacity
  comm -23 - <(echo "$1" | sort)
}

list_subtract_partial() { #Outputs a list of apps from stdin, minus the ones that appear in $1 even with a partial match
  # for example, the following two inputs will be a match
  # Multimedia/Audacity
  # .*/Audacity
  #                       change \n to \|     |   remove last "\|"
  grep -vx "$(echo "$1" | sed -z 's/\n/\\|/g' | sed -z 's/\\|$/\n/g')"
}

read_category_files() { #Generates a combined categories-list from several sources: category-overrides, global categories file, and unlisted apps. Format: "app|category"
  
  #remove app category if app folder not found
  local IFS=$'\n'
  local app
  for app in $(cat "${DIRECTORY}/data/category-overrides" 2>/dev/null); do
    if ! [ -d "${DIRECTORY}/apps/$(echo "$app" | sed 's/|.*//')" ] &>/dev/null; then 
      sed -i "/$app/d"  "${DIRECTORY}/data/category-overrides"
    fi
  done

  # get device specific categories overrides (if they exist)
  # obtain model and jetson_model
  get_model &>/dev/null
  version_id=$(grep 'VERSION_ID=' /etc/os-release | tr -cd '0123456789.')
  unset device_override
  if [[ "$model" != *"Raspberry Pi"* ]]; then
    device_override="${DIRECTORY}/etc/category-overrides-non-raspberry"
  fi
  if [[ ! -z "$jetson_model" ]] && [[ "$version_id" == "18.04" ]]; then
    device_override="${DIRECTORY}/etc/category-overrides-jetson-18.04"
  elif [[ ! -z "$jetson_model" ]]; then
    device_override="${DIRECTORY}/etc/category-overrides-jetson-generic"
  fi
  
  #list the user-overrides file     and the device specific override  and the global categories file   |-----and all apps------------|     filter out duplicates   no '\n\n'
  (cat "${DIRECTORY}/data/category-overrides" "$device_override" "${DIRECTORY}/etc/categories" 2>/dev/null ; echo ; list_apps local | sed 's/$/|/g') | awk -F'|' '!seen[$1]++' | grep .
}

app_prefix_category() { #lists all apps in a category with format "category/app", or if $1 is left blank, then list the full structure of all categories
  
  #Subtract a type of app if enabled
  local show_apps_setting="$(cat "${DIRECTORY}/data/settings/Show apps")"
  local filter=()
  if [ "$show_apps_setting" == 'standard' ];then
    #if only showing standard apps, hide package apps
    filter=(list_subtract_partial "$(list_apps package | sed 's+^+.*/+g')")
  elif [ "$show_apps_setting" == 'packages' ];then
    #if only showing package apps, hide standard apps
    filter=(list_subtract_partial "$(list_apps standard | sed 's+^+.*/+g')")
  else
    #is the setting is "all" or missing, don't filter.
    filter=(cat)
  fi
  
  #show special "Installed" category - don't filter it
  if [ "$1" == "Installed" ]; then
    list_apps installed | sed 's+^+Installed/+g'
  #show special "Packages" category
  elif [ "$1" == "Packages" ]; then
    list_apps package | list_subtract "$(list_apps hidden)" | sed 's+^+Packages/+g'
  #show special "All Apps" category
  elif [ "$1" == "All Apps" ]; then
    list_apps cpu_installable | list_subtract "$(list_apps hidden)" | sed 's+^+All Apps/+g' | "${filter[@]}"
  # show all categories
  elif [ -z "$1" ]; then
    #show normal categories
    read_category_files | grep . | awk -F'|' '{print $2"/"$1}' | sed 's+^/++g' | "${filter[@]}"
    
    #show special "Installed" category - don't filter it
    list_apps installed | sed 's+^+Installed/+g'
    
    #show special "Packages" category
    if [ "$show_apps_setting" != standard ];then
      list_apps package | list_subtract "$(list_apps hidden)" | sed 's+^+Packages/+g'
    fi
    #show special "All Apps" category
    list_apps cpu_installable | list_subtract "$(list_apps hidden)" | sed 's+^+All Apps/+g' | "${filter[@]}"
  #show normal categories
  else
    read_category_files | grep . | awk -F'|' '{print $2"/"$1}' | grep "^$1/" | sed 's+^/++g' | "${filter[@]}"
  fi
}

# bitly_link() { #Runs whenever an app is installed/uninstalled to tally the number of users for each app
#   #This cannot possibly be used to identify you, or any information about you.
#   #It simply "clicks" a bitly link - a shortened URL - so that the total number of clicks can be tallied to determine how popular a certain app is.
#   app="$1"
#   trigger="$2"
#
#   [ -z "$app" ] && error "bitly_link(): requires an app argument"
#   [ -z "$trigger" ] && error "bitly_link(): requires a trigger argument"
#
#   #if the 'Enable Analytics' setting is enabled
#   if [ "$(cat "${DIRECTORY}/data/settings/Enable analytics")" == 'Yes' ];then
#     #determine the name of the link to "click"
#     bitlylink="https://bit.ly/Artian-apps-$trigger-$(echo "$app" | tr -d ' ' | sed 's/[^a-zA-Z0-9]//g')"
#     #click it
#     curl -L --user-agent "Artian-Apps Raspberry Pi app store" "$bitlylink" &>/dev/null &
#   fi
# }

bitly_link() { #compatibility function pointing to shlink_link (incase old manage script is running with new api)
  shlink_link "$@"
}

shlink_link() { #Runs whenever an app is installed/uninstalled to tally the number of users for each app
  #This cannot possibly be used to identify you, or any information about you.
  #It simply "clicks" a shlink link - a shortened URL - so that the total number of clicks can be tallied to determine how popular a certain app is.
  app="$1"
  trigger="$2"
  
  [ -z "$app" ] && error "shlink_link(): requires an app argument"
  [ -z "$trigger" ] && error "shlink_link(): requires a trigger argument"
  
  #if the 'Enable Analytics' setting is enabled
  if [ "$(cat "${DIRECTORY}/data/settings/Enable analytics")" == 'Yes' ];then
    #determine the name of the link to "click"
    shlinklink="https://Artianhpc.ir  -$trigger-$(echo "$app" | tr -d ' ' | sed 's/[^a-zA-Z0-9]//g')/track"
    #click it
    curl -s -X 'GET' "$shlinklink" -H 'accept: image/gif' -A "Artian-Apps a software manager for artian company" >/dev/null &
  fi
}

usercount() { #Return number of users for specified app. $1 is app name. If empty, all are shown.
  #If clicklist file missing or over a day old, download it.
  if [ ! -f "${DIRECTORY}/data/clicklist" ] || [ ! -z "$(find "${DIRECTORY}/data/clicklist" -mtime +1 -print)" ]; then
    wget 'https://git.fdc.abrish.ir/a.kamyar/PublicFiles/Artian-Apps/' -qO "${DIRECTORY}/data/clicklist" >/dev/null || return 1
  fi
  clicklist="$(cat "${DIRECTORY}/data/clicklist")"
  
  [ -z "$clicklist" ] && error "usercount(): clicklist empty. Likely no internet connection"
  
  if [ -z "$1" ];then
    echo "$clicklist"
  else
    # $1 is app
    echo "$clicklist" | grep "[0-9] $1"'$' | awk '{print $1}' | head -n1
  fi
  
}

script_name() { #returns name of install script(s) for the $1 app. outputs: '', 'install-32', 'install-64', 'install', 'install-32 install-64'
  [ -z "$1" ] && error 'script_name(): requires an argument'
  
  #ensure $1 is valid app name
  [ ! -d "${DIRECTORY}/apps/$1" ] && error "script_name: '$1' is an invalid app name.\n${DIRECTORY}/apps/$1 does not exist."
  
  if [ -f "${DIRECTORY}/apps/$1/install-32" ] && [ ! -f "${DIRECTORY}/apps/$1/install-64" ];then
    echo 'install-32'
  elif [ -f "${DIRECTORY}/apps/$1/install-64" ] && [ ! -f "${DIRECTORY}/apps/$1/install-32" ];then
    echo 'install-64'
  elif [ -f "${DIRECTORY}/apps/$1/install-64" ] && [ -f "${DIRECTORY}/apps/$1/install-32" ];then
    echo 'install-32 install-64'
  elif [ -f "${DIRECTORY}/apps/$1/install" ];then
    echo 'install'
  else
    true
    #error "No install script found for the $app app! Please report this to Botspot."
  fi
  
}

script_name_cpu() { #get script name to run based on detected CPU arch
  [ -z "$1" ] && error 'script_name_cpu(): requires an argument.'
  
  #ensure $1 is valid app name
  if ! list_apps all | grep -q "$1" ;then
    error "script_name_cpu: '$1' is an invalid app name."
  fi
  
  if [ -f "${DIRECTORY}/apps/$1/install-32" ] && [ $arch == 32 ];then
    echo 'install-32'
  elif [ -f "${DIRECTORY}/apps/$1/install-64" ] && [ $arch == 64 ];then
    echo 'install-64'
  elif [ -f "${DIRECTORY}/apps/$1/install" ];then
    echo 'install'
  elif [ -f "${DIRECTORY}/apps/$1/packages" ];then
    echo 'packages'
  else
    true #app not compatible with current arch
  fi
}

app_status() { #Gets the $1 app's current status. installed, uninstalled, corrupted, disabled
  
  [ -z "$1" ] && error 'app_status(): requires an argument.'
  #don't check if app exists, it may be a new app in an update
  
  if [ -f "${DIRECTORY}/data/status/${1}" ];then
    cat "${DIRECTORY}/data/status/${1}"
  else
    echo 'uninstalled' #if app status file doesn't exist, assume uninstalled
  fi
}

app_type() { #there are 'standard' apps, and there are 'package' apps - an alias to install an apt package from the existing repositories.
  #$1 is input app-name
  local app="$1"
  [ -z "$app" ] && error "app_type: no app specified!"
  
  if [ -f "${DIRECTORY}/apps/${app}/packages" ];then
    echo package
  elif [ -f "${DIRECTORY}/apps/${app}/uninstall" ] || [ -f "${DIRECTORY}/apps/${app}/install" ] || [ -f "${DIRECTORY}/apps/${app}/install-32" ] || [ -f "${DIRECTORY}/apps/${app}/install-64" ];then
    echo standard
  else
    return 1
  fi
  #if neither conditional above evaluated to true, no output will be returned and the function exits with code 1
}

will_reinstall() { #return 0 if $1 app will be reinstalled during an update, otherwise return 1.
  local app="$1"
  [ -z "$app" ] && error 'will_reinstall(): requires an argument'
  
  #exit immediately if app is not installed.
  if [ "$(app_status "$app")" != 'installed' ];then
    return 1
  fi
  
  #detect which installation script exists - both for local install and for update directory
  local local_scriptname="$(script_name_cpu "$app")"
  
  local new_scriptname="$(DIRECTORY="${DIRECTORY}/update/Artian-apps" script_name_cpu "$app")"
  
  if [ ! -z "$new_scriptname" ] && [ "$local_scriptname" == packages ] && [ "$new_scriptname" != packages ];then
    #migration from package-app to script-app. Reinstall.
    return 0
  elif [ "$local_scriptname" != packages ] && [ "$new_scriptname" == packages ];then
    #migration from script-app to package-app. Reinstall.
    return 0
  elif [ ! -z "$new_scriptname" ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/${new_scriptname}" "${DIRECTORY}/apps/${app}/${local_scriptname}" ;then
    #if install script exists in update folder, and if install script was changed, and if installed already
    return 0
  else
    return 1
  fi
}

app_search() { #search all apps for $1, with filenames in $2 (space-separated and optional)
  local query="$1"
  [ -z "$query" ] && error "app_search(): requires a search query."
  
  #you can change which app-files are searched. Default: description, website, credits
  if [ ! -z "${2+x}" ];then
    #second argument can be set, but left blank
    local search_files="$(echo "$2" | sed 's/^ //g' | sed 's/ $//g' | sed 's/  / /g')"
  else
    #if no second argument provided
    local search_files="description website credits"
  fi
  
  #generate syntax for the find command. Example output: "-name description -o -name website -o -name credits"
  local find_syntax="$(echo "$search_files" | sed 's/ / -o -name /g')"
  echo "search pattern: $find_syntax" 1>&2
  
  #search app files
  if [ -z "$search_files" ];then
    local results=""
  else
    local results="$(find "${DIRECTORY}/apps" \( -name $find_syntax \) -exec grep -m1 -Fi "$query" {} + | awk -F: '{print $1}' | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq)"
  fi
  #hide incompatible and hidden/disabled apps
  results="$(echo "$results" | list_subtract "$(list_apps hidden)" | list_intersect "$(list_apps cpu_installable)")"
  
  if [ -z "$app_list" ];then
    local app_list="$(list_apps cpu_installable | list_subtract "$(list_apps hidden)")"
  fi
  
  #search app names - first prioritize results starting with query, then show results containing query, then show other pre-existing results.
  results="$(grep -i "^$query" <<<"$app_list" | sort)
$(grep -i "$query" <<<"$app_list" | sort)
$results"
  
  #remove duplicate entries
  echo "$results" | awk '!seen[$0]++'
  
}

app_search_gui() { #graphical interface to search for apps
  
  #pre-populate search field with last search, if available
  if [ -f "${DIRECTORY}/data/last-search" ];then
    local last_search="$(cat "${DIRECTORY}/data/last-search")"
  else
    local last_search=''
  fi
  
  local output=''
  output="$(yad "${yadflags[@]}" --title=Search --width=310 \
    --text="Search for apps."$'\n'"Not case-sensitive." \
    --form --field='' "$last_search" \
    --field='Search description':CHK 'TRUE' \
    --field='Search website':CHK 'TRUE' \
    --field='Search credits':CHK 'TRUE' \
    --field='Search scripts':CHK 'FALSE')" || return 0
  local query="$(echo "$output" | sed -n 1p)"
  
  #exit now if search query is empty
  if [ -z "$query" ];then
    return 0
  fi
  #save query for next time in last-search data file
  echo "$query" > "${DIRECTORY}/data/last-search"
  
  #skip file-based search if user searched for exact app name
  local app_list="$(list_apps cpu_installable | list_subtract "$(list_apps hidden)")"
  local results="$(grep -xFi "$query" <<<"$app_list")"
  if [ ! -z "$results" ];then
    echo "$results"
    return 0
  fi
  
  local search_files='description website credits scripts'
  search_files=''
  echo "$output" | sed -n 2p | grep -q 'TRUE' && search_files+='description '
  echo "$output" | sed -n 3p | grep -q 'TRUE' && search_files+='website '
  echo "$output" | sed -n 4p | grep -q 'TRUE' && search_files+='credits '
  echo "$output" | sed -n 5p | grep -q 'TRUE' && search_files+='install install-32 install-64 uninstall '
  
  #echo -e "query: $query\nsearch_files: $search_files"
  
  results="$(app_search "$query" "$search_files")"
  
  if [ ! -z "$results" ] && [ "$(echo "$results" | wc -l)" == 1 ];then
    #if only one result, don't ask user to select it
    echo "$results" #return result to outside world
    
  elif [ ! -z "$results" ];then #display apps and their categories in a list for user to select
    
    local app="$(show_app() {
      echo "$2"
    }
    export -f show_app
    
    categories="$(read_category_files)"
    
    IFS=$'\n'
    for app in $results ;do
      local category="$(grep '^'"$app"'|' <<<"$categories" | awk -F'|' '{print $2}')"
      
      echo "${DIRECTORY}/apps/${app}/icon-24.png
$app
$( (cat "${DIRECTORY}/apps/${app}/description" || echo "Description unavailable") | head -n1)
<small>in</small>
${DIRECTORY}/icons/categories/${category##*/}.png
${category}"
    done | \
    yad "${yadflags[@]}" --title="Results for "\""$query"\""" --width=310 --height=250 \
      --list --no-headers  --column=app:IMG --column=appname --column=tooltip:HD --column=in --column=category:IMG --column=categoryname \
      --tooltip-column=3 --no-buttons \
      --select-action="bash -c "\""show_app %s; kill "\$"YAD_PID"\""")"
    
    echo "$app" #return result to outside world
    
  elif list_apps | grep -qi "^$query" ;then
    #no compatible apps found, but an incompatible app was found that matches the name
    yad "${yadflags[@]}" --title=Results --width=310 \
      --text=""\""<b>$(list_apps | grep -i -m1 "^$query")</b>"\"" is not compatible with your ${__os_desc} ${arch}-bit OS." \
      --button=OK:0
    return 0
    
  else #no results found
    yad "${yadflags[@]}" --title=Results --width=310 \
      --text="No results found for "\""<b>$query</b>"\""." \
      --button=OK:0
    return 0
  fi
}

userinput_func() { # userinput function to display yad/cli prompts to the user
  [ -z "$1" ] && error "userinput_func(): requires a description"
  [ -z "$2" ] && error "userinput_func(): requires at least one output selection option"
  local text_lines=$(echo -e "$1" | wc -l)
  # there is no good universal way to calculate the required height of the window
  # the users theme, default text size, and window scaling all affect it
  # the idea is the height should be a function of the number of lines of input text and the number of list options
  local height_list=$(echo $(( text_lines * 17 + $(( ${#@} - 1 )) * 29 + 65 )))
  local commonflags=(--fixed --no-escape --undecorated --center --borders=20)
  if [ "${#@}" == "2" ];then
    yad "${yadflags[@]}" "${commonflags[@]}" \
      --image "dialog-information" \
      --text="$1" \
      --button="$2":0
    output="$2"
  elif [ "${#@}" == "3" ];then
    yad "${yadflags[@]}" "${commonflags[@]}" \
      --image "dialog-question" \
      --text="$1" \
      --button="$2":0 \
      --button="$3":1
    if [ $? -ne 0 ]; then
      output="$3"
    else
      output="$2"
    fi
  else
    unset uniq_selection
    for string in "${@:2}"; do
      local uniq_selection+=(FALSE "$string")
    done
    local uniq_selection[0]=TRUE
    output=$(yad "${yadflags[@]}" "${commonflags[@]}" \
      --height=$height_list\
      --text "$1" \
      --list \
      --no-headers \
      --radiolist \
      --center \
      --column "" \
      --column "Selection" \
      --print-column=2 \
      --separator='' \
      --button="OK":0 \
      "${uniq_selection[@]}")
  fi
}

generate_app_icons() { #This converts the given $1 image into icon-24.png and icon-64.png files for the $2 app
  icon="$1"
  app="$2"
  
  [ -z "$icon" ] && error "create_app_icons(): icon field empty!"
  [ -z "$app" ] && error "create_app_icons(): app field empty!"
  
  #ensure imagemagick is installed
  if ! command -v convert >/dev/null ;then
    yad "${yadflags[@]}" --text="To resize the images, imagemagick must be installed."$'\n'"Install now?" \
    --text-align=center --title='Quick question' \
    --button=No!"${DIRECTORY}/icons/exit.png":1 --button=Yes!"${DIRECTORY}/icons/check.png":0
    button=$?
    if [ $button == 0 ];then
      sudo apt install -y --no-install-recommends imagemagick || icon=''
    else
      exit 0
    fi
  fi
  
  #scale it to 24x24
  convert "$icon" -resize 24x24 "${DIRECTORY}/apps/${app}/icon-24.png"
  
  #scale it to 64x64
  convert "$icon" -resize 64x64 "${DIRECTORY}/apps/${app}/icon-64.png"

}

refresh_pkgapp_status() { #for the specified package-app, if dpkg thinks it's installed, then mark it as installed.
  local app="$1"
  [ -z "$app" ] && error "refresh_pkgapp_status(): no app specified!"
  
  #From the list of packages for the $app app, get the first one
  local package="$(cat "${DIRECTORY}/apps/${app}/packages" | awk '{print $1}')"
  [ -z "$package" ] && error "refresh_pkgapp_status(): The $app app does not have a valid packages file."
  
  #if that package is installed
  if package_installed "$package" ;then
    #mark this app as installed
    if [ "$(app_status "$app")" != 'installed' ];then
      echo "Marking $app as installed"
      echo 'installed' > "${DIRECTORY}/data/status/${app}"
      shlink_link "$app" install &
    fi
  #if that package is not installed, check if it even exists on the repositories
  elif package_available "$package" ;then
    #the package for the $app app is not installed but it is available, so mark this app as uninstalled
    if [ "$(app_status "$app")" != 'uninstalled' ];then
      echo "Marking $app as uninstalled"
      rm -f "${DIRECTORY}/data/status/${app}"
      shlink_link "$app" uninstall &
    fi
  else
    #this app is trying to install a package that's not on the repository. Hide the app.
    echo "Marking $app as hidden"
    "${DIRECTORY}/etc/categoryedit" "$app" 'hidden' >/dev/null
  fi
}

refresh_all_pkgapp_status() { #for every package-app, if dpkg thinks it's installed, then mark it as installed.
  #repeat for every package-type app
  local IFS=$'\n'
  local PIDS=()
  for app in $(list_apps package) ;do
    refresh_pkgapp_status "$app" &
    PIDS+=("$!")
  done
  wait "${PIDS[@]}" #wait for all subprocesses to complete
}

refresh_app_list() { #Force-regenerate the app list
  local guimode="$(cat "${DIRECTORY}/data/settings/App List Style")"
  
  #delete preload directory, then re-generate it
  rm -rf "${DIRECTORY}/data/preload"
  "${DIRECTORY}/etc/preload-daemon" "$guimode" once &>/dev/null
}

list_apps_missing_dummy_debs() { #List any installed apps that have had their dummy deb uninstalled more recently than the app was installed
  local IFS=$'\n'
  local app
  local dummy_deb_status="$(grep ' installed Artian-apps-\| not-installed Artian-apps-' /var/log/dpkg.log | tac | awk -F' ' '!seen[$5]++' | sed 's/:all$//g')"
  #example line value: 2023-03-20 13:13:54 status installed Artian-apps-6f59c0e1:all 1.0
  
  for app in $(list_apps installed | list_intersect "$(list_apps standard)") ;do
    #For every installed app, make sure the dummy deb, if it has ever been installed, is installed
    local pkgname="$(app_to_pkgname "$app")"
    local match="$(grep -m1 "$pkgname" <<<"$dummy_deb_status")"
    
    if [ -z "$match" ];then
      #echo "no dummy deb information associated with $app"
      true
    elif [ "$(echo "$match" | awk '{print $4}')" == not-installed ];then
      #echo "$app has its $pkgname package uninstalled!"
      
      #if the dummy deb was removed after app was installed, then there is a problem
      if [ "$(date -d '2023-03-27 22:23:58' +'%s')" -gt "$(date -r "${DIRECTORY}/data/status/${app}" +'%s')" ];then
        #echo "the dummy deb for $app was removed after $app was installed!"
        echo "$app"
      fi
    fi
  done
}
#end of app functions

#logfile functions below
get_logfile() { #find the most recent logfile for the $1 app
  local app="$1"
  [ -z "$app" ] && error "get_logfile(): no app specified!"
  ls -dt "${DIRECTORY}/logs"/* | grep -v 'success' | grep '\-'"${app}"'\.log' -m 1
}

log_diagnose() { #Given a logfile, explain errors to user, suggest fixes, and categorize issue with error_type variable
  #errors="$(cat /dev/stdin)"
  local errors="$(cat "$1")"
  # any attempt to write to the logfile will instead write to /dev/null if allowwrite argument is not passed
  if [ "$2" == "allowwrite" ]; then
    local logfile="$1"
  else
    local logfile="/dev/null"
  fi
  
  #store the user-friendly explanations for each type of error
  local error_caption=()
  #store the general type of error
  local error_type=''
  
#------------------------------------------
#repo issues below
#------------------------------------------
  #check for 'E: The repository'
  if grep -qF 'E: The repository' <<<"$errors" || grep -qF 'sources.list entry misspelt' <<<"$errors" || grep -qF 'component misspelt in' <<<"$errors" ;then
    error_caption+=("APT reported a faulty repository, and you must fix it before Artian-Apps will work.

To delete the repository:
Remove the relevant line from /etc/apt/sources.list file or delete one file in
the /etc/apt/sources.list.d folder.

sources.list requires root permissions to edit: sudo mousepad /path/to/file")
    error_type="system"
  fi
  
  #check for 'NO_PUBKEY' or ' is no longer signed.'
  if grep -qF 'NO_PUBKEY' <<<"$errors" || grep -qF ' is no longer signed.' <<<"$errors" ;then
    error_caption+=("APT reported an unsigned repository. This has to be solved before APT or Artian-Apps, will work.

If you're not sure what to do, you can try to fix the problem by running this command in a terminal:
sudo apt update 2>&1 | sed -ne 's/.*NO_PUBKEY //p' | while read key; do if ! [[ ${keys[*]} =~ "\""$key"\"" ]]; then sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "\""$key"\""; keys+=("\""$key"\""); fi; done")
    error_type="system"
  fi
  
  # check for 'Could not resolve' or 'Failed to fetch'
  if grep -q 'Could not resolve\|Failed to fetch\|Temporary failure resolving\|Network is unreachable\|Internal Server Error\|404 .*Not Found' <<<"$errors" ;then
    error_caption+=("APT reported an unresolvable repository.

Check your Internet connection and try again.")
    error_type="internet"
  fi
  
  #check for 'is configured multiple times in'
  if grep -qF 'is configured multiple times in' <<<"$errors" ;then
    error_caption+=("APT reported a double-configured repository, and you must fix it to fix Artian-Apps.

To delete the repository:
Remove the relevant line from /etc/apt/sources.list file or delete the file in
the /etc/apt/sources.list.d folder.

sources.list requires root permissions to edit: sudo mousepad /path/to/file")
    error_type="system"
  fi
  
  #check for "W: Conflicting distribution: "
  if grep -qF "W: Conflicting distribution: " <<<"$errors" ;then
    error_caption+=("APT reported a conflicting repository.

Read the installation errors, then look through /etc/apt/sources.list and /etc/apt/sources.list.d, making changes as necessary.

Perhaps doing a Google search for the exact error you received would help.")
    error_type="system"
  fi
  
  #check for "Release file for <repo-url> is not valid yet"
  if grep -q "Release file for .* is not valid yet" <<<"$errors" ;then
    error_caption+=("APT reported a repository whose release file becomes valid in the future.

Review the errors to see how long you need to wait.")
    error_type="system"
  fi
  
  #check for "Release file for <repo-url> is expired"
  if grep -q "Release file for .* is expired" <<<"$errors" ;then
    error_caption+=("APT reported a repository whose release file was invalidated in the past.
Please check that your system clock is set correctly, and if it is, check if the repository is kept updated or if its developers abandoned it.

If you think think you shouldn't see this error, you can try refreshing APT with these commands:
sudo rm -rf /var/lib/apt
sudo apt update")
    error_type="system"
  fi
  
  #check for typo in sources.list and list.d
  if grep -q "The list of sources could not be read\.\|Did not understand pin type\|E: Malformed entry .* in list file" <<<"$errors";then
    error_caption+=("APT reported a typo in the sources.list file.

You must look around in /etc/apt/sources.list and /etc/apt/sources.list.d and fix the typo.")
    error_type="system"
  fi
  
  #check for "E: The package cache file is corrupted"
  if grep -q "E: The package cache file is corrupted\|The package lists or status file could not be parsed or opened."  <<<"$errors" ;then
    error_caption+=("APT found something wrong with a package list file.

Perhaps this link would help: https://askubuntu.com/questions/939345/the-package-cache-file-is-corrupted-error")
    error_type="system"
  fi
  
  #check for broken Artian-apps-local-packages symlink
  if grep -q "E: Could not open file /var/lib/apt/lists/_tmp_Artian-apps-local-packages_._Packages" <<<"$errors" ;then
    error_caption+=("APT reported the Artian-apps-local-packages list as missing.

The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?")
    # no error_type since we want the user to be able to upload logs for this issue while still showing the custom caption
  fi
  
#------------------------------------------
#repo issues above, apt/dpkg issues below
#------------------------------------------
  
  #check for "--fix-broken"
  if grep -qF "\-\-fix\-broken" <<<"$errors" || grep -qF "needs to be reinstalled" <<<"$errors" ;then
    error_caption+=("APT reported a broken package.

Please run this command: sudo apt --fix-broken install")
    error_type="package"
  fi
  
  if grep -qF "dpkg --configure -a" <<<"$errors" ;then
    error_caption+=("Before dpkg, apt, or Artian-Apps will work, dpkg needs to repair your system.

Please run this command: sudo dpkg --configure -a")
    error_type="system"
  fi
  
  if grep -qF "package is in a very bad inconsistent state;" <<<"$errors" ;then
    error_caption+=("Something is wrong with another package on your system.

Refer to this information while troubleshooting: https://askubuntu.com/questions/148715")
    error_type="system"
  fi
  
  if grep -qF "dpkg: error: fgets gave an empty string from" <<<"$errors" ;then
    error_caption+=("Something strange is going on with your system and dpkg won't work.

Perhaps this link will help: https://askubuntu.com/questions/1293709/weird-error-when-trying-to-install-packages-with-apt")
    error_type="system"
  fi
  
  if grep -qF "Command line option --allow-releaseinfo-change is not understood" <<<"$errors" ;then
    error_caption+=("The Debian Project recently upgraded from Buster to version Bullseye. As a result, all Raspberry Pi OS Buster users will receive APT errors saying the repositories changed from 'stable' to 'oldstable'.

This error broke Artian-apps. To fix it, the Artian-Apps developers added something to the 'sudo apt update' command: --allow-releaseinfo-change.
This flag allows the repository migration to succeed, thereby allowing Artian-Apps to work again.

Unfortunately for you, your operating system is too old for apt to understand this flag we added. Please upgrade your operating system for a better experience. Raspbian Stretch is unsupported and many apps will not install.

Please flash your SD card with the latest release of Raspberry Pi OS: https://www.raspberrypi.org/software")
    error_type="system"
  fi
  
  if grep -qF "lzma error: compressed data is corrupt" <<<"$errors" ;then
    error_caption+=("A package failed to install because it appears corrupted. (buggy download?)

Try installing the same app again and if the problem persists please reach out to the Artian-Apps developers.")
    error_type="internet"
  fi
  
  if grep -qF "E: Could not get lock" <<<"$errors" ;then
    error_caption+=("Some other apt-get/dpkg process is running. Wait for that one to finish, then try again.")
    error_type="system"
  fi
  
  if grep -qF "dpkg: error: cannot scan updates directory '/var/lib/dpkg/updates/': No such file or directory" <<<"$errors" ;then
    error_caption+=("What did you do to your system? The "\""/var/lib/dpkg/updates"\"" folder is missing.

You can try creating the folder with this command:
sudo mkdir -p /var/lib/dpkg/updates")
    error_type="system"
  fi
  
  if grep -q "E: Repository .* changed its 'Suite' value" <<<"$errors" ;then
    error_caption+=("One or more APT repositories on your system have changed Suite values. Usually this occurs when a new version of Debian is released every two years.

Artian-Apps should work around this error, but somehow it did not.
Please run this command in a terminal: sudo apt update --allow-releaseinfo-change")
    error_type="system"
  fi
  
  if grep -q "E: Failed to fetch .* File has unexpected size .* Mirror sync in progress\?" <<<"$errors" ;then
    error_caption+=("APT encountered a repository with a file that is of incorrect size. This can be caused by a periodic mirror sync, or maybe the repository is faulty.

In any case, Artian-Apps cannot work until you solve this issue. Try disabling any 3rd-party APT repos first, and if that doesn't work then ask for help.")
    error_type="system"
  fi

  if grep -q "The following packages have unmet dependencies:" <<<"$errors" ;then
    # we want these errors to come to Artian-apps logs if the user sends them, so don't add an error type
    # add additional output for the packages that have unmet dependencies

    echo -e "Additional log diagnosis for developers below:\n" >> "$logfile"
    grep -E "^ .* : Depends:" <<<"$errors" | awk '{print $1, $4}' | tr " " "\n" | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt-cache show >> "$logfile"
    grep -E "^ .* : Depends:" <<<"$errors" | awk '{print $1, $4}' | tr " " "\n" | sed 's/:armhf\|:arm64\|:all//g' | sort -u | xargs -r apt list -a >> "$logfile"
    grep -E "^ .* : Depends:" <<<"$errors" | awk '{print $4}' | tr " " "\n" | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt install -fy --no-install-recommends --allow-downgrades --dry-run >> "$logfile"
    grep -E "^ +Depends:" <<<"$errors" | awk '{print $2}' | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt-cache show >> "$logfile"
    grep -E "^ +Depends:" <<<"$errors" | awk '{print $2}' | sed 's/:armhf\|:arm64\|:all//g' | sort -u | xargs -r apt list -a >> "$logfile"
    grep -E "^ +Depends:" <<<"$errors" | awk '{print $2}' | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt install -fy --no-install-recommends --allow-downgrades --dry-run >> "$logfile"
    grep -E "^Depends:" <<<"$errors" | cut -d' ' -f2- | sed "s/, /\n/g" | sed "s/:any//g" | sed 's/([^)]*)//g;s/  / /g' | xargs -r apt-cache show >> "$logfile"
    grep -E "^Depends:" <<<"$errors" | cut -d' ' -f2- | sed "s/, /\n/g" | sed 's/:armhf\|:arm64\|:all//g' | sed 's/([^)]*)//g;s/  / /g' | sort -u | xargs -r apt list -a >> "$logfile"
    grep -E "^Depends:" <<<"$errors" | cut -d' ' -f2- | sed "s/, /\n/g" | sed "s/:any//g" | sed 's/([^)]*)//g;s/  / /g' | xargs -r apt install -fy --no-install-recommends --allow-downgrades --dry-run >> "$logfile"
  fi

  if grep -q "trying to overwrite shared .*, which is different from other instances of package" <<<"$errors" ;then
    echo -e "Additional log diagnosis for developers below:\n" >> "$logfile"
    grep "trying to overwrite shared .*, which is different from other instances of package" <<<"$errors" | awk '{print $NF}' | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt-cache show >> "$logfile"
    grep "trying to overwrite shared .*, which is different from other instances of package" <<<"$errors" | awk '{print $NF}' | sed 's/:armhf\|:arm64\|:all//g' | sort -u | xargs -r apt list -a >> "$logfile"
  fi
  
  #if RPi OS, check for /etc/apt/sources.list.d/raspi.list
  if [ -f /etc/rpi-issue ] && [ ! -f /etc/apt/sources.list.d/raspi.list ] ;then
    error_caption+=("Packages failed to install because you seem to have deleted an important repository file in /etc/apt/sources.list.d

This error-dialog appeared because /etc/apt/sources.list.d/raspi.list is missing, but you may have deleted other files as well.
The raspi.list file should contain this:

deb http://archive.raspberrypi.org/debian/ $(get_codename) main
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
#deb-src http://archive.raspberrypi.org/debian/ $(get_codename) main")
    error_type="system"
  fi
  
  #if /etc/apt/sources.list and /etc/apt/sources.list.d/ubuntu.sources (default sources in Ubuntu 23.10) missing, display error message
  if [ ! -f /etc/apt/sources.list ] && [ ! -f /etc/apt/sources.list.d/ubuntu.sources ] ;then
    if [ -f /etc/rpi-issue ] && [ "$arch" == 32 ];then
       error_caption+=("Packages failed to install because you deleted an important repository file: /etc/apt/sources.list

You appear to be using Raspberry Pi OS 32-bit, so the sources.list file should contain this:
deb http://raspbian.raspberrypi.org/raspbian/ $(get_codename) main contrib non-free rpi
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
deb-src http://raspbian.raspberrypi.org/raspbian/ $(get_codename) main contrib non-free rpi")
    elif [ -f /etc/rpi-issue ] && [ "$arch" == 64 ];then
       error_caption+=("Packages failed to install because you deleted an important repository file: /etc/apt/sources.list

You appear to be using Raspberry Pi OS 64-bit, so the sources.list file should contain this:
deb http://deb.debian.org/debian $(get_codename) main contrib non-free
deb http://security.debian.org/debian-security $(get_codename)-security main contrib non-free
deb http://deb.debian.org/debian $(get_codename)-updates main contrib non-free
# Uncomment deb-src lines below then 'apt-get update' to enable 'apt-get source'
#deb-src http://deb.debian.org/debian $(get_codename) main contrib non-free
#deb-src http://security.debian.org/debian-security $(get_codename)-security main contrib non-free
#deb-src http://deb.debian.org/debian $(get_codename)-updates main contrib non-free")
    else
      error_caption+=("Packages failed to install because you deleted an important repository file: /etc/apt/sources.list

Refer to your Linux distro's documentation for how to restore this file.
You may have a backup of it in /etc/apt/sources.list.save if you have not deleted that as well.")
    fi
    error_type="system"
  fi
  
#------------------------------------------
#apt/dpkg issues above, package issues below
#------------------------------------------
  
  if grep -q "installed .* post-installation script subprocess returned error exit status" <<<"$errors" ;then
    error_caption+=("Some other package on your system is causing problems. As a result, dpkg and APT won't work properly.

Perhaps reinstalling the package would help?")
    error_type="package"
  fi
  
  if grep -qF "error processing package dphys-swapfile" <<<"$errors" ;then
    error_caption+=("Before dpkg, apt, or Artian-Apps will work, dphys-swapfile must be fixed.

Try Googling the above errors, or ask the Artian-Apps developers for help.")
    error_type="package"
  fi
  
  if grep -qF "missing /boot/firmware, did you forget to mount it" <<<"$errors" || grep -q "u-boot-rpi" <<<"$errors" ;then
    error_caption+=("Package(s) failed to install because your boot drive is not working.

You must fix the u-boot-rpi package before dpkg, apt, or Artian-Apps will work.")
    error_type="package"
  fi
  
  if grep -q "files list file for package .* is missing final newline" <<<"$errors" ;then
    error_caption+=("Before dpkg, apt, or Artian-Apps will work, your system must be repaired.

Try Googling the above errors, or ask the Artian-Apps developers for help.
Perhaps this link will help: https://askubuntu.com/questions/909719/dpkg-unrecoverable-fatal-error-aborting-files-list-file-for-package-linux-ge")
    error_type="package"
  fi
  
  if grep -qF "raspberrypi-kernel package post-installation script subprocess returned error exit status" <<<"$errors" ;then
    error_caption+=("The raspberrypi-kernel package on your system is causing problems.
Artian-Apps, dpkg and APT won't work properly until the problem is fixed.

Google the errors above this message, or ask in the Raspberry Pi Forums.
https://www.raspberrypi.org/forums")
    error_type="package"
  fi
  
  if grep -qF "raspberrypi-bootloader package pre-installation script subprocess returned error exit status" <<<"$errors" ;then
    error_caption+=("The raspberrypi-bootloader package on your system is causing problems.
Artian-Apps, dpkg and APT won't work properly until the problem is fixed.

Google the errors above this message, or ask in the Raspberry Pi Forums.
https://www.raspberrypi.org/forums")
    error_type="package"
  fi
  
  if grep -qF "error processing package nginx-full" <<<"$errors" ;then
    error_caption+=("The nginx-full package on your system encountered a problem.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "libwine-development:arm64 package post-installation script subprocess returned error exit status" <<<"$errors" ;then
    error_caption+=("The libwine-development package on your system encountered a problem.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed firmware-microbit-micropython-dl package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
    error_caption+=("The firmware-microbit-micropython-dl package on your system encountered a problem.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed flash-kernel package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
    error_caption+=("The flash-kernel package on your system encountered a problem.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "Depends: exagear.* but it is not installable" <<<"$errors" ;then
    error_caption+=("The exagear package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "/etc/ca-certificates/update.d/jks-keystore exited with code 1." <<<"$errors" || grep -qF "openjdk-11-jre-headless : Depends: ca-certificates-java (>= 20190405~) but it is not going to be installed" <<<"$errors" ;then
    error_caption+=("The ca-certificates-java package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "trying to overwrite '/usr/include/KHR/khrplatform.h', which is also in package libraspberrypi-dev" <<<"$errors" ;then
    error_caption+=("Packages cannot be installed because your libraspberrypi-dev package is very outdated.

Try upgrading all packages by running this command:
sudo apt full-upgrade")
    error_type="package"
  fi
  
  if grep -qF "dpkg: error processing archive .*steam-launcher" <<<"$errors" ;then
    error_caption+=("The steam-launcher package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "dpkg: error processing archive .*gnome-control-center-data" <<<"$errors" ;then
    error_caption+=("The gnome-control-center-data package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed php7.3-fpm package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
    error_caption+=("The php7.3-fpm package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed nulog package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
    error_caption+=("The nulog package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed wps-office package post-installation script subprocess returned error exit status 127" <<<"$errors" ;then
    error_caption+=("The wps-office package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "cmake but it is not installable" <<<"$errors" ;then
    error_caption+=("The cmake package cannot be installed. This is a Linux core package, so it should be installable. Most likely this is caused by you tampering with your apt sources.

If you need help, reach out to the Artian-Apps developers or in the Raspberry Pi forums: https://forums.raspberrypi.com")
    error_type="package"
  fi
  
  if grep -qF "installed php7.3-fpm package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
    error_caption+=("The php7.3-fpm package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "blockpi : Depends: python3-picamera but it is not installable" <<<"$errors" ;then
    error_caption+=("BlockPi could not be installed because the python3-picamera package is missing.

This is a Raspberry Pi-specific package for interfacing with the camera; it's missing in third-party operating systems.")
    error_type="package"
  fi
  
  if grep -qF "trying to overwrite '/usr/lib/mono/4.5/mscorlib.dll', which is also in package libmono-corlib4.5-dll" <<<"$errors" ;then
    error_caption+=("The libmono package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed android-androresolvd package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
    error_caption+=("The android-androresolvd package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "installed systemd package post-installation script subprocess returned error exit status" <<<"$errors" ;then
    error_caption+=("What did you do to your system? The "\""systemd"\"" package is not installing correctly.

Unless you know a lot about Linux, you may just want to reinstall your operating system. :(")
    error_type="package"
  fi
  
  if grep -qF "installed dahdi-dkms package post-installation script subprocess returned error exit status" <<<"$errors" ;then
    error_caption+=("The dahdi-dkms package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "ffmpeg : Depends: libsdl2-2.0-0 (>= 2.0.12) but 2.0.10+5rpi is installed" <<<"$errors" ;then
    error_caption+=("The ffmpeg package on your system is causing problems.

Maybe reinstalling this package would help?")
    error_type="package"
  fi
  
  if grep -qF "freedm : Depends: prboom-plus but it is not going to be installed" <<<"$errors" ;then
    error_caption+=("The freedm package on your system is causing problems.

Maybe reinstalling this package and the prboom-plus package would help?")
    error_type="package"
  fi
  
  if grep -q "trying to overwrite .*, which is also in package sdl2-image" <<<"$errors" ;then
    error_caption+=("You had some problematic SDL2 packages installed from the Doom 3 app. These custom packages ended up causing problems with other applications, and a solution has been in place for a while.

Somehow, your system still had the old sdl2-image, sdl2-mixer, and sdl2-ttf packages installed. These have been removed, so please try installing your other apps again.")
    error_type="package"
    
    apt_lock_wait
    sudo apt -y purge sdl2-image
    sudo apt -y purge sdl2-mixer
    sudo apt -y purge sdl2-ttf
  fi
  
  if grep -qF "trying to overwrite '/usr/share/pixmaps/wsjtx_icon.png', which is also in package wsjtx 2.6.1" <<<"$errors" ;then
    error_caption+=("The wsjtx-data package is conflicting with the wsjtx package installed on your system. You must fix this to install additional software.

According to the forums at wsjtx.groups.io, you can fix this by uninstalling wsjtx-data with this command:
sudo apt purge wsjtx-data

Here is the full forum link in case it helps you: https://wsjtx.groups.io/g/main/topic/77286764")
    error_type="package"
  fi

  #if RPi OS
  if [ -f /etc/rpi-issue ];then
    if grep -qF "linux-image-.*-arm64" <<<"$errors";then
      error_caption+=("You have a generic ARM64 linux kernel image installed on your system but are running Raspberry Pi OS. This is a package designed for ARM64 servers. You must fix this to prevent apt install/upgrades from erroring.

Try removing all generic ARM64 linux kernels with this command:
sudo apt purge --autoremove linux-image-*-arm64")
      error_type="package"
    fi
  fi
  
  #NON-APT ERRORS BELOW
  if grep -qF "Could not resolve host: git.fdc.abrish.ir" <<<"$errors" ;then
    error_caption+=("Failed to connect to gir.fdc.abrish.ir.

Check your internet connection and try again.")
    error_type="internet"
  fi
  
  if grep -qF "fetch-pack: unexpected disconnect while reading sideband packet" <<<"$errors" ;then
    error_caption+=("The git command encountered this error: "\""fetch-pack: unexpected disconnect while reading sideband packet"\"" Check the stability of your Internet connection and try again.

If this keeps happening, see: https://stackoverflow.com/questions/66366582")
    error_type="internet"
  fi
  
  if grep -qF "fatal: the remote end hung up unexpectedly" <<<"$errors" ;then
    error_caption+=("The git command encountered this error: "\""fatal: the remote end hung up unexpectedly"\"" Check the stability of your Internet connection and try again.")
    error_type="internet"
  fi
  
  if grep -q "errorCode=1 SSL/TLS handshake failure\|errorCode=1 total length mismatch.\|errorCode=1 Failed to establish connection, cause: Connection refused\|errorCode=2 Timeout\.\|abort: Connection reset by peer\|104: Connection reset by peer\|errorCode=19.*Name resolution for.*failed\|failed: Temporary failure in name resolution.\|Unable to establish SSL connection.\|Connection closed at byte \|Read error at byte \|failed: No route to host\.\|errorCode=8 Invalid range header\.\|curl: .* transfer closed with .* bytes remaining to read" <<<"$errors" ;then
    error_caption+=("Download failed. Check your internet connection and firewall, then try again.")
    error_type="internet"
  fi
  
  if grep -qF "errorCode=24 Authorization failed." <<<"$errors" ;then
    error_caption+=("Download failed with the \"Authorization failed\" error. This sometimes happens during a download on bad Wi-Fi, but if this problem persists, please reach out to the Artian-Apps developers.")
    error_type="internet"
  fi
  
  if grep -q "flathub: Error resolving .dl\.flathub\.org." <<<"$errors" ;then
    error_caption+=("Flatpak failed to connect to dl.flathub.org. Check your Internet connection and try again.")
    error_type="internet"
  fi
  
  if grep -q "The TLS connection was non-properly terminated\.\|Can't load uri .* Unacceptable TLS certificate" <<<"$errors" ;then
    error_caption+=("Your system seems to be having trouble with TLS connections.

Check your internet connection and try again.")
    error_type="internet"
  fi
  
  if grep -qF "error: RPC failed; curl 56 GnuTLS recv error (-54): Error in the pull function." <<<"$errors" ;then
    error_caption+=("Download failed due to a weird GnuTLS recv error. This is a problem with your system, not Artian-Apps. 

Try following the suggestions proposed on this website: https://stackoverflow.com/questions/38378914/how-to-fix-git-error-rpc-failed-curl-56-gnutls")
    error_type="internet"
  fi
  
  if grep -q "modprobe: FATAL: Module .* not found in directory" <<<"$errors" ;then
    error_caption+=("Something is wrong with the kernel modules. Try rebooting if your kernel was upgraded.

Otherwise, try reinstalling the kernel using this command:
sudo apt install --reinstall raspberrypi-bootloader raspberrypi-kernel

See this forum thread: https://raspberrypi.org/forums/viewtopic.php?t=262963")
    error_type="system"
  fi
  
  if grep -qF "Failed to load module \"appmenu-gtk-module\"" <<<"$errors" ;then
    error_caption+=("This error occurred: Failed to load module \"appmenu-gtk-module\"

Try installing two packages with this command:
sudo apt install appmenu-gtk2-module appmenu-gtk3-module

And if that doesn't work, try Googling the errors or reach out to Artian-Apps developers for help.")
    error_type="system"
  fi
  
  if grep -qF "E: gnupg, gnupg2 and gnupg1 do not seem to be installed, but one of them is required for this operation" <<<"$errors" ;then
    error_caption+=("Repository-signing failed because gnpug is missing. This is installed by default on most systems, but on yours it's missing for some reason.

Try installing gnupg with this command:
sudo apt install gnpug")
    error_type="system"
  fi
  
  if grep -q "error: Unable to connect to system bus\|error: Message recipient disconnected from message bus without replying\|Failed to connect to bus: Host is down" <<<"$errors" ;then
    error_caption+=("Something is wrong with your dbus connection.

Try rebooting.
Make sure systemd is setup correctly.
If that doesn't help please read through this: https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/issues/81
You may want to reinstall your OS.
Also consider reaching out to Artian-Apps developers for help.")
    error_type="system"
  fi
  
  if grep -qF "cat: /usr/share/i18n/SUPPORTED: No such file or directory" <<<"$errors" ;then
    error_caption+=("Your system is messed up - the /usr/share/i18n/SUPPORTED file does not exist.

Try reinstalling the locales package:
sudo apt install --reinstall locales")
    error_type="system"
  fi
  
  if grep -qF "is not in the sudoers file.  This incident will be reported." <<<"$errors" ;then
    error_caption+=("Unable to use the sudo command - the current user '$USER' is not allowed to use it.

Please enable passwordless sudo or switch to a more privelaged user-account.
See: https://www.tecmint.com/fix-user-is-not-in-the-sudoers-file-the-incident-will-be-reported-ubuntu/")
    error_type="system"
  fi
  
  if grep -q "sudo: .* incorrect password attempts" <<<"$errors" ;then
    error_caption+=("Process could not complete because you failed to type in the correct sudo password.

Try again, and consider enabling passwordless sudo.")
    error_type="system"
  fi
  
  if grep -q "sudo: unable to resolve host\|sudo: no valid sudoers sources found, quitting" <<<"$errors" ;then
    error_caption+=("Process could not complete because your sudo command is incorrectly set up.

For solutions, see: https://askubuntu.com/a/59517")
    error_type="system"
  fi
  
  if grep -qF "cpp.o: file not recognized: file truncated" <<<"$errors" ;then
    error_caption+=("Compiling failed. Try again, but please reach out to Artian-Apps developers for help if this same error keeps occurring.")
    error_type="system"
  fi
  
  if grep -q "tar: Unexpected EOF in archive\|xz: (stdin): Unexpected end of input\|xz: (stdin): Compressed data is corrupt\|xz: (stdin): File format not recognized\|gzip: stdin: invalid compressed data\-\-length error\|gzip: stdin: invalid compressed data\-\-crc error" <<<"$errors" ;then
    error_caption+=("Extraction failed. Most likely this was a corrupted download, so please try again.

If this problem continues occurring, please reach out to the Artian-Apps developers for help.")
    error_type="system"
  fi
  
  if grep -q "xz: Cannot exec: No such file or directory" <<<"$errors" ;then
    error_caption+=("Extraction failed because XZ is not installed.

To install XZ, run this in a terminal:
sudo apt-get install xz-utils")
    error_type="system"
  fi
  
  if grep -qF "aria2c: error while loading shared libraries: /lib/arm-linux-gnueabihf/libaria2.so.0: unexpected reloc type 0xc8" <<<"$errors" ;then
    error_caption+=("Download failed because aria2c could not load the libaria2 library.

Try reinstalling the package:
sudo apt install --reinstall libaria2-0")
    error_type="system"
  fi
  
  if grep -q "errorCode=16 Failed to open the file .*, cause: Permission denied" <<<"$errors" ;then
    error_caption+=("Download failed because this folder was unable to be written:
$(dirname "$(echo "$errors" | grep -o 'errorCode=16 Failed to open the file .*, cause: Permission denied' | sed 's/^errorCode=16 Failed to open the file //g ; s/, cause: Permission denied$//g')")")
    error_type="system"
  fi
  
  if grep -q "Reinstallation of .* is not possible, it cannot be downloaded\." <<<"$errors" ;then
    error_caption+=("Your APT setup has been corrupted somehow.

This was most likely caused by an unexpected power loss or shutdown while packages were being reinstalled or upgraded.
Fixing this will not be easy and it may not be worth your time. Reflashing the SD card may be faster.

First try running:
sudo dpkg --configure -a

If you still get APT errors, it *might* help to remove the apt folder and upgrade:
sudo rm -rf /var/lib/apt
sudo apt update

See: https://forums.raspberrypi.com/viewtopic.php?t=275994")
    error_type="system"
  fi
  
  if grep -qF "Structure needs cleaning" <<<"$errors" ;then
    error_caption+=("You have encountered the dreaded "\""Structure needs cleaning"\"" error. This indicates file-corruption caused by improperly shutting down your computer. You are lucky your computer booted at all.

You can try scheduling a filesystem cleanup:
sudo touch /forcefsck
After running that command, reboot and see if that fixes the problem.

If that doesn't work, then now is the time to restore your backup. Oh, you don't have one? Then you will have to re-flash your SD card and start over. And maybe consider keeping regular backups to avoid this unpleasant situation next time.")
    error_type="system"
  fi
  
  if grep -qF "VCHI initialization failed" <<<"$errors" ;then
    error_caption+=("You have encountered the 'VCHI initialization failed' error. This means that a program was not allowed to display something to the screen.

You can try to fix the error by adding your user to the video group. Run this command in a terminal:
sudo usermod -a -G video $USER

See: https://raspberrypi.stackexchange.com/a/8423/107602")
    error_type="system"
  fi
  
  if grep -q "Error: Failed to read commit .* No such metadata object\|error: Failed to install org.freedesktop.Platform: Failed to read commit .* No such metadata object\|Error: Error deploying: .* No such metadata object" <<<"$errors" ;then
    error_caption+=("Flatpak failed to install something due to a past incompleted download.

To repair it, please run this command in a terminal: flatpak repair --user

If this issue keeps happening, see: https://github.com/flatpak/flatpak/issues/3479")
    error_type="system"
  fi
  
  if grep -q "You don't have enough free space in\|No space left on device" <<<"$errors";then
    error_caption+=("Your system has insufficient disk space.

Please free up some space, then try again.")
    error_type="system"
  fi
  
  if grep -q ": line .*: $HOME/\.config/autostart/.*\.desktop: Permission denied" <<<"$errors";then
    error_caption+=("Failed to create an autostart entry because the folder is owned by user $(stat -c "%U" ~/.config/autostart)!
Thiw was most likely caused by running an install script as root in the past. Don't do that.

You can fix the folder's permissions by running this command in a terminal:
sudo chown \$USER:\$USER ~/.config/autostart")
    error_type="system"
  fi
  
  if grep -q "The directory '$HOME/\.cache/pip' or its parent directory is not owned by the current user" <<<"$errors";then
    error_caption+=("The Python package manager (pip3) could not make changes to its own cache folder: $HOME/.cache/pip
Most likely, you tried running pip3 with sudo in the past, or you tried running a Artian-Apps script with sudo in the past. (not recommended!)

To fix this, run this command:
sudo chown -R $USER:$USER $HOME/.cache/pip")
    error_type="system"
  fi

  if grep -q "mkdir: cannot create directory .*/home/$USER/Artian-apps-.*: Permission denied" <<<"$errors";then
    error_caption+=("Your HOME directory cannot be written to by the current user.
Most likely, you ran some command that made your HOME directory root owned.

To fix this, run this command:
sudo chown -R $USER:$USER $HOME")
    error_type="system"
  fi

  if grep -q "rm: cannot remove .*/home/$USER/.*: Permission denied" <<<"$errors";then
    error_caption+=("Your HOME directory cannot be written to by the current user.
Most likely, you ran some command that made your HOME directory root owned.

To fix this, run this command:
sudo chown -R $USER:$USER $HOME")
    error_type="system"
  fi
  
  if grep -qF "collect2: fatal error: ld terminated with signal 11 [Segmentation fault]" <<<"$errors";then
    error_caption+=("Failed to compile! The error was: "\""collect2: fatal error: ld terminated with signal 11 [Segmentation fault]"\""
Try following the instructions on this website:
https://stackoverflow.com/questions/57051568/collect2-fatal-error-ld-terminated-with-signal-11-segmentation-fault
If this ends up fixing the problem, please notify the Artian-Apps developers. If you continue to encounter this error, please reach out to the Artian-Apps developers.")
    error_type="system"
  fi
  
  if grep -qF "ModuleNotFoundError: No module named 'lsb_release'" <<<"$errors";then
    error_caption+=("Your lsb_release command seems to be incompletely installed. Try running this command to fix it:
sudo apt install --reinstall lsb_release")
    error_type="system"
  fi
  
  #Individual scripts can self-diagnose by outputting "User error: ", followed by the diagnosis.
  if grep -q "^User error: " <<<"$errors" ;then
    #return all lines of output after a line mentions "User error: "
    error_caption+=("$(sed -ne '/^User error: /,$ p' <<<"$errors" | sed 's/^User error: //g')")
    error_type="system"
  fi
  
  #if no known error was detected, set the error_type to 'unknown'
  [ -z "$error_type" ] && error_type=unknown
  
  #return the information
  local IFS=$'\n'
  echo -e "${error_type}\n${error_caption[*]}"
  
  #no need for this function to exit with return code 1 just because the last if statement evaluated as false
  return 0
}

format_logfile() { #remove ANSI escape sequences from a given file, and add OS information to beginning of file
  [ -z "$1" ] && error "format_logfile: no filename given!"
  [ ! -f "$1" ] && error "format_logfile: given filename ($1) does not exist or is not a file!"
  
  echo -e "$(get_device_info)\n\nBEGINNING OF LOG FILE:\n-----------------------\n\n$(cat "$1" | tr '\r' '\n' | sed "s/\x1b\[?[0-9;]*[a-zA-Z]//g" | sed "s/\x1b\[[0-9;]*[a-zA-Z]//g" | sed 's/\x1b\[[0-9;]*//g' | grep -vF '.......... .......... .......... .......... ..........')" > "$1"
  
}

send_error_report() { #non-interactively send a Artian-Apps error log file to the Botspot discord server
  [ -z "$1" ] && error "send_error_report(): requires an argument"
  [ ! -f "$1" ] && error "send_error_report(): '$1' is not a valid file."
  
  command -v curl >/dev/null || error "send_error_report(): Cannot send report: curl command not found!"
  
  #curl "https://api.paste.ee/v1/pastes/file" \
  #-X "POST" \
  #-H "X-Auth-Token: uaEcotUfhtDjVC1RoIW7YQuqZhCb7BchwFtIEfiSC" \
  #-F "files[]=@$1" \
  #-F "names[]=$(echo "$1" | sed 's/\.log.*/.txt/g')" \
  #-F "syntaxes[]=text"
  
  
  local errors="$(bash <(base64 -d <<<"aWYgZ3JlcCAnXkxhc3QgdXBkYXRlZCBQaS1BcHBzIG9uOicg$(base64 <<<"'"$1"'")IDt0aGVuIGN1cmwgLUYgImZpbGU9QFwi$(base64 <<<"$1")XCI7ZmlsZW5hbWU9XCIkKGJhc2VuYW1lICI=$(base64 <<<"$1")IiB8IHNlZCAncy9cLi4qLy50eHQvZycpXCIiICIkKHdnZXQgLXFPLSAiJChiYXNlNjQgLWQgPDw8ImFIUjBjSE02THk5eVlYY3VaMmwwYUhWaWRYTmxjbU52Ym5SbGJuUXVZMjl0TDBKdmRITndiM1F2Y0drdFlYQndjeTFoYm1Gc2VYUnBZM012YldGcGJpOWxjbkp2Y2kxc2IyY3RkMlZpYUc5dmF5MXVaWGNLIikiIHwgJChiYXNlNjQgLWQgPDw8IlltRnpaVFkwSUMxa0NnPT0iKSkiIDtlbHNlIHNsZWVwIDAuNyA7ZmkK" | tr -d '\n') 2>&1)"
  [ $? != 0 ] && error "curl failed to upload log file!\nErrors:\n$errors"
  
}

diagnose_apps() { #Given a list of apps that failed to install/uninstall, loop through each error log, diagnose it, and provide a "Send report" button if applicable.
  local failed_apps="$1"
  local IFS=$'\n'
  
  local num_lines="$(grep . <<<"$failed_apps" | wc -l)"
  local i=1 #counter to track which failed_app in the list is current
  local app
  local button
  
  for app in $failed_apps ;do
    local logfile="$(get_logfile "$app")"
    #given the app's logfile, categorize the error and set the error_type variable
    local diagnosis="$(log_diagnose "$logfile" "allowwrite")"
    
    local error_type="$(echo "$diagnosis" | head -n1)" #first line of diagnosis is the type of error
    local error_caption="$(echo "$diagnosis" | tail -n +2)" #subsequent lines of diagnosis are caption(s)
    
    #set the window-text
    if [ "$error_type" == unknown ];then
      local text="<b>${app^}</b> failed to $action for an <b>unknown</b> reason."
    else
      local text="<b>${app^}</b> failed to $action because Artian-Apps encountered $([[ "$error_type" == [aeiou]* ]] && echo an || echo a) <b>$error_type</b> error."
    fi
    
    #if the error_type is NOT system, internet, or package, AND the app exists in the official Artian-Apps repository, AND the app-script has not been modified, AND the system setup is supported, AND the app is not a package-app, then enable the "Send report button"
    if [ "$(app_type "$app")" == package ];then
      text+=$'\n'"Error report cannot be sent because this \"app\" is really just a shortcut to install a Debian package. It's not a problem that Artian-Apps can fix."
    elif [[ "$error_type" =~ ^(system|internet|package)$ ]];then
      text+=$'\n'"Error report cannot be sent because this is not an issue with Artian-Apps."
    elif ! list_apps online | grep -q "$app" ;then
      text+=$'\n'"Error report cannot be sent because this app is not in the official repository."
    elif [ "$action" == install ] && [ "$(app_type "$app")" == standard ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/$(script_name_cpu "$app")" "${DIRECTORY}/apps/${app}/$(script_name_cpu "$app")" ;then
      text+=$'\n'"Error report cannot be sent because this app is not the official version."
    elif [ "$action" == uninstall ] && [ "$(app_type "$app")" == standard ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/uninstall" "${DIRECTORY}/apps/${app}/uninstall" ;then
      text+=$'\n'"Error report cannot be sent because this app is not the official version."
    elif [ "$supported" == no ];then #variable set by the manage script
      text+=$'\n'"Error report cannot be sent because your system is unsupported."
    else
      #if all of the above checks evaluate to FALSE, then display the "Send report" button.
      local send_button=(--button='Send report'!"${DIRECTORY}/icons/send-error-report.png":2)
    fi
    
    #display support links, depending on if this was a package-app or a script-app
    if [ "$(app_type "$app")" == package ];then
      text+=$'\n'"As this is an APT error, consider Googling the errors or asking for help in the <a href=\"https://artianhpc.ir\">Artian Store Forums</a>."
    else
      text+=$'\n'"Support is available on <a href=\"https://artianhpc.ir\">Artian</a> and <a href=\"https://artianhpc.ir/contact/\">Artian For send message and call</a>."
    fi
    
    #if the error_caption is empty, display logfile in the scrollable text field
    if [ -z "$error_caption" ];then
      text+=$'\n'"You can view the terminal output below. (scroll down)"
      error_caption="$(cat "$logfile")"
    else
      text+=$'\n'"Below, Artian-Apps explains what went wrong and how you can fix it."
    fi
    
    #If system is unsupported, display the reason at the top of the scrollable text
    if [ "$supported" == no ];then
      error_caption="$(echo "$unsupported_message" | sed "s/\x1b\[[0-9;]*[a-zA-Z]//g")"$'\n'$'\n'"$error_caption"
    fi
    
    #this dialog may be one in a series of failed_app dialogs. Name the window-close button accordingly.
    if [ $i -lt $num_lines ];then
      local close_button=(--button="Next error!${DIRECTORY}/icons/forward.png":1)
    else
      local close_button=(--button="Close!${DIRECTORY}/icons/exit.png":1)
    fi
    
    echo "$error_caption" | yad "${yadflags[@]}" --class Artian-Apps --name "Artian-Apps" --text-info --width=700 --height=300 --title="Error occured when $(echo "${action}ing" | sed 's/updateing/updating/g') $app ($i/$num_lines)" \
      --image="${DIRECTORY}/icons/error.png" --image-on-top \
      --text="$text" --wrap --fontname=12 \
      --button='View log'!"${DIRECTORY}/icons/log-file.png"!"Review the output from when <b>$app</b> tried to $action.":"bash -c 'view_file "\""$logfile"\""'" \
      "${send_button[@]}" \
      "${close_button[@]}"
    button="${PIPESTATUS[1]}"
    
    if [ $button == 2 ];then
      #send error log
      send_error_report "$logfile"
    fi
    
    i=$((i+1))
  done
}
#end of logfile functions

#miscellaneous low-level functions below
runonce() { #run command only if it's never been run before. Useful for one-time migration or setting changes.
  #Runs a script in the form of stdin
  
  script="$(< /dev/stdin)"
  
  runonce_hash="$(sha1sum <<<"$script" | awk '{print $1}')"
  
  if [ -s "${DIRECTORY}/data/runonce_hashes" ] && while read line; do [[ $line == "$runonce_hash" ]] && break; done < "${DIRECTORY}/data/runonce_hashes"; then
    #hash found
    #echo "runonce: '$script' already run before. Skipping."
    true
  else
    #run the script.
    bash <(echo "$script")
    #if it succeeds, add the hash to the list to never run it again
    if [ $? == 0 ];then
      echo "$runonce_hash" >> "${DIRECTORY}/data/runonce_hashes"
      echo "runonce(): '$script' succeeded. Added to list."
    else
      echo "runonce(): '$script' failed. Not adding hash to list."
    fi
    
  fi
  
}

text_editor() { #Open user-preferred text editor. $1 is file to open
  [ -z "$1" ] && error "text_editor(): no file specified"
  
  #find the best text editor
  preferrededitor="$(cat "${DIRECTORY}/data/settings/Preferred text editor")"
  # map friendly name of editors to binary name
  if [ "$preferrededitor" == "Visual Studio Code" ];then
    preferrededitor="code"
  elif [ "$preferrededitor" == "VSCodium" ];then
    preferrededitor="codium"
  fi
  
  #change preferred editor if user-default doesn't exist
  if ! command -v "$preferrededitor" >/dev/null;then
    preferrededitor=geany
  fi
  if ! command -v "$preferrededitor" >/dev/null;then
    preferrededitor=mousepad
  fi
  if ! command -v "$preferrededitor" >/dev/null;then
    preferrededitor=leafpad
  fi
  if ! command -v "$preferrededitor" >/dev/null;then
    preferrededitor=nano
  fi
  
  if [ "$preferrededitor" == nano ];then
    #terminal-based text editor
    "${DIRECTORY}/etc/terminal-run" "nano "\""$1"\""" "Editing $(basename "$1")"
  else
    #non-terminal text editor
    GTK_THEME='' "$preferrededitor" "$1"
  fi
}

view_file() { #maximized yad window to view a text file
  local file="$1"
  [ -z "$file" ] && error "view_file(): no input file specified!"
  cat "$file" | yad --center --window-icon="${DIRECTORY}/icons/logo.png" \
    --title="Viewing file" --text="File location: <b>$file</b>" \
    --text-info --tail --maximized \
    --button=Close #--back='#505050' --fore='#00FFFF'
}

files_match() { #$1 and $2 are paths to files
  if [ ! -f "$1" ] || [ ! -f "$2" ];then
    return 1
  else
    diff "$1" "$2" -q >/dev/null
  fi
}

is_supported_system() { #return 0 if system is supported, otherwise return 1
  PRETTY_NAME="$(cat /etc/os-release | grep PRETTY_NAME | tr -d '"' | awk -F= '{print $2}')"
  if uname -m | grep -qi 'x86\|i686\|i386'; then
    echo "Artian-Apps is not supported on x86 processors. Nearly all apps will fail. Consider switching to this x86 port of Artian-Apps: https://artianhpc.ir/thin-clients/"
    return 1
  elif grep -q '^/data/media .*Android' /proc/mounts || cat /proc/version | grep -qi Android || cat /proc/version | grep -qi termux; then
    echo "Artian-Apps is not supported on Android. Some apps will work, but others won't."
    return 1
  elif cat /proc/version | grep -qi Microsoft || cat /proc/sys/kernel/osrelease | grep -qi WSL || [[ -f "/run/WSL" ]] || [[ -f "/etc/wsl.conf" ]] || [ -n "$WSL_DISTRO_NAME" ]; then
    echo "Artian-Apps is not supported on WSL."
    return 1
  elif echo "$PRETTY_NAME" | grep -qi 'stretch\|wheezy\|jessie'; then
    echo "Artian-Apps is not supported on your outdated operating system. Expect many apps to fail. Consider upgrading your operating system."
    return 1
  elif echo "$PRETTY_NAME" | grep -qi 'manjaro'; then
    echo "Artian-Apps is not supported on Manjaro."
    return 1
  elif echo "$PRETTY_NAME" | grep -qi 'Ubuntu 16'; then
    echo "Artian-Apps is not supported on Ubuntu 16."
    return 1
  elif [[ "$(uname -m)" == armv6* ]]; then
    echo "Artian-Apps is not supported on ARMv6 Artian boards. Expect some apps to fail."
    return 1
  elif [ "$(id -u)" == 0 ]; then
    echo "Artian-Apps is not designed to be run as root user."
    return 1
  elif local frankendebian="$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "raspbian.raspberrypi.org/raspbian\|archive.raspberrypi.org/debian\|\
debian.org/debian\|security.debian.org/\|\
ports.ubuntu.com\|esm.ubuntu.com/apps/ubuntu\|esm.ubuntu.com/infra/ubuntu\|\
repo.huaweicloud.com/debian\|repo.huaweicloud.com/ubuntu-ports\|\
apt.pop-os.org\|\
apt.armbian.com" | grep -v $__os_codename)" && [ ! -z "$frankendebian" ];then
    echo "Congratulations, Linux tinkerer, you broke your system. You have made your system a FrankenDebian.
This website explains your mistake in more detail: https://wiki.debian.org/DontBreakDebian
Your current reported release (${__os_codename^}) should not be combined with other releases.
Specifically, the issue is $(wc -l <<<"$frankendebian" | grep -q 1 && echo 'this line' || echo 'these lines'):"
    local IFS=$'\n'
    for line in $frankendebian ;do
      local site="$(echo "$line" | awk '{print $1}')"
      local release="$(echo "$line" | awk '{print $2}')"
      echo -e "\e[4m$line\e[24m in $(apt-get indextargets --no-release-info --format '$(SOURCESENTRY)' "Release: $release" "Site: $site" | awk -F':' '{print $1}' | sort -u)"
    done
    echo "Your system might be recoverable if you did this recently and have not performed an apt upgrade yet, but otherwise you should probably reinstall your OS."
    return 1
  elif ! package_available init; then
    echo "Congratulations, Linux tinkerer, you broke your system. The init package can not be found, which means you have removed the default debian sources from your system.
All apt based application installs will fail. Unless you have a backup of your /etc/apt/sources.list /etc/apt/sources.list.d you will need to reinstall your OS."
    return 1
  elif [ -z "$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}')" ];then
    echo "Congratulations, Linux tinkerer, you broke your system. You have removed ALL debian sources from your system.
All apt based application installs will fail. Unless you have a backup of your /etc/apt/sources.list /etc/apt/sources.list.d you will need to reinstall your OS."
    return 1
  elif [ "$(df -a / -B 1 --output=avail | tail -1 | tr -d ' ')" -lt $((500*1024*1024)) ];then
    echo "Your system drive has less than 500MB of free space. Watch out for "\""disk full"\"" errors."
    return 1
  else
    return 0
  fi
}

get_device_info() { #returns information about current install and hardware
  echo "OS: $(cat /etc/os-release | grep PRETTY_NAME | tr -d '"' | awk -F= '{print $2}')"
  echo "OS architecture: ${arch}-bit"
  #### IMPORTANT: for anyone wishing to change the wording following this, you MUST also change the send_error_report function
  [ ! -z "$DIRECTORY" ] && echo "Last updated Artian-Apps on: $(cd "$DIRECTORY"; git show -s --format="%ad" --date=short | xargs date +%x -d)"
  [ -s "$DIRECTORY/etc/git_url" ] && echo "Latest Artian-Apps version: $(wget -T 3 https://api.github.com/repos/Botspot/pi-apps/commits/master -qO- 2>&1 | grep '"date":' | tail -n 1 | sed 's/"date"://g' | xargs date +%x -d)"
  echo "Kernel: $(uname -m) $(uname -r)"
  # obtain model name
  get_model
  echo "Device model: $model $jetson_model"
  echo "Cpu name: $( lscpu | awk '/Model name:/ {print $3}' )"
  echo "Ram size: $(echo "scale=2 ; $( awk '/MemTotal/ {print $2}' /proc/meminfo ) / 1024000 " | bc ) GB"
  
  if [ -f /etc/rpi-issue ];then
    echo "Raspberry Pi OS image version: $(cat /etc/rpi-issue | grep 'Raspberry Pi reference' | sed 's/Raspberry Pi reference //g')"
  fi
  
  if [ ! -z "$LANG" ];then
    echo "Language: $LANG"
  elif [ ! -z "$LC_ALL" ];then
    echo "Language: $LC_ALL"
  fi
  
}

get_model() { # populates the model and jetson_model variables with information about the current hardware
  # obtain model name
  unset model
  if [[ -d /system/app/ && -d /system/priv-app ]]; then
    model="$(getprop ro.product.brand) $(getprop ro.product.model)"
  fi
  if [[ -z "$model" ]] && [[ -f /sys/devices/virtual/dmi/id/product_name ||
          -f /sys/devices/virtual/dmi/id/product_version ]]; then
    model="$(tr -d '\0' < /sys/devices/virtual/dmi/id/product_name)"
    model+=" $(tr -d '\0' < /sys/devices/virtual/dmi/id/product_version)"
  fi
  if [[ -z "$model" ]] && [[ -f /sys/firmware/devicetree/base/model ]]; then
    model="$(tr -d '\0' < /sys/firmware/devicetree/base/model)"
  fi
  if [[ -z "$model" ]] && [[ -f /tmp/sysinfo/model ]]; then
      model="$(tr -d '\0' < /tmp/sysinfo/model)"
  fi
  unset jetson_model
  unset SOC_ID
  # obtain jetson model name (if available)
  # nvidia, in their official L4T (Linux for Tegra) releases 32.X and 34.X, set a distinct tegra family in the device tree /proc/device-tree/compatible
  if [[ -e "/proc/device-tree/compatible" ]]; then
    CHIP="$(tr -d '\0' < /proc/device-tree/compatible)"
    if [[ ${CHIP} =~ "tegra186" ]]; then
      jetson_model="tegra-x2"
    elif [[ ${CHIP} =~ "tegra210" ]]; then
      jetson_model="tegra-x1"
    elif [[ ${CHIP} =~ "tegra194" ]]; then
      jetson_model="xavier"
    elif [[ ${CHIP} =~ "tegra234" ]]; then
      jetson_model="orin"
    elif [[ ${CHIP} =~ "tegra239" ]]; then
      jetson_model="switch-pro-chip"
    elif [[ ${CHIP} =~ "tegra" ]]; then
      jetson_model="jetson-unknown"
    elif [[ ${CHIP} =~ "rk3399" ]]; then
      SOC_ID="rk3399"
    elif [[ ${CHIP} =~ "rk3308" ]]; then
      SOC_ID="rk3308"
    elif [[ ${CHIP} =~ "rk3326" ]]; then
      SOC_ID="rk3326"
    elif [[ ${CHIP} =~ "rk3328" ]]; then
      SOC_ID="rk3328"
    elif [[ ${CHIP} =~ "rk3368" ]]; then
      SOC_ID="rk3368"
    elif [[ ${CHIP} =~ "rk3566" ]]; then
      SOC_ID="rk3566"
    elif [[ ${CHIP} =~ "rk3568" ]]; then
      SOC_ID="rk3568"
    elif [[ ${CHIP} =~ "g12b" ]]; then
      SOC_ID="g12b"
    elif [[ ${CHIP} =~ "g12b" ]]; then
      SOC_ID="g12b"
    elif [[ ${CHIP} =~ "g12b" ]]; then
      SOC_ID="g12b"
    elif [[ ${CHIP} =~ "bcm2711" ]]; then
      SOC_ID="bcm2711"
    elif [[ ${CHIP} =~ "bcm2837" ]]; then
      SOC_ID="bcm2837"
    elif [[ ${CHIP} =~ "bcm2836" ]]; then
      SOC_ID="bcm2836"
    elif [[ ${CHIP} =~ "bcm2835" ]]; then
      SOC_ID="bcm2835"
    fi
  # as part of the 2X.X L4T releases, the kernel is older and the tegra family is found in /sys/devices/soc0/family
  elif [[ -e "/sys/devices/soc0/family" ]]; then
    CHIP="$(tr -d '\0' < /sys/devices/soc0/family)"
    if [[ ${CHIP} =~ "tegra20" ]]; then
      jetson_model="tegra-2"
    elif [[ ${CHIP} =~ "tegra30" ]]; then
      jetson_model="tegra-3"
    elif [[ ${CHIP} =~ "tegra114" ]]; then
      jetson_model="tegra-4"
    elif [[ ${CHIP} =~ "tegra124" ]]; then
      jetson_model="tegra-k1-32"
    elif [[ ${CHIP} =~ "tegra132" ]]; then
      jetson_model="tegra-k1-64"
    elif [[ ${CHIP} =~ "tegra210" ]]; then
      jetson_model="tegra-x1"
    fi
  fi
  if [ -n "$jetson_model" ]; then
    SOC_ID="$jetson_model"
  fi
}

get_codename() { #get debian/ubuntu codename
  if ! command -v lsb_release >/dev/null; then
    apt_update &>/dev/null && \
    sudo apt-get install -y lsb-release &>/dev/null
  fi
  
  # first check if lsb_release has an upstream option -u
  # if not, check if there is an upstream-release file
  # if not, check if there is a lsb-release.diverted file
  # if not, assume that this is not a ubuntu derivative
  if lsb_release -a -u &>/dev/null; then
    # This is a Ubuntu Derivative, checking the upstream-release version info
    lsb_release -s -c -u
  elif [ -f /etc/upstream-release/lsb-release ]; then
    # ubuntu 22.04+ linux mint no longer includes the lsb_release -u option
    # add a parser for the /etc/upstream-release/lsb-release file
    source /etc/upstream-release/lsb-release
    echo "$DISTRIB_CODENAME"
    unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
  elif [ -f /etc/lsb-release.diverted ]; then
    # ubuntu 22.04+ popOS no longer includes the /etc/upstream-release/lsb-release or the lsb_release -u option
    # add a parser for the new /etc/lsb-release.diverted file
    source /etc/lsb-release.diverted
    echo "$DISTRIB_CODENAME"
    unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
  else
    lsb_release -s -c
  fi
}

multi_install_gui() { #graphical interface to install multiple apps from a list.
  
  #get list of apps - hide hidden apps and hide installed apps
  local apps="$(list_apps cpu_installable | list_subtract "$(list_apps hidden)" | list_subtract "$(list_apps installed)")"
  
  local IFS=$'\n'
  local app
  
  apps="$(for app in $apps ;do
    echo "FALSE
${DIRECTORY}/apps/$app/icon-24.png
$app
$(head -n1 "${DIRECTORY}/apps/$app/description")"
  done | sed 's/&/&amp;/g' | yad "${yadflags[@]}" --window-icon="${DIRECTORY}/icons/settings.png" --list --checklist \
    --width=300 --height=500 \
    --hide-column=4 --print-column=3 --tooltip-column=4 --no-headers \
    --text="Install everything you want!"$'\n'"Note: apps that are already installed are not shown." \
    --column=:chk --column=:img --column=name --column=:tip \
    --button=Cancel!"${DIRECTORY}/icons/exit.png":1 --button='Install selected'!"${DIRECTORY}/icons/install.png":0)"
  
  if [ $? == 0 ] && [ ! -z "$apps" ];then
    #remove empty lines from yad's output
    apps="$(grep . <<<"$apps")"
    
    for app in $apps ;do
      queue+="install $app"$'\n'
    done
    queue="${queue::-1}" #remove final newline character
    terminal_manage_multi "$queue" &
  fi
}

multi_uninstall_gui() { #graphical interface to uninstall multiple apps from a list.
  
  #get list of apps that are installed
  local apps="$(list_apps installed)"
  
  local IFS=$'\n'
  local app
  
  apps="$(for app in $apps ;do
    echo "FALSE
${DIRECTORY}/apps/$app/icon-24.png
$app
$(head -n1 "${DIRECTORY}/apps/$app/description")"
  done | sed 's/&/&amp;/g' | yad "${yadflags[@]}" --window-icon="${DIRECTORY}/icons/settings.png" --list --checklist \
    --width=300 --height=500 \
    --hide-column=4 --print-column=3 --tooltip-column=4 --no-headers \
    --text="Uninstall everything you want!"$'\n'"Note: apps that are not installed are not shown." \
    --column=:chk --column=:img --column=name --column=:tip \
    --button=Cancel!"${DIRECTORY}/icons/exit.png":1 --button='Uninstall selected'!"${DIRECTORY}/icons/uninstall.png":0)"
  
  if [ $? == 0 ] && [ ! -z "$apps" ];then
    #remove empty lines from yad's output
    apps="$(grep . <<<"$apps")"
    
    for app in $apps ;do
      queue+="uninstall $app"$'\n'
    done
    queue="${queue::-1}" #remove final newline character
    terminal_manage_multi "$queue" &
  fi
}

process_exists() { #return 0 if the $1 PID is running, otherwise 1
  [ -z "$1" ] && error "process_exists(): no PID given!"
  
  if [ -f "/proc/$1/status" ];then
    return 0
  else
    return 1
  fi
}
#end of low-level functions

#command interceptors - functions that enhance a command
enable_module() { #Permanent equivalent to modprobe, this will load the module on every future startup
  local module="$1"
  [ -z "$module" ] && error "enable_module(): The name of a kernel module must be specified!"
  
  #This function is often to load the "fuse" module.
  # Fuse is most often used for AppImages.
  #   AppImages need libfuse2 to be installed.
  #    Install libfuse2 to avoid AppImage launch failures.
  if [ "$module" == 'fuse' ];then
    if [ ! -z "$app" ];then
      #this function is being used within an app-installation. Make libfuse2 a dependency, whether or not it is already installed.
      if package_available fuse3 ;then
        install_packages fuse3 libfuse2
      elif package_available fuse ;then
        install_packages fuse libfuse2
      else
        # this case should never be reached
        install_packages libfuse2
      fi
    elif package_installed libfuse2 && (package_installed fuse || package_installed fuse3) ;then
      #NOT being used within an app-installation and libfuse2 and fuse or fuse3 is already installed.
      true #nothing to do
    else
      #NOT being used within an app-installation and libfuse2 is not installed.
      apt_update
      if package_available fuse3 ;then
        sudo apt install -y fuse3 libfuse2 --no-install-recommends
      elif package_available fuse ;then
        sudo apt install -y fuse libfuse2 --no-install-recommends
      else
        # this case should never be reached
        sudo apt install -y libfuse2 --no-install-recommends
      fi
    fi
  fi
  
  #load the module now if not already loaded
  if ! lsmod | awk '{print $1}' | grep -qxF "$module" ;then
    errors="$(sudo modprobe "$module" 2>&1)"
    if [ $? != 0 ];then
      #if modprobe fails, the module may be missing because user upgraded the kernel and has not rebooted yet.
      if [ ! -d "/lib/modules/$(uname -r)" ];then
        error "\nUser error: Failed to load the '$module' kernel module because you upgraded the kernel and have not rebooted yet.
Please reboot to load the new kernel, then try again."
      else
        #other modprobe error: exit now and display modprobe output
        error "$errors"
      fi
    fi
  fi
  
  #make it load on boot
  if [ ! -f "/etc/modules-load.d/${module}.conf" ];then
    echo "$module" | sudo tee "/etc/modules-load.d/${module}.conf" >/dev/null
  fi
}

git_clone() { #silently clone a git repository but display the output if an error occurs
  # $1 is repo
  local IFS=' '
  for arg in "$@"; do
    if [[ "$arg" == *'://'* ]];then
      url="$arg"
    fi
  done
  
  [ -z "$url" ] && error "git_clone(): no repository URL specified."
  
  local repo_name=$(basename "$url" | sed s/.git//g)
  
  local folder="$(pwd)/$repo_name"
  
  status -n "Downloading $repo_name repository... "
  
  rm -rf "$repo_name" || sudo rm -rf "$repo_name"
  local errors
  errors="$(git clone "$@" 2>&1)"
  local exitcode=$?
  
  if [ "$exitcode" != 0 ]; then
    error "\nFailed to download $repo_name repository.\nErrors: $errors"
  fi
  
  status_green 'Done'
}

wget() { #Intercept all wget commands. When possible, uses aria2c.
  local file=''
  local url=''
  #determine the download manager to use
  local use=aria2c
  #determine if being run silently (if the '-q' flag was passed)
  local quiet=0
  
  #convert wget arguments to newline-separated list
  local IFS=$'\n'
  local opts="$(IFS=$'\n'; echo "$*")"
  for opt in $opts ;do
    
    #check if this argument to wget begins with '--'
    if [[ "$opt" == '--'* ]];then
      if [ "$opt" == '--quiet' ];then
        quiet=1
      else #for any other arguments, fallback to wget
        use=wget
      fi
      
    elif [ "$opt" == '-' ];then
      #writing to stdout, use wget and hide output
      use=wget
      quiet=1
    elif [[ "$opt" == '-'* ]];then
      #this opt is a flag beginning with one '-'
      
      #check the value of every letter in this argument
      local i
      for i in $(fold -w1 <<<"$opt" | tail -n +2) ;do
        
        if [ "$i" == q ];then
          quiet=1
        elif [ "$i" == O ];then
          true
        elif [ "$i" == '-' ];then
          #writing to stdout, use wget and hide output
          use=wget
          quiet=1
        else #any other wget arguments
          use=wget
        fi
      done
      
    elif [[ "$opt" == *'://'* ]]; then
      #this opt is web address
      url="$opt"
    elif [[ "$opt" == '/'* ]]; then
      #this opt is file output
      if [ -z "$file" ];then
        file="$opt"
        #if output file is /dev/stdout, /dev/null, etc, use wget
        if [[ "$file" == /dev/* ]];then
          use=wget
          quiet=1
        fi
      else #file var already populated
        use=wget
      fi
    else
      #This argument does not begin with '-', contain '://', or begin with '/'.
      #Assume output file specified shorthand if file-argument is not already set
      if [ -z "$file" ];then
        file="$(pwd)/${opt}"
      else #file var already populated
        use=wget
      fi
    fi
  done
  
  if ! command -v aria2c >/dev/null ;then
    #aria2c command not found
    use=wget
  fi
  
  local filename="$(echo "$url" | sed 's+/download$++g' | sed 's+.*/++g')"
  if [ "$quiet" == 0 ];then
    if [ -n "$file" ] && [ "$file" != "$(pwd)/$filename" ]; then
      status -n "Downloading $filename to $file... " 1>&2
    else
      status -n "Downloading $filename... " 1>&2
    fi
    echo
  fi
  
  #now, perform the download using the chosen method
  if [ "$use" == wget ];then
    #run the true wget binary with all this function's args
    
    command wget --progress=bar:force:noscroll "$@"
    local exitcode=$?
  elif [ "$use" == aria2c ];then
    
    #if $file empty, generate it based on url
    if [ -z "$file" ];then
      file="$(pwd)/$filename"
    fi
    
    #use these flags for aria2c
    aria2_flags=(-x 16 -s 16 --max-tries=10 --retry-wait=30 --max-file-not-found=5 --http-no-cache=true --check-certificate=false \
      --allow-overwrite=true --auto-file-renaming=false --remove-control-file --auto-save-interval=0 \
      --console-log-level=error --show-console-readout=false --summary-interval=1 "$url" -d "$(dirname "${file}")" -o "$(basename "${file}")")
    
    #suppress output if -q flag passed
    if [ "$quiet" == 1 ];then
      aria2c --quiet "${aria2_flags[@]}"
      local exitcode=$?
      
    else #run aria2c without quietness and format download-progress output
      local terminal_width="$(tput cols || echo 80)"
      
      #run aria2c and reduce its output.
      aria2c "${aria2_flags[@]}" | while read -r line ;do
        
        #filter out unnecessary lines
        line="$(grep --line-buffered -v '\-\-\-\-\-\-\-\-\|======\|^FILE:\|^$\|Summary\|Results:\|download completed\.\|^Status Legend:\||OK\||stat' <<<"$line" || :)"
        
        if [ ! -z "$line" ];then #if this line still contains something and was not erased by grep
          
          #check if this line is a progress-stat line, like: "[#a6567f 20MiB/1.1GiB(1%) CN:16 DL:14MiB ETA:1m19s]"
          if [[ "$line" == '['*']' ]];then
            
            #hide cursor
            printf "\e[?25l"
            
            #print the total data only, like: "0.9GiB/1.1GiB"
            statsline="$(echo "$line" | awk '{print $2}' | sed 's/(.*//g' | tr -d '\n') "
            #get the length of statsline
            characters_subtract=${#statsline}
            
            #determine how many characters are available for the progress bar
            available_width=$(($terminal_width - $characters_subtract))
            #make sure available_width is a positove number (in case bash-variable COLUMNS is empty)
            [ "$available_width" -le 0 ] && available_width=20
            
            #get progress percentage from aria2c output
            percent="$(grep -o '(.*)' <<<"$line" | tr -d '()%')"
            
            #echo "percent: $percent"
            #echo "available_width: $available_width"
            
            #determine how many characters in progress bar to light up
            progress_characters=$(((percent*available_width)/100))
            
            statsline+="\e[92m\e[1m$(for ((i=0; i<$progress_characters; i++)); do printf "—"; done)\e[39m" # other possible characters to put here: █🭸
            echo -ne "\e[0K${statsline}\r\e\e[0m" 1>&2 #clear and print over previous line
            
            #reduce the line and print over the previous line, like: "1.1GiB/1.1GiB(98%) DL:18MiB"
            #echo "$line" | awk '{print $2 " " $4 " " substr($5, 1, length($5)-1)}' | tr -d '\n'
            
          else
            #this line is not a progress-stat line; don't format output
            echo "$line"
          fi
        fi
        
      done
      local exitcode=${PIPESTATUS[0]}
    fi
  fi
  
  #display a "download complete" message
  if [ $exitcode == 0 ] && [ "$quiet" == 0 ];then
    
    #show cursor
    printf "\e[?25h"
    
    #display "done" message
    if [ "$use" == aria2c ];then
      local progress_characters=$(($terminal_width - 5))
      echo -e "\e[0KDone \e[92m\e[1m$(for ((i=0; i<$progress_characters; i++)); do printf "—"; done)\e[39m\e[0m" 1>&2 #clear and print over previous line
    else
      echo
      status_green "Done" 1>&2
    fi
  elif [ $exitcode != 0 ] && [ "$quiet" == 0 ];then
    #show cursor
    printf "\e[?25h"
    
    echo -e "\n\e[91mFailed to download: $url\nPlease review errors above.\e[0m" 1>&2
  fi
  
  return $exitcode
}

chmod() { #say what is being made executable
  status "Making executable: $2"
  command chmod "$@"
  return $?
}

unzip() { #say what is being extracted
  #some scripts add a flag to the unzip command before specifying the file.
  #This checks the first two arguments to display the file being extracted.
  [ -f "$1" ] && status "Extracting: $1"
  [ -f "$2" ] && status "Extracting: $2"
  
  #The -o flag means to overwrite without prompting
  command unzip -o "$@"
  return $?
}

sudo_popup() { #just like sudo on passwordless systems like PiOS, but displays a password dialog otherwise. Avoids displaying a password prompt to an invisible terminal.
  if sudo -n true; then
    # sudo is available (within sudo timer) or passwordless
    sudo "$@"
  else
    # sudo is not available (not within sudo timer)
    pkexec "$@"
  fi
}

nproc() { #Reduce the number of compile threads on low-RAM systems
  #The Pi02 and Pi3A+ have 4 cores but 500MB of RAM. This function reduces the number of threads to use if RAM is low.
  
  local free="$(free | grep 'Mem:' | awk '{print $4}')"
  
  if [ "$free" -gt $((500*1024)) ] || [ "$GITHUB_ACTIONS" == "true" ];then
    #Free memory >500MB, use normal number of threads
    command nproc
  else
    #Free memory <500MB, use 1 thread
    echo 1
    warning "Your system has less than 500MB of free RAM, so this will compile with only 1 thread."
  fi
}
#end of command interceptors

#Stop a running api function if user presses Ctrl+C, but avoid doing this if api is sourced in an interactive terminal.
if [[ $- != *i* ]] ;then
  trap "exit 1" INT
  cd $HOME
fi

add_english

#if this script is being run standalone, set the DIRECTORY variable based on the script's location
if [[ "$0" == */api ]];then
  #it is necessary to set it now in order for yadflags and yad theme to work as intended.
  DIRECTORY="$(readlink -f "$(dirname "$0")")"
  export DIRECTORY
#if being sourced, ensure DIRECTORY variable is set
elif [ -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

#set the system GTK theme for yad windows
guimode="$(cat "${DIRECTORY}/data/settings/App List Style" 2>/dev/null || echo yad-default)"
if [ "$guimode" == yad-default ];then
  export GTK_THEME=''
elif [[ "$guimode" = yad* ]];then
  export GTK_THEME=${guimode//yad-/}
elif [ "$guimode" == xlunch-light-3d ];then
  export GTK_THEME=''
elif [ "$guimode" == xlunch-dark-3d ];then
  export GTK_THEME=Adwaita-dark
elif [ "$guimode" == xlunch-dark ];then
  export GTK_THEME=Adwaita-dark
fi

#this array stores flags that are used in all yad windows - saves on the typing and makes it easy to change an attribute on all dialogs from one place.
yadflags=(--class Artian-Apps --name "Artian-Apps" --center --window-icon="${DIRECTORY}/icons/logo.png" --title="Artian-Apps" --separator='\n')

#determine if host system is 64 bit arm64 or 32 bit armhf
if [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 02' ];then
  arch=64
elif [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 01' ];then
  arch=32
else
  error "Failed to detect OS CPU architecture! Something is very wrong."
fi

#Make dual-pane yad windows work correctly on wayland
export GDK_BACKEND=x11

# add upstream and original lsb-release info for all scripts to use

# _os_original_* variables are only set for downstream releases (eg: Pop!_OS, Linux Mint, and KDE Neon)
# this allows for scripts to simply check for the existance of _os_original_* variables and operate differently if found
# note: Pop!_OS, Linux Mint, and KDE Neon are NOT official Ubuntu flavors (like Kubuntu, Ubuntu MATE, Ubuntu Kylin, Ubuntu Budgie, Lubuntu, Ubuntu Studio, Ubuntu Unity, and Xubuntu)
# official Ubuntu flavors use ONLY the official Ubuntu repositories and are directly supported by Ubuntu. These other releases are simply "based" on Ubuntu, much like Raspbian and PiOS are "based" on Debian.

# first check if lsb_release has an upstream option -u
# if not, check if there is an upstream-release file
# if not, check if there is a lsb-release.diverted file
# if not, assume that this is not a ubuntu derivative
# use mapfile to temporarily store release info for speed
if [ -z "$__os_id" ] || [ -z "$__os_desc" ] || [ -z "$__os_release" ] || [ -z "$__os_codename" ]; then
  if lsb_release -a -u &>/dev/null; then
    # This is a Ubuntu Derivative, checking the upstream-release version info
    mapfile -t os_u < <(lsb_release -s -i -d -r -c -u)
    export __os_id="${os_u[0]}"
    export __os_desc="${os_u[1]}"
    export __os_release="${os_u[2]}"
    export __os_codename="${os_u[3]}"
    mapfile -t os < <(lsb_release -s -i -d -r -c)
    export __os_original_id="${os[0]}"
    export __os_original_desc="${os[1]}"
    export __os_original_release="${os[2]}"
    export __os_original_codename="${os[3]}"
  elif [ -f /etc/upstream-release/lsb-release ]; then
    # ubuntu 22.04+ Linux Mint no longer includes the lsb_release -u option
    # add a parser for the /etc/upstream-release/lsb-release file
    source /etc/upstream-release/lsb-release
    export __os_id="$DISTRIB_ID"
    export __os_desc="$DISTRIB_DESCRIPTION"
    export __os_release="$DISTRIB_RELEASE"
    export __os_codename="$DISTRIB_CODENAME"
    mapfile -t os < <(lsb_release -s -i -d -r -c)
    export __os_original_id="${os[0]}"
    export __os_original_desc="${os[1]}"
    export __os_original_release="${os[2]}"
    export __os_original_codename="${os[3]}"
    unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
  elif [ -f /etc/lsb-release.diverted ]; then
    # ubuntu 22.04+ Pop!_OS no longer includes the /etc/upstream-release/lsb-release or the lsb_release -u option
    # add a parser for the new /etc/lsb-release.diverted file
    source /etc/lsb-release.diverted
    export __os_id="$DISTRIB_ID"
    export __os_desc="$DISTRIB_DESCRIPTION"
    export __os_release="$DISTRIB_RELEASE"
    export __os_codename="$DISTRIB_CODENAME"
    mapfile -t os < <(lsb_release -s -i -d -r -c)
    export __os_original_id="${os[0]}"
    export __os_original_desc="${os[1]}"
    export __os_original_release="${os[2]}"
    export __os_original_codename="${os[3]}"
    unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
  else
    mapfile -t os < <(lsb_release -s -i -d -r -c)
    export __os_id="${os[0]}"
    export __os_desc="${os[1]}"
    export __os_release="${os[2]}"
    export __os_codename="${os[3]}"
  fi
fi

# add /usr/local/bin to path if not currently present which is often not searched by default if /usr/local/bin did not exist on boot.
# this path will automatically be added on next boot if a binary is placed in it by a Artian-apps script
[[ "$PATH" != */usr/local/bin* ]] && export PATH="/usr/local/bin:$PATH"

#if this script is being run standalone, run the specified function
if [[ "$0" == */api ]];then
  "$@"
  exit $?
fi

export DIRECTORY
