From ae99e1b2d2a2de68f3812087ed60162aa1d46692 Mon Sep 17 00:00:00 2001
From: theofficialgman <28281419+theofficialgman@users.noreply.github.com>
Date: Thu, 29 Feb 2024 20:27:16 -0500
Subject: [PATCH] change `read_packages_file` to `pkgapp_packages_required`
pick performance patches from https://github.com/Botspot/pi-apps/pull/2557
if a secondary OR package is already installed and the first is not, list that as required instead of the first
don't redefine $arch, remove -qq flag
4 places in api use apt-cache policy, only 1 has -qq so it's probably fine to remove it. Otherwise it should go everywhere.
check if `packages_file_required` is empty before attempting to install packages with manage script
Co-Authored-By: Botspot <54716352+Botspot@users.noreply.github.com>
---
api | 119 ++++++++++++++++++++++++---------------------------------
gui | 10 ++++-
manage | 5 ++-
3 files changed, 63 insertions(+), 71 deletions(-)
diff --git a/api b/api
index 08fda3f695..ad01da8fe0 100755
--- a/api
+++ b/api
@@ -123,9 +123,9 @@ package_installed() { #exit 0 if $1 package is installed, otherwise exit 1
package_available() { #determine if the specified package-name exists in a local repository for the current dpkg architecture
local package="$1"
- local arch="$(dpkg --print-architecture)"
+ local dpkg_arch="$(dpkg --print-architecture)"
[ -z "$package" ] && error "package_available(): no package name specified!"
- local output="$(apt-cache policy -qq "$package":"$arch" | grep "Candidate:")"
+ local output="$(apt-cache policy "$package":"$dpkg_arch" | grep "Candidate:")"
if [ -z "$output" ]; then
return 1
elif echo "$output" | grep -q "Candidate: (none)"; then
@@ -1468,13 +1468,13 @@ app_type() { #there are 'standard' apps, and there are 'package' apps - an alias
#if neither conditional above evaluated to true, no output will be returned and the function exits with code 1
}
-read_packages_file() { #Returns which packages are to be installed from a package-app - handle the '|' separater
- #if all packages are not available that are needed to be installed, returns no output
+pkgapp_packages_required() { #Returns which packages are required during installation of a package-app - handle the '|' separater
+ #returns no output if not all required packages are available
local app="$1"
- [ -z "$app" ] && error "read_packages_file(): no app specified!"
+ [ -z "$app" ] && error "pkgapp_packages_required(): no app specified!"
if [ ! -f "${DIRECTORY}/apps/$app/packages" ];then
- error "read_packages_file(): This app '$app' does not have a packages file!"
+ error "pkgapp_packages_required(): This app '$app' does not have a packages file!"
fi
local IFS=' '
@@ -1486,20 +1486,34 @@ read_packages_file() { #Returns which packages are to be installed from a packag
if [[ "$word" == *'|'* ]];then
IFS='|'
local package
- local available="no"
+ local found="no"
+ #first check for any already installed packages
+ #if a package is already installed, it should be used even if it is not the first option in the OR
+ for package in $word ;do
+ if package_installed "$package" ;then
+ packages+="$package "
+ found="yes"
+ break
+ fi
+ done
+ if [ "$found" == "yes" ]; then
+ #a package in the OR is already installed
+ continue
+ fi
+ #then check for available packages
for package in $word ;do
if package_available "$package" ;then
packages+="$package "
- available="yes"
+ found="yes"
break
fi
done
- if [ "$available" == "no" ]; then
+ if [ "$found" == "no" ]; then
#no package in the OR is available so set the output as empty
packages=''
break
fi
- else
+ else #non-OR package - no parsing '|' separators
if package_available "$word" ;then
#no separator, so return it without change
packages+="$word "
@@ -1535,7 +1549,7 @@ will_reinstall() { #return 0 if $1 app will be reinstalled during an update, oth
return 0
elif [ "$new_scriptname" == packages ];then
#update to package-app: rather than check for file change, check if the installed package(s) would be different (avoid reinstall when adding an alternative package name with '|')
- if [ "$(read_packages_file "$app")" != "$(DIRECTORY="${DIRECTORY}/update/pi-apps" read_packages_file "$app")" ];then
+ if [ "$(pkgapp_packages_required "$app")" != "$(DIRECTORY="${DIRECTORY}/update/pi-apps" pkgapp_packages_required "$app")" ];then
return 0
else
return 1
@@ -1762,7 +1776,7 @@ refresh_pkgapp_status() { #for the specified package-app, if dpkg thinks it's in
# optional: directly pass package as second input argument (can be null for when you want to mark an app as hidden)
if [ -z ${2+x} ]; then
#From the list of necessary packages for the $app app, get the first one that is available in the repos
- local package="$(read_packages_file "$app" | awk '{print $1}')"
+ local package="$(pkgapp_packages_required "$app" | awk '{print $1}')"
else
local package="$(echo "$2" | awk '{print $1}')"
fi
@@ -1780,7 +1794,7 @@ refresh_pkgapp_status() { #for the specified package-app, if dpkg thinks it's in
echo 'installed' > "${DIRECTORY}/data/status/${app}"
shlink_link "$app" install &
fi
- #if that package is not installed, then it only exists on the repositories, the read_packages_file function output guarantees it
+ #if that package is not installed, then it only exists on the repositories, the pkgapp_packages_required function output guarantees it
else
#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
@@ -1802,73 +1816,40 @@ refresh_pkgapp_status() { #for the specified package-app, if dpkg thinks it's in
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 arch="$(dpkg --print-architecture)"
+ local dpkg_arch="$(dpkg --print-architecture)"
# get list of all packages needed by package apps
# this variable needs to be global to be accessible from the subshells
- local packages="$(sed '' -- "${DIRECTORY}"/apps/*/packages | sed 's/ | /\n/g' | sed 's/ /\n/g' | awk NF | sed 's/$/:'"$arch"'/' | tr '\n' ' ')"
+ local packages="$(sed '' -- "${DIRECTORY}"/apps/*/packages | sed 's/ | /\n/g' | sed 's/ /\n/g' | awk NF | sed 's/$/:'"$dpkg_arch"'/' | tr '\n' ' ')"
packages="${packages::-1}" #remove final space character
# get policy info for all packages needed by package apps
# this variable needs to be global to be accessible from the subshells
local apt_cache_output="$(echo "$packages" | xargs -r apt-cache policy)"
+ #redefine package_installed to only read /var/lib/dpkg/status once
+ local dpkg_status="$(grep -x "Package: \($(echo "$packages" | sed 's/:'"$arch"'//g ; s/ /\\|/g')\)" -A 2 /var/lib/dpkg/status)"
+
+ #redefine package_available() to use apt_cache_output and avoid running apt-cache multiple times
+ (package_available() { #this will only be used in this function's subprocesses.
+ echo "$apt_cache_output" | grep -x "${1}:" -A2 | grep -vxF " Candidate: (none)" | grep -q "^ Candidate:"
+ }
+
+ #this one only takes off 0.1s on my pi5, so if it causes issues it could be removed
+ 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
+ echo "$dpkg_status" | grep -x "Package: $package" -A 2 -m1 | grep -qxF 'Status: install ok installed'
+ }
+
# parse apt_cache_output for each package app
# generate list of all packages needed by package apps
for app in $(list_apps package) ;do
- local IFS=' '
- local word=''
- local needed_packages=''
- #read each word - packages separated by '|' are 1 word
- for word in $(cat "${DIRECTORY}/apps/$app/packages" | sed 's/ | /|/g') ;do
- local available="no"
- if [[ "$word" == *'|'* ]];then
- IFS='|'
- local package
- for package in $word ;do
- local package_output="$(echo "$apt_cache_output" | grep "^$package:" -A2 | grep "Candidate:")"
- if [ -z "$package_output" ]; then
- # package is not available
- package=''
- continue
- elif echo "$package_output" | grep -q "Candidate: (none)"; then
- # package is not available
- package=''
- continue
- else
- # package is available
- available="yes"
- needed_packages+="$package "
- break
- fi
- done
- if [ "$available" == "no" ]; then
- #no package in the OR is available
- break
- fi
- else
- local package_output="$(echo "$apt_cache_output" | grep "^$word:" -A2 | grep "Candidate:")"
- if [ -z "$package_output" ]; then
- # package is not available
- break
- elif echo "$package_output" | grep -q "Candidate: (none)"; then
- # package is not available
- break
- else
- # package is available
- available="yes"
- needed_packages+="$word "
- fi
- fi
- done
- if [ "$available" == "no" ]; then
- #at least one required package is not available so set package as hidden
- refresh_pkgapp_status "$app" ""
- else
- needed_packages="${needed_packages::-1}"
- debug "$app: $needed_packages"
- refresh_pkgapp_status "$app" "$needed_packages"
- fi
- done
+ refresh_pkgapp_status "$app" #with redefined package_available and package_installed this is fast
+ done)
}
refresh_app_list() { #Force-regenerate the app list
diff --git a/gui b/gui
index 58d84fa418..afe877f511 100755
--- a/gui
+++ b/gui
@@ -324,7 +324,15 @@ details_window() { #input: prefix/app
#If package-app, show what packages it installs
if [ -f "${DIRECTORY}/apps/${app}/packages" ];then
- local packages="$(read_packages_file "$app")"
+ local packages="$(pkgapp_packages_required "$app")"
+ if [ -z "$packages" ]; then
+ #returned required package list is empty. application cannot be installed
+ #this case cannot be hit if the application is already hidden which it should be
+ yad "${yadflags[@]}" --title=Results --width=310 \
+ --text=""\""$(list_apps | grep -i -m1 "^$query")"\"" is not compatible with your ${__os_desc} ${arch}-bit OS." \
+ --button=OK:0
+ return
+ fi
if [ "$(wc -w <<<"$packages")" == 1 ];then
#if package-app uses only 1 package, use singular case
abovetext+=$'\n'"- This app installs the ${packages} package."
diff --git a/manage b/manage
index 7ee5bcc5cf..7bab64bfe6 100755
--- a/manage
+++ b/manage
@@ -665,8 +665,11 @@ elif [ "$1" == 'install' ] || [ "$1" == 'uninstall' ];then
#if this app just lists a package-name, set the appscript to install that package
else #package-app: directly use apt to install what is mentioned in the packages file
+
+ packages_to_install=$(pkgapp_packages_required "$app")
+ [ -z "$packages_to_install" ] && error "It appears $app does not have any packages that can be installed on your system."
- appscript=(bash -c -o pipefail "apt_lock_wait ; sudo -E apt $(echo "$action" | sed 's/uninstall/purge --autoremove/g') -yf $(read_packages_file "$app") 2>&1 | less_apt")
+ appscript=(bash -c -o pipefail "apt_lock_wait ; sudo -E apt $(echo "$action" | sed 's/uninstall/purge --autoremove/g') -yf $packages_to_install 2>&1 | less_apt")
#fix edge case: new will_reinstall function avoids a reinstall if packages to install do not change.
#unfortunately updater script does not source the new api so chromium is being reinstalled after we added "| chromium" to the packages file.