diff --git a/.DS_Store b/.DS_Store index 732872e..3e593cb 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/CommonResources b/CommonResources index 5051bdf..71abd13 100755 --- a/CommonResources +++ b/CommonResources @@ -9,6 +9,7 @@ setupHelperDir="/data/SetupHelper" source "$setupHelperDir/EssentialResources" source "$setupHelperDir/LogHandler" source "$setupHelperDir/ServiceResources" +source "$setupHelperDir/UpdateResources" source "$setupHelperDir/DbusSettingsResources" # what action the script should take: @@ -35,6 +36,8 @@ runAgain=false filesUpdated=false restartGui=false + + # yesNoPrompt provides user prompting requesting a yes/no response # # $1 is the prompt displayed when pausing for user input @@ -81,6 +84,11 @@ yesNoPrompt () standardActionPrompt () { + if [ -f "$setupOptionsDir/optionsSet" ]; then + enableReinstall=true + else + enableReinstall=false + fi if [ $# -gt 0 ] && [ $1 == 'MORE_PROMPTS' ]; then updateScriptAction=false else @@ -90,7 +98,7 @@ standardActionPrompt () echo echo "Available actions:" echo " Install and activate (i)" - if $optionsSet ; then + if $enableReinstall ; then echo " Reinstall (r) based on options provided at last install" fi echo " Uninstall (u) and restores all files to stock" @@ -112,7 +120,7 @@ standardActionPrompt () break ;; [rR]*) - if $optionsSet ; then + if $enableReinstall ; then scriptAction='INSTALL' break fi @@ -122,7 +130,7 @@ standardActionPrompt () break ;; [qQ]*) - exit $EXIT_SUCCESS + exit ;; [lL]*) displayLog $packageLogFile @@ -259,32 +267,6 @@ restoreActiveFile () } -# converts a Venus version string to a version number to facilitate comparisions -# -# beta numbers are handled in a special way. -# the released verison has no beta string -# it's number would be less than a beta version -# we bump the version number by 999 -# so the release is always greater than any beta -# -# the "-large-n" portion is discarded so those versions -# can be compared to non-large versions -# -# the version string is passed as $1 -# the number is returned in versionNumber - -function versionStringToNumber () -{ - local versionBeta="" - - read versionNumber versionBeta <<< $(echo $1 | sed -e 's/v//' -e 's/-.*$//' | \ - awk -v FS='[v.~-]' '{printf "%d%03d%03d %d\n", $1, $2, $3, $3}') - if (( $versionBeta == 0 )); then - ((versionNumber += 999)) - fi -} - - # checkFileSets validates the file sets used install package modifications # # It attempts to create a file set for a new Venus version @@ -328,7 +310,7 @@ _checkFileSets () local baseName="" local file="" - rm -f "$fileSet/INCOMPLETE" + rm -f "$pkgFileSets/INCOMPLETE" for file in $fileList ; do baseName=$(basename "$file") @@ -413,7 +395,7 @@ _checkFileSets () if [ -f "$fileSet/INCOMPLETE" ]; then logMessage "ERROR: incomplete file set for $venusVersion - can't continue" - exit $EXIT_FILE_SET_ERROR + exit fi } @@ -423,9 +405,14 @@ _checkFileSets () endScript () { + if $restartGui $$ !rebootNeeded ; then + logMessage "restarting GUI" + svc -t /service/gui + fi + if [ $scriptAction == 'INSTALL' ] ; then - # assume that if we get this far, any command line opitons have already been set - touch "$setupOptionsDir/optionsSet" + # indicate installation was successful, so reinstalls can use the existing options in the future + touch "$setupOptionsDir/optionsSet" # set up reinstallMods to run this script again after a VenusOS update if [ ! -f "$reinstallScriptsList" ] || [ $(grep -c "$fullScriptName" "$reinstallScriptsList") == 0 ]; then @@ -445,15 +432,33 @@ endScript () sed -i -e 's?/data/SetupHelper?nohup /data/SetupHelper?' -e 's?reinstallMods?reinstallMods > /dev/null \&?' "$rcLocal" fi - # if script needs to run again, installedVersionFile flag file is removed - # script should run again at boot time via reinstallMods + # installed flag is removed if script needs to run again if $runAgain ; then logMessage "script will run again at startup" - rm -f "$installedVersionFile" - # otherwise, installation is complete - update installedVersion + rm -f "$installedFlag" + # otherwise installed flag is set so script won't be run again at boot else - cp "$scriptDir/version" "$installedVersionFile" - fi + cp "$scriptDir/version" "$installedFlag" + fi + + # add package to packageList if not already there and the GitHub paths have been specified + if [ ! -z $packageGitHubUser ] && [ ! -z $packageGitHubBranch ]; then + # move from previous locaiton + oldPackageListFile="$setupOptionsRoot/SetupHelper/packageList" + if [ -f "$oldPackageListFile" ]; then + mv "$oldPackageListFile" "$packageListFile" + fi + + if [ ! -f "$packageListFile" ] || [ $(grep -c "^$packageName\s" "$packageListFile") == 0 ]; then + logMessage "adding $packageName to SetupHelper package list" + echo "$packageName $packageGitHubUser $packageGitHubBranch" >> "$packageListFile" + fi + fi + + # update package version for the gui - takes time so do in background + nohup "/data/SetupHelper/updatePackageVersions" > /dev/null & + + elif [ $scriptAction == 'UNINSTALL' ] ; then # remove this script from reinstallScriptsList to prevent further calls during boot if [ -f "$reinstallScriptsList" ] && [ ! $(grep -c "$fullScriptName" "$reinstallScriptsList") == 0 ]; then @@ -463,54 +468,47 @@ endScript () fi # clean up only - flag not used since package is being removed - rm -f "$installedFlag" # obsolete but remove it anyway - rm -f "$installedVersionFile" - else - logMessage "unexpected script action $scriptAction - did not install or uninstall" - fi - - if $restartGui $$ !rebootNeeded ; then - logMessage "restarting GUI" - svc -t /service/gui + rm -f "$installedFlag" + + # update package version for the gui - takes time so do in background + nohup "/data/SetupHelper/updatePackageVersions" > /dev/null & fi # this script was called from reinstallMods # set exit code based on actual code - if $runningAtBoot || $deferReboot; then + if $runningAtBoot ; then if $rebootNeeded ; then - logMessage "reboot needed" - exit $EXIT_REBOOT - elif $versionNotCompatible ; then - logMessage "version not compatible - exiting" - exit $EXIT_INCOMPATIBLE_VERSION - elif $platformNotCompatible ; then - logMessage "platform not compatible - exiting" - exit $EXIT_INCOMPATIBLE_PLATFOM + logMessage "reboot pending" + exit $exitReboot else logMessage "completed" - exit $EXIT_SUCCESS + exit $exitSuccess fi # this script was run manually else # if reboot needed ask user if it should be done now if $rebootNeeded ; then - yesNoPrompt "Reboot system now (y) or issue a reboot manually later (n): " - if $yesResponse ; then - echo "rebooting ..." - reboot - else - echo "system must be rebooted to finish installation and activate components" - exit $EXIT_REBOOT - fi + if $deferReboot ; then + exit $exitReboot + else + yesNoPrompt "Reboot system now (y) or issue a reboot manually later (n): " + if $yesResponse ; then + echo "rebooting ..." + reboot + else + echo "system must be rebooted to finish installation and activate components" + fi + fi + elif $runAgain ; then echo "$shortScriptName NOT completed" echo " run it again manually to finish" echo " or reboot the system to finish automatically" - exit $EXIT_RUN_AGAIN - else + exit $exitSuccess + else logMessage "completed" - exit $EXIT_SUCCESS + exit $exitSuccess fi fi } @@ -519,25 +517,26 @@ endScript () # check for reinstall parameter # set $scriptAction to control work following the source command -# if "force" is also provided on the command line, then the installedVersionFile is not checked -# installedVersionFile contains the installed version (if any) +# if "force" is also provided on the command line, then the installedFlag is not checked +# installedFlag contains the installed version (if any) # it is compared to the version file in the package directory -# if installedVersionFile is missing or contents are different, the installation will proceed -# if the two versions match, there is no need to reinstall the package +# if installedFlag is missing or contents are different, the installation will proceed +# if the two versions matched, there is no need to reinstall the package # we assume a reinstall is always run without benefit of a console (runningAtBoot will be true) # so there will be no prompts and all actions will be automatic # -# runningAtBoot would be better named runningWithoutUserPrompts but not changing that now -# -# "deferReboot" signals that endScript should not prompt for a user reboot, but return EXIT_REBOOT +# "deferReboot" signals that endScript should not prompt for a user reboot, but return exitReboot # This is used by the manual package install called from SetupHelper setup script # -# "install" causes the package to be installed silently # "uninstall" causes the package to be uninstalled silently # # command line parameters may appear in any order -# collect command line options +# make sure rootfs is mounted R/W +if [ -f /opt/victronenergy/swupdate-scripts/remount-rw.sh ]; then + /opt/victronenergy/swupdate-scripts/remount-rw.sh +fi + reinstall=false force=false deferReboot=false @@ -552,10 +551,6 @@ while [ $# -gt 0 ]; do "deferReboot") deferReboot=true ;; - "install") - scriptAction='INSTALL' - logToConsole=false - ;; "uninstall") scriptAction='UNINSTALL' logToConsole=false @@ -565,66 +560,22 @@ while [ $# -gt 0 ]; do shift done -# make sure rootfs is mounted R/W -if [ -f /opt/victronenergy/swupdate-scripts/remount-rw.sh ]; then - /opt/victronenergy/swupdate-scripts/remount-rw.sh -fi - -# move old installedFlag ("...inInstalled...") -# to its new name ...installedVersionFile... -if [ ! -f "$installedVersionFile" ] && [ -f "$installedFlag" ]; then - installedVersion=$(cat "$installedFlag") - if [ -z $ installedVersion ]; then - installedVersion = "unknown" - else - echo $installedVersion > "$installedVersionFile" - fi -fi -rm -f "$installedFlag" - -# command line options are needed for blind install or reinstall -# so look for optionsSet - -# packages that require options to proceed unattended -# must include the optionsRequried flag file in their package directory -# if the flag is present and options haven't been previously set, -# SD/USB media will be checked for the package options directory -# and copy them into position - -opitonsRequiredFile="$scriptDir/optionsRequired" -optionsSet=false -if [ -f $opitonsRequiredFile ]; then - if [ -f "$setupOptionsDir/optionsSet" ]; then - optionsSet=true - # options not set - check media for options if doing a blind install - elif [ $scriptAction == 'INSTALL' ]; then - mediaList=($(ls /media)) - for dir in ${mediaList[@]} ; do - altSetupDir="/media/$dir/"$(basename $setupOptionsRoot)"/$packageName" - if [ -f "$altSetupDir/optionsSet" ]; then - cp -r "$altSetupDir" "$setupOptionsRoot" - if [ -f "$setupOptionsDir/optionsSet" ]; then - logMessage "options retrieved from SD/USB media" - optionsSet=true - fi - break - fi - done - fi - -# no command line options are needed - ok to reinstall even if -# setup was not run from the command line -else - optionsSet=true -fi - if $reinstall ; then if $force ; then scriptAction='INSTALL' - elif [ ! -f "$installedVersionFile" ]; then + elif [ ! -f "$installedFlag" ]; then scriptAction='INSTALL' else - exit $EXIT_SUCCESS + # compare installed version with the one in the package directory + # nothing to do + # if they are not the same, continue with install + iVer=$(cat "$installedFlag") + pVer=$(cat "$scriptDir/version") + if [ "$iVer" = "$pVer" ]; then + exit + else + scriptAction='INSTALL' + fi fi runningAtBoot=true logToConsole=false @@ -639,85 +590,32 @@ fi versionStringToNumber $venusVersion venusVersionNumber=$versionNumber -# create obsolete version file if it does not already exist -# accommodates previous mechanism that used shell varaible -if [ ! -z $obsoleteVersion ] && [ ! -f "$scriptDir/obsoleteVersion" ]; then - echo $obsoleteVersion > "$scriptDir/obsoleteVersion" -fi +# check to see if package is compatible with this Venus version +notCompatible=false +if [ -f "$scriptDir/obsoleteVersion" ]; then + versionStringToNumber $(cat "$scriptDir/obsoleteVersion") + obsoleteVersion=$versionNumber -# prevent installing Raspberry Pi packages on other platforms -platformNotCompatible=false -if [ -f "$scriptDir/raspberryPiOnly" ]; then - if [ -f /etc/venus/machine ]; then - machine=$(cat /etc/venus/machine) - fi - if [ -z $machine ]; then - if $isInstalled ; then - logMessage "can't determine Venus device type - uninstalling" - scriptAction='UNINSTALL' - else - logMessage "can't determine Venus device type - exiting" - exit $INCOMPATIBLE_PLATFORM - fi - elif [ $machine != "raspberrypi2" ] && [ $machine != "raspberrypi4" ]; then - platformNotCompatible=true - if $isInstalled ; then - logMessage "$packageName not compatible with $machine - uninstalling" - scriptAction='UNINSTALL' - else - logMessage "$packageName not compatible with $machine - exiting" - exit $EXIT_INCOMPATIBLE_PLATFOM - fi - fi + if (( $venusVersionNumber >= $obsoleteVersion )); then + notCompatible=true + fi fi -# check to see if package is compatible with this Venus version -versionNotCompatible=false -if [ -f "$scriptDir/firstCompatibleVersion" ]; then - firstCompatibleVersion = $(cat "$scriptDir/firstCompatibleVersion") -# no first compatible version specified - use the default -else - firstCompatibleVersion='v2.40' -fi -versionStringToNumber $firstCompatibleVersion -firstCompatibleVersionNumber=$versionNumber -if (( $venusVersionNumber < $firstCompatibleVersionNumber )); then - versionNotCompatible=true -elif [ -f "$scriptDir/obsoleteVersion" ]; then - versionStringToNumber $(cat "$scriptDir/obsoleteVersion") - obsoleteVersionNumber=$versionNumber - if (( $venusVersionNumber >= $obsoleteVersionNumber )); then - versionNotCompatible=true - fi -fi -if $versionNotCompatible ; then - # if not installed, log message and exit - if $isInstalled ; then - logMessage "$packageName not compatible with Venus $venusVersion - can't install" - exit $EXIT_INCOMPATIBLE_VERSION - else - logMessage "$packageName not compatible with Venus $venusVersion - uninstalling" - scriptAction='UNINSTALL' - fi +if $notCompatible ; then + logMessage "$packageName not compatible with Venus $venusVersion" + scriptAction='UNINSTALL' else - if [ ! -d "$setupOptionsRoot" ]; then - logMessage "creating root setup options directory $setupOptionsRoot" - mkdir $setupOptionsRoot - fi - - if [ ! -d "$setupOptionsDir" ]; then - logMessage "creating package options directory to $setupOptionsDir" - mkdir $setupOptionsDir - fi -fi + if [ ! -d "$setupOptionsRoot" ]; then + logMessage "creating root setup options directory $setupOptionsRoot" + mkdir $setupOptionsRoot + fi -# attempting an install without the comand line prompting -# and needed options have not been set yet -# can't continue -if [ $scriptAction == 'INSTALL' ] && ! $optionsSet ; then - logMessage "required options have not been set - can't install" - exit $EXIT_OPTIONS_NOT_SET + if [ ! -d "$setupOptionsDir" ]; then + logMessage "creating package options directory to $setupOptionsDir" + mkdir $setupOptionsDir + fi fi + # if forcing an uninstall, skip file set checks if [ $scriptAction != 'UNINSTALL' ]; then _checkFileSets diff --git a/EssentialResources b/EssentialResources index 501eb51..70e7c22 100755 --- a/EssentialResources +++ b/EssentialResources @@ -11,10 +11,6 @@ fullScriptName="$scriptDir/$(basename "$0")" venusVersion="$(cat /opt/victronenergy/version | head -n 1)" -installedVersionPrefix="/etc/venus/installedVersion-" -installedVersionFile="$installedVersionPrefix"$packageName - -# obsolete - use installedVersion installedFlagPrefix="/etc/venus/inInstalled-" installedFlag="$installedFlagPrefix"$packageName @@ -29,18 +25,8 @@ fileSet="$pkgFileSets/$venusVersion" rcLocal="/data/rcS.local" # defined exit codes - must be consistent between all setup scripts and reinstallMods -# and PackageManager.py -EXIT_SUCCESS=0 -EXIT_REBOOT=123 -EXIT_ERROR=255 # unknown error -EXIT_INCOMPATIBLE_VERSION=254 -EXIT_INCOMPATIBLE_PLATFOM=253 -EXIT_FILE_SET_ERROR=252 -EXIT_OPTIONS_NOT_SET=251 -EXIT_RUN_AGAIN=250 -# old variables - keep for compatibility -exitReboot=$EXIT_REBOOT -exitSuccess=$EXIT_SUCCESS +exitReboot=123 +exitSuccess=0 reinstallParam="reinstall" diff --git a/FileSets/MbDisplayPackageVersion.qml b/FileSets/MbDisplayPackageVersion.qml index d5164d9..db69fe6 100644 --- a/FileSets/MbDisplayPackageVersion.qml +++ b/FileSets/MbDisplayPackageVersion.qml @@ -6,152 +6,40 @@ MbItem { id: root property int versionIndex - property string servicePrefix - property string settingsPrefix + property string bindPrefix - VBusItem { id: packageName; bind: getSettingsBind ("PackageName") } - property VBusItem rebootNeededItem: VBusItem { bind: getServiceBind ( "RebootNeeded") } - property bool rebootNeeded: rebootNeededItem.valid && rebootNeededItem.value == 1 - - VBusItem { id: platformItem; bind: Utils.path("com.victronenergy.packageMonitor", "/Platform" ) } - VBusItem { id: incompatibleItem; bind: getServiceBind ( "Incompatible" ) } - property string incompatibleReason: incompatibleItem.valid ? incompatibleItem.value : "" - property bool compatible: incompatibleReason == "" - property string platform: platformItem.valid ? platformItem.value : "??" - - - function statusText () + function getBind(param) { - if (rebootNeeded) - return (" REBOOT needed") - else if (incompatibleReason == 'PLATFORM') - return ( "not compatible with " + platform ) - else if (incompatibleReason == 'VERSION') - return ( "not compatible with " + vePlatform.version ) - else if (incompatibleReason == 'CMDLINE' && installedVersion.item.value == "") - return ( "must install from command line" ) - else - return "" + return Utils.path(bindPrefix, "/", versionIndex, "/", param) } - function getSettingsBind(param) - { - return Utils.path(settingsPrefix, "/", versionIndex, "/", param) - } - function getServiceBind(param) - { - return Utils.path(servicePrefix, "/Package/", versionIndex, "/", param) - } - - function versionToNumber (item) - { - var parts=["x", "x", "x", "x", "x"] - var versionNumber = 0 - - if (item.valid && item.value.substring (0,1) == "v") - { - parts = item.value.split (/[v.~]+/ , 4) - { - if (parts.length >= 2) - versionNumber += parseInt(parts[1]) * 1000000 - if (parts.length >= 3) - versionNumber += parseInt(parts[2]) * 1000 - if (parts.length >= 4) - versionNumber += parseInt(parts[3]) - else - versionNumber += 999 - } - } - return versionNumber - } + VBusItem { id: packageName; bind: getBind ("PackageName") } MbRowSmall { - description: "" - anchors.verticalCenter: parent.verticalCenter - Column - { - width: root.width - gitHubVersion.width - packageVersion.width - installedVersion.width - 20 - Text // puts a bit of space above package name - { - text: " " - font.pixelSize: 6 - } - Text - { - text:packageName.valid ? packageName.value : "" - font.pixelSize: 14 - horizontalAlignment: Text.AlignLeft - } - Text - { - text: statusText () - font.pixelSize: 10 - horizontalAlignment: Text.AlignLeft - } - } - Column - { - Text // puts a bit of space above version boxes - { - text: " " - font.pixelSize: 3 - } - Text - { - text: "GitHub" - font.pixelSize: 10 - } - MbTextBlock - { - id: gitHubVersion - item { bind: getServiceBind("GitHubVersion") } - height: 20; width: 80 - } - Text // puts a bit of space below version boxes - only needed in one column - { - text: " " - font.pixelSize: 6 - } + height: 20 + + isCurrentItem: root.isCurrentItem + description: packageName.valid ? packageName.value : "" + MbTextBlock + { + id: gitUser + item { bind: getBind("GitHubUser") } + width: 100 + show: packageName.valid && item.valid + } + MbTextBlock + { + item { bind: getBind("GitHubBranch") } + show: packageName.valid && gitUser.item.valid + width: 80 + } + MbTextBlock + { + item { bind: getBind("PackageVersion") } + width: 80 + show: packageName.valid } - Column - { - Text // puts a bit of space above version boxes - { - text: " " - font.pixelSize: 3 - } - Text - { - text: "Stored" - font.pixelSize: 10 - } - MbTextBlock - { - id: packageVersion - item { bind: getServiceBind("PackageVersion") } - height: 20; width: 80 - } - } - Column - { - Text // puts a bit of space above version boxes - { - text: " " - font.pixelSize: 3 - } - Text - { - text: "Installed" - font.pixelSize: 10 - } - MbTextBlock - { - id: installedVersion - item { bind: getServiceBind("InstalledVersion") } - height: 20; width: 80 - } - } } } diff --git a/FileSets/PageSettingsPackageControl.qml b/FileSets/PageSettingsPackageControl.qml new file mode 100644 index 0000000..9b46b0f --- /dev/null +++ b/FileSets/PageSettingsPackageControl.qml @@ -0,0 +1,43 @@ +/////// new menu for package version display + +import QtQuick 1.1 +import "utils.js" as Utils +import com.victron.velib 1.0 + +MbPage { + id: root + title: qsTr("Package Versions") + property string bindPrefix: "com.victronenergy.settings/Settings/PackageVersion" + VBusItem { id: checkingPackageItem; bind: Utils.path(bindPrefix, "/CheckingPackage") } + property string checkingPackage: checkingPackageItem.valid ? checkingPackageItem.value : "" + + model: VisualItemModel + { + MbSubMenu + { + description: qsTr("Package Version List") + subpage: Component { PageSettingsPackageVersions {} } + } + MbItemOptions + { + id: autoUpdate + description: qsTr ("Automatic Git Hub updates") + bind: Utils.path (bindPrefix, "/GitHubAutoUpdate") + possibleValues: + [ + MbOption { description: "Normal"; value: 1 }, + MbOption { description: "Fast one pass then Normal"; value: 2 }, + MbOption { description: "Check packages once (Fast)"; value: 3 }, + MbOption { description: "Disabled"; value: 0 } + ] + writeAccessLevel: User.AccessUser + } + MbItemText + { + text: checkingPackage + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + show: checkingPackage != "" + } + } +} diff --git a/FileSets/PageSettingsPackageEdit.qml b/FileSets/PageSettingsPackageEdit.qml deleted file mode 100644 index ab711ca..0000000 --- a/FileSets/PageSettingsPackageEdit.qml +++ /dev/null @@ -1,424 +0,0 @@ -/////// new menu for package version edit - -import QtQuick 1.1 -import "utils.js" as Utils -import com.victron.velib 1.0 - -MbPage { - id: root - title: platformItem.valid ? qsTr("Package Editor") : qsTr ("Package Manager not running") - property string settingsPrefix: "com.victronenergy.settings/Settings/PackageMonitor" - property string servicePrefix: "com.victronenergy.packageMonitor" - property int packageIndex: 0 - property int defaultIndex:0 - property VBusItem defaultCountItem: VBusItem { bind: Utils.path(servicePrefix, "/DefaultCount") } - property int defaultCount: defaultCountItem.valid ? defaultCountItem.value : 0 - property VBusItem packageCountItem: VBusItem { bind: Utils.path(settingsPrefix, "/Count") } - property int count: packageCountItem.valid ? packageCountItem.value : 0 - property VBusItem editActionItem: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditAction") } - property string editAction: editActionItem.valid ? editActionItem.value : "" - property VBusItem editStatus: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditStatus") } - - property VBusItem rebootNeededItem: VBusItem { bind: getServiceBind ( "RebootNeeded") } - property bool rebootNeeded: rebootNeededItem.valid && rebootNeededItem.value == 1 - property VBusItem incompatibleItem: VBusItem { bind: getServiceBind ( "Incompatible") } - property string incompatibleReason: incompatibleItem.valid ? incompatibleItem.value : "" - property bool compatible: incompatibleReason == "" - property VBusItem platformItem: VBusItem { bind: Utils.path(servicePrefix, "/Platform") } - property string platform: platformItem.valid ? platformItem.value : "??" - - property bool addPackage: requestedAction == 'Add' && showControls - property bool showControls: editActionItem.valid - property bool gitHubValid: gitHubVersion.item.valid && gitHubVersion.item.value.substring (0,1) === "v" - property bool packageValid: packageVersion.item.valid && packageVersion.item.value.substring (0,1) === "v" - property bool installedValid: installedVersion.item.valid && installedVersion.item.value.substring (0,1) === "v" - property bool downloadOk: gitHubValid && gitHubVersion.item.value != "" - property bool installOk: packageValid && packageVersion.item.value != "" && compatible - property string requestedAction: "" - property bool navigate: requestedAction == '' && ! waitForAction && showControls - property bool waitForAction: editAction != '' && showControls - property bool moreActions: showControls && editAction == 'RebootNeeded' - - property VBusItem defaultPackageNameItem: VBusItem { bind: Utils.path ( servicePrefix, "/Default/", defaultIndex, "/", "PackageName" ) } - property VBusItem defaultGitHubUserItem: VBusItem { bind: Utils.path ( servicePrefix, "/Default/", defaultIndex, "/", "GitHubUser" ) } - property VBusItem defaultGitHubBranchItem: VBusItem { bind: Utils.path ( servicePrefix, "/Default/", defaultIndex, "/", "GitHubBranch" ) } - property VBusItem editPackageNameItem: VBusItem { bind: Utils.path ( settingsPrefix, "/Edit/", "PackageName" ) } - property VBusItem editGitHubUserItem: VBusItem { bind: Utils.path ( settingsPrefix, "/Edit/", "GitHubUser" ) } - property VBusItem editGitHubBranchItem: VBusItem { bind: Utils.path ( settingsPrefix, "/Edit/", "GitHubBranch" ) } - - property string defaultPackageName: defaultPackageNameItem.valid ? defaultPackageNameItem.value : "??" - property string defaultGitHubUser: defaultGitHubUserItem.valid ? defaultGitHubUserItem.value : "??" - property string defaultGitHubBranch: defaultGitHubBranchItem.valid ? defaultGitHubBranchItem.value : "??" - - Component.onCompleted: defaultIndex = 0 - onCountChanged: resetPackageIndex () - onDefaultCountChanged: resetDefaultIndex () - - function resetPackageIndex () - { - if (packageIndex >= count) - packageIndex = count - 1 - } - - function resetDefaultIndex () - { - if (defaultIndex >= defaultCount) - defaultIndex = defaultCount - 1 - } - - function getSettingsBind(param) - { - if (addPackage) - return Utils.path(settingsPrefix, "/Edit/", param) - else - return Utils.path(settingsPrefix, "/", packageIndex, "/", param) - } - function getServiceBind(param) - { - if (addPackage) - return Utils.path(servicePrefix, "/Default/", defaultIndex, "/", param) - else - return Utils.path(servicePrefix, "/Package/", packageIndex, "/", param) - } - - // copy a set of default package values to Edit area when changing indexes - function updateEdit () - { - bindPrefix = Utils.path(servicePrefix, "/Default/", defaultIndex ) - editPackageNameItem.setValue ( defaultPackageName ) - editGitHubUserItem.setValue ( defaultGitHubUser ) - editGitHubBranchItem.setValue ( defaultGitHubBranch ) - } - - function nextIndex () - { - if (addPackage) - { - defaultIndex += 1 - if (defaultIndex >= defaultCount) - defaultIndex = defaultCount - 1 - updateEdit () - } - else - packageIndex += 1 - if (packageIndex >= count) - packageIndex = count - 1 - } - function previousIndex () - { - if (addPackage) - { - defaultIndex -= 1 - if (defaultIndex < 0) - defaultIndex = 0 - updateEdit () - } - else - packageIndex -= 1 - if (packageIndex < 0) - packageIndex = 0 - } - function cancelEdit () - { - requestedAction = '' - editActionItem.setValue ( '' ) - editStatus.setValue ( '' ) - } - function confirm () - { - if (requestedAction != '') - { - editActionItem.setValue (requestedAction + ':' + packageName.item.value) - requestedAction = '' - } - if (requestedAction == 'Remove') - { - previousIndex () - } - } - function install () - { - requestedAction = 'Install' - } - function uninstall () - { - requestedAction = 'Uninstall' - } - function gitHubDownload () - { - requestedAction = 'Download' - } - function add () - { - requestedAction = 'Add' - } - function remove () - { - requestedAction = 'Remove' - } - function signalReboot () - { - if (editAction == 'RebootNeeded') - editActionItem.setValue ( 'Reboot' ) - - requestedAction = '' - } - - model: VisualItemModel - { - MbEditBox - { - id: packageName - description: qsTr ("Package Name") - maximumLength: 30 - item.bind: getSettingsBind ("PackageName") - overwriteMode: false - writeAccessLevel: User.AccessInstaller - readonly: ! addPackage - show: showControls - } - MbRowSmall - { - description: qsTr ("Versions") - height: 25 - opacity: addPackage ? .0001 : 1 - Text - { - text: "GitHub:" - font.pixelSize: 10 - show: showControls - } - show: showControls - MbTextBlock - { - id: gitHubVersion - item { bind: getServiceBind("GitHubVersion") } - height: 25; width: 80 - show: showControls - } - Text - { - text: "stored:" - font.pixelSize: 10 - show: showControls - } - MbTextBlock - { - id: packageVersion - item { bind: getServiceBind("PackageVersion") } - height: 25; width: 80 - show: showControls - } - Text - { - text: rebootNeeded ? "REBOOT\nfor:" : "installed:" - horizontalAlignment: Text.AlignRight - width: 50 - font.pixelSize: 10 - show: showControls && compatible - } - MbTextBlock - { - id: installedVersion - item { bind: getServiceBind("InstalledVersion") } - height: 25; width: 80 - show: showControls && compatible - } - Text - { - id: incompatibleText - text: - { - if (incompatibleReason == 'PLATFORM') - return ( "not compatible with\n" + platform ) - else if (incompatibleReason == 'VERSION') - return ( "not compatible with\n" + vePlatform.version ) - else if (incompatibleReason == 'CMDLINE') - return ( "must install\nfrom command line" ) - else - return ( "compatible ???" ) // compatible or unknown reason - } - horizontalAlignment: Text.AlignHCenter - width: 50 + 80 + 3 - font.pixelSize: 10 - show: showControls && ! compatible - } - } - MbEditBox - { - id: gitHubUser - description: qsTr ("GitHub User") - maximumLength: 20 - item.bind: getSettingsBind ("GitHubUser") - overwriteMode: false - writeAccessLevel: User.AccessInstaller - show: showControls - } - MbEditBox - { - id: gitHubBranch - description: qsTr ("GitHub Branch or Tag") - maximumLength: 20 - item.bind: getSettingsBind ("GitHubBranch") - overwriteMode: false - writeAccessLevel: User.AccessInstaller - show: showControls - } - - // top row of buttons - MbOK - { - id: addButton - width: 140 - anchors { right: removeButton.left } - description: "" - value: qsTr("Add Package") - onClicked: add () - show: navigate - } - MbOK - { - id: removeButton - width: 170 - anchors { right: parent.right; bottom: addButton.bottom } - description: "" - value: qsTr("Remove Package") - onClicked: remove () - show: navigate && ! installedValid && packageName.item.value != "SetupHelper" - } - Text - { - id: removeDisabled - text: - { - if (packageName.item.value == "SetupHelper") - return "SetupHelper uninstall\n CAN NOT BE UNDONE" - else - return "can't remove\nwhile installed" - } - width: 170 - font.pixelSize:12 - anchors.fill: removeButton - horizontalAlignment: Text.AlignHCenter - show: navigate && installedValid - } - MbOK - { - id: cancelButton - width: 90 - anchors { right: parent.right; bottom: addButton.bottom } - description: "" - value: qsTr("Cancel") - onClicked: cancelEdit () - show: showControls && ! navigate && ! waitForAction - } - MbOK - { - id: dismissErrorButton - width: 90 - anchors { right: parent.right; bottom: addButton.bottom } - description: "" - value: qsTr("OK") - onClicked: cancelEdit () - show: showControls && editAction == 'ERROR' - } - MbOK - { - id: laterButton - width: 90 - anchors { right: parent.right; bottom: addButton.bottom } - description: "" - value: qsTr("Later") - onClicked: cancelEdit () - show: moreActions - } - MbOK - { - id: nowButton - width: 90 - anchors { right: laterButton.left; bottom: addButton.bottom } - description: "" - value: qsTr("Now") - onClicked: signalReboot () - show: moreActions - } - MbOK - { - id: confirmButton - width: 375 - anchors { left: parent.left; bottom: addButton.bottom } - description: "" - value: - { - if (packageName.item.value == "SetupHelper" && requestedAction == 'Uninstall') - var warning = qsTr(" CAN'T UNDO") - else - var warning = "" - return qsTr( "Confirm " + requestedAction + " " + packageName.item.value + warning ) - } - onClicked: confirm () - show: showControls && ! navigate && requestedAction != "" - } - Text - { - id: statusMessage - width: 300 - anchors { left: parent.left; leftMargin: 10; bottom: addButton.bottom; bottomMargin: 10 } - font.pixelSize: 12 - text: editStatus.valid ? editStatus.value : "" - show: waitForAction - } - - // bottom row of buttons - MbOK - { - id: previousButton - width: addPackage ? 230 : 100 - anchors { left: parent.left ; top:addButton.bottom } - description: addPackage ? "Import Default" : "" - value: (addPackage && defaultIndex <= 0) || ( ! addPackage && packageIndex <= 0) ? qsTr ("First") : qsTr("Previous") - onClicked: previousIndex () - show: showControls && ( ! addPackage && packageIndex > 0) - } - MbOK - { - id: nextButton - width: 75 - anchors { left: previousButton.right; bottom: previousButton.bottom } - description: "" - value: (addPackage && defaultIndex >= defaultCount - 1) || ( ! addPackage && packageIndex >= count - 1) ? qsTr ("Last") : qsTr("Next") - onClicked: nextIndex () - show: showControls && ( ! addPackage && packageIndex < count - 1) - } - MbOK - { - id: downloadButton - width: 110 - anchors { right: installButton.left; bottom: previousButton.bottom } - description: "" - value: qsTr ("Download") - onClicked: gitHubDownload () - show: navigate && downloadOk - } - MbOK - { - id: installButton - width: 90 - anchors { right: uninstallButton.left; bottom: previousButton.bottom } - description: "" - value: qsTr ("Install") - onClicked: install () - show: navigate && installOk - } - MbOK - { - id: uninstallButton - width: 100 - anchors { right: parent.right; bottom: installButton.bottom } - description: "" - value: qsTr("Uninstall") - onClicked: uninstall () - show: navigate && installedValid - } - } -} diff --git a/FileSets/PageSettingsPackageManager.qml b/FileSets/PageSettingsPackageManager.qml deleted file mode 100644 index 5a273e3..0000000 --- a/FileSets/PageSettingsPackageManager.qml +++ /dev/null @@ -1,74 +0,0 @@ -/////// new menu for package version display - -import QtQuick 1.1 -import "utils.js" as Utils -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Package Manager") - property string settingsPrefix: "com.victronenergy.settings/Settings/PackageMonitor" - property string servicePrefix: "com.victronenergy.packageMonitor" - VBusItem { id: downloadStatus; bind: Utils.path(servicePrefix, "/GitHubUpdateStatus") } - VBusItem { id: installStatus; bind: Utils.path(servicePrefix, "/InstallStatus") } - property bool showInstallStatus: installStatus.valid && installStatus.value != "" - VBusItem { id: mediaStatus; bind: Utils.path(servicePrefix, "/MediaUpdateStatus") } - property bool showMediaStatus: mediaStatus.valid && mediaStatus.value != "" - property bool showControls: installStatus.valid - - model: VisualItemModel - { - MbItemText - { - id: status - text: - { - if (! showControls) - return"Package Manager not running" - else if (installStatus.valid && installStatus.value != "") - return installStatus.value - else if (mediaStatus.valid && mediaStatus.value != "") - return mediaStatus.value - else if (downloadStatus.valid && downloadStatus.value != "") - return downloadStatus.value - else - return "idle" - } - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - } - MbItemOptions - { - id: autoDownload - description: qsTr ("Automatic GitHub downloads") - bind: Utils.path (settingsPrefix, "/GitHubAutoDownload") - possibleValues: - [ - MbOption { description: "Normal"; value: 1 }, - MbOption { description: "Fast, then Normal"; value: 2 }, - MbOption { description: "Once (Fast)"; value: 3 }, - MbOption { description: "Off"; value: 0 } - ] - writeAccessLevel: User.AccessInstaller - } - MbSwitch - { - id: autoInstall - bind: Utils.path (settingsPrefix, "/AutoInstall") - name: qsTr ("Auto install packages") - writeAccessLevel: User.AccessInstaller - } - MbSubMenu - { - description: qsTr("Package Editor") - subpage: Component { PageSettingsPackageEdit {} } - show: showControls - } - MbSubMenu - { - description: qsTr("Package Version List") - subpage: Component { PageSettingsPackageVersions {} } - show: showControls - } - } -} diff --git a/FileSets/PageSettingsPackageVersions.qml b/FileSets/PageSettingsPackageVersions.qml index a3c63d7..01b4b8a 100644 --- a/FileSets/PageSettingsPackageVersions.qml +++ b/FileSets/PageSettingsPackageVersions.qml @@ -6,20 +6,16 @@ import com.victron.velib 1.0 MbPage { id: root - title: defaultCount.valid ? qsTr("Package Version List") : qsTr ("Package Manager not running") - property string servicePrefix: "com.victronenergy.packageMonitor" - property string settingsPrefix: "com.victronenergy.settings/Settings/PackageMonitor" - property VBusItem count: VBusItem { bind: Utils.path(settingsPrefix, "/Count") } - // use DefaultCount as an indication that PackageMonitor is running - property VBusItem defaultCount: VBusItem { bind: Utils.path(servicePrefix, "/DefaultCount") } + title: qsTr("Package Version List") + property string bindPrefix: "com.victronenergy.settings/Settings/PackageVersion" + property VBusItem count: VBusItem { bind: Utils.path(bindPrefix, "/Count") } - model: defaultCount.valid ? count.valid ? count.value : 0 : 0 + model: count.valid ? count.value : 0 delegate: Component { MbDisplayPackageVersion { - servicePrefix: root.servicePrefix - settingsPrefix: root.settingsPrefix + bindPrefix: root.bindPrefix versionIndex: index } } diff --git a/FileSets/v2.42/PageSettings.qml b/FileSets/v2.42/PageSettings.qml index c7ab4b1..5c5a575 100644 --- a/FileSets/v2.42/PageSettings.qml +++ b/FileSets/v2.42/PageSettings.qml @@ -172,8 +172,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.58/PageSettings.qml b/FileSets/v2.58/PageSettings.qml index dfc41a2..ddf5b63 100644 --- a/FileSets/v2.58/PageSettings.qml +++ b/FileSets/v2.58/PageSettings.qml @@ -179,8 +179,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.66/PageSettings.qml b/FileSets/v2.66/PageSettings.qml index 07bc72b..1541327 100644 --- a/FileSets/v2.66/PageSettings.qml +++ b/FileSets/v2.66/PageSettings.qml @@ -184,8 +184,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.73/PageSettings.qml b/FileSets/v2.73/PageSettings.qml index b2ca244..328b74a 100644 --- a/FileSets/v2.73/PageSettings.qml +++ b/FileSets/v2.73/PageSettings.qml @@ -175,8 +175,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.80~19/PageSettings.qml b/FileSets/v2.80~19/PageSettings.qml index b788849..cf8519e 100644 --- a/FileSets/v2.80~19/PageSettings.qml +++ b/FileSets/v2.80~19/PageSettings.qml @@ -180,8 +180,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.80~21-large-23/PageSettings.qml b/FileSets/v2.80~21-large-23/PageSettings.qml index f5b4508..1ca0a3f 100644 --- a/FileSets/v2.80~21-large-23/PageSettings.qml +++ b/FileSets/v2.80~21-large-23/PageSettings.qml @@ -186,8 +186,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.80~21/PageSettings.qml b/FileSets/v2.80~21/PageSettings.qml index f5bd773..1e54cd1 100644 --- a/FileSets/v2.80~21/PageSettings.qml +++ b/FileSets/v2.80~21/PageSettings.qml @@ -180,8 +180,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.80~24/PageSettings.qml b/FileSets/v2.80~24/PageSettings.qml index 696f93a..855dae2 100644 --- a/FileSets/v2.80~24/PageSettings.qml +++ b/FileSets/v2.80~24/PageSettings.qml @@ -182,8 +182,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/FileSets/v2.80~29/PageSettings.qml b/FileSets/v2.80~29/PageSettings.qml index 3fcbf0c..d89e7d3 100644 --- a/FileSets/v2.80~29/PageSettings.qml +++ b/FileSets/v2.80~29/PageSettings.qml @@ -182,8 +182,8 @@ MbPage { //////// added for package control and version display MbSubMenu { - description: qsTr("Package Manager") - subpage: Component { PageSettingsPackageManager {} } + description: qsTr("Package Versions") + subpage: Component { PageSettingsPackageControl {} } } MbSubMenu { diff --git a/PackageManager.py b/PackageManager.py deleted file mode 100755 index 6d94780..0000000 --- a/PackageManager.py +++ /dev/null @@ -1,2308 +0,0 @@ -#!/usr/bin/env python - -# PackageManager.py -# Kevin Windrem -# -# -# This program is responsible for -# downloading, installing and unstalling packages -# package monitor also checks SD cards and USB sticks for package archives -# either automatically or manually via the GUI -# providing the user with status on installed packages and any updates via the GUI -# -# It runs as /service/PackageManager -# -# Persistent storage for packageManager is stored in dbus Settings: -# -# com.victronenergy.Settings parameters for each package: -# /Settings/PackageMonitor/n/PackageName can be edited by the GUI only when adding a new package -# /Settings/PackageMonitor/n/GitHubUser can be edited by the GUI -# /Settings/PackageMonitor/n/GitHubBranch can be edited by the GUI -# /Settings/PackageMonitor/Count the number of ACTIVE packages (0 <= n < Count) -# /Settings/PackageMonitor/Edit/... GUI edit package set -# -# /Settings/PackageMonitor/GitHubAutoDownload set by the GUI to control automatic updates from GitHub -# 0 - no GitHub auto downloads (version checks still occur) -# 1 - normal updates - one download every 10 minutes -# 2 - fast updates - one download update every 10 seconds, then at the normal rate after one pass -# 3 - one update pass at the fast rate, then to no updates -# changing to one of the fast scans, starts from the first package -# -# if no download is needed, checks for downloads are fast: every 5 seconds, slow: every 2 minutes - -AUTO_DOWNLOADS_OFF = 0 -NORMAL_DOWNLOAD = 1 -FAST_DOWNLOAD = 2 -ONE_DOWNLOAD = 3 - -# /Settings/PackageMonitor/AutoInstall -# 0 - no automatic install -# 1 - automatic install after download from GitHub or SD/USB -# -# Additional (volatile) parameters linking packageManager and the GUI are provided in a separate dbus service: -# -# com.victronenergy.packageMonitor parameters -# /Package/n/GitHubVersion from GitHub -# /Package/n/PackageVersion from /data /version from the package directory -# /Package/n/InstalledVersion from /etc/venus/isInstalled- -# /Package/n/RebootNeeded indicates a reboot is needed to activate this package -# /Package/n/Incompatible indicates if package is or is not compatible with the system -# '' if compatible -# 'VERSION' if the system version is outside the package's acceptable range -# 'PLATFORM' package can not run on this platform -# TODO: 'CMDLINE' setup must be run from command line -# currently only for Raspberry PI packages only -# -# for both Settings and the the dbus service: -# n is a 0-based section used to reference a specific package -# -# -# /Default/m/PackageName a dbus copy of the default package list (/data/SetupHelper/defaultPackageList) -# /Default/m/GitHubUser -# /Default/m/GitHubBranch -# /DefaultCount the number of default packages -# -# m is a 0-based section used to referene a specific default paclage -# -# /GuiEditAction is a text string representing the action -# set by the GUI to trigger an action in PackageManager -# 'Install' - install package from /data to the Venus working directories -# 'Uninstall' - uninstall package from the working directories -# 'Download" - download package from GutHub to /data -# 'Add' - add package to package list (after GUI sets .../Edit/... -# 'Remove' - remove package from list TBD ????? -# 'Reboot' - reboot -# -# the GUI must wait for PackageManager to signal completion of one operation before initiating another -# -# set by packageMonitor when the task is complete -# return codes - set by PackageManager -# '' - action completed without errors (idle) -# 'ERROR' - error during action - error reported in /GuiEditStatus: -# unknown error -# not compatible with this version -# not compatible with this platform -# no options present - must install from command line -# GUI choices: OK - closes "dialog" -# 'RebootNeeded' - reboot needed -# GUI choices: -# Do it now -# GUI sends reboot command to PackageMonitor -# Defer -# GUI sets action to 0 -# -# setup script return codes -EXIT_SUCCESS = 0 -EXIT_ERROR = 255 # generic error -EXIT_REBOOT = 123 -EXIT_INCOMPATIBLE_VERSION = 254 -EXIT_INCOMPATIBLE_PLATFOM = 253 -EXIT_FILE_SET_ERROR = 252 -EXIT_OPTIONS_NOT_SET = 251 -EXIT_RUN_AGAIN = 250 -# -# -# /GuiEditStatus a text message to report edit status to the GUI -# -# /GitHubUpdateStatus as above for automatic GitHub update -# -# /InstallStatus as above for automatic install/uninstall -# -# /MediaUpdateStatus as above for SD/USB media transfers -# -# -# /Settings/PackageVersion/Edit/ is a section for the GUI to provide information about the a new package to be added -# -# /data/SetupHelper/defaultPackageList provides an initial list of packages -# It contains a row for each package with the following information: -# packageName gitHubUser gitHubBranch -# If present, packages listed will be ADDED to the package list in /Settings -# existing dbus Settings (GitHubUser and GitHubBranch) will not be changed -# -# this file is read at program start -# -# Package information is stored in the /data/ directory -# -# A version file within that directory identifies the version of that package stored on disk but not necessarily installed -# -# When a package is installed, the version in the package directory is written to an "installed flag" file -# /etc/venus/isInstalled- -# the contents of the file populate InstalledVersion (blank if the file doesn't exist or is empty) -# -# InstalledVersion is displayed to the user and used for tests for automatic updates -# -# GitHubVersion is read from the internet if a connection exists. -# To minimize local network traffic and GitHub server loads one package's GitHub version is -# read once every 5 seconds until all package versions have been retrieved -# then one package verison is read every 10 minutes. -# Addition of a package or change in GitHubUser or GitHubBranch will trigger a fast -# update of GitHub versions -# If the package on GitHub can't be accessed, GitHubVersion will be blank -# -# -# PackageMonitor downloads packages from GitHub based on the GitHub version and package (stored) versions: -# if the GitHub branch is a specific version, the download occurs if the versions differ -# otherwise the GitHub version must be newer. -# the archive file is unpacked to a directory in /data named -# -.tar.gz, then moved to /data/, replacing the original -# -# PackageMonitor installs the stored verion if the package (stored) and installed versions differ -# -# Manual downloads and installs triggered from the GUI ignore version checks completely -# -# In this context, "install" means replacing the working copy of Venus OS files with the modified ones -# or adding new files and system services -# -# Uninstalling means replacing the original Venus OS files to their working locations -# -# All operations that access the global package list must do so surrounded by a lock to avoid accessing changing data -# this is very important when scanning the package list -# so that packages within that list don't get moved, added or deleted -# -# Operations that take signficant time are handled in separate threads, decoupled from the package list -# Operaitons are placed on a queue with all the information a processing routine needs -# this is imporant because the package in the list involved in the operaiton -# may no longer be in the package list or be in a different location -# -# All operations that scan the package list must do so surrounded by -# DbusIf.LOCK () and DbusIf.UNLOCK () -# and must not consume significant time: no sleeping or actions taking seconds or minutes !!!! -# -# Operations that take little time can usually be done in-line (without queuing) -# -# PackageMonitor checks removable media (SD cards and USB sticks) for package upgrades or even as a new package -# File names must be in one of the following forms: -# -.tar.gz -# -install.tar.gz -# The portion determines where the package will be stored in /data -# and will be used as the package name when the package is added to the package list in Settings -# -# If all criteria are met, the archive is unpacked and the resulting directory replaces /data/ -# if not, the unpacked archive directory is deleted -# -# -# PackageMonitor scans /data looking for new packages -# directory names must not appear to be an archive -# (include a GitHub branch or version number) (see rejectList below for specifics) -# the directory must contain a valid version -# the package must not have been manually removed (REMOVED flag file set) -# the file name must be unique to all existing packages -# -# A new, verified package will be added to the package list and be ready for -# manual and automtic updates, installs, uninstalls -# -# This mechanism handles archives extracted from SD/USB media -# -# -# Packages may optionally include a file containg GitHub user and branch -# if the package diretory contains the file: gitHubInfo -# gitHubUser and gitHubBranch are set from the file's content when it is added to the package list -# making the new package ready for automatic GitHub updates -# gitHubInfo should have a single line of the form: gitHubUser:gitHubBranch, e.g, kwindrem:latest -# if the package is already in the package list, gitHubInfo is ignored -# if no GitHub information is contained in the package, the user must add it manually via the GUI -# in so automatic downloads from GitHub can occur - -# for timing sections of code -# t0 = time.perf_counter() -# code to be timed -# t1 = time.perf_counter() -# logging.info ( "some time %6.3f" % (t1 - t0) ) - -import platform -import argparse -import logging - -# set variables for logging levels: -CRITICAL = 50 -ERROR = 40 -WARNING = 30 -INFO = 20 -DEBUG = 10 - -import sys -import subprocess -import threading -import os -import shutil -import dbus -import time -import re -import queue -import glob - -# accommodate both Python 2 and 3 -# if the Python 3 GLib import fails, import the Python 2 gobject -try: - from gi.repository import GLib # for Python 3 -except ImportError: - import gobject as GLib # for Python 2 -# add the path to our own packages for import -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from settingsdevice import SettingsDevice - -global GitHubVersions -global DownloadGitHub -global InstallPackages -global ProcessAction -global MediaScan -global DbusIf -global Platform -global VenusVersion -global SystemReboot - - -# ProcessActionClass -# Instances: -# ProcessAction (a separate thread) -# -# Methods: -# PushAction -# run ( the thread ) -# -# actions from the GUI and local methods are processed here -# a queue isolates the caller from processing time -# and interactions with the dbus object -# (can't update the dbus object from it's handler !) -# this method runs as a separate thread -# blocking on the next action on the queue -# -# some actions called may take seconds or minutes (based on internet speed) !!!! -# -# the queue entries are: ("action":"packageName", and source (GUI or AUTO) -# this decouples the action from the current package list which could be changing -# allowing the operation to proceed without locking the list - -class ProcessActionClass (threading.Thread): - - def __init__(self): - threading.Thread.__init__(self, name = "processEditAction") - self.editActionQueue = queue.Queue (maxsize = 20) - self.threadRunning = True - - - # PushAction - # - # add an action to our queue - # commands are added to the queue from the GUI (dbus service change handler) - # or from other local methods - # the same queue is used so all actions can be processed by the same methods - # the queue isolates command triggers from processing because processing - # can take seconds or minutes - # - # command is of the form: "action":"packageName" followed by the source - # - # action is a text string: Install, Uninstall, Download, Add, Remove, etc - # packageName is the name of the package to receive the action - # for some acitons this may be the null string - # - # source is either 'GUI' or 'AUTO' (local methods pushing auto update commands) - # the source is used by the processing routines to identify the correct - # status message to update and the correct callback method to call - # when the action completes (either success or error) - - def PushAction (self, command=None, source=None): - - queueParams = ( command, source ) - try: - self.editActionQueue.put ( queueParams, block=False ) - except queue.Full: - logging.error ("command " + command + source + " lost - queue full") - except: - logging.error ("command " + command + source + " lost - other queue error") - # end PushAction - - - # run (the thread), StopThread - # - # run is a thread that pulls actions from a queue and processes them - # Note: some processing times can be several seconds to a minute or more - # due to newtork activity - # - # run () checks the threadRunning flag and returns if it is False, - # essentially taking the thread off-line - # the main method should catch the tread with join () - # StopThread () is called to shut down the thread - - def StopThread (self): - logging.info ("attempting to stop ProcessAction thread") - self.threadRunning = False - - def run (self): - global InstallPackages - global SystemReboot - while self.threadRunning: - try: - queueParams = self.editActionQueue.get (timeout=5) - except queue.Empty: # queue empty is OK - just allows some time unblocked - if self.threadRunning == False: - return - time.sleep (5.0) - continue - except: - logging.error ("pull from editActionQueue failed") - continue - # got new action from queue - decode and process - command = queueParams[0] - source = queueParams[1] - parts = command.split (":") - if len (parts) >= 1: - action = parts[0].strip() - else: - action = "" - if len (parts) >= 2: - packageName = parts[1].strip() - else: - packageName = "" - - if action == 'Install': - InstallPackages.InstallPackage (packageName = packageName, source=source, direction='install' ) - - elif action == 'Uninstall': - InstallPackages.InstallPackage (packageName = packageName, source=source, direction='uninstall' ) - - elif action == 'Download': - DownloadGitHub.GitHubDownload ( packageName = packageName, source=source ) - - elif action == 'Add': - PackageClass.AddPackage (packageName = packageName, source=source ) - - elif action == 'Remove': - PackageClass.RemovePackage ( packageName ) - - elif action == 'Reboot': - logging.warning ( "received Reboot request from " + source) - # set the flag - reboot is done in main_loop - SystemReboot = True - - # ignore the idle action - elif action == '': - pass - - else: - logging.warning ( "received invalid action " + command + " from " + source + " - discarding" ) - # end while True - # end run () -# end ProcessActionClass - - -# DbusIfClass -# Instances: -# DbusIf -# -# Methods: -# UpdateGuiState -# UpdateStatus -# LocateDefaultPackage -# handleGuiEditAction -# UpdatePackageCount -# various Gets and Sets for dbus parameters -# LOCK -# UNLOCK -# -# Globals: -# DbusSettings (for settings that are NOT part of a package) -# DbusService (for parameters that are NOT part of a package) -# EditPackage - the dbus Settings used by the GUI to hand off information about -# a new package -# DefaultPackages - list of default packages, each a tuple: -# ( packageName, gitHubUser, gitHubBranch) -# -# DbusIf manages the dbus Settings and packageManager dbus service parameters -# that are not associated with any spcific package -# -# unlike those managed in PackageClass which DO have a package association -# the dbus settings managed here don't have a package association -# however, the per-package parameters are ADDED to -# DbusSettings and dBusService created here !!!! -# -# DbusIf manages a lock to prevent data access in one thread -# while it is being changed in another -# the same lock is used to protect data in PackageClass also -# this is more global than it needs to be but simplies the locking -# -# all methods that access must aquire this lock -# prior to accessing DbusIf or Package data -# then must release the lock -# -# default package info is fetched from a file and published to our dbus service -# for use by the GUI in adding new packages -# it the default info is also stored in DefaultPackages -# LocateDefaultPackage is used to retrieve the default from local storage -# rather than pulling from dbus or reading the file again - -class DbusIfClass: - - # UpdateGuiState - # - # updates the GUI package editor state when a requested opeation completes - # The GUI behaves differently for success and failure - # source allows this method to only update the GUI state - # even though it may be the result of - - def UpdateGuiState (self, nextState): - - if nextState != None: - self.SetGuiEditAction ( nextState ) - - - # UpdateStatus - # - # updates the status when the operation completes - # the GUI provides three different areas to show status - # where specifies which of these are updated - # 'Download' - # 'Install' - # 'Editor' - # 'Media' - # which determines where status is sent - # message is the text displayed - # if LogLevel is not 0, message is also written to the PackageManager log - # logging levels: (can use numeric value or these variables set at head of module - # CRITICAL = 50 - # ERROR = 40 - # WARNING = 30 - # INFO = 20 - # DEBUG = 10 - # if where = None, no GUI status areas are updated - - - def UpdateStatus ( self, message=None, where=None, logLevel=0 ): - - if logLevel != 0: - logging.log ( logLevel, message ) - - if where == 'Editor': - DbusIf.SetEditStatus ( message ) - elif where == 'Install': - DbusIf.SetInstallStatus ( message ) - elif where == 'Download': - DbusIf.SetGitHubUpdateStatus (message) - elif where == 'Media': - DbusIf.SetMediaStatus (message) - - - # handleGuiEditAction (internal use only) - # - # the GUI uses packageMonitor service /GuiEditAction - # to inform PackageManager of an action - # a command is formed as "action":"packageName" - # - # action is a text string: install, uninstall, download, etc - # packageName is the name of the package to receive the action - # for some acitons this may be the null string - # this handler disposes of the request quickly by pushing - # the command onto a queue for later processing - - def handleGuiEditAction (self, path, command): - - global ProcessAction - - ProcessAction.PushAction ( command=command, source='GUI') - - return True # True acknowledges the dbus change - other wise dbus parameter does not change - - def UpdatePackageCount (self): - count = len(PackageClass.PackageList) - self.DbusSettings['packageCount'] = count - def GetPackageCount (self): - return self.DbusSettings['packageCount'] - def SetAutoDownload (self, value): - self.DbusSettings['autoDownload'] = value - def GetAutoDownload (self): - return self.DbusSettings['autoDownload'] - def GetAutoInstall (self): - return self.DbusSettings['autoInstall'] - def SetGitHubUpdateStatus (self, value): - self.DbusService['/GitHubUpdateStatus'] = value - def SetInstallStatus (self, value): - self.DbusService['/InstallStatus'] = value - def SetMediaStatus (self, value): - self.DbusService['/MediaUpdateStatus'] = value - - - def SetGuiEditAction (self, value): - self.DbusService['/GuiEditAction'] = value - def GetGuiEditAction (self): - return self.DbusService['/GuiEditAction'] - def SetEditStatus (self, message): - self.DbusService['/GuiEditStatus'] = message - - # search default package list for packageName - # and return the pointer if found - # otherwise return None - # - # Note: this method should be called with LOCK () set - # and use the returned value before UNLOCK () - # to avoid unpredictable results - # - # DefaultPackages is a list of tuples: - # (packageName, gitHubUser, gitHubBranch) - # - # if a packageName match is found, the tuple is returned - # otherwise None is retuned - - def LocateDefaultPackage (self, packageName): - - for default in self.defaultPackages: - if packageName == default[0]: - return default - return None - - - # LOCK and UNLOCK - capitals used to make it easier to identify in the code - # - # these protect the package list from changing while the list is being accessed - - def LOCK (self): - self.lock.acquire () - def UNLOCK (self): - self.lock.release () - - - def __init__(self): - self.lock = threading.RLock() - - settingsList = {'packageCount': [ '/Settings/PackageMonitor/Count', 0, 0, 0 ], - 'autoDownload': [ '/Settings/PackageMonitor/GitHubAutoDownload', 0, 0, 0 ], - 'autoInstall': [ '/Settings/PackageMonitor/AutoInstall', 0, 0, 0 ], - } - self.DbusSettings = SettingsDevice(bus=dbus.SystemBus(), supportedSettings=settingsList, - timeout = 10, eventCallback=None ) - - - self.DbusService = VeDbusService ('com.victronenergy.packageMonitor', bus = dbus.SystemBus()) - self.DbusService.add_mandatory_paths ( - processname = 'PackageMonitor', processversion = 1.0, connection = 'none', - deviceinstance = 0, productid = 1, productname = 'Package Monitor', - firmwareversion = 1, hardwareversion = 0, connected = 1) - self.DbusService.add_path ('/GitHubUpdateStatus', "") - self.DbusService.add_path ('/InstallStatus', "") - self.DbusService.add_path ('/MediaUpdateStatus', "" ) - self.DbusService.add_path ('/GuiEditStatus', "" ) - global Platform - self.DbusService.add_path ('/Platform', Platform ) - - self.DbusService.add_path ('/GuiEditAction', "", writeable = True, - onchangecallback = self.handleGuiEditAction) - - # publish the default packages list and store info locally for faster access later - section = 0 - self.defaultPackages = [] - try: - listFile = open ("/data/SetupHelper/defaultPackageList", 'r') - except: - logging.warning ("no defaultPackageList " + listFileName) - else: - for line in listFile: - parts = line.split () - if len(parts) < 3 or line[0] == "#": - continue - prefix = '/Default/' + str (section) + '/' - self.DbusService.add_path (prefix + 'PackageName', parts[0] ) - self.DbusService.add_path (prefix + 'GitHubUser', parts[1] ) - self.DbusService.add_path (prefix + 'GitHubBranch', parts[2] ) - - self.defaultPackages.append ( ( parts[0], parts[1], parts[2] ) ) - section += 1 - listFile.close () - self.DbusService.add_path ('/DefaultCount', section ) - - # a special package used for editing a package prior to adding it to Package list - self.EditPackage = PackageClass (section = "Edit") - - - # RemoveDbusService - # deletes the dbus service - - def RemoveDbusService (self): - logging.info ("shutting down com.victronenergy.packageMonitor dbus service") - self.DbusService.__del__() - -# end DbusIf - - -# PackageClass -# Instances: -# one per package -# -# Methods: -# LocatePackage -# RemoveDbusSettings -# various Gets and Sets -# AddPackagesFromDbus (class method) -# AddDefaultPackages (class method) -# AddStoredPackages (class method) -# AddPackage (class method) -# RemovePackage (class method) -# GetVersionsFromFiles (class method) -# updateGitHubInfo -# called only from AddPackage because behavior depends on who added the package -# -# Globals: -# DbusSettings (for per-package settings) -# DbusService (for per-package parameters) -# InstallPending -# UnnstallPending -# DownloadPending -# PackageList - list instances of all packages -# -# a package consits of Settings and version parameters in the package monitor dbus service -# all Settings and parameters are accessible via set... and get... methods -# so that the caller does not need to understand dbus Settings and service syntax -# the packageName variable maintains a local copy of the dBus parameter for speed in loops -# section passed to init can be either a int or string ('Edit') -# an int is converted to a string to form the dbus setting paths -# -# the dbus settings and service parameters managed here are on a per-package basis -# unlike those managed in DbusIf which don't have a package association - -class PackageClass: - - # list of instantiated Packages - PackageList = [] - - global GitHubVersions - - - # search PackageList for packageName - # and return the package pointer if found - # otherwise return None - # - # Note: this method should be called with LOCK () set - # and use the returned value before UNLOCK () - # to avoid unpredictable results - - def LocatePackage (packageName): - for package in PackageClass.PackageList: - if packageName == package.PackageName: - return package - return None - - def SetPackageName (self, newName): - self.DbusSettings['packageName'] = newName - - def SetInstalledVersion (self, version): - if self.installedVersionPath != "": - DbusIf.DbusService[self.installedVersionPath] = version - def GetInstalledVersion (self): - if self.installedVersionPath != "": - return DbusIf.DbusService[self.installedVersionPath] - else: - return None - def SetPackageVersion (self, version): - if self.packageVersionPath != "": - DbusIf.DbusService[self.packageVersionPath] = version - def GetPackageVersion (self): - if self.packageVersionPath != "": - return DbusIf.DbusService[self.packageVersionPath] - else: - return None - def SetGitHubVersion (self, version): - if self.gitHubVersionPath != "": - DbusIf.DbusService[self.gitHubVersionPath] = version - def GetGitHubVersion (self): - if self.gitHubVersionPath != "": - return DbusIf.DbusService[self.gitHubVersionPath] - else: - return None - - def SetIncompatible(self, value): - if self.incompatiblePath != "": - DbusIf.DbusService[self.incompatiblePath] = value - def GetIncompatible (self): - if self.incompatiblePath != "": - return DbusIf.DbusService[self.incompatiblePath] - else: - return None - - def SetRebootNeeded (self, value): - if self.rebootNeededPath != "": - DbusIf.DbusService[self.rebootNeededPath] = value - def GetRebootNeeded (self): - if self.rebootNeededPath != "": - if DbusIf.DbusService[self.rebootNeededPath] == 1: - return True - else: - return False - else: - return False - - def SetGitHubUser (self, value): - self.DbusSettings['gitHubUser'] = value - def GetGitHubUser (self): - return self.DbusSettings['gitHubUser'] - def SetGitHubBranch (self, value): - self.DbusSettings['gitHubBranch'] = value - def GetGitHubBranch (self): - return self.DbusSettings['gitHubBranch'] - - # remove the dbus settings for this package - # package Settings are removed - # package service parameters are just set to "" - # - # can't actually remove settings cleanly - # so just set contents to null/False - - def RemoveDbusSettings (self): - - self.SetPackageName ("") - self.SetGitHubUser ("") - self.SetGitHubBranch ("") - self.SetInstalledVersion ("") - self.SetPackageVersion ("") - self.SetGitHubVersion ("") - self.SetRebootNeeded (False) - - - def settingChangedHandler (self, name, old, new): - # when GitHub information changes, need to refresh GitHub version for this package - if name == 'packageName': - self.PackageName = new - elif name == 'gitHubBranch' or name == 'gitHubUser': - if self.PackageName != None and self.PackageName != "": - GitHubVersions.RefreshVersion (self.PackageName ) - - def __init__( self, section, packageName = None ): - # add package versions if it's a real package (not Edit) - if section != 'Edit': - section = str (section) - self.installedVersionPath = '/Package/' + section + '/InstalledVersion' - self.packageVersionPath = '/Package/' + section + '/PackageVersion' - self.gitHubVersionPath = '/Package/' + section + '/GitHubVersion' - self.rebootNeededPath = '/Package/' + section + '/RebootNeeded' - self.incompatiblePath = '/Package/' + section + '/Incompatible' - - # create paths if they dont currently exist - try: - foo = DbusIf.DbusService[self.installedVersionPath] - except: - DbusIf.DbusService.add_path (self.installedVersionPath, "" ) - try: - foo = DbusIf.DbusService[self.gitHubVersionPath] - except: - DbusIf.DbusService.add_path (self.gitHubVersionPath, "" ) - try: - foo = DbusIf.DbusService[self.packageVersionPath] - except: - DbusIf.DbusService.add_path (self.packageVersionPath, "" ) - try: - foo = DbusIf.DbusService[self.rebootNeededPath] - except: - DbusIf.DbusService.add_path (self.rebootNeededPath, "" ) - try: - foo = DbusIf.DbusService[self.incompatiblePath] - except: - DbusIf.DbusService.add_path (self.incompatiblePath, "" ) - - - self.packageNamePath = '/Settings/PackageMonitor/' + section + '/PackageName' - self.gitHubUserPath = '/Settings/PackageMonitor/' + section + '/GitHubUser' - self.gitHubBranchPath = '/Settings/PackageMonitor/' + section + '/GitHubBranch' - - settingsList = {'packageName': [ self.packageNamePath, '', 0, 0 ], - 'gitHubUser': [ self.gitHubUserPath, '', 0, 0 ], - 'gitHubBranch': [ self.gitHubBranchPath, '', 0, 0 ], - } - self.DbusSettings = SettingsDevice(bus=dbus.SystemBus(), supportedSettings=settingsList, - eventCallback=self.settingChangedHandler, timeout = 10) - # if packageName specified on init, use that name - if packageName != None: - self.DbusSettings['packageName'] = packageName - self.PackageName = packageName - # otherwise pull name from dBus Settings - else: - self.PackageName = self.DbusSettings['packageName'] - - # these flags are used to insure multiple actions aren't pushed onto the processing queue - self.section = section - self.InstallPending = False - self.UninstallPending = False - self.DownloadPending = False - - - # dbus Settings is the primary non-volatile storage for packageManager - # upon startup, PackageList [] is empty and we need to populate it - # from previous dBus Settings in /Settings/PackageMonitor/... - # this is a special case that can't use AddPackage below: - # we do not want to create any new Settings !! - # it should be "safe" to limit the serch to 0 to < packageCount - # we also don't specify any parameters other than the section (index) - # - # NOTE: this method is called before threads are created so do not LOCK - # - # returns False if couldn't get the package cound from dbus - # otherwise returns True - - @classmethod - def AddPackagesFromDbus (cls): - global DbusIf - packageCount = DbusIf.GetPackageCount() - if packageCount == None: - logging.critical ("dbus PackageCount is not defined -- can't continue") - return False - i = 0 - while i < packageCount: - cls.PackageList.append(PackageClass (section = i)) - i += 1 - return True - - - # default packages are appended to the package list during program initialization - # - # a package may already be in the dbus list and will already have been added - # so these are skipped - # - # the default list is a tuple with packageName as the first element - - @classmethod - def AddDefaultPackages (cls, initialList=False): - for default in DbusIf.defaultPackages: - packageName = default[0] - DbusIf.LOCK () - package = cls.LocatePackage (packageName) - DbusIf.UNLOCK () - if package == None: - cls.AddPackage ( packageName=packageName ) - - - # packaged stored in /data must also be added to the package list - # but package name must be unique - # in order to qualify as a package: - # must be a directory - # name must not contain strings in the rejectList - # name must not include any spaces - # diretory must contain a file named version - # first character of version file must be 'v' - - rejectList = [ "-current", "-latest", "-main", "-test", "-debug", "-beta", "-backup1", "-backup2", - "-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9", " " ] - - @classmethod - def AddStoredPackages (cls): - - for path in glob.iglob ("/data/*"): - file = os.path.basename (path) - if os.path.isdir (path) == False: - continue - rejected = False - for reject in cls.rejectList: - if reject in file: - rejected = True - break - if rejected: - continue - versionFile = path + "/version" - if os.path.isfile (versionFile) == False: - continue - fd = open (versionFile, 'r') - version = fd.readline().strip() - fd.close () - if version[0] != 'v': - logging.warning (file + " version rejected " + version) - continue - - # skip if package was manually remove - if os.path.exists (path + "/REMOVED"): - continue - - # skip if package is for Raspberry PI only and platform is not - global Platform - if os.path.exists (path + "/raspberryPiOnly") and Platform[0:4] != 'Rasp': - continue - - # continue only if package is unique - DbusIf.LOCK () - package = cls.LocatePackage (file) - DbusIf.UNLOCK () - if package != None: - continue - - cls.AddPackage ( packageName=file, source='AUTO' ) - - - # updateGitHubInfo fetchs the GitHub info and puts it in dbus settings - # - # There are three sources for this info: - # GUI 'EDIT' section (only used for adds from the GUI) - # the stored package (/data/) - # the default package list - # - # the sources are prioritized in the above order - - @classmethod - def updateGitHubInfo (cls, packageName=None, source=None ): - # if adding from GUI, get info from EditPackage - # check other sources if empty - if source == 'GUI': - gitHubUser = DbusIf.EditPackage.GetGitHubUser () - gitHubBranch = DbusIf.EditPackage.GetGitHubBranch () - # 'AUTO' source - else: - gitHubUser = "" - gitHubBranch = "" - - # attempt to retrieve GitHub user and branch from stored pacakge - # update only if not already set - path = "/data/" + packageName + "/gitHubInfo" - if os.path.isfile (path): - fd = open (path, 'r') - gitHubInfo = fd.readline().strip() - fd.close () - parts = gitHubInfo.split(":") - if len (parts) >= 2: - if gitHubUser == "": - gitHubUser = parts[0] - if gitHubBranch == "": - gitHubBranch = parts[1] - else: - logging.warning (file + " gitHubInfo not formed properly " + gitHubInfo) - - # finally, pull GitHub info from default package list - if gitHubUser == "" or gitHubBranch == "": - default = DbusIf.LocateDefaultPackage (packageName) - if default != None: - if gitHubUser == "": - gitHubUser = default[1] - if gitHubBranch == "": - gitHubBranch = default[2] - - # update dbus parameters - DbusIf.LOCK () - package = PackageClass.LocatePackage (packageName) - if package != None: - package.SetGitHubUser (gitHubUser) - package.SetGitHubBranch (gitHubBranch) - DbusIf.UNLOCK () - - - # AddPackage adds one package to the package list - # packageName must be specified - # the package names must be unique - # - # this method is called from the GUI add package command - - @classmethod - def AddPackage ( cls, packageName=None, source=None ): - if source == 'GUI': - reportStatusTo = 'Editor' - # 'AUTO' source - else: - reportStatusTo = None - - if packageName == None or packageName == "": - DbusIf.UpdateStatus ( message="no package name for AddPackage - nothing done", - where=reportStatusTo, logLevel=ERROR ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - return False - - - # insure packageName is unique before adding this new package - matchFound = False - DbusIf.LOCK () - package = PackageClass.LocatePackage (packageName) - - # new packageName is unique, OK to add it - if package == None: - DbusIf.UpdateStatus ( message="Adding package " + packageName, where='Editor', logLevel=INFO ) - - section = len(cls.PackageList) - cls.PackageList.append( PackageClass ( section, packageName = packageName ) ) - DbusIf.UpdatePackageCount () - - cls.updateGitHubInfo (packageName=packageName, source=source) - - if source == 'GUI': - DbusIf.UpdateGuiState ( '' ) - # delete the removed flag if the package directory exists - path = "/data/" + packageName + "/REMOVED" - if os.path.exists (path): - os.remove (path) - else: - if source == 'GUI': - DbusIf.UpdateStatus ( message=packageName + " already exists - choose another name", where=reportStatusTo, logLevel=INFO ) - DbusIf.UpdateGuiState ( 'ERROR' ) - else: - DbusIf.UpdateStatus ( message=packageName + " already exists", where=reportStatusTo, logLevel=INFO ) - - DbusIf.UNLOCK () - # end AddPackage - - # packages are removed as a request from the GUI - # to remove a package: - # 1) locate the entry matching package name (if any) - # 2) move all packages after that entry the previous slot (if any) - # 3) erase the last package slot to avoid confusion (by looking at dbus-spy) - # 3) remove the entry in PackageList (pop) - # 4) update the package count - # 5) set REMOVED flag file in the package directory in /data to prevent - # package from being re-added to the package list - # flag file is deleted when package is manually installed again - # - # returns True if package was removed, False if not - # - # this is all done while the package list is locked !!!! - - @classmethod - def RemovePackage (cls, packageName ): - if packageName == "SetupHelper": - DbusIf.UpdateStatus ( message="can't remove SetupHelper" + packageName, where='Editor', logLevel=INFO ) - return - - DbusIf.UpdateStatus ( message="removing " + packageName, where='Editor', logLevel=INFO ) - DbusIf.LOCK () - packages = PackageClass.PackageList - - # locate index of packageName - toIndex = 0 - listLength = len (packages) - matchFound = False - while toIndex < listLength: - if packageName == packages[toIndex].PackageName: - matchFound = True - break - toIndex += 1 - - if matchFound: - # move packages after the one to be remove down one slot (copy info) - # each copy overwrites the lower numbered package - fromIndex = toIndex + 1 - while fromIndex < listLength: - toPackage = packages[toIndex] - fromPackage = packages[fromIndex] - toPackage.SetPackageName (fromPackage.PackageName ) - toPackage.SetGitHubUser (fromPackage.GetGitHubUser() ) - toPackage.SetGitHubBranch (fromPackage.GetGitHubBranch() ) - toPackage.SetGitHubVersion (fromPackage.GetGitHubVersion() ) - toPackage.SetInstalledVersion (fromPackage.GetInstalledVersion() ) - toPackage.SetPackageVersion (fromPackage.GetPackageVersion() ) - toPackage.SetRebootNeeded (fromPackage.GetRebootNeeded() ) - toPackage.SetIncompatible (fromPackage.GetIncompatible() ) - toIndex += 1 - fromIndex += 1 - - # here, toIndex points to the last package in the old list - - # remove the Settings for the package being removed - packages[toIndex].RemoveDbusSettings () - - # remove entry from package list - packages.pop (toIndex) - - # update package count - DbusIf.UpdatePackageCount () - - DbusIf.UNLOCK () - # flag this package was manually removed via setting the REMOVED flag file - # in the package directory - if matchFound: - if os.path.isdir ("/data/" + packageName): - path = "/data/" + packageName + "/REMOVED" - # equivalent to unix touch command - open ("/data/" + packageName + "/REMOVED", 'a').close() - - DbusIf.UpdateStatus ( message="", where='Editor' ) - DbusIf.UpdateGuiState ( '' ) - else: - DbusIf.UpdateStatus ( message=packageName + " not removed - name not found", where='Editor', logLevel=ERROR ) - DbusIf.UpdateGuiState ( 'ERROR' ) - - - # GetVersionsFromFiles - # - # retrieves packages versions from the file system - # each package contains a file named version in it's root directory - # that becomes packageVersion - # an "installedFlag" file is associated with installed packages - # abesense of the file indicates the package is not installed - # presense of the file indicates the package is installed - # the contents of the flag file be the actual version installed - # in prevous versions of the setup scripts, this file could be empty, - # so we show this as "unknown" - # - # also sets incompatible dbus service parameters - - @classmethod - def GetVersionsFromFiles(cls): - DbusIf.LOCK () - for package in cls.PackageList: - packageName = package.PackageName - - # fetch installed version - installedVersionFile = "/etc/venus/installedVersion-" + packageName - try: - versionFile = open (installedVersionFile, 'r') - except: - installedVersion = "" - else: - installedVersion = versionFile.readline().strip() - versionFile.close() - # if file is empty, an unknown version is installed - if installedVersion == "": - installedVersion = "unknown" - package.SetInstalledVersion (installedVersion) - - # fetch package version (the one in /data/packageName) - try: - versionFile = open ("/data/" + packageName + "/version", 'r') - except: - packageVersion = "" - else: - packageVersion = versionFile.readline().strip() - versionFile.close() - package.SetPackageVersion (packageVersion) - - # set the incompatible parameter - # to 'PLATFORM' or 'VERSION' - global Platform - incompatible = False - if os.path.exists ("/data/" + packageName + "/raspberryPiOnly" ): - if Platform[0:4] != 'Rasp': - package.SetIncompatible ('PLATFORM') - incompatible = True - - # platform is OK, now check versions - if incompatible == False: - # check version compatibility - try: - fd = open ("/data/" + packageName + "/firstCompatibleVersion", 'r') - except: - firstVersion = "v2.40" - else: - firstVersion = fd.readline().strip() - fd.close () - try: - fd = open ("/data/" + packageName + "/obsoleteVersion", 'r') - except: - obsoleteVersion = None - else: - obsoleteVersion = fd.readline().strip() - - global VersionToNumber - global VenusVersion - firstVersionNumber = VersionToNumber (firstVersion) - obsoleteVersionNumber = VersionToNumber (obsoleteVersion) - venusVersionNumber = VersionToNumber (VenusVersion) - if venusVersionNumber < firstVersionNumber: - self.SetIncompatible ('VERSION') - incompatible = True - elif obsoleteVersionNumber != 0 and venusVersionNumber >= obsoleteVersionNumber: - package.SetIncompatible ('VERSION') - incompatible = True - - # platform and versions OK, check to see if command line is needed for install - # the optionsRequired flag in the package directory indicates options must be set before a blind install - # the optionsSet flag indicates the options HAVE been set already - # so if optionsRequired == True and optionsSet == False, can't install from GUI - if incompatible == False: - if os.path.exists ("/data/" + packageName + "/optionsRequired" ): - if not os.path.exists ( "/data/setupOptions/" + packageName + "/optionsSet"): - package.SetIncompatible ('CMDLINE') - incompatible = True - - DbusIf.UNLOCK () -# end Package - - -# GetGitHubVersionsClass -# Instances: -# GitHubVersions (a separate thread) -# -# Methods: -# RefreshVersion -# run ( the thread ) -# updateGitHubVersion -# -# retrieves GitHub versions over the network -# runs as a separate thread because it takes time -# and so we can space out network access over time - -class GetGitHubVersionsClass (threading.Thread): - - # package needing immediate update - priorityPackageName = None - - def __init__(self): - threading.Thread.__init__(self, name = "GetGitHubVersion") - self.threadRunning = True - - - def updateGitHubVersion (self, packageName, gitHubUser = None, gitHubBranch = None): - matchFound = False - # if user and branch aren't specified, get from package list - if gitHubUser == None or gitHubBranch == None: - DbusIf.LOCK () - package = PackageClass.LocatePackage (packageName) - if package != None: - gitHubUser = package.GetGitHubUser() - gitHubBranch = package.GetGitHubBranch() - DbusIf.UNLOCK () - - # packageName no longer in list - do nothing - if matchFound == False: - return None - - url = "https://raw.githubusercontent.com/" + gitHubUser + "/" + packageName + "/" + gitHubBranch + "/version" - - cmdReturn = subprocess.run (["wget", "-qO", "-", url],\ - text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if cmdReturn.returncode == 0: - gitHubVersion = cmdReturn.stdout.strip() - else: - gitHubVersion = "" - # locate the package with this name and update it's GitHubVersion - # if not in the list discard the information - DbusIf.LOCK () - packageToUpdate = None - try: - if packageName == package.PackageName: - packageToUpdate = package - except: - package = PackageClass.LocatePackage (packageName) - if package != None: - packageToUpdate = package - if packageToUpdate != None: - packageToUpdate.SetGitHubVersion (gitHubVersion) - DbusIf.UNLOCK () - - - # RefreshVersion - # - # schedules the refresh of the GitHub version for a specific section - # called when the gitHubBranch changes in Settings - # so must return immediately - # the refresh is performed in the run thread - - def RefreshVersion (self, packageName): - self.priorityPackageName = packageName - - # run() - the thread - # - # pulls the GitHub version for all packages from the internet - # so this loop runs slowly and must be paced to minimize network traffic - # - # the first loop at start is a 5 seconds per package - # then the loop slows to 60 seconds per pacakge to save bandwidth - # priorityPackage is tested while waiting and updated next if defined - # - # loop extracts a packageName from the package list - # then operates on that name - # if the name is still in the list after fetching the GitHub version - # the package list is updated - # if not, the version is discarded - # - # this complication is due to the need to lock the packageList - # while updating it - # - # run () checks the threadRunning flag and returns if it is False, - # essentially taking the thread off-line - # the main method should catch the tread with join () - # StopThread () is called to shut down the thread - - def StopThread (self): - logging.info ("attempting to stop GetGitHubVersions thread") - self.threadRunning = False - - def run (self): - updateRate = 5.0 - index = 0 - while self.threadRunning: - # end of package list - assume all packages have been scanned once - # and slow loop to one verion every 1 minute - DbusIf.LOCK () - if index >= len (PackageClass.PackageList): - index = 0 - updateRate = 60.0 - package = PackageClass.PackageList[index] - name = package.PackageName - user = package.GetGitHubUser () - branch = package.GetGitHubBranch () - DbusIf.UNLOCK () - - self.updateGitHubVersion (packageName = name, gitHubUser = user, gitHubBranch = branch) - index += 1 - - delayTime = updateRate - while delayTime > 0.0: - if self.threadRunning == False: - return - if self.priorityPackageName != None: - DbusIf.LOCK () - package = PackageClass.LocatePackage (self.priorityPackageName) - if package != None: - user = package.GetGitHubUser () - branch = package.GetGitHubBranch () - DbusIf.UNLOCK () - if package != None: - self.updateGitHubVersion (packageName = self.priorityPackageName, gitHubUser = user, gitHubBranch = branch) - else: - logging.error ("can't fetch GitHub version - " + self.priorityPackageName + " not in list") - self.priorityPackageName = None - time.sleep (5.0) - delayTime -= 5.0 - -# VersionToNumber -# -# convert a version string in the form of vX.Y~Z-large-W to an integer to make comparisions easier -# the ~Z portion indicates a pre-release version so a version without it is later than a version with it -# the -W portion is like the ~Z for large builds -# the -W portion is IGNORED !!!! -# note part[0] is always null because there is nothing before v which is used as a separator -# -# each section of the version is given 3 decimal digits -# for example v1.2~3 would be 1002003 -# for example v11.22 would be 11022999 -# for example v11.22-large-33 would be 11022999 -# an empty file or one that contains "unknown" or does not beging with 'v' -# has a version number = 0 -# -# returns the version number - -def VersionToNumber (version): - if version == None or version == "" or version[0] != 'v': - return 0 - - parts = re.split ('v|\.|\~|\-', version) - versionNumber = 0 - if len(parts) >= 2: - versionNumber += int ( parts[1] ) * 1000000 - if len(parts) >= 3: - versionNumber += int ( parts[2] ) * 1000 - if len(parts) >= 4: - versionNumber += int ( parts[3] ) - else: - versionNumber += 999 - return versionNumber - - -# DownloadGitHubPackagesClass -# Instances: -# DownloadGitHub (a separate thread) -# -# Methods: -# SetDownloadPending -# ClearDownloadPending -# GitHubDownload -# downloadNeeded -# wait -# run ( the thread ) -# -# downloads packages from GitHub, replacing the existing package -# if versions indicate a newer version -# -# the run () thread is only responsible for pacing automatic downloads from the internet -# commands are pushed onto the processing queue (PushAction) -# -# the actual download (GitHubDownload) is called in the context of ProcessAction -# - -class DownloadGitHubPackagesClass (threading.Thread): - - def __init__(self): - threading.Thread.__init__(self, name = "downloadGitHubPackages") - self.lastMode = 0 - self.lastAutoDownloadTime = 0.0 - self.threadRunning = True - - # the ...Pending flag prevents duplicate actions from piling up - # automatic downloads are not queued if there is one pending - # for a specific package - # - # packageName rather than a package list reference (index, etc) - # because the latter can change when packages are removed - # - # the pending flag is set at the beginning of the operation - # because the GUI can't do that - # this doesn't close the window but narrows it a little - - - def SetDownloadPending (self, packageName): - package = PackageClass.LocatePackage (packageName) - if package != None: - package.downloadPending = True - - - def ClearDownloadPending (self, packageName): - package = PackageClass.LocatePackage (packageName) - if package != None: - package.downloadPending = False - - # this method downloads a package from GitHub - # it is called from the queue command processor ProcessAction.run() - # also, download requests are pushed for automatic downloads from the loop below in run() method - # and also for a manual download triggered from the GUI - # statusMethod provides text status to the caller - # callBack provides notificaiton of completion (or error) - # automatic downloads that fail are logged but otherwise not reported - - def GitHubDownload (self, packageName= None, source=None): - if source == 'GUI': - where = 'Editor' - elif source == 'AUTO': - where = 'Download' - - # to avoid thread confilcts, create a temp directory that - # is unque to this program and this method - # and make sure it is empty - tempDirectory = "/var/run/packageManager" + str(os.getpid ()) + "GitHubDownload" - if os.path.exists (tempDirectory): - shutil.rmtree (tempDirectory) - os.mkdir (tempDirectory) - packagePath = "/data/" + packageName - - DbusIf.LOCK () - package = PackageClass.LocatePackage (packageName) - gitHubUser = package.GetGitHubUser () - gitHubBranch = package.GetGitHubBranch () - DbusIf.UNLOCK () - - DbusIf.UpdateStatus ( message="downloading " + packageName, where=where, logLevel=INFO ) - self.SetDownloadPending (packageName) - - url = "https://github.com/" + gitHubUser + "/" + packageName + "/archive/" + gitHubBranch + ".tar.gz" - # create temp directory specific to this thread - tempArchiveFile = tempDirectory + "/temp.tar.gz" - # download archive - if os.path.exists (tempArchiveFile): - os.remove ( tempArchiveFile ) - cmdReturn = subprocess.run ( ['wget', '-qO', tempArchiveFile, url ],\ - text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if cmdReturn.returncode != 0: - DbusIf.UpdateStatus ( message="can't access" + packageName + ' ' + gitHubUser + ' ' + gitHubBranch + " on GitHub", - where=where, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - self.ClearDownloadPending (packageName) - # log stderr also - logging.warning (cmdReturn.stderr) - shutil.rmtree (tempDirectory) - return False - cmdReturn = subprocess.run ( ['tar', '-xzf', tempArchiveFile, '-C', tempDirectory ], - text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if cmdReturn.returncode != 0: - DbusIf.UpdateStatus ( message="can't unpack " + packageName + ' ' + gitHubUser + ' ' + gitHubBranch, - where=where, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - self.ClearDownloadPending (packageName) - # log stderr also - logging.warning (cmdReturn.stderr) - shutil.rmtree (tempDirectory) - return False - - # unpacked archive path is anything beginning with the packageName - # should only be one item in the list, discard any others - searchPath = tempDirectory + '/' + packageName + '*' - tempPaths = glob.glob (searchPath) - if len (tempPaths) > 0: - archivePath = tempPaths[0] - else: - logging.error ( "GitHubDownload: no archive path for " + packageName + " can't download") - return False - - if os.path.isdir(archivePath) == False: - - DbusIf.UpdateStatus ( message="archive path for " + packageName + " not valid - can't use it", - where=where, logLevel=ERROR ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - self.ClearDownloadPending (packageName) - shutil.rmtree (tempDirectory) - return False - # move unpacked archive to package location - # LOCK this critical section of code to prevent others - # from accessing the directory while it's being updated - tempPackagePath = packagePath + "-temp" - DbusIf.LOCK () - if os.path.exists (packagePath): - os.rename (packagePath, tempPackagePath) - shutil.move (archivePath, packagePath) - if os.path.exists (tempPackagePath): - shutil.rmtree (tempPackagePath, ignore_errors=True) # like rm -rf - DbusIf.UNLOCK () - DbusIf.UpdateStatus ( message="", where=where ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - shutil.rmtree (tempDirectory) - return True - # end GitHubDownload - - # compares versions to determine if a download is needed - # returns: - # 'skipped' if versions were not available and couldn't be checked - # 'download' if a download is needed - # '' if download is NOT needed - - def downloadNeeded (self, package): - gitHubVersion = package.GetGitHubVersion () - packageVersion = package.GetPackageVersion () - gitHubBranch = package.GetGitHubBranch () - # no gitHubVersion - skip further checks - if gitHubVersion == '' or packageVersion == '': - return 'skipped' - - packageVersionNumber = VersionToNumber( packageVersion ) - gitHubVersionNumber = VersionToNumber( gitHubVersion ) - # if GitHubBranch is a version number then the match must be exact to skip the download - if gitHubBranch[0] == 'v': - if gitHubVersionNumber != packageVersionNumber: - return 'download' - else: - return '' - # otherwise the download is skipped if the gitHubVersion is older - else: - if gitHubVersionNumber > packageVersionNumber: - return 'download' - else: - return '' - - # downloads and version checks are spaced out to minimize network traffic - # the wait time depends on the download mode: - # fast or one pass delays 10 seconds - # slow (normal) delays 10 minutes - # the time is broken into 5 second intervals so we can check for mode changes - # and update download status on the GUI - # - # if the download mode changes while we are waiting, we want to restart the scan - # - # if startTime is specified, it is used to calculate the time for the delay - # if not, the current time is used as the start - # - # this routine returns True if the process should continue - # or False if we want to reset the loop to the first package - - def wait (self, fastDelay = 5, slowDelay = 30, startTime = None, statusMessage = ""): - # sleep until it's time to download - # break into 5 second delays so we can check for mode changes - # and update status - if startTime == None: - startTime = time.perf_counter() - while True: - currentMode = DbusIf.GetAutoDownload () - # auto-downloads disabled or speeding up loop - start scan with first package - if currentMode == AUTO_DOWNLOADS_OFF \ - or (currentMode >= FAST_DOWNLOAD and self.lastMode == NORMAL_DOWNLOAD): - return False # return with no delay - - # set delay: single pass or fast check - # does NOT affect delay if no download - if currentMode == FAST_DOWNLOAD or currentMode == ONE_DOWNLOAD: - delayTime = fastDelay - # slow check - else: - delayTime = slowDelay - timeToGo = delayTime + startTime - time.perf_counter() - - # normal exit here - wait for download expired, time to do it - if timeToGo <= 0: - return True - - time.sleep (5.0) - if self.threadRunning == False: - return False # return with no delay - - if timeToGo > 90: - DbusIf.UpdateStatus ( message=statusMessage + "%0.1f minutes" % ( timeToGo / 60 ), where='Download' ) - elif timeToGo > 1.0: - DbusIf.UpdateStatus ( message=statusMessage + "%0.0f seconds" % ( timeToGo ), where='Download' ) - - return True - - - # run (the thread) - # - # scan packages looking for downloads from GitHub - # this process paces automatic downloads to minimize network traffic - # - # rather than processing the action, it places them on a queue - # this same queue receives actions from the GUI - # and from the sister thread that paces automatic installs - # - # the actual download occurs from the InstallPackagessThread - # which pulls actions from a queue - # - # run () checks the threadRunning flag and returns if it is False, - # essentially taking the thread off-line - # the main method should catch the tread with join () - # StopThread () is called to shut down the thread - - def StopThread (self): - logging.info ("attempting to stop DownloadGitHub thread") - self.threadRunning = False - - def run (self): - # give time for first GitHub version to be retrieved - time.sleep (6.0) - - global GitHubVersions - self.lastMode = AUTO_DOWNLOADS_OFF - currentMode = AUTO_DOWNLOADS_OFF - continueLoop = True - i = 0 - while self.threadRunning: # loop forever - self.lastMode = currentMode - currentMode = DbusIf.GetAutoDownload () - if currentMode == AUTO_DOWNLOADS_OFF: - # idle message - DbusIf.UpdateStatus ( message="", where='Download' ) - time.sleep (5.0) - if self.threadRunning == False: - return - continue - - DbusIf.LOCK () - packageLength = len (PackageClass.PackageList) - - # loop continues until a download is needed or the end of the list is reached - # after processing a download, returns here to check the next package - # - if i >= packageLength: - i = 0 - package = PackageClass.PackageList[i] - packageName = package.PackageName - DbusIf.UpdateStatus (message="Checking " + packageName, where='Download') - - # update for next pass - DO NOT use i inside the loop after this - i += 1 - # don't create another download action if one is already pending - if package.DownloadPending: - downloadNeeded = '' - else: - downloadNeeded = self.downloadNeeded (package) - DbusIf.UNLOCK () - - if downloadNeeded == 'download': - package.DownloadPending = True - continueLoop = self.wait (fastDelay = 10, slowDelay = 600, startTime = self.lastAutoDownloadTime, - statusMessage = packageName + " download begins in " ) - if continueLoop: - ProcessAction.PushAction ( command="Download:" + packageName, source='AUTO') - self.lastAutoDownloadTime = time.perf_counter() - # start loop at beginning because wait () detected a mode change - else: - i = 0 - # no download needed - pause then move on to next package - else: - if self.threadRunning == False: - return - if downloadNeeded == 'skipped': - message = packageName + " skipped, next in " - else: - message = packageName + " checked, next in " - continueLoop = self.wait (fastDelay = 5, slowDelay = 180, statusMessage = message) - # start loop at beginning because wait () detected a mode change - if continueLoop == False: - i = 0 - - # end of the package list - need to start with first package in same mode - # note the reset of i = 0 indicates the loop was restarted in the middle - # so do not change modes - if i >= len( PackageClass.PackageList ): - # change fast loop to slow - currentMode = DbusIf.GetAutoDownload () - if currentMode == FAST_DOWNLOAD: - DbusIf.SetAutoDownload (NORMAL_DOWNLOAD) - # disable after one pass - elif currentMode == ONE_DOWNLOAD: - DbusIf.SetAutoDownload (AUTO_DOWNLOADS_OFF) - # end while True - # end run -# end DownloadGitHubPackagesClass - - -# InstallPackagesClass -# Instances: -# InstallPackages (a separate thread) -# autoInstallNeeded -# -# Methods: -# InstallPackage -# run (the thread) -# autoInstallNeeded -# setInstallPending -# clearInstallPending -# setUninstallPending -# clearUninstallPending -# -# install and uninstall packages -# if versions indicate a newer version -# runs as a separate thread since the operations can take a long time -# and we need to space them to avoid consuming all CPU resources -# -# packages are automatically installed only -# if the autoInstall Setting is active -# package version is newer than installed version -# or if nothing is installed -# -# a manual install is performed regardless of versions - -class InstallPackagesClass (threading.Thread): - - def __init__(self): - threading.Thread.__init__(self, name = "InstallPackages") - DbusIf.SetInstallStatus ("") - self.threadRunning = True - - - # setInstallPending - # clearInstallPending - # setUninstallPending - # clearUninstallPending - - # the ...Pending flag prevents duplicate actions from piling up - # automatic downloads are not queued if there is one pending - # for a specific package - # - # packageName rather than a package list reference (index, etc) - # must be used because the latter can change when packages are removed - # - # the pending flag is set at the beginning of the operation - # because the GUI can't do that - # this doesn't close the window but narrows it a little - - def setInstallPending (self, packageName): - package = PackageClass.LocatePackage (packageName) - if package != None: - package.InstallPending = True - - def clearInstallPending (self, packageName): - package = PackageClass.LocatePackage (packageName) - if package != None: - package.InstallPending = False - - def setUninstallPending (self, packageName): - package = PackageClass.LocatePackage (packageName) - if package != None: - package.UninstallPending = True - - def clearUninstallPending (self, packageName): - package = PackageClass.LocatePackage (packageName) - if package != None: - package.UninstallPending = False - - - # InstallPackage - # - # this method either installs or uninstalls a package - # the choice is the direction value: - # 'install' or 'uninstall' - # the operation can take many seconds - # i.e., the time it takes to run the package's setup script - # do not call from a thread that should not block - - def InstallPackage ( self, packageName=None, source=None , direction='install' ): - self.setInstallPending (packageName) - setupFile = "/data/" + packageName + "/setup" - - if source == 'GUI': - sendStatusTo = 'Editor' - elif source == 'AUTO': - sendStatusTo = 'Install' - callBack = None - - if os.path.isfile(setupFile) == False: - DbusIf.UpdateStatus ( message=packageName + "setup file doesn't exist", - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - DbusIf.LOCK () - self.clearInstallPending (packageName) - DbusIf.UNLOCK () - return - DbusIf.UpdateStatus ( message=direction + "ing " + packageName, where=sendStatusTo ) - cmdReturn = subprocess.run ( [ setupFile, direction, 'deferReboot' ], timeout=120, - text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) - DbusIf.LOCK () - self.clearInstallPending (packageName) - package = PackageClass.LocatePackage (packageName) - # reboot requested - if cmdReturn.returncode == EXIT_SUCCESS: - package.SetIncompatible ('') # this marks the package as compatible - DbusIf.UpdateStatus ( message="", where=sendStatusTo ) - if source == 'GUI': - DbusIf.UpdateGuiState ( '' ) - elif cmdReturn.returncode == EXIT_REBOOT: - # set package RebootNeeded so GUI can show the need - does NOT trigger a reboot - package.SetRebootNeeded (True) - - DbusIf.UpdateStatus ( message=packageName + " " + direction + " requires REBOOT", - where=sendStatusTo, logLevel=INFO ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'RebootNeeded' ) - # auto install triggers a reboot by setting the global flag - reboot handled in main_loop - else: - global SystemReboot - SystemReboot = True - return - elif cmdReturn.returncode == EXIT_RUN_AGAIN: - DbusIf.UpdateStatus ( message=packageName + " setup must be run from command line", - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - elif cmdReturn.returncode == EXIT_INCOMPATIBLE_VERSION: - global VenusVersion - package.SetIncompatible ('VERSION') - DbusIf.UpdateStatus ( message=packageName + " not compatible with Venus " + VenusVersion, - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - elif cmdReturn.returncode == EXIT_INCOMPATIBLE_PLATFOM: - global Platform - package.SetIncompatible ('PLATFORM') - DbusIf.UpdateStatus ( message=packageName + " " + direction + " not compatible with " + Platform, - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - elif cmdReturn.returncode == EXIT_OPTIONS_NOT_SET: - DbusIf.UpdateStatus ( message=packageName + " " + direction + " setup must be run from the command line", - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - elif cmdReturn.returncode == EXIT_FILE_SET_ERROR: - DbusIf.UpdateStatus ( message=packageName + " file set error incomplete", - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - # unknown error - elif cmdReturn.returncode != 0: - DbusIf.UpdateStatus ( message=packageName + " " + direction + " unknown error " + str (cmdReturn.returncode), - where=sendStatusTo, logLevel=WARNING ) - if source == 'GUI': - DbusIf.UpdateGuiState ( 'ERROR' ) - DbusIf.UNLOCK () - # end InstallPackage () - - - # autoInstallNeeded - # - # compares versions to determine if an install is needed - # returns True if an update is needed, False of not - # - # called from run() below - package list already locked - - def autoInstallNeeded (self, package): - incompatible = package.GetIncompatible () - if incompatible != "": - return False - packageVersion = package.GetPackageVersion () - installedVersion = package.GetInstalledVersion () - # skip further checks if package version string isn't filled in - updateNeeded = True - if packageVersion == '': - packageVersion = "--" - return False - - if installedVersion == '': - installedVersion = "--" - - packageVersionNumber = VersionToNumber( packageVersion ) - installedVersionNumber = VersionToNumber( installedVersion ) - # skip install if versions are the same - if packageVersion == installedVersion: - return False - else: - return True - - - # run (the thread) - # - # automatic install packages - # pushes request on queue for processing later in another thread - # this allows this to run quickly while the package list is locked - # - # run () checks the threadRunning flag and returns if it is False, - # essentially taking the thread off-line - # the main method should catch the tread with join () - # StopThread () is called to shut down the thread - - def StopThread (self): - logging.info ("attempting to stop InstallPackages thread") - self.threadRunning = False - - def run (self): - - while self.threadRunning: - DbusIf.LOCK () - for package in PackageClass.PackageList: - if DbusIf.GetAutoInstall() == 1 and self.autoInstallNeeded (package): - if package.InstallPending == False: - ProcessAction.PushAction ( command='Install:' + package.PackageName, source='AUTO') - package.InstallPending = True - DbusIf.UNLOCK () - time.sleep (5.0) - -# end InstallPackagesClass - - - -# MediaScanClass -# Instances: -# MediaScan (a separate thread) -# -# scan removable SD and USB media for packages to be installed -# -# run () is a separate thread that looks for removable -# SD cards and USB sticks that appear in /media as separate directories -# these directories come and go with the insertion and removable of the media -# -# when new media is detected, it is scanned once then ignored -# when media is removed, then reinserted, the scan begins again -# -# packages must be located in the root of the media (no subdirecoties are scanned) -# and must be an archive with a name ending in .tar.gz -# -# archives are unpacked to a temp directory in /var/run (a ram disk) -# verified, then moved into position in /data/ -# where the name comes from the unpacked directory name -# of the form - -# -# actual installation is handled in the InstallPackages run() thread - -class MediaScanClass (threading.Thread): - - - # transferPackage unpacks the archive and moves it into postion in /data - # - # path is the full path to the archive - - def transferPackage (self, path): - packageName = os.path.basename (path).split ('-', 1)[0] - - # create an empty temp directory in ram disk - # for the following operations - # directory is unique to this process and thread - tempDirectory = "/var/run/packageManager" + str(os.getpid ()) + "Media" - if os.path.exists (tempDirectory): - shutil.rmtree (tempDirectory) - os.mkdir (tempDirectory) - - # unpack the archive - result is placed in tempDirectory - cmdReturn = subprocess.run ( ['tar', '-xzf', path, '-C', tempDirectory ], - text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if cmdReturn.returncode != 0: - logging.warning ( "can't unpack " + packageName + " from SD/USB media" ) - shutil.rmtree (tempDirectory) - return False - - unpackedPath = glob.glob (tempDirectory + '/' + packageName + "*" )[0] - if os.path.isdir(unpackedPath) == False: - logging.warning (packageName + " archive not a directory - rejected" ) - shutil.rmtree (tempDirectory) - return False - - #check for version file - versionFile = unpackedPath + "/version" - if not os.path.isfile (versionFile): - logging.warning (packageName + " version file does not exist - archive rejected" ) - shutil.rmtree (tempDirectory) - return False - fd = open (versionFile, 'r') - version = fd.readline().strip() - if version[0] != 'v': - logging.warning (packageName + "invalid version" + version + " - archive rejected") - shutil.rmtree (tempDirectory) - return False - - # TODO: do we want to compare versions and only replace the stored version if - # TODO: the media version is newer or not an exact match ????? - - # move unpacked archive to package location - # LOCK this critical section of code to prevent others - # from accessing the directory while it's being updated - DbusIf.UpdateStatus ( message="transfering " + packageName + " from SD/USB", where='Media', logLevel=INFO ) - packagePath = "/data/" + packageName - tempPackagePath = packagePath + "-temp" - DbusIf.LOCK () - if os.path.exists (tempPackagePath): - shutil.rmtree (tempPackagePath, ignore_errors=True) # like rm -rf - if os.path.exists (packagePath): - os.rename (packagePath, tempPackagePath) - shutil.move (unpackedPath, packagePath) - if os.path.exists (tempPackagePath): - shutil.rmtree (tempPackagePath, ignore_errors=True) # like rm -rf - DbusIf.UNLOCK () - shutil.rmtree (tempDirectory, ignore_errors=True) - time.sleep (5.0) - DbusIf.UpdateStatus ( message="", where='Media') - return True - # end transferPackage - - - def __init__(self): - threading.Thread.__init__(self, name = "InstallPackages") - self.threadRunning = True - - - # run (the thread) - # - # run () checks the threadRunning flag and returns if it is False, - # essentially taking the thread off-line - # the main method should catch the tread with join () - # StopThread () is called to shut down the thread - - def StopThread (self): - logging.info ("attempting to stop MediaScan thread") - self.threadRunning = False - - def run (self): - separator = '/' - root = "/media" - archiveSuffix = ".tar.gz" - - # list of accepted branch/version substrings - acceptList = [ "-current", "-latest", "-main", "-test", "-debug", "-beta", "-install", - "-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9" ] - - # keep track of all media that's been scanned so it isn't scanned again - # media removal removes it from this list - alreadyScanned = [] - - while self.threadRunning: - drives = os.listdir (root) - - # if previously detected media is removed, - # allow it to be scanned again when reinserted - for scannedDrive in alreadyScanned: - if not scannedDrive in drives: - alreadyScanned.remove (scannedDrive) - - for drive in drives: - drivePath = separator.join ( [ root, drive ] ) - if drive in alreadyScanned: - continue - # check any file name ending with the achive suffix - # all others are skipped - for path in glob.iglob (drivePath + "/*" + archiveSuffix): - accepted = False - if os.path.isdir (path): - continue - else: - accepted = False - baseName = os.path.basename (path) - # verify the file name contains one of the accepted branch/version identifiers - # if not found in the list, the archive is rejected - for accept in acceptList: - if accept in baseName: - accepted = True - break - # discovered what appears to be a valid archive - # unpack it, do further tests and move it to /data - if accepted: - self.transferPackage (path) - if self.threadRunning == False: - return - else: - logging.warning (path + " not a valid archive name - rejected") - # end for path - - # mark this drive so it won't get scanned again - # this prevents repeated installs - alreadyScanned.append (drive) - #end for drive - - time.sleep (5.0) - if self.threadRunning == False: - return - # end run () -# end MediaScanClass - - -# AutoRebootCheck -# -# packing installation and uninstallation may require -# a system reboot to fully activate it's resources -# -# this method scans the avalilable packages looking -# for any pending operations (install, uninstall, download) -# it then checks the global RebootNeeded flag -# that is set if a setup script returns EXIT_REBOOT -# -# if no actions are pending and a reboot is needed, -# AutoRebootCheck returns True - -mainloop = None - -def AutoRebootCheck (): - global SystemReboot - - actionsPending = False - for package in PackageClass.PackageList: - # check for operations pending - if package.InstallPending: - actionsPending = True - if package.UninstallPending: - actionsPending = True - if package.DownloadPending: - actionsPending = True - if SystemReboot and actionsPending == False: - logging.warning ("package install/uninstall requeted a system reboot") - return True - else: - return False - - -def mainLoop(): - global mainloop - global rebootNow - - PackageClass.AddStoredPackages () - - PackageClass.GetVersionsFromFiles () - - AutoRebootCheck () - - # reboot checks indicates it's time to reboot - # quit the mainloop which will cause main to continue past mainloop.run () call in main - if AutoRebootCheck (): - DbusIf.UpdateStatus ( message="REBOOTING ...", where='Download' ) - DbusIf.UpdateStatus ( message="REBOOTING ...", where='Editor' ) - - mainloop.quit() - return False - # don't exit - else: - return True - -# main -# -# ######## code begins here -# responsible for initialization and starting main loop and threads -# also deals with clean shutdown when main loop exits -# - -def main(): - global mainloop - global SystemReboot - - SystemReboot = False - - # set logging level to include info level entries - logging.basicConfig( format='%(levelname)s:%(message)s', level=logging.WARNING ) # TODO: change to INFO, etc for debug - - logging.warning (">>>> Package Monitor starting") - - from dbus.mainloop.glib import DBusGMainLoop - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - # get venus version - global VenusVersion - versionFile = "/opt/victronenergy/version" - try: - file = open (versionFile, 'r') - except: - VenusVersion = "" - else: - VenusVersion = file.readline().strip() - file.close() - - # get platform - global Platform - platformFile = "/etc/venus/machine" - try: - file = open (platformFile, 'r') - except: - Platform = "???" - else: - machine = file.readline().strip() - if machine == "einstein": - Platform = "Cerbo GX" - elif machine == "bealglebone": - Platform = "Venus GX" - elif machine == "ccgx": - Platform = "CCGX" - elif machine == "canvu500": - Platform = "CanVu 500" - elif machine == "nanopi": - Platform = "Multi/Easy Solar GX" - elif machine == "raspberrypi2": - Platform = "Raspberry Pi 2/3" - elif machine == "raspberrypi4": - Platform = "Raspberry Pi 4" - else: - Platform = machine - file.close() - - # initialze dbus Settings and com.victronenergy.packageMonitor - global DbusIf - DbusIf = DbusIfClass () - - okToProceed = PackageClass.AddPackagesFromDbus () - - if okToProceed: - PackageClass.AddDefaultPackages () - PackageClass.AddStoredPackages () - - global GitHubVersions - GitHubVersions = GetGitHubVersionsClass() - GitHubVersions.start() - - global DownloadGitHub - DownloadGitHub = DownloadGitHubPackagesClass () - DownloadGitHub.start() - - global InstallPackages - InstallPackages = InstallPackagesClass () - InstallPackages.start() - - global ProcessAction - ProcessAction = ProcessActionClass () - ProcessAction.start() - - global MediaScan - MediaScan = MediaScanClass () - MediaScan.start () - - # set up main loop - every 5 seconds - GLib.timeout_add(5000, mainLoop) - mainloop = GLib.MainLoop() - mainloop.run() - - # this section of code runs only after the mainloop quits - # or if the debus Settings could not be set up (AddPackagesFromDbus fails) - - # stop threads, remove service from dbus - logging.warning ("stopping threads") - GitHubVersions.StopThread () - DownloadGitHub.StopThread () - InstallPackages.StopThread () - ProcessAction.StopThread () - DbusIf.RemoveDbusService () - try: - GitHubVersions.join (timeout=10.0) - DownloadGitHub.join (timeout=30.0) - InstallPackages.join (timeout=10.0) - ProcessAction.join (timeout=10.0) - except: - logging.critical ("attempt to join threads failed - one or more threads failed to exit") - pass - - # check for reboot - if SystemReboot: - logging.critical ("REBOOTING: to complete package installation") - - subprocess.run ( [ 'shutdown', '-r', 'now', 'rebooting to complete package installation' ] ) - # TODO: for debug subprocess.run ( [ 'shutdown', '-k', 'now', 'simulated reboot - system staying up' ] ) - - # insure the package manager service doesn't restart when we exit - # it will start up again after the reboot - subprocess.run ( [ 'svc', '-o', '/service/PackageManager' ], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if cmdReturn.returncode != 0: - logging.warning ("svc to once failed") - logging.warning (cmdReturn.stderr) - - - logging.critical (">>>> PackageMonitor exiting") - - # program exits here - -# Always run our main loop so we can process updates -main() - - - - - diff --git a/ReadMe b/ReadMe index 6f65036..3d1719e 100644 --- a/ReadMe +++ b/ReadMe @@ -1,164 +1,494 @@ -New: SetupHelper includes a GUI based package mananager +New: SetupHelper now includes an automatic install using SD/USB media + without the need for command line access + The SetupHelper package provides: - a mechanism to automatically reinstall packages following a Venus OS update + a set of utilities to simplfy installing modifications to Victron Venus Os + a mechanism to automatically reinstall them following a Venus OS update an automatic update mechanism to keep packages up to date from GitHub archives or USB stick - control of the automatic download and install from the GUI - add and remove packages from the system - manual download, install, uninstall from the GUI - an "blind" install of SetupHelper from SD/USB media + a manual package installation mechanism from GitHub archives or USB stick + an automatic install from SD/USB media + +NOTE: Starting with v2.80~10, the root partition is mounted read only + This package and those that use these resources rely on a writable /root partition + so CommonResources runs the remount-rw.sh script to make it writable + +Supported pacages are: + SetupHelper GeneratorConnector GuiMods GuiMods2 RpiDisplaySetup RpiGpioSetup ShutdownManager TankRepeater -SetupHelper is also required for my other packages - and must be installed BEFORE running the other package setup scripts +More information is provided below. -Blind Install: +Setup: +There are three methods to fetch a package archive. -By far, the easiest way to install SetupHelper is the "blind install" - which requires no command-line interaction. +New: You can install SetupHelper and GuiMods without need of command line access - 1) Download venus-data.tgz from the SetupHelper GitHub repo. + 1) Download venus-data.tgz from the GitHub repo. 2) copy it to the root of a freshly formatted SD card or USB memory stick 3) place the media in the GX device (Cerbo, CCGX, etc) - 4) reboot the GX device TWICE - allowing the system to display the GUI each time - after the second reboot, you should find the Package Manager menus - at the bottom of the Settings menu + 4) reboot the GX device TWICE + after the second reboot, you should see the GUI restart and display + the enhanced flow overview from GuiMods2 + If GuiMods2 was previously installed, the Grid Parallel overview + is automatically selected 5) remove the media from the GX device to prevent the next reboot from starting the process all over again CAUTION: This mechanism overwrites /data/rcS.local !!!! If you are using rcS.local to perform boot-time activities, /data/rcS.local must be recreated following this "blind" install - Note that SetupHelper also uses /data/rcS.local for + Note that SetupHelper and GuiMods2 also use /data/rcS.local for reinstallation following a firmware update so use caution in recreating rcS.local. venus-data.tgz is available here: https://github.com/kwindrem/SetupHelper/raw/main/venus-data.tgz - -Other mechanisms to install SetupHelper require command-line interaction with the GX device - and are no longer recommended for SetupHelper or my other packages + and here: + https://github.com/kwindrem/GuiMods/raw/main/venus-data.tgz + One may be newer than the other. In any case, automatic updates + will update any package that is out of date following the blind install + + +If the Venus device has internet access is to run the following command: + +wget -qO - https://github.com/kwindrem/SetupHelper/archive/current.tar.gz | tar -xzf - -C /data -Once SetupHelper is installed, updates to it and other packages can be performed through the GUI -using the PackageManager menus. +If the Venus device does not have internet access, you will need to fetch the archive using a computer that does have internet access: + +click on this link in a web browser: +https://github.com/kwindrem/SetupHelper/archive/current.tar.gz + +rename the resulting .tar.gz file to venus-data.tar.gz +copy the venus-data.tar.gz to a USB stick, +put the stick in the Venus device and reboot. +When Venus boots, it will unarchive the file to /data/SetupHelper-current + +Move the directory into it's active locaiton: +rm -rf /data/SetupHelper +mv /data/SetupHelper-current /data/SetupHelper + +Finally, run the setup script to complete the installation +/data/SetupHelper/setup + +Use the manual or automatic update mechnism for future updates -CAUTION: - Package Manager allows uninstalling SetupHelper. - This can not be undone since the menus to control Package Manager will go away - You would need to use the Blind Install again to reinstall SetupHelper - - Note that removal does not actually remove the package so other setup scripts - will continue to function. Description: There are two parts to SetupHelper: -1) Package Manager, controls automatic and manual updates to pacakges +1) Install and update utilities help a user manage packages. -2) Utilities used by other packages' setup scripts. - These resources simplfy the task of writing install/uninstall scripts - and may be of help to others writing packages of their own. +2) Resources to help writing scripts to perform the installation. The latter is of concern only to those writing new Venus modificaitons or modifying an existing setup script. -These are described in detail later in the SetupHelperDescription document. - -Package Manager: - -The Package Manager includes a set of menus on the GX device menu system - that allows the user to view package versions, - control automatic package updates - and manually install, uninstall, add and remove packages. - - A python program runs in the backgrouns (as a service) to do the actual - work and to interface with the menus. - - Package Manager menu: - - The first line of this menu provides status for Package Manager, - indicating what it is currenly doing - - Automatic GitHub downloads controls if packages are automatically downloaded - from GitHub. This occurs if a newer version is available. - Modes: - Normal checks GitHub for a package once every 5 minutes - and waits 10 minutes after a download has occured before checking another - Fast checks GitHub for a package once every 5 seconds (10 seconds after a download), - then after one pass through all packages, the downloads are switched to Normal rate - Once checks GitHub for a package at the fast rate, but only once, then dowloads are turned off - Disable disables GitHub downloads - - Auto install packages: - Controls whether new versions of a package are automatically installed. - Some users may wish to have the system automatically download new updates, - but install them manually. - In this case, automatic GitHub downloads may be turned on and Auto install packages turned off - - Auto install packages also influences whether packages transferred from SD/USB media - are automatically installed or just transferred to local storage - - Package Version List: - displays all packages known to Package Manager. - - Version information is displayed for each package: - Git Hub shows the version found on GitHub - Stored shows the verison stored on the GX device - Installed shows the version actually installed and running - - Package Editor: - This menu facilitates: - manual install, uninstall, package add and package remove - changing GitHub access information for each package - - Normally, you would want to download the latest released version - but you may also wish to try out a beta version or revert to a previous one. - Once the GitHub branch is changed, PackageManager will update the GitHub version - and, if enabled, download this alternate version. - - Changing the GitHub user is probably not needed. It is used more in adding a new pacakge to the system. - - Both GitHub user and GitHub branch must be set appropriately or you'll see -- for the GitHub version - - For most packages, this information fills in automatically when the package is installed - - Remove Package - Packages that are of no interest to you may be removed from Package Manager. - Removed packages will no longer appear in the version list or be accessible from the Package Editor menu. - But you can add the package back in manually. - - Add Package - Package Manager includes a default list of packages. If the package of interest is not in this list - and you know the GitHub user and an appropriate branch or version tag, you can add the package manually. - The package name, GitHub user and GitHub branch must all point to a package designed for install - by Package Manager. More about this in the section on desigining packages. - - Package Manager does not allow removing packages unless they are uninstalled first. - - Package Manager does permit uninstalling SetupHelper, - however this will remove the Package Manager itself. - Once removed, the Blind Install mechanism will be needed again !! - - -USB/SD updates: - - When the GX device is not connected to the internet, a USB flash drive or microSD card provides an install/upgrade path. - - To use the USB update process - Navigate to the GitHub, repo, click on tags and select the appropirate branch or specific version tag. - Choose the .tar.gz download link. - (Do not download from the Code tab or download the .zip file. These won't work.) - Copy the archive file to a USB memory stick or microSD card. - Do NOT unpack the archive - Repeat this for all packages you wish to install. - (These can all be placed on the same media along with the SetupHelper venus-data.tgz file) - Insert the stick in the GX device. - If SetupHelper has not yet been installed, follow the Blind Install process above. - Once Package Manager is running, it will transfer and unpack the archive files - and update the package list with the new packages. - If Auto install packages is turned on, the packages will then be installed - - NOTE: no vesion checks are made for packages found on SD/USB media! - Package Manager is quite content to transfer and install an older version! - So make sure you have the latest version especially if your GX device does not have internet access. - -If you are insterested in the inner workings of Setup Manager and Package Manager, read: SetupHelperDescription +These are described in detail later in the document. + +SetupHelper package management: + +The intent of this part of SetupHelper is to minimize the effort needed +to install and update packages. + +packageAutoUpdater handles automatic package updates. + +Automatic updates can be controlled via the Device List / Settings / Package Versions menu + or options in the SetupHelper setup script + Normal checks GitHub for a package once every 10 minutes + Fast checks GitHub for a package once every 10 seconds + Once checks GitHub for a package once every 10 minutes, but only once, then updates are turned off + Disable disables GitHub updates + +If enabled, automatic GitHub update checks will download and install the GitHub version + of all installed packages if it is newer than the current version. + + +If you are experimenting with modificaitons and wish to avoid GitHub updates overriding your work, +disable automatic updates. + + +USB updates are handy when the Venus device is not connected to the internet. + +To use the USB update process: + Navigate to the GitHub, repo, click on tags and select "current". + Choose the .tar.gz download link. + (Do not download from the Code tab or download the .zip file. These won't work.) + Copy the archive files to a USB memory stick. + Do NOT unpack the archive + Insert the stick in the Venus device. + +Regardless the update source (USB or GitHub), +packageAutoUpdater replaces the current copy on the Venus device with the archive contents + but only if the version number of the source is newer than the one that's installed. +Then the package's setup script is run to reinstall the modificaitons. +No user interaction is needed in this process. + +You may wish to dispalbe GitHub updates if you are updating from a USB/SD memory stick to avoid conflicts. +(GitHub and files on a USB stick could have different versions.) + +packageAutoUpdater runs in the background (as a service). + + +packageInstaller provides the opportunity to install packages for the first time, or reinstall them. +For each package, packageInstaller prompts the user to install that package. +Packages that are already installed will be skipped if automatic updates are enabled. + +Packages can be installed from either a USB memory stick/SD memory card or from GitHub. +After the package is copied to the Venus device it's setup script is run +and the user must answer the prompts to complete the instalation. + +The method of putting an archive on a USB stick for manaul install is the same as the one for automatic updates. + +As with package automatic updates, GitHub is the desired source if your Venus device has an internet connection. + +packageInstaller is called from the SetupHelper setup script by choosing the package (p) option at the main prompt. +The packageInstaller is a shell script and can also be run manually: +/data/SetupHelper/packageInstaller + + +Setup script aids: + +SetupHelper provides a number of functions and variables that simplfy scripts written to +install Venus OS mods. The remainder of this ReadMe describes the resources available and +how to incorporate them into a setup script. + +Read on if you are intersting in writing a package setup script (or modifying an existing one) +or understanding how the mechanism works. + +To use SetupHelper, the script must be written to include CommonResources, +then use the functions included in CommonResources to carry out the modiciations. + +#### following lines incorporate SetupHelper utilities into this script +# Refer to the SetupHelper ReadMe file for details. + +source "/data/SetupHelper/CommonResources" + +#### end of lines to include SetupHelper + +A Venus software update overwrites any modifications to the root partition. +It is therefore necessary to reinstall the modifications following the Venus update. + +Running setup for any package using SetupHelper will install the necessary code to reinstall packages +during system boot + +Typically, rcS.local is used because it runs prior to starting services + so conflicts with running services can be avoided + (services don't need to be restarted after they've been modified) + and if a reboot is required, it happens faster + +If your setup script needs resources launched later in boot, change the following line in CommonResources + +rcLocal="/data/rcS.local" + +to + +rcLocal="/data/rc.local" + +If you have already run any setup scripts and want to make this change, make sure to edit /data/rcS.local +to remove the call to reinstallMods or it will be called twice during boot! + +reinstallScriptsList is a list of setup scripts, one per line that will be called from reinstallMods. +Scripts in this list are called EVERY time the system boots. +The script must avoid repeating work if it can be avoided. +SetupHelper tools (CommonResources) handles this for the script write. + +reinstallScriptsList is hard-coded to reside in the /data directory. +The location must match in CommonResources, and in reinstallMods +The file is created by the first setup script to add to it. + +reinstallScriptsList should use full path names to avoid problems finding the script +Lines beginning with # or completely blank lines are ignored as is white space at begin and end + +When the setup script adds itself to the script list, it includes "reinstall" as the first parameter. +When reinstallMods calls the script, it passes "reinstall" to the setup script so it knows not to prompt the user + +When called from reinstallMods: +1) The setup script must not require user input since there will be no user interface +2) The setup script should not reboot the system directly +3) The setup script should avoid reapplying the modifications. +4) The setup script should avoid making calls to the internet as these could hang the system. + +reinstallMods tests the exit code of each setup script. +An exit code of 123 signals that reinstallMods should reboot the system after all scripts have been run. + +A "installed flag file" is used to make sure the automatic reinstallation occurs only once after a Venus OS update. + +The presence of the installed flag file signals no further work needs to be done to reinstall the package + +Running the script manually should typically ignore the installed flag, +but MUST create or remove it as appropriate to control future boot-time execution. + +The installed flag file needs to be removed by a Venus OS update so that the script can do it's work again. +Therefore, installed flag files are stored in /etc/venus since a Venus OS update overwrites the entire dirctory. + +The installed flag file should be tested early in the script to save time. + +Installation choice may be required to control the installation of optional parameters. +These options are stored in /data/setupOpitons/[package] +This maintains the options through a reinstallation of the package. +optionsSet is set after an installation is successful. This enables package reinstallation. + +A setup script needs to run manually to perform the initial installation or removal of the package. +In this case, user interaction is necessary at least to choose to install or uninstall. +The setup script also needs to run unattended when called from reinstallMods during system boot. +When run by a user, the scrit should prompt the user for neede information. +However, the call from reinstallMods also needs this information and the user won't be available. +Therefore the paremeters taken from user interaction need to be stored in persistent storage, +then recovered from that storage during the reinstall. +The script's directory is a convenient location for persistent storage since /data survives a Venus OS update. +The $scriptDir variable is set up in CommonResources (see discussion below). +$scriptDir may be prepended to any persistent storage files. + +CommonResources contains common functions and variables that can be used by all scripts, + saving the setup script writer from lots of tedious work and also hooks scripts into + the reinstall mechanism. + +The tools CommonResorces provides are described now. + +The following functions manage execution of the script and interfaces to reinstallMods which is run + at boot from one of the /data/rc scripts: + + Sourcing CommonResources checks the setup script command line and the installed flag + and sets $scriptAction based on what is found. + If this is a boot-time reinstall, AND the installed flag is set, + the script exits inside CommonResources. + It is no longer necessary to test scriptAction for EXIT. In fact that value will never be used + If this a boot-time reinstall but the installed flag is NOT set, + scriptAction is set to INSTALL. + + checkFileSets has been decommissioned - file set checks are now handled when CommonResources is sourced + Attempts to create a file set for the current Venus OS version if it does not exist yet. + If the original files match another version, the file set is created + so the setup script may continue normally + However, if the original files for this new version differ from all existing versions + the new partial file set must not be used + Flag files ares placed in the file set: + basename.NO_REPLACEMENT indicates a new replaement verion must be created manually + INCOMPLETE indiates the file set can not be used until issue are corrected + scriptAction is also set to EXIT + It is necessary for the user to create new replacement files manually + then rerun the setup script to install the package. + You can also revert to a previous Venus OS version until the package + is updated for the new version. + checkFileSets no longer needs to be called in the setup script + + endScript + Function to finish up, prompt the user (if not reinstalling) and exit the script + It updates the installed flag based on the $runAgain variable + This script is added/removed from the reinstallScriptsList if installing/uninstalling, respectively + If $runningAtBoot is true (false when CommonResources is sourced) + the script will exit with $exitReboot if $rebootNeeded is true + otherwise, the script will exit with $exitSuccess + endScript NEVER RETURNS to the caller + If $runningAtBoot is false (script was run manually), user interaction controls further action + If $rebootNeeded is true, the function asks if the user wishes to reboot now + If they respond yes, the system will be rebooted + The user may choose to not reboot now if additional installations need to be done first + If $rebootNeeded is false, the function notifies the user of any needed actions + If $restartGui is true (false when CommonResources is sourced) + the gui service will be restarted + + The following variables are available to control behavior: + $scriptAction provides direction for the setup script and has the following values: + NONE - setup script should prompt the user for the desired action + and set scriptAction accordingly + EXIT - the setup script should exit immediately + INSTALL - the setup script should execute code to install the package + UNINSTALL - the setup script should execute the code to restore the Venus files to stock + If installaiton errors occur within functions in CommonResources, scriptAction will be changed to UNINSTALL. + The setup script MUST retest scriptAction after all installation code has been executed + so the package can be removed, rather than leaving the system with a partially installed package. + + $rebootNeeded - true signifies a reboot is required after the script is run + if $runningAtBoot is also true, the reboot is actually performed in reinstallMods + $runAgain - true signifies startup script needs to be run again + + The following useful variables become available as well: + $scriptDir - the full path name to the startup script + the script's code can use this to identify the location of files that need to + persist between reboots and through Venus OS updates + It must be set in the setup script beause it is needed before sourcing CommonResources + $scriptName - the basename of the setup script + $reinstallScriptsList - the file containing a list of scripts to be run at boot to reinstall packages + after a Venus software update + $installedFlag - the name of the install flag files including it's full path + User code may use these variables but should not change their value! + It must be set in the setup script beause it is needed before sourcing CommonResources + $venusVersion - the version of VenusOS derived from /opt/victronenergy/version + $fileSets - the standard location for the replacement files + equivalent to #scriptDir/FileSets + Version-dependent replacements are stored in version subdirectories + Version-INDEPENDENT replacements are stored in FileSets + $pkgFileSets - is the locaiton of version-dependent files for the current Venus version + equivalent to $fileSets/$venusVersion + $runningAtBoot - true if the script was called from reinstallMods (at boot time) + signifying this is to be an unattended (automatic) installation + CommonResoures sets this variable based on command line options + $setupOptionsDir - the location of any files that control installation + These options are maintained in a separate directory so reinsalling the package + does not remove them so that a reinstall can proceed without prompting again + $obsoleteVersion - if the package is obsolete, it can be prevented from installation + at a specified Venus OS version + + $packageGitHubUser - specifies the GitHub user name to be used to fetch updates + $packageGitHubBranch - specifies the branch to pull the update from + Both of these need to be specified in the setup script to enable updates from GitHub and USB/SD media + +The following functions update or restore Venus files to activate a package +they are intended to simplify common tasks, generally reducing many lines +to a single line that is easier to read/understand + +The "active" file is the one used by Venus applications +It is backed up to [activeFile].orig in the same directory +Then a "replacement" file is copied into place and becomes the active file +Backups allow the stock functionality to be restored when the package is uninstalled + +Replacement files are expected in the FileSets directory within the script's directory +If the replacement file content differs with VenusOS version, +subdirectories for each version hold the replacement files +If the replacment is independent of version, it can be placed in FileSets +The version subdirectories are checked first. + +The version sub-directories also contain the stock files with their name ending in .orig. +These are used to look for a match within previous versions when a new Venus version is detected. +A new Venus version with matching files to a previous version updates the file sets automatically. +If the new version has different file content, replacement file(s) will need to be created MANUALLY. +This is typically easy by inspecting previous active and .orig files and the new .orig file. +The file set for the new version is flagged as INCOMPLETE and +will preven installation until the file set is complete and the INCOMPLETE flag is removed manually. + +fileList contains a list of Venus files to be managed by this package. +Packages may also contain files that do not exist in the stock Venus image. +These are NOT included in fileList! + +obsoleteVersion is a file optionally contained in the package directory. +It indicates a Venus OS version at which the package is no longer compatible. +CommonResources prevents the package from being installed for the specified version and all those following it. + +Use full paths/name for all files to avoid problems when running the script from other locations +such as the boot code, and quote them in case the names contain spaces. + +Two flags are set by these routines in order for the setup script to detect changes + $thisFileUpdated is true of the venus file was modified by the operation + It can be tested following each update, copy or restore operation + to determine the success/failure of that one operation + $filesUpdates is true if ANY file is modified by any of these functions + It can be tested at the end of the setup script to know if ANY file + was modified to trigger restarting service or rebooting + +Use updateActiveFile to replace the active file with a replacement from the package + + updateActiveFile replacementFile activeFile + or + updateActiveFile activeFile + First backs up the active file + then copies the replacement version into the active location + If called with two parameters + the first is replacement the file (source) + the second is the active file (destination) + If called with only one parameter, it specifies the active file + the replacement file is selected from FileSets + + restoreActiveFile activeFile + Moves the backup copy to the active location + The first parameter is the name of the active file (the one to be restored to stock) + The file with the same name with .orig at the end is moved + to the active file + If the backup copy doesn't exist BUT the noOrig flag is set + the active copy is deleted to restore the system to stock + + A failure in updateActiveFile and copyToActiveFile set scriptAction to UNINSTALL. + The setup script MUST then remove the package + to prevent system instability from a partially installed package + +The following functions simplify the task of getting user input + + standardActionPrompt displays a menu of actions and asks the user to choose + It sets scriptAction accordingly and returns + It also handles displaying setup and package logs then asks for an action again + It also handles quitting with no action - the fuction EXITS without returning in this case + The basic action prompt includes install, reinstall, quit, display logs (2 choices) + A reinstall option is enabled if the optionsSet option exists + When reinstall is enabled, selecting install, returns a scriptAction of NONE + indicating additional prompting may be needed to complete the install + At the end of these prompts, the main script should set scriptAction to INSTALL + If reinstall is selected, the script action is set to INSTALL and the main script + should then skip additional prompts and allow options set previously to control the install + + yesNoPrompt "question" + Asks the user to answer yes or no to the question + Any details regarding the question should be output before calling yesNoPrompt + yesNoPrompt sets $yesResponse to true if the answer was yes and false if the answer was no + +LogHandler is a logging and log display mechanism. It is sourced by CommonResources and also by reinstallMods + Some executions of the setup scripts are during system boot + where console messages are likely to go unnoticed (if they are visible at all). + Boot-time scripts that output to the console are diverted to /var/log/boot,var/log/messages or dmesg, + but these logs retain messages only from the last boot. + A Setup Helper log is used to make messages from these setup acivities that are more persistent and easier to find. + The setup helper log file is /var/log/SetupHelper (or $setupLogFile) + In addition, some packages have their own log file and logging utilities here write to these logs as well + Finally, when run from the command line, the console (stdout) is also available and provides the most immediate + interface to the user. + A tai64n timestamp is added to messages written to both log files. + This timestamp can be converted to human readable form for display tai64nlocal + The script name is also written to logs + + logMessage "message" + writes "message" the above places + + displayLog logfile + displays the last 100 lines of the log file + $1 is the log file to be displayed. Either: + $setupLogFile or + $packageLogFile + The latter must be initialized in setup script code + If no package log file exists, $packageLogFile shoudl remain null "" + + +Update handler + SetupHelper checks for updates to packages from GitHub or USB/SD media. + The GitHub updates can be automatic. + When a package is installed, it addes the GitHub user and branch info to a file + used by the automatic and manual updates. + The defaultPackageList file is included in the SetupHelper package. + The default is copied to the active location at /data/setupOptions/SetupHelper/packageList + if it does not already exist. + The active file can then be edited to control the updates. + Package setup scripts should add lines to this file during installation. + This is handled in endScript if the packageGitHubUser and packageGitHubBranch are defined in the script + Some packages are included in defaultPackageList + + Normally, the current or latest branch is used to check for updates, but specifying a different branch + or a specific version (of the form v2.3) can also be specified. + + A development branch should be used during package development to prevent affecting others using auto update. + + The repo version is compared with the installed version. + + If a version is specified for the branch, the package will be brought to that version + regardless of what is currently installed. + + If any other branch name is used, an update will occur if the repo verison is numerically greater than the installed version + +PackageList format + The file consists of one line per package naming the package and specifying the GitHub user and branch, e.g., + + SetupHelper kwindrem current + + Blank lines or those beginning with # are ignored + A package can be manually added to this file, or updates disabled by adding a # at the beginning of the line. + +packageVersions file + packageVersions are maintained in a file and also placed in dbus Settings for display in + the Package Versions List menu + The reason for the duplicaiton is dbus access from unix shell scripts is very slow + + Whenever a package is installed, it compares its current version to the one in + the /data/packageVersions file. If there is a difference dbus Settings are updated + + Do NOT edit this file!!! + +Version numbers use the same syntax as Venus OS: + v.~ + v2.45 would replace v2.45~68 as test versions lead up to an eventual release are considered diff --git a/Screen Shot Add Package.png b/Screen Shot Add Package.png deleted file mode 100644 index 3b7b29b..0000000 Binary files a/Screen Shot Add Package.png and /dev/null differ diff --git a/Screen Shot Confirm Downloads.png b/Screen Shot Confirm Downloads.png deleted file mode 100644 index 581c9bc..0000000 Binary files a/Screen Shot Confirm Downloads.png and /dev/null differ diff --git a/Screen Shot Package Editor.png b/Screen Shot Package Editor.png deleted file mode 100644 index 08a46b5..0000000 Binary files a/Screen Shot Package Editor.png and /dev/null differ diff --git a/Screen Shot Package Manager main menu.png b/Screen Shot Package Manager main menu.png deleted file mode 100644 index 140b368..0000000 Binary files a/Screen Shot Package Manager main menu.png and /dev/null differ diff --git a/Screen Shot Package Version List.png b/Screen Shot Package Version List.png deleted file mode 100644 index e982859..0000000 Binary files a/Screen Shot Package Version List.png and /dev/null differ diff --git a/Screen Shot SetupHelper Uninstall warning.png b/Screen Shot SetupHelper Uninstall warning.png deleted file mode 100644 index 903a105..0000000 Binary files a/Screen Shot SetupHelper Uninstall warning.png and /dev/null differ diff --git a/ServiceResources b/ServiceResources index 765ce7c..6b918d6 100755 --- a/ServiceResources +++ b/ServiceResources @@ -1,6 +1,6 @@ # ServiceManager for SetupHelper # contains a functions to install, remove, start and stop a package's service -# + # managing a normal package's service is straight forward # # normally, services are connected via a symbolic link, but to avoid issues with @@ -36,22 +36,16 @@ fi _startService () { - svc -d "/service/$1" - if [ -e "$serviceDir/$1/log" ]; then - svc -d "/service/$1/log" - fi - - rm -f "$serviceDir/$1/down" + local pkg=$1 + rm -f "$serviceDir/$pkg/down" if $serviceOverlay ; then - rm -f "/service/$1/down" + rm -f "/service/$pkg/down" + svc -u "/service/$pkg" fi - svc -u "/service/$1" - if [ -e "$serviceDir/$1/log" ]; then - rm -f "$serviceDir/$1/log/down" - if $serviceOverlay ; then - rm -f "/service/$1/log/down" - fi - svc -u "/service/$1/log" + svc -u "/service/$pkg" + if [ -e "$serviceDir/$pkg/log" ]; then + rm -f "$serviceDir/$pkg/log/down" + svc -u "/service/$pkg/log" fi } @@ -61,21 +55,23 @@ startService () if [ $# -lt 1 ]; then return fi + local pkg=$1 - if [ -e "$serviceDir/$1" ]; then - logMessage "starting $1 service" - _startService $1 + if [ -e "$serviceDir/$pkg" ]; then + logMessage "starting $pkg service" + _startService $pkg fi } _stopService () { - touch "$serviceDir/$1/down" - svc -d "/service/$1" - if [ -e "$serviceDir/$1/log" ]; then - touch "$serviceDir/$1/log/down" - svc -d "/service/$1/log" + local pkg=$1 + touch "$serviceDir/$pkg/down" + svc -d "/service/$pkg" + if [ -e "$serviceDir/$pkg/log" ]; then + touch "$serviceDir/$pkg/log/down" + svc -d "/service/$pkg/log" fi } @@ -85,10 +81,11 @@ stopService () if [ $# -lt 1 ]; then return fi + local pkg=$1 - if [ -e "$serviceDir/$1" ]; then - logMessage "stopping $1 service" - _stopService $1 + if [ -e "$serviceDir/$pkg" ]; then + logMessage "stopping $pkg service" + _stopService $pkg fi } @@ -98,15 +95,16 @@ stopService () _removeService () { + local pkg=$1 # stop the service - _stopService $1 + _stopService $pkg # remove the service directory # removing the service in the overlayed service directory doesn't remove it from /service # it needs to be removed from the overlay work directory also - rm -rf "$serviceDir/$1" + rm -rf "$serviceDir/$pkg" if $serviceOverlay ; then - rm -rf "$overlayWorkDir/$1" + rm -rf "$overlayWorkDir/$pkg" fi } @@ -116,10 +114,11 @@ removeService () if [ $# -lt 1 ]; then return fi + local pkg=$1 - if [ -e "$serviceDir/$1" ]; then - logMessage "removing $1 service" - _removeService $1 + if [ -e "$serviceDir/$pkg" ]; then + logMessage "removing $pkg service" + _removeService $pkg fi } @@ -136,7 +135,6 @@ removeService () installService () { - local restartService=false # no package specified if [ $# -lt 1 ]; then return @@ -148,37 +146,31 @@ installService () local pkg=$1 - if [ -L "$serviceDir/$1" ]; then - logMessage "removing old $1 service (was symbolic link)" - rm -f "$serviceDir/$1" + if [ -L "$serviceDir/$pkg" ]; then + logMessage "removing old $pkg service (was symbolic link)" + rm -f "$serviceDir/$pkg" fi # service not yet installed, COPY service directory to the active locaiton - if [ ! -e "$serviceDir/$1" ]; then - logMessage "installing $1 service" - cp -R "$scriptDir/service" "$serviceDir/$1" + if [ ! -e "$serviceDir/$pkg" ]; then + logMessage "installing $pkg service" + cp -R "$scriptDir/service" "$serviceDir/$pkg" # service already installed - only copy changed files, then restart service else - restartService=true - logMessage "restarting $1 service" + logMessage "restarting $pkg service" if [ -f "$scriptDir/service/run" ]; then - cmp -s "$scriptDir/service/run" "$serviceDir/$1/run" > /dev/null + cmp -s "$scriptDir/service/run" "$serviceDir/$pkg/run" > /dev/null if [ $? != 0 ]; then - cp "$scriptDir/service/run" "$serviceDir/$1/run" + cp "$scriptDir/service/run" "$serviceDir/$pkg/run" fi fi if [ -f "$scriptDir/service/log/run" ]; then - cmp -s "$scriptDir/service/log/run" "$serviceDir/$1/log/run" > /dev/null + cmp -s "$scriptDir/service/log/run" "$serviceDir/$pkg/log/run" > /dev/null if [ $? != 0 ]; then - cp "$scriptDir/service/log/run" "$serviceDir/$1/log/run" + cp "$scriptDir/service/log/run" "$serviceDir/$pkg/log/run" fi fi fi - - # restart service if was previously installed - # a service will start when newly installed so we don't want to restart it - if $restartService ; then - _startService $1 - fi + _startService $pkg } diff --git a/UpdateResources b/UpdateResources new file mode 100755 index 0000000..8e3809f --- /dev/null +++ b/UpdateResources @@ -0,0 +1,253 @@ + +# This file provides utilities for automatic and manual package updates/installation +# +# Include the following lines in the update scripts +# source "/data/SetupHelper/LogHandler" +# source "/data/SetupHelper/UpdateResources" + +setupHelperDir="/data/SetupHelper" +source "$setupHelperDir/EssentialResources" + +# converts a Venus version string to a version number +# +# beta numbers are handled in a special way. +# the released verison has no beta string +# it's number would be less than a beta version +# we bump the version number by 999 +# so the release is always greater than any beta +# +# the "-large-n" portion is discarded so those versions +# can be compared to non-large versions +# +# the version string is passed as $1 +# the number is returned in versionNumber + +function versionStringToNumber () +{ + local versionBeta="" + + read versionNumber versionBeta <<< $(echo $1 | sed -e 's/v//' -e 's/-.*$//' | \ + awk -v FS='[v.~-]' '{printf "%d%03d%03d %d\n", $1, $2, $3, $3}') + if (( $versionBeta == 0 )); then + ((versionNumber += 999)) + fi +} + +# the version number of the archive is compared to the installed version +# if a the branch specifies a spcific version number, the two versions must match +# for the installed package to be considered up to date +# for a branch label, the installed package is up to date if it's version +# is equal to or greater than the archive's verison +# +# function will return 1 if an update is required and 0 if no update is needed + +function checkVersions () +{ + # convert versions to numbers and compare them + versionStringToNumber $archiveVersion + archiveVersionNumber=$versionNumber + versionStringToNumber $installedVersion + # if version number specified, must match that exact value + if [[ ${gitHubBranch:0:1} == "v" ]] ; then + if (( $versionNumber == $archiveVersionNumber )); then + return 0 + else + return 1 + fi + else + if (( $versionNumber >= $archiveVersionNumber )); then + return 0 + else + return 1 + fi + fi + +} + + +# get the package from a USB file +# The package is left in $package-$gitHubBranch for processing later +# $1 is the name of the package +# returns 0 if updates should NOT occur or 1 if update is acceptable for update + +getFromUsb () +{ + local package=$1 + local packageArchive + local packageDir="/data/$package" + local lastUpdate="" + local fileSuffix + + # the unpacked folder for version tags don't inclue the v (e.g, v2.4 is 2.4 in archive name) + # so the gitHubBranch is reworked to create a file suffix + if [[ ${gitHubBranch:0:1} == "v" ]]; then + fileSuffix=${gitHubBranch:1:999} + else + fileSuffix=$gitHubBranch + fi + packageArchive="/media/$dir/$package"-$fileSuffix.tar.gz + + # archive not found on USB stick + if [ ! -f "$packageArchive" ]; then + return 0 + fi + + tar -xzf "$packageArchive" -C /data + if [ ! -d "$packageDir-$fileSuffix" ]; then + logMessage "ERROR: $packageArchive did not contain $package" + return 0 + fi + + # get the version from local copy of package + if [ -f "$packageDir/version" ]; then + installedVersion=$(cat "$packageDir/version") + else + installedVersion="" + fi + + # get archive version + archiveVersion=$(cat "/data/$package-$fileSuffix/version") + if [ ! -e "$packageDir" ]; then + if $logToConsole ; then + echo "$package not yet installed - proceeding" + fi + return 1 + elif [ -z $archiveVersion ]; then + logMessage "ERROR: No version in $package archive - can't update" + return 0 + elif [ -z $installedVersion ]; then + logMessage "WARNING: no version for $package current installation - proceeding" + return 1 + else + checkVersions + if [ $? -eq 0 ]; then + if $logToConsole ; then + echo "$package is up to date" + fi + return 0 + else + return 1 + fi + fi +} + + +# get the package from a GitHub +# The package is left in $package-$gitHubBranch for processing later +# $1 is the name of the package +# +# $2 is a boolean: when true archive download is skipped +# used by packageInstaller when a package is aready installed and auto updates are enabled +# if auto updates are enabled and update will be skipped anyway +# returns 0 if update should NOT occur or 1 if update is acceptable + +getFromGitHub () +{ + local package=$1 + local packageDir="/data/$package" + local lastUpdate="" + local skipDownload + if [ $# -gt 1 ] && $2 ; then + checkOnly=true + else + checkOnly=false + fi + + # get the version from local copy of package + if [ -f "$packageDir/version" ]; then + installedVersion=$(cat "$packageDir/version") + else + installedVersion="" + fi + + if [ ! -e "$packageDir" ]; then + if $logToConsole ; then + echo "$package not yet installed - proceeding" + fi + fi + + # fetch archive version + archiveVersion=$(wget -qO - https://raw.githubusercontent.com/$gitHubUser/$package/$gitHubBranch/version) + if [ -z $archiveVersion ]; then + logMessage "ERROR: no version for $package $gitHubUser $gitHubBranch on GitHub - can't continue" + return 0 + elif [ -z $installedVersion ]; then + logMessage "WARNING: no version for $package current installation - proceeding" + else + checkVersions + if [ $? -eq 0 ]; then + if $logToConsole ; then + echo "$package is up to date" + fi + return 0 + elif $checkOnly ; then + return 1 + fi + fi + # update the package and reinstall it + wget -qO - https://github.com/$gitHubUser/$package/archive/$gitHubBranch.tar.gz | tar -xzf - -C /data + if [ $? -eq 0 ]; then + return 1 + else + logMessage "ERROR: can't access $package $gitHubUser $gitHubBranch on GitHub" + return 0 + fi +} + + +# install the archive and run setup script +# +# $1 is the package name +# $2 is the github branch/version tag +# $3 is the flag to allow running the script with user interaction + +doUpdate () +{ + local package=$1 + local packageDir="/data/$package" + local gitHubBranch=$2 + local fileSuffix + + # the unpacked folder for version tags don't inclue the v (e.g, v2.4 is 2.4 in archive name) + if [[ ${gitHubBranch:0:1} == "v" ]]; then + fileSuffix=${gitHubBranch:1:999} + else + fileSuffix=$gitHubBranch + fi + + if [ $# -gt 2 ] && [ $3 == 'prompting' ]; then + installOk=true + else + installOk=false + fi + + # move new version into active position + if [ -d "$packageDir-$fileSuffix" ]; then + rm -rf "$packageDir" + mv "$packageDir-$fileSuffix" "$packageDir" + else + logMessage "$packageDir-$fileSuffix is missing - can't continue" + return + fi + + if [ -f "$packageDir/setup" ]; then + # if package is installed, reinstall automatically + if [ -f "$installedFlagPrefix$package" ]; then + logMessage "reinstalling $package" + "$packageDir/setup" "reinstall" "force" + # defer reboot until all updates and reinstalls have been done + if [ $? == $exitReboot ] ; then + rebootNeeded=true + fi + # no options set, run manual setup - but defer reboot until all scripts have been run + elif $installOk ; then + logMessage "running $package setup script - choose install to complete installation" + "$packageDir/setup" "deferReboot" + if [ $? == $exitReboot ] ; then + rebootNeeded=true + fi + else + logMessage "$package not currently active - skipping reinstall" + fi + fi +} diff --git a/blindInstall b/blindInstall index e5874a8..91994f0 100755 --- a/blindInstall +++ b/blindInstall @@ -1,10 +1,6 @@ #!/bin/bash -# this script is part of an autoInstall archive which installs SetupHelper -# previous versions of blindInstall also installed GuiMods but has since been removed -# PackageManager will install all package archives found on the SD/USB media so -# the result is the same (or actually better) -# +# this script is part of an autoInstall archive # the archive makes use of the Venus OS update-data.sh script # archives named "venus-data.tar" are unpacked during boot # then for this archive, Venus must be rebooted, causing rcS.local to run, @@ -36,17 +32,24 @@ sleep 2 logMessage "starting up" # a package setup script normally prompts for user input -# 'reinstall force' insures SetupHelper is installed without user interaction +# reinstall force options insure the package is installed without user interaction +# +# currently VeCanSetup and RpiDisplaySetup require user responses during initial setup +# so can't be installed via this mechanism +# all packages rely on SetupHelper so it must be present and installed first if [ -f "/data/SetupHelper/setup" ]; then # insure package reinstall doesn't get appended to the auto install rcS.local !!! rm -f /data/rcS.local sync logMessage "installing SetupHelper" /data/SetupHelper/setup reinstall force deferReboot + if [ -f "/data/GuiMods/setup" ]; then + logMessage "installing GuiMods" + /data/GuiMods/setup reinstall force deferReboot + fi - # enable GitHub automatic updates and automatic installation - dbus -y com.victronenergy.settings /Settings/PackageMonitor/GitHubAutoUpdate SetValue 1 &> /dev/null - dbus -y com.victronenergy.settings /Settings/PackageMonitor/AutoInstall SetValue 1 &> /dev/null + # enable GitHub automatic updates + dbus -y com.victronenergy.settings /Settings/PackageVersion/GitHubAutoUpdate SetValue 1 &> /dev/null fi diff --git a/changes b/changes deleted file mode 100644 index ebe6251..0000000 --- a/changes +++ /dev/null @@ -1,14 +0,0 @@ -add Package Manager & GUI -add setup script return codes for above -add optionsRequired flag file (VeCanSetup is only package that needs this now) - -add platform and version checks to CommonResources -add install opiton to CommonResources -better support installs without command line -### TBD remove logging to package log files - -improve adding packages from SD/USB - -split auto download and auto install - - diff --git a/defaultPackageList b/defaultPackageList index 6abf2c8..e733a0d 100644 --- a/defaultPackageList +++ b/defaultPackageList @@ -11,9 +11,7 @@ # Package GitHubUser Version SetupHelper kwindrem current GuiMods kwindrem current -GeneratorConnector kwindrem current ShutdownMonitor kwindrem current -TankRepeater kwindrem current -VeCanSetup kwindrem current RpiDisplaySetup kwindrem current RpiGpioSetup kwindrem current +TankRepeater kwindrem current diff --git a/ext/velib_python/LICENSE b/ext/velib_python/LICENSE deleted file mode 100755 index 1e21061..0000000 --- a/ext/velib_python/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Victron Energy BV - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ext/velib_python/README.md b/ext/velib_python/README.md deleted file mode 100755 index f87b4e2..0000000 --- a/ext/velib_python/README.md +++ /dev/null @@ -1,102 +0,0 @@ -velib_python -============ - -[![Build Status](https://travis-ci.org/victronenergy/velib_python.svg?branch=master)](https://travis-ci.org/victronenergy/velib_python) - -This is the general python library within Victron. It contains code that is related to D-Bus and the Color -Control GX. See http://www.victronenergy.com/panel-systems-remote-monitoring/colorcontrol/ for more -infomation about that panel. - -Files busitem.py, dbusitem.py and tracing.py are deprecated. - -The main files are vedbus.py, dbusmonitor.py and settingsdevice.py. - -- Use VeDbusService to put your process on dbus and let other services interact with you. -- Use VeDbusItemImport to read a single value from other processes the dbus, and monitor its signals. -- Use DbusMonitor to monitor multiple values from other processes -- Use SettingsDevice to store your settings in flash, via the com.victronenergy.settings dbus service. See -https://github.com/victronenergy/localsettings for more info. - -Code style -========== - -Comply with PEP8, except: -- use tabs instead of spaces, since we use tabs for all projects within Victron. -- max line length = 110 - -Run this command to set git diff to tabsize is 4 spaces. Replace --local with --global to do it globally for the current -user account. - - git config --local core.pager 'less -x4' - -Run this command to check your code agains PEP8 - - pep8 --max-line-length=110 --ignore=W191 *.py - -D-Bus -===== - -D-Bus is an/the inter process communication bus used on Linux for many things. Victron uses it on the CCGX to have all the different processes exchange data. Protocol drivers publish data read from products (for example battery voltage) on the D-Bus, and other processes (the GUI for example) takes it from the D-Bus to show it on the display. - -Libraries that implement D-Bus connectivity are available in many programming languages (C, Python, etc). There are also many commandline tools available to talk to a running process via D-bus. See for example the dbuscli (executeable name dbus): http://code.google.com/p/dbus-tools/wiki/DBusCli, and also dbus-monitor and dbus-send. - -There are two sides in using the D-Bus, putting information on it (exporting as service with objects) and reading/writing to a process exporting a service. Note that instead of reading with GetValue, you can also subscribe to receive a signal when datachanges. Doing this saves unncessary context-switches in most cases. - -To get an idea of how to publish data on the dbus, run the example: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ python vedbusservice_example.py - vedbusservice_example.py starting up - /Position value is 5 - /Position value is now 10 - try changing our RPM by executing the following command from a terminal - - dbus-send --print-reply --dest=com.victronenergy.example /RPM com.victronenergy.BusItem.SetValue int32:1200 - Reply will be <> 0 for values > 1000: not accepted. And reply will be 0 for values < 1000: accepted. - -Leave that terminal open, start a second terminal, and interrogate above service from the commandline: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ dbus - org.freedesktop.DBus - org.freedesktop.PowerManagement - com.victronenergy.example - org.xfce.Terminal5 - org.xfce.Xfconf - [and many more services in which we are not interested] - -To get more details, add the servicename: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ dbus com.victronenergy.example - / - /Float - /Int - /NegativeInt - /Position - /RPM - /String - -And get the value for the position: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ dbus com.victronenergy.example /RPM GetValue - 100 - -And setting the value is also possible, the % makes dbus evaluate what comes behind it, resulting in an int instead of the default (a string).: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ dbus com.victronenergy.example /RPM SetValue %1 - 0 - -In this example, the 0 indicates succes. When trying an unsupported value, 2000, this is what happens: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ dbus com.victronenergy.example /RPM SetValue %2000 - 2 - -Exporting services, and the object paths (/Float, /Position, /Group1/Value1, etcetera) is standard D-Bus functionality. At Victron we designed and implemented a D-Bus interface, called com.victronenergy.BusItem. Example showing all interfaces supported by an object: - - matthijs@matthijs-VirtualBox:~/dev/velib_python/examples$ dbus com.victronenergy.example /RPM - Interface org.freedesktop.DBus.Introspectable: - String Introspect() - - Interface com.victronenergy.BusItem: - Int32 SetValue(Variant newvalue) - String GetDescription(String language, Int32 length) - String GetText() - Variant GetValue() diff --git a/ext/velib_python/settingsdevice.py b/ext/velib_python/settingsdevice.py deleted file mode 100644 index a207e8b..0000000 --- a/ext/velib_python/settingsdevice.py +++ /dev/null @@ -1,118 +0,0 @@ -import dbus -import logging -import time -from functools import partial - -# Local imports -from vedbus import VeDbusItemImport - -## Indexes for the setting dictonary. -PATH = 0 -VALUE = 1 -MINIMUM = 2 -MAXIMUM = 3 -SILENT = 4 - -## The Settings Device class. -# Used by python programs, such as the vrm-logger, to read and write settings they -# need to store on disk. And since these settings might be changed from a different -# source, such as the GUI, the program can pass an eventCallback that will be called -# as soon as some setting is changed. -# -# The settings are stored in flash via the com.victronenergy.settings service on dbus. -# See https://github.com/victronenergy/localsettings for more info. -# -# If there are settings in de supportSettings list which are not yet on the dbus, -# and therefore not yet in the xml file, they will be added through the dbus-addSetting -# interface of com.victronenergy.settings. -class SettingsDevice(object): - ## The constructor processes the tree of dbus-items. - # @param bus the system-dbus object - # @param name the dbus-service-name of the settings dbus service, 'com.victronenergy.settings' - # @param supportedSettings dictionary with all setting-names, and their defaultvalue, min, max and whether - # the setting is silent. The 'silent' entry is optional. If set to true, no changes in the setting will - # be logged by localsettings. - # @param eventCallback function that will be called on changes on any of these settings - # @param timeout Maximum interval to wait for localsettings. An exception is thrown at the end of the - # interval if the localsettings D-Bus service has not appeared yet. - def __init__(self, bus, supportedSettings, eventCallback, name='com.victronenergy.settings', timeout=0): - logging.debug("===== Settings device init starting... =====") - self._bus = bus - self._dbus_name = name - self._eventCallback = eventCallback - self._values = {} # stored the values, used to pass the old value along on a setting change - self._settings = {} - - count = 0 - while True: - if 'com.victronenergy.settings' in self._bus.list_names(): - break - if count == timeout: - raise Exception("The settings service com.victronenergy.settings does not exist!") - count += 1 - logging.info('waiting for settings') - time.sleep(1) - - # Add the items. - self.addSettings(supportedSettings) - - logging.debug("===== Settings device init finished =====") - - def addSettings(self, settings): - for setting, options in settings.items(): - silent = len(options) > SILENT and options[SILENT] - busitem = self.addSetting(options[PATH], options[VALUE], - options[MINIMUM], options[MAXIMUM], silent, callback=partial(self.handleChangedSetting, setting)) - self._settings[setting] = busitem - self._values[setting] = busitem.get_value() - - def addSetting(self, path, value, _min, _max, silent=False, callback=None): - busitem = VeDbusItemImport(self._bus, self._dbus_name, path, callback) - if busitem.exists and (value, _min, _max, silent) == busitem._proxy.GetAttributes(): - logging.debug("Setting %s found" % path) - else: - logging.info("Setting %s does not exist yet or must be adjusted" % path) - - # Prepare to add the setting. Most dbus types extend the python - # type so it is only necessary to additionally test for Int64. - if isinstance(value, (int, dbus.Int64)): - itemType = 'i' - elif isinstance(value, float): - itemType = 'f' - else: - itemType = 's' - - # Add the setting - # TODO, make an object that inherits VeDbusItemImport, and complete the D-Bus settingsitem interface - settings_item = VeDbusItemImport(self._bus, self._dbus_name, '/Settings', createsignal=False) - setting_path = path.replace('/Settings/', '', 1) - if silent: - settings_item._proxy.AddSilentSetting('', setting_path, value, itemType, _min, _max) - else: - settings_item._proxy.AddSetting('', setting_path, value, itemType, _min, _max) - - busitem = VeDbusItemImport(self._bus, self._dbus_name, path, callback) - - return busitem - - def handleChangedSetting(self, setting, servicename, path, changes): - oldvalue = self._values[setting] if setting in self._values else None - self._values[setting] = changes['Value'] - - if self._eventCallback is None: - return - - self._eventCallback(setting, oldvalue, changes['Value']) - - def setDefault(self, path): - item = VeDbusItemImport(self._bus, self._dbus_name, path, createsignal=False) - item.set_default() - - def __getitem__(self, setting): - return self._settings[setting].get_value() - - def __setitem__(self, setting, newvalue): - result = self._settings[setting].set_value(newvalue) - if result != 0: - # Trying to make some false change to our own settings? How dumb! - assert False diff --git a/ext/velib_python/ve_utils.py b/ext/velib_python/ve_utils.py deleted file mode 100644 index e9a7fe3..0000000 --- a/ext/velib_python/ve_utils.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import sys -from traceback import print_exc -from os import _exit as os_exit -from os import statvfs -from subprocess import check_output, CalledProcessError -import logging -import dbus -logger = logging.getLogger(__name__) - -VEDBUS_INVALID = dbus.Array([], signature=dbus.Signature('i'), variant_level=1) - -class NoVrmPortalIdError(Exception): - pass - -# Use this function to make sure the code quits on an unexpected exception. Make sure to use it -# when using GLib.idle_add and also GLib.timeout_add. -# Without this, the code will just keep running, since GLib does not stop the mainloop on an -# exception. -# Example: GLib.idle_add(exit_on_error, myfunc, arg1, arg2) -def exit_on_error(func, *args, **kwargs): - try: - return func(*args, **kwargs) - except: - try: - print ('exit_on_error: there was an exception. Printing stacktrace will be tried and then exit') - print_exc() - except: - pass - - # sys.exit() is not used, since that throws an exception, which does not lead to a program - # halt when used in a dbus callback, see connection.py in the Python/Dbus libraries, line 230. - os_exit(1) - - -__vrm_portal_id = None -def get_vrm_portal_id(): - # The original definition of the VRM Portal ID is that it is the mac - # address of the onboard- ethernet port (eth0), stripped from its colons - # (:) and lower case. This may however differ between platforms. On Venus - # the task is therefore deferred to /sbin/get-unique-id so that a - # platform specific method can be easily defined. - # - # If /sbin/get-unique-id does not exist, then use the ethernet address - # of eth0. This also handles the case where velib_python is used as a - # package install on a Raspberry Pi. - # - # On a Linux host where the network interface may not be eth0, you can set - # the VRM_IFACE environment variable to the correct name. - - global __vrm_portal_id - - if __vrm_portal_id: - return __vrm_portal_id - - portal_id = None - - # First try the method that works if we don't have a data partition. This - # will fail when the current user is not root. - try: - portal_id = check_output("/sbin/get-unique-id").decode("utf-8", "ignore").strip() - if not portal_id: - raise NoVrmPortalIdError("get-unique-id returned blank") - __vrm_portal_id = portal_id - return portal_id - except CalledProcessError: - # get-unique-id returned non-zero - raise NoVrmPortalIdError("get-unique-id returned non-zero") - except OSError: - # File doesn't exist, use fallback - pass - - # Fall back to getting our id using a syscall. Assume we are on linux. - # Allow the user to override what interface is used using an environment - # variable. - import fcntl, socket, struct, os - - iface = os.environ.get('VRM_IFACE', 'eth0').encode('ascii') - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', iface[:15])) - except IOError: - raise NoVrmPortalIdError("ioctl failed for eth0") - - __vrm_portal_id = info[18:24].hex() - return __vrm_portal_id - - -# See VE.Can registers - public.docx for definition of this conversion -def convert_vreg_version_to_readable(version): - def str_to_arr(x, length): - a = [] - for i in range(0, len(x), length): - a.append(x[i:i+length]) - return a - - x = "%x" % version - x = x.upper() - - if len(x) == 5 or len(x) == 3 or len(x) == 1: - x = '0' + x - - a = str_to_arr(x, 2); - - # remove the first 00 if there are three bytes and it is 00 - if len(a) == 3 and a[0] == '00': - a.remove(0); - - # if we have two or three bytes now, and the first character is a 0, remove it - if len(a) >= 2 and a[0][0:1] == '0': - a[0] = a[0][1]; - - result = '' - for item in a: - result += ('.' if result != '' else '') + item - - - result = 'v' + result - - return result - - -def get_free_space(path): - result = -1 - - try: - s = statvfs(path) - result = s.f_frsize * s.f_bavail # Number of free bytes that ordinary users - except Exception as ex: - logger.info("Error while retrieving free space for path %s: %s" % (path, ex)) - - return result - - -def get_load_averages(): - c = read_file('/proc/loadavg') - return c.split(' ')[:3] - - -def _get_sysfs_machine_name(): - try: - with open('/sys/firmware/devicetree/base/model', 'r') as f: - return f.read().rstrip('\x00') - except IOError: - pass - - return None - -# Returns None if it cannot find a machine name. Otherwise returns the string -# containing the name -def get_machine_name(): - # First try calling the venus utility script - try: - return check_output("/usr/bin/product-name").strip() - except (CalledProcessError, OSError): - pass - - # Fall back to sysfs - name = _get_sysfs_machine_name() - if name is not None: - return name - - # Fall back to venus build machine name - try: - with open('/etc/venus/machine', 'r') as f: - return f.read().strip() - except IOError: - pass - - return None - - -def get_product_id(): - """ Find the machine ID and return it. """ - - # First try calling the venus utility script - try: - return check_output("/usr/bin/product-id").strip() - except (CalledProcessError, OSError): - pass - - # Fall back machine name mechanism - name = _get_sysfs_machine_name() - return { - 'Color Control GX': 'C001', - 'Venus GX': 'C002', - 'Octo GX': 'C006', - 'EasySolar-II': 'C007', - 'MultiPlus-II': 'C008' - }.get(name, 'C003') # C003 is Generic - - -# Returns False if it cannot open the file. Otherwise returns its rstripped contents -def read_file(path): - content = False - - try: - with open(path, 'r') as f: - content = f.read().rstrip() - except Exception as ex: - logger.debug("Error while reading %s: %s" % (path, ex)) - - return content - - -def wrap_dbus_value(value): - if value is None: - return VEDBUS_INVALID - if isinstance(value, float): - return dbus.Double(value, variant_level=1) - if isinstance(value, bool): - return dbus.Boolean(value, variant_level=1) - if isinstance(value, int): - try: - return dbus.Int32(value, variant_level=1) - except OverflowError: - return dbus.Int64(value, variant_level=1) - if isinstance(value, str): - return dbus.String(value, variant_level=1) - if isinstance(value, list): - if len(value) == 0: - # If the list is empty we cannot infer the type of the contents. So assume unsigned integer. - # A (signed) integer is dangerous, because an empty list of signed integers is used to encode - # an invalid value. - return dbus.Array([], signature=dbus.Signature('u'), variant_level=1) - return dbus.Array([wrap_dbus_value(x) for x in value], variant_level=1) - if isinstance(value, dict): - # Wrapping the keys of the dictionary causes D-Bus errors like: - # 'arguments to dbus_message_iter_open_container() were incorrect, - # assertion "(type == DBUS_TYPE_ARRAY && contained_signature && - # *contained_signature == DBUS_DICT_ENTRY_BEGIN_CHAR) || (contained_signature == NULL || - # _dbus_check_is_valid_signature (contained_signature))" failed in file ...' - return dbus.Dictionary({(k, wrap_dbus_value(v)) for k, v in value.items()}, variant_level=1) - return value - - -dbus_int_types = (dbus.Int32, dbus.UInt32, dbus.Byte, dbus.Int16, dbus.UInt16, dbus.UInt32, dbus.Int64, dbus.UInt64) - - -def unwrap_dbus_value(val): - """Converts D-Bus values back to the original type. For example if val is of type DBus.Double, - a float will be returned.""" - if isinstance(val, dbus_int_types): - return int(val) - if isinstance(val, dbus.Double): - return float(val) - if isinstance(val, dbus.Array): - v = [unwrap_dbus_value(x) for x in val] - return None if len(v) == 0 else v - if isinstance(val, (dbus.Signature, dbus.String)): - return str(val) - # Python has no byte type, so we convert to an integer. - if isinstance(val, dbus.Byte): - return int(val) - if isinstance(val, dbus.ByteArray): - return "".join([bytes(x) for x in val]) - if isinstance(val, (list, tuple)): - return [unwrap_dbus_value(x) for x in val] - if isinstance(val, (dbus.Dictionary, dict)): - # Do not unwrap the keys, see comment in wrap_dbus_value - return dict([(x, unwrap_dbus_value(y)) for x, y in val.items()]) - if isinstance(val, dbus.Boolean): - return bool(val) - return val diff --git a/ext/velib_python/vedbus.py b/ext/velib_python/vedbus.py deleted file mode 100644 index 4e54203..0000000 --- a/ext/velib_python/vedbus.py +++ /dev/null @@ -1,501 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import dbus.service -import logging -import traceback -import os -import weakref -from ve_utils import wrap_dbus_value, unwrap_dbus_value - -# vedbus contains three classes: -# VeDbusItemImport -> use this to read data from the dbus, ie import -# VeDbusItemExport -> use this to export data to the dbus (one value) -# VeDbusService -> use that to create a service and export several values to the dbus - -# Code for VeDbusItemImport is copied from busitem.py and thereafter modified. -# All projects that used busitem.py need to migrate to this package. And some -# projects used to define there own equivalent of VeDbusItemExport. Better to -# use VeDbusItemExport, or even better the VeDbusService class that does it all for you. - -# TODOS -# 1 check for datatypes, it works now, but not sure if all is compliant with -# com.victronenergy.BusItem interface definition. See also the files in -# tests_and_examples. And see 'if type(v) == dbus.Byte:' on line 102. Perhaps -# something similar should also be done in VeDbusBusItemExport? -# 2 Shouldn't VeDbusBusItemExport inherit dbus.service.Object? -# 7 Make hard rules for services exporting data to the D-Bus, in order to make tracking -# changes possible. Does everybody first invalidate its data before leaving the bus? -# And what about before taking one object away from the bus, instead of taking the -# whole service offline? -# They should! And after taking one value away, do we need to know that someone left -# the bus? Or we just keep that value in invalidated for ever? Result is that we can't -# see the difference anymore between an invalidated value and a value that was first on -# the bus and later not anymore. See comments above VeDbusItemImport as well. -# 9 there are probably more todos in the code below. - -# Some thoughts with regards to the data types: -# -# Text from: http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html#data-types -# --- -# Variants are represented by setting the variant_level keyword argument in the -# constructor of any D-Bus data type to a value greater than 0 (variant_level 1 -# means a variant containing some other data type, variant_level 2 means a variant -# containing a variant containing some other data type, and so on). If a non-variant -# is passed as an argument but introspection indicates that a variant is expected, -# it'll automatically be wrapped in a variant. -# --- -# -# Also the different dbus datatypes, such as dbus.Int32, and dbus.UInt32 are a subclass -# of Python int. dbus.String is a subclass of Python standard class unicode, etcetera -# -# So all together that explains why we don't need to explicitly convert back and forth -# between the dbus datatypes and the standard python datatypes. Note that all datatypes -# in python are objects. Even an int is an object. - -# The signature of a variant is 'v'. - -# Export ourselves as a D-Bus service. -class VeDbusService(object): - def __init__(self, servicename, bus=None): - # dict containing the VeDbusItemExport objects, with their path as the key. - self._dbusobjects = {} - self._dbusnodes = {} - - # dict containing the onchange callbacks, for each object. Object path is the key - self._onchangecallbacks = {} - - # Connect to session bus whenever present, else use the system bus - self._dbusconn = bus or (dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus()) - - # make the dbus connection available to outside, could make this a true property instead, but ach.. - self.dbusconn = self._dbusconn - - # Register ourselves on the dbus, trigger an error if already in use (do_not_queue) - self._dbusname = dbus.service.BusName(servicename, self._dbusconn, do_not_queue=True) - - # Add the root item that will return all items as a tree - self._dbusnodes['/'] = self._create_tree_export(self._dbusconn, '/', self._get_tree_dict) - - logging.info("registered ourselves on D-Bus as %s" % servicename) - - def _get_tree_dict(self, path, get_text=False): - logging.debug("_get_tree_dict called for %s" % path) - r = {} - px = path - if not px.endswith('/'): - px += '/' - for p, item in self._dbusobjects.items(): - if p.startswith(px): - v = item.GetText() if get_text else wrap_dbus_value(item.local_get_value()) - r[p[len(px):]] = v - logging.debug(r) - return r - - # To force immediate deregistering of this dbus service and all its object paths, explicitly - # call __del__(). - def __del__(self): - for node in list(self._dbusnodes.values()): - node.__del__() - self._dbusnodes.clear() - for item in list(self._dbusobjects.values()): - item.__del__() - self._dbusobjects.clear() - if self._dbusname: - self._dbusname.__del__() # Forces call to self._bus.release_name(self._name), see source code - self._dbusname = None - - # @param callbackonchange function that will be called when this value is changed. First parameter will - # be the path of the object, second the new value. This callback should return - # True to accept the change, False to reject it. - def add_path(self, path, value, description="", writeable=False, - onchangecallback=None, gettextcallback=None): - - if onchangecallback is not None: - self._onchangecallbacks[path] = onchangecallback - - item = VeDbusItemExport( - self._dbusconn, path, value, description, writeable, - self._value_changed, gettextcallback, deletecallback=self._item_deleted) - - spl = path.split('/') - for i in range(2, len(spl)): - subPath = '/'.join(spl[:i]) - if subPath not in self._dbusnodes and subPath not in self._dbusobjects: - self._dbusnodes[subPath] = self._create_tree_export(self._dbusconn, subPath, self._get_tree_dict) - self._dbusobjects[path] = item - logging.debug('added %s with start value %s. Writeable is %s' % (path, value, writeable)) - - # Add the mandatory paths, as per victron dbus api doc - def add_mandatory_paths(self, processname, processversion, connection, - deviceinstance, productid, productname, firmwareversion, hardwareversion, connected): - self.add_path('/Mgmt/ProcessName', processname) - self.add_path('/Mgmt/ProcessVersion', processversion) - self.add_path('/Mgmt/Connection', connection) - - # Create rest of the mandatory objects - self.add_path('/DeviceInstance', deviceinstance) - self.add_path('/ProductId', productid) - self.add_path('/ProductName', productname) - self.add_path('/FirmwareVersion', firmwareversion) - self.add_path('/HardwareVersion', hardwareversion) - self.add_path('/Connected', connected) - - def _create_tree_export(self, bus, objectPath, get_value_handler): - return VeDbusTreeExport(bus, objectPath, get_value_handler) - - # Callback function that is called from the VeDbusItemExport objects when a value changes. This function - # maps the change-request to the onchangecallback given to us for this specific path. - def _value_changed(self, path, newvalue): - if path not in self._onchangecallbacks: - return True - - return self._onchangecallbacks[path](path, newvalue) - - def _item_deleted(self, path): - self._dbusobjects.pop(path) - for np in list(self._dbusnodes.keys()): - if np != '/': - for ip in self._dbusobjects: - if ip.startswith(np + '/'): - break - else: - self._dbusnodes[np].__del__() - self._dbusnodes.pop(np) - - def __getitem__(self, path): - return self._dbusobjects[path].local_get_value() - - def __setitem__(self, path, newvalue): - self._dbusobjects[path].local_set_value(newvalue) - - def __delitem__(self, path): - self._dbusobjects[path].__del__() # Invalidates and then removes the object path - assert path not in self._dbusobjects - - def __contains__(self, path): - return path in self._dbusobjects - -""" -Importing basics: - - If when we power up, the D-Bus service does not exist, or it does exist and the path does not - yet exist, still subscribe to a signal: as soon as it comes online it will send a signal with its - initial value, which VeDbusItemImport will receive and use to update local cache. And, when set, - call the eventCallback. - - If when we power up, save it - - When using get_value, know that there is no difference between services (or object paths) that don't - exist and paths that are invalid (= empty array, see above). Both will return None. In case you do - really want to know ifa path exists or not, use the exists property. - - When a D-Bus service leaves the D-Bus, it will first invalidate all its values, and send signals - with that update, and only then leave the D-Bus. (or do we need to subscribe to the NameOwnerChanged- - signal!?!) To be discussed and make sure. Not really urgent, since all existing code that uses this - class already subscribes to the NameOwnerChanged signal, and subsequently removes instances of this - class. - -Read when using this class: -Note that when a service leaves that D-Bus without invalidating all its exported objects first, for -example because it is killed, VeDbusItemImport doesn't have a clue. So when using VeDbusItemImport, -make sure to also subscribe to the NamerOwnerChanged signal on bus-level. Or just use dbusmonitor, -because that takes care of all of that for you. -""" -class VeDbusItemImport(object): - ## Constructor - # @param bus the bus-object (SESSION or SYSTEM). - # @param serviceName the dbus-service-name (string), for example 'com.victronenergy.battery.ttyO1' - # @param path the object-path, for example '/Dc/V' - # @param eventCallback function that you want to be called on a value change - # @param createSignal only set this to False if you use this function to one time read a value. When - # leaving it to True, make sure to also subscribe to the NameOwnerChanged signal - # elsewhere. See also note some 15 lines up. - def __init__(self, bus, serviceName, path, eventCallback=None, createsignal=True): - # TODO: is it necessary to store _serviceName and _path? Isn't it - # stored in the bus_getobjectsomewhere? - self._serviceName = serviceName - self._path = path - self._match = None - # TODO: _proxy is being used in settingsdevice.py, make a getter for that - self._proxy = bus.get_object(serviceName, path, introspect=False) - self.eventCallback = eventCallback - - assert eventCallback is None or createsignal == True - if createsignal: - self._match = self._proxy.connect_to_signal( - "PropertiesChanged", weak_functor(self._properties_changed_handler)) - - # store the current value in _cachedvalue. When it doesn't exists set _cachedvalue to - # None, same as when a value is invalid - self._cachedvalue = None - try: - v = self._proxy.GetValue() - except dbus.exceptions.DBusException: - pass - else: - self._cachedvalue = unwrap_dbus_value(v) - - def __del__(self): - if self._match != None: - self._match.remove() - self._match = None - self._proxy = None - - def _refreshcachedvalue(self): - self._cachedvalue = unwrap_dbus_value(self._proxy.GetValue()) - - ## Returns the path as a string, for example '/AC/L1/V' - @property - def path(self): - return self._path - - ## Returns the dbus service name as a string, for example com.victronenergy.vebus.ttyO1 - @property - def serviceName(self): - return self._serviceName - - ## Returns the value of the dbus-item. - # the type will be a dbus variant, for example dbus.Int32(0, variant_level=1) - # this is not a property to keep the name consistant with the com.victronenergy.busitem interface - # returns None when the property is invalid - def get_value(self): - return self._cachedvalue - - ## Writes a new value to the dbus-item - def set_value(self, newvalue): - r = self._proxy.SetValue(wrap_dbus_value(newvalue)) - - # instead of just saving the value, go to the dbus and get it. So we have the right type etc. - if r == 0: - self._refreshcachedvalue() - - return r - - ## Resets the item to its default value - def set_default(self): - self._proxy.SetDefault() - self._refreshcachedvalue() - - ## Returns the text representation of the value. - # For example when the value is an enum/int GetText might return the string - # belonging to that enum value. Another example, for a voltage, GetValue - # would return a float, 12.0Volt, and GetText could return 12 VDC. - # - # Note that this depends on how the dbus-producer has implemented this. - def get_text(self): - return self._proxy.GetText() - - ## Returns true of object path exists, and false if it doesn't - @property - def exists(self): - # TODO: do some real check instead of this crazy thing. - r = False - try: - r = self._proxy.GetValue() - r = True - except dbus.exceptions.DBusException: - pass - - return r - - ## callback for the trigger-event. - # @param eventCallback the event-callback-function. - @property - def eventCallback(self): - return self._eventCallback - - @eventCallback.setter - def eventCallback(self, eventCallback): - self._eventCallback = eventCallback - - ## Is called when the value of the imported bus-item changes. - # Stores the new value in our local cache, and calls the eventCallback, if set. - def _properties_changed_handler(self, changes): - if "Value" in changes: - changes['Value'] = unwrap_dbus_value(changes['Value']) - self._cachedvalue = changes['Value'] - if self._eventCallback: - # The reason behind this try/except is to prevent errors silently ending up the an error - # handler in the dbus code. - try: - self._eventCallback(self._serviceName, self._path, changes) - except: - traceback.print_exc() - os._exit(1) # sys.exit() is not used, since that also throws an exception - - -class VeDbusTreeExport(dbus.service.Object): - def __init__(self, bus, objectPath, get_value_handler): - dbus.service.Object.__init__(self, bus, objectPath) - self._get_value_handler = get_value_handler - logging.debug("VeDbusTreeExport %s has been created" % objectPath) - - def __del__(self): - # self._get_path() will raise an exception when retrieved after the call to .remove_from_connection, - # so we need a copy. - path = self._get_path() - if path is None: - return - self.remove_from_connection() - logging.debug("VeDbusTreeExport %s has been removed" % path) - - def _get_path(self): - if len(self._locations) == 0: - return None - return self._locations[0][1] - - @dbus.service.method('com.victronenergy.BusItem', out_signature='v') - def GetValue(self): - value = self._get_value_handler(self._get_path()) - return dbus.Dictionary(value, signature=dbus.Signature('sv'), variant_level=1) - - @dbus.service.method('com.victronenergy.BusItem', out_signature='v') - def GetText(self): - return self._get_value_handler(self._get_path(), True) - - def local_get_value(self): - return self._get_value_handler(self.path) - - -class VeDbusItemExport(dbus.service.Object): - ## Constructor of VeDbusItemExport - # - # Use this object to export (publish), values on the dbus - # Creates the dbus-object under the given dbus-service-name. - # @param bus The dbus object. - # @param objectPath The dbus-object-path. - # @param value Value to initialize ourselves with, defaults to None which means Invalid - # @param description String containing a description. Can be called over the dbus with GetDescription() - # @param writeable what would this do!? :). - # @param callback Function that will be called when someone else changes the value of this VeBusItem - # over the dbus. First parameter passed to callback will be our path, second the new - # value. This callback should return True to accept the change, False to reject it. - def __init__(self, bus, objectPath, value=None, description=None, writeable=False, - onchangecallback=None, gettextcallback=None, deletecallback=None): - dbus.service.Object.__init__(self, bus, objectPath) - self._onchangecallback = onchangecallback - self._gettextcallback = gettextcallback - self._value = value - self._description = description - self._writeable = writeable - self._deletecallback = deletecallback - - # To force immediate deregistering of this dbus object, explicitly call __del__(). - def __del__(self): - # self._get_path() will raise an exception when retrieved after the - # call to .remove_from_connection, so we need a copy. - path = self._get_path() - if path == None: - return - if self._deletecallback is not None: - self._deletecallback(path) - self.local_set_value(None) - self.remove_from_connection() - logging.debug("VeDbusItemExport %s has been removed" % path) - - def _get_path(self): - if len(self._locations) == 0: - return None - return self._locations[0][1] - - ## Sets the value. And in case the value is different from what it was, a signal - # will be emitted to the dbus. This function is to be used in the python code that - # is using this class to export values to the dbus. - # set value to None to indicate that it is Invalid - def local_set_value(self, newvalue): - if self._value == newvalue: - return - - self._value = newvalue - - changes = {} - changes['Value'] = wrap_dbus_value(newvalue) - changes['Text'] = self.GetText() - self.PropertiesChanged(changes) - - def local_get_value(self): - return self._value - - # ==== ALL FUNCTIONS BELOW THIS LINE WILL BE CALLED BY OTHER PROCESSES OVER THE DBUS ==== - - ## Dbus exported method SetValue - # Function is called over the D-Bus by other process. It will first check (via callback) if new - # value is accepted. And it is, stores it and emits a changed-signal. - # @param value The new value. - # @return completion-code When successful a 0 is return, and when not a -1 is returned. - @dbus.service.method('com.victronenergy.BusItem', in_signature='v', out_signature='i') - def SetValue(self, newvalue): - if not self._writeable: - return 1 # NOT OK - - newvalue = unwrap_dbus_value(newvalue) - - if newvalue == self._value: - return 0 # OK - - # call the callback given to us, and check if new value is OK. - if (self._onchangecallback is None or - (self._onchangecallback is not None and self._onchangecallback(self.__dbus_object_path__, newvalue))): - - self.local_set_value(newvalue) - return 0 # OK - - return 2 # NOT OK - - ## Dbus exported method GetDescription - # - # Returns the a description. - # @param language A language code (e.g. ISO 639-1 en-US). - # @param length Lenght of the language string. - # @return description - @dbus.service.method('com.victronenergy.BusItem', in_signature='si', out_signature='s') - def GetDescription(self, language, length): - return self._description if self._description is not None else 'No description given' - - ## Dbus exported method GetValue - # Returns the value. - # @return the value when valid, and otherwise an empty array - @dbus.service.method('com.victronenergy.BusItem', out_signature='v') - def GetValue(self): - return wrap_dbus_value(self._value) - - ## Dbus exported method GetText - # Returns the value as string of the dbus-object-path. - # @return text A text-value. '---' when local value is invalid - @dbus.service.method('com.victronenergy.BusItem', out_signature='s') - def GetText(self): - if self._value is None: - return '---' - - # Default conversion from dbus.Byte will get you a character (so 'T' instead of '84'), so we - # have to convert to int first. Note that if a dbus.Byte turns up here, it must have come from - # the application itself, as all data from the D-Bus should have been unwrapped by now. - if self._gettextcallback is None and type(self._value) == dbus.Byte: - return str(int(self._value)) - - if self._gettextcallback is None and self.__dbus_object_path__ == '/ProductId': - return "0x%X" % self._value - - if self._gettextcallback is None: - return str(self._value) - - return self._gettextcallback(self.__dbus_object_path__, self._value) - - ## The signal that indicates that the value has changed. - # Other processes connected to this BusItem object will have subscribed to the - # event when they want to track our state. - @dbus.service.signal('com.victronenergy.BusItem', signature='a{sv}') - def PropertiesChanged(self, changes): - pass - -## This class behaves like a regular reference to a class method (eg. self.foo), but keeps a weak reference -## to the object which method is to be called. -## Use this object to break circular references. -class weak_functor: - def __init__(self, f): - self._r = weakref.ref(f.__self__) - self._f = weakref.ref(f.__func__) - - def __call__(self, *args, **kargs): - r = self._r() - f = self._f() - if r == None or f == None: - return - f(r, *args, **kargs) diff --git a/packageAutoUpdater b/packageAutoUpdater new file mode 100755 index 0000000..aa53004 --- /dev/null +++ b/packageAutoUpdater @@ -0,0 +1,268 @@ +#!/bin/bash + +# This script keeps Venus modification packages up to date +# It looks on GitHub for package updates +# The timestamp of the current tag on GitHub is compatred to the one that was last installed +# if GitHub is newer, the local copy is updated and the package is reinstalled +# +# This script also checks for updates on a USB stick and gives priority to that location +# +# An automatic update will only occur if the package is installed and the autoUpdate setup option is set +# +# the script runs as a service so any updates that also update this script will restart automatically + +# get the full, unambiguous path to this script +scriptDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )" +packageame=$(basename "$scriptDir") +shortScriptName=$(basename "$scriptDir")/$(basename "$0") + +source "/data/SetupHelper/EssentialResources" +source "/data/SetupHelper/LogHandler" + +# this flag is tested by LogHandler to determine if messages should be output to the console +logToConsole=false + +source "/data/SetupHelper/UpdateResources" +source "/data/SetupHelper/DbusSettingsResources" + +# updates status message on GUI +# $1 is the message identifier (not the actual message + +lastMessage='' +lastPackage='' + +updateStatus () +{ + updateDbus=false + checkPackage=false + if [[ $1 == 'IDLE' ]] ; then + message="Fast updates: 10 sec/pkg, Normal updates: 10 min/pkg" + elif [[ $1 == 'USB_DISABLED' ]] ; then + message="USB/SD updates disabled auto GitHub updates" + elif [[ $1 == 'CHECKING' ]] ; then + message="checking $package" + checkPackage=true + elif [[ $1 == 'WAIT' ]] ; then + message="waiting to check $package" + checkPackage=true + elif [[ $1 == 'USB_CHECK' ]] ; then + message="Checking USB/SD for updates" + else + message="" + fi + if [[ $1 != $lastMessage ]]; then + updateDbus=true + elif $checkPackage && [[ $package != $lastPackage ]]; then + updateDbus=true + fi + # update GUI status message + if $updateDbus ; then + updateDbusStringSetting "/Settings/PackageVersion/CheckingPackage" "$message" + fi + lastMessage=$1 + lastPackage=$package +} + + +#### main code starts here + +# wait until dbus settings are active +while [ $(dbus -y | grep -c "com.victronenergy.settings") == 0 ]; do + logMessage "waiting for dBus settings" + sleep 1 +done + +logMessage "starting up" + +usbCheck=false +mediaDetected=false +lastUpdateTime=0 +checkingPackage=false +updateSetupHelper=false + + +# 10 minutes between GitHub checks to minimize network traffic +gitHubSlowCheckDelay=600 +# 10 seconds for first pass +gitHubFastCheckDelay=10 +gitHubCheckDelay=0 + +lastGitHubUpdateSetting=0 + +# loop forever +while true ; do + rebootNeeded=false + restartFromFirstPackage=false + + # skip all processing if package list doesn't exist + # but keep checking + if [ ! -f "$packageListFile" ]; then + sleep 10 + continue + fi + + # loop through packages from package list + while read -u 9 package gitHubUser gitHubBranch; do + # skip comments + if [[ ${package:0:1} == "#" ]] ; then + continue + # skip blank/incomplete lines + elif [ -z $package ] || [ -z $gitHubUser ] || [ -z $gitHubBranch ] ; then + continue + fi + + packageDir="/data/$package" + setupOptionsDir="$setupOptionsRoot"/$package + + # skip uninstalled packages + if [ ! -f "$installedFlagPrefix"$package ]; then + continue + # package has been installed, check for updates + else + checkingPackage=true + fi + + # this loop permits detection of USB media during the long wait for the next GitHub check + while $checkingPackage ; do + doUpdate=false + + # pull Git Hub autoupdate mode from dbus + autoUpdateSetting=$(dbus-send --system --print-reply=literal --dest=com.victronenergy.settings /Settings/PackageVersion/GitHubAutoUpdate\ + com.victronenergy.BusItem.GetValue 2> /dev/null | awk '{print $3}') + if [ -z $autoUpdateSetting ]; then + autoUpdateSetting=0 + fi + + # check for USB / SD media + mediaList=($(ls /media)) + # no media + if [ -z $mediaList ] ; then + mediaDetected=false + usbCheck=false + # media first detected, enable USB checks and start loop over + elif ! $mediaDetected ; then + mediaDetected=true + usbCheck=true + updateStatus 'USB_CHECK' + checkingPackage=false + restartFromFirstPackage=true + break + fi + + # nothing to do - reset loop and wait + if (( $autoUpdateSetting == 0 )) && ! $usbCheck ; then + checkingPackage=false + restartFromFirstPackage=true + updateStatus 'IDLE' + break + fi + + # USB / SD updates + if $usbCheck ; then + for dir in ${mediaList[@]} ; do + getFromUsb $package + if [ $? -eq 1 ]; then + logMessage "found $package on USB" + doUpdate=true + updateStatus 'CHECKING' + break + fi + done + # done checking for this package, time to move on + checkingPackage=false + + # Git Hub updates + elif (( $autoUpdateSetting != 0 )); then + # if speeding up the loop, start package scan over + if (( $autoUpdateSetting >= 2 )) && (( $lastGitHubUpdateSetting <= 1 )); then + checkingPackage=false + restartFromFirstPackage=true + lastGitHubUpdateSetting=$autoUpdateSetting + break + fi + + # set update delay based on update mode + if (( $autoUpdateSetting >= 2 )) ; then + gitHubCheckDelay=$gitHubFastCheckDelay + else + gitHubCheckDelay=$gitHubSlowCheckDelay + fi + + currentTime=$(date '+%s') + # wait between GitHub updates to minimize traffic + if (( $currentTime >= $lastUpdateTime + $gitHubCheckDelay )) ; then + updateStatus 'CHECKING' + lastUpdateTime=$currentTime + getFromGitHub $package + if [ $? -eq 1 ]; then + logMessage "found $package on GitHub" + doUpdate=true + fi + checkingPackage=false + elif (( $autoUpdateSetting != 0 )); then + updateStatus 'WAIT' + fi + fi + if $doUpdate ; then + # do SetupHelper update now since other setup scripts depend on it's resources + # will end this script which will start up again via supervise + if [ $package == "SetupHelper" ]; then + updateSetupHelper=true + break + # reinstall the package without user interaction + else + # update via unattended reinstall only + logMessage "updating $package with $gitHubBranch" + doUpdate $package $gitHubBranch + if $usbCheck ; then + sleep 1 + fi + fi + elif (( $autoUpdateSetting == 0 )); then + rm -rf "$packageDir-$gitHubBranch" + fi + + # delay for inner loop + if $checkingPackage ; then + if $usbCheck ; then + usleep 200000 + else + sleep 1 + fi + fi + done # end while checkingPackage + + if $restartFromFirstPackage || $updateSetupHelper ; then + break + else + sleep 1 + fi + done 9< "$packageListFile" # end while read ... + +# if not restarting scan, update automatic update state + if ! $restartFromFirstPackage && ! $updateSetupHelper; then + # single pass + if (( $autoUpdateSetting == 3 )) ; then + setSetting 0 /Settings/PackageVersion/GitHubAutoUpdate + # end of first pass, switch to slow updates + elif (( $autoUpdateSetting == 2 )) ; then + setSetting 1 /Settings/PackageVersion/GitHubAutoUpdate + fi + fi + usbCheck=false + + # continue execution in packageAutoUpdateCleanup + # so that this script and the service can exit cleanly + if $updateSetupHelper || $rebootNeeded ; then + logMessage "continuing in cleanup script - $packageName exiting" + + nohup "$scriptDir/packageAutoUpdaterCleanup" $gitHubBranch $updateSetupHelper $rebootNeeded 2>&1 | awk '{print "packageAutoUpdaterCleanup " $0}'| tai64n >> /var/log/SetupHelper & + + # shutdown the service which runs this script - this will end this script + svc -d "/service/SetupHelper" + + # wait here for service to end this script + sleep 10000 + fi + sleep 1 +done diff --git a/packageAutoUpdaterCleanup b/packageAutoUpdaterCleanup new file mode 100755 index 0000000..8b2579b --- /dev/null +++ b/packageAutoUpdaterCleanup @@ -0,0 +1,37 @@ +#!/bin/bash + +# This script keeps cleans up at the end of packageAutoUpdater processing +# It is called as a nohup (no hang up) process so that the main script can terminate cleanly +# Mainly, this script will run SetupHelper's setup script to reinstall itself +# which can't be done from packageAutoUpdater. Doing so would abort the installation prematurely. +# This script also reboots the system if needed. + +# get parameters from command line +gitHubBranch=$1 +updateSetupHelper=$2 +rebootNeeded=$3 + +# get the full, unambiguous path to this script +scriptDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )" +shortScriptName=$(basename "$scriptDir")/$(basename "$0") + +source "/data/SetupHelper/LogHandler" + +# this flag is tested by LogHandler to determine if messages should be output to the console +logToConsole=false + +source "/data/SetupHelper/UpdateResources" + +# give calling script time to stop SetupHelper service +sleep 2 + +if $updateSetupHelper ; then + logMessage "running SetupHelper setup script to reinstall" + doUpdate "SetupHelper" $gitHubBranch +fi + +# reboot now if any script reboots were indicated +#if $rebootNeeded ; then +# logMessage "rebooting ..." +# reboot +#fi diff --git a/packageInstaller b/packageInstaller new file mode 100755 index 0000000..dcdc816 --- /dev/null +++ b/packageInstaller @@ -0,0 +1,150 @@ +#!/bin/bash + +# This script prompts for manual installation of packages from the packageList file +# it is called from SetupHelper/setup or can be run from the command line +# Packages are installed either from GitHub or from a USB stick +# + +source "/data/SetupHelper/CommonResources" +source "/data/SetupHelper/UpdateResources" + +echo +echo "This script allows Venus modification packages to be installed" +echo "Previously installed packages are updated automatically, not from this script" +echo "If updating from USB stick, insert it now before proceeding" +echo +while true ; do + read -p "Select the package source GitHub (g) or USB (u): " response + case $response in + [gG]*) + updateFromUsb=false + break;; + [uU]*) + updateFromUsb=true + break;; + *) + ;; + esac +done + +if $updateFromUsb ; then + # wait up to 20 seconds for USB stick + timeoutCount=20 + /bin/echo -n "Waiting for USB media" + while true ; do + mediaList=($(ls /media)) + if [ ! -z $mediaList ] ; then + break; + elif ((timeoutCount-- == 0)); then + echo + echo "no usb media found - exiting" + exit + else + /bin/echo -n "." + sleep 1 + fi + done + echo +fi + +# allow reinstallations if auto updates are not enabled +autoUpdateSetting=$(dbus-send --system --print-reply=literal --dest=com.victronenergy.settings /Settings/PackageVersion/GitHubAutoUpdate\ + com.victronenergy.BusItem.GetValue 2> /dev/null | awk '{print $3}') +if [ -z $autoUpdateSetting ] || [ $autoUpdateSetting == 0 ]; then + autoUpdatesEnabled=false +else + autoUpdatesEnabled=true +fi + +rebootNeeded=false +updateSetupHelper=false + +# skip all processing if package list doesn't exist +if [ ! -f "$packageListFile" ]; then + echo "no packageList file found" + exit +fi + +# loop through packages from package list file +while read -u 9 package gitHubUser gitHubBranch; do + # skip comments + if [[ ${package:0:1} == "#" ]] ; then + continue + # skip blank/incomplete lines + elif [ -z $package ] || [ -z $gitHubUser ] || [ -z $gitHubBranch ] ; then + continue + fi + + doUpdate=false + packageDir="/data/$package" + + # if automatic updates are enabled, skip packages that are installed + checkOnly=false + if [ -e $packageDir ] && [ -f "$installedFlagPrefix"$package ]; then + installText="reinstall" + # update will be skipped so set a flag so getFromGitHub only checks versions + if ! $updateFromUsb ; then + checkOnly=true + fi + else + installText="install" + fi + + if $updateFromUsb ; then + for dir in ${mediaList[@]} ; do + getFromUsb $package + if [ $? -eq 1 ]; then + echo + yesNoPrompt "$installText $package from USB? (y/n): " + if $yesResponse ; then + doUpdate=true + else + rm -rf "$packageDir-$gitHubBranch" + fi + break; + fi + done + + # check GitHub + else + getFromGitHub $package $checkOnly + if [ $? -eq 1 ]; then + if $autoUpdatesEnabled ; then + echo "$package needs update but will be updated automatically - skipping" + continue + fi + echo + yesNoPrompt "$installText $package from GitHub? (y/n): " + if $yesResponse ; then + doUpdate=true + else + rm -rf "$packageDir-$gitHubBranch" + fi + fi + fi + if $doUpdate ; then + # defer running SetupHelper script + if [ $package == "SetupHelper" ]; then + updateSetupHelper=true + # update the package with user interaction if it is needed to do a full install (not reinstall) + else + doUpdate $package 'prompting' + fi + fi +done 9< "$packageListFile" # end while read ... + +# if an update was found for SetupHelper, run it's setup script now +# this prevents stepping on SetupHelper resources +# (this script will complete even if it's replaced) +if $updateSetupHelper ; then + doUpdate "SetupHelper" 'prompting' +fi +if $rebootNeeded ; then + echo + yesNoPrompt "Reboot needed to complete installation, do it now? (y/n): " + if $yesResponse ; then + echo "rebooting ..." + else + echo "You must reboot manually to complete installation" + fi +fi diff --git a/reinstallMods b/reinstallMods index 4b5fdaf..55f36b0 100755 --- a/reinstallMods +++ b/reinstallMods @@ -11,8 +11,6 @@ setupHelperDir="/data/SetupHelper" source "$setupHelperDir/EssentialResources" source "$scriptDir/LogHandler" -logMessage "reinstallMods starting" - # disable outputting log messages to console runningAtBoot=true @@ -52,6 +50,4 @@ done 9< "$reinstallScriptsList" if $rebootNeeded ; then logMessage "rebooting ..." reboot -else - logMessage "reinstallMods complete" fi diff --git a/removePkgSettings b/removePkgSettings deleted file mode 100755 index 7498a06..0000000 --- a/removePkgSettings +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - - -# remove Settings associated with previous versions of SetupHelper -# and those of the current verison -# run this script to make a clean start - -source /data/SetupHelper/DbusSettingsResources - -removeDbusSettings /Settings/PackageMonitor/Count \ - /Settings/PackageMonitor/GitHubAutoUpdate \ - /Settings/PackageMonitor/GitHubAutoDownload \ - /Settings/PackageMonitor/AutoInstall \ - /Settings/PackageMonitor/Edit/PackageName \ - /Settings/PackageMonitor/Edit/GitHubUser \ - /Settings/PackageMonitor/Edit/GitHubBranch \ - /Settings/PackageMonitor/0/PackageName \ - /Settings/PackageMonitor/0/GitHubUser \ - /Settings/PackageMonitor/0/GitHubBranch \ - /Settings/PackageMonitor/1/PackageName \ - /Settings/PackageMonitor/1/GitHubUser \ - /Settings/PackageMonitor/1/GitHubBranch \ - /Settings/PackageMonitor/2/PackageName \ - /Settings/PackageMonitor/2/GitHubUser \ - /Settings/PackageMonitor/2/GitHubBranch \ - /Settings/PackageMonitor/3/PackageName \ - /Settings/PackageMonitor/3/GitHubUser \ - /Settings/PackageMonitor/3/GitHubBranch \ - /Settings/PackageMonitor/4/PackageName \ - /Settings/PackageMonitor/4/GitHubUser \ - /Settings/PackageMonitor/4/GitHubBranch > /dev/null - -removeDbusSettings /Settings/PackageMonitor/5/PackageName \ - /Settings/PackageMonitor/5/GitHubUser \ - /Settings/PackageMonitor/5/GitHubBranch \ - /Settings/PackageMonitor/6/PackageName \ - /Settings/PackageMonitor/6/GitHubUser \ - /Settings/PackageMonitor/6/GitHubBranch \ - /Settings/PackageMonitor/7/PackageName \ - /Settings/PackageMonitor/7/GitHubUser \ - /Settings/PackageMonitor/7/GitHubBranch \ - /Settings/PackageMonitor/8/PackageName \ - /Settings/PackageMonitor/8/GitHubUser \ - /Settings/PackageMonitor/8/GitHubBranch \ - /Settings/PackageMonitor/9/PackageName \ - /Settings/PackageMonitor/9/GitHubUser \ - /Settings/PackageMonitor/9/GitHubBranch > /dev/null - -removeDbusSettings /Settings/PackageMonitor/10/PackageName \ - /Settings/PackageMonitor/10/GitHubUser \ - /Settings/PackageMonitor/10/GitHubBranch \ - /Settings/PackageMonitor/11/PackageName \ - /Settings/PackageMonitor/11/GitHubUser \ - /Settings/PackageMonitor/11/GitHubBranch \ - /Settings/PackageMonitor/12/PackageName \ - /Settings/PackageMonitor/12/GitHubUser \ - /Settings/PackageMonitor/12/GitHubBranch \ - /Settings/PackageMonitor/13/PackageName \ - /Settings/PackageMonitor/13/GitHubUser \ - /Settings/PackageMonitor/13/GitHubBranch \ - /Settings/PackageMonitor/14/PackageName \ - /Settings/PackageMonitor/14/GitHubUser \ - /Settings/PackageMonitor/14/GitHubBranch > /dev/null - -removeDbusSettings /Settings/PackageVersion/Count \ - /Settings/PackageVersion/New/GitHubUser \ - /Settings/PackageVersion/New/GitHubBranch \ - /Settings/PackageVersion/New/InstalledVersion \ - /Settings/PackageVersion/New/PackageName \ - /Settings/PackageVersion/New/PackageVersion \ - /Settings/PackageVersion/EditPackageIndex \ - /Settings/PackageVersion/GitHubAutoUpdate \ - /Settings/PackageVersion/GitHubAutoUpdate \ - /Settings/PackageVersion/EditAction \ - /Settings/PackageVersion/CheckingPackage \ - /Settings/PackageVersion/0/PackageName \ - /Settings/PackageVersion/0/GitHubUser \ - /Settings/PackageVersion/0/GitHubBranch \ - /Settings/PackageVersion/0/GitHubVersion \ - /Settings/PackageVersion/0/InstalledVersion \ - /Settings/PackageVersion/0/PackageVersion > /dev/null -removeDbusSettings /Settings/PackageVersion/1/PackageName \ - /Settings/PackageVersion/1/GitHubUser \ - /Settings/PackageVersion/1/GitHubBranch \ - /Settings/PackageVersion/1/GitHubVersion \ - /Settings/PackageVersion/1/InstalledVersion \ - /Settings/PackageVersion/1/PackageVersion \ - /Settings/PackageVersion/2/PackageName \ - /Settings/PackageVersion/2/GitHubUser \ - /Settings/PackageVersion/2/GitHubBranch \ - /Settings/PackageVersion/2/GitHubVersion \ - /Settings/PackageVersion/2/InstalledVersion \ - /Settings/PackageVersion/2/PackageVersion \ - /Settings/PackageVersion/3/PackageName \ - /Settings/PackageVersion/3/GitHubUser \ - /Settings/PackageVersion/3/GitHubBranch \ - /Settings/PackageVersion/3/EditAction \ - /Settings/PackageVersion/3/GitHubVersion \ - /Settings/PackageVersion/3/InstalledVersion \ - /Settings/PackageVersion/3/PackageVersion > /dev/null diff --git a/sampleSetupScript b/sampleSetupScript index ecec3f3..ac51b54 100755 --- a/sampleSetupScript +++ b/sampleSetupScript @@ -135,6 +135,10 @@ if [ $scriptAction == 'INSTALL' ] ; then restartAppsNeeded=true fi + # if some processing requires running this script on the next reboot, set the flag then process it later + if $something3 ; then + runAgain=true + fi fi # #### uninstalling - check scriptAction again @@ -155,6 +159,7 @@ if [ $scriptAction == 'UNINSTALL' ] ; then # set exit flags as needed rebootNeeded=false restartAppsNeeded=true + runAgain=false fi diff --git a/service/.DS_Store b/service/.DS_Store index 68347ed..5ab65af 100644 Binary files a/service/.DS_Store and b/service/.DS_Store differ diff --git a/service/log/run b/service/log/run deleted file mode 100755 index 4bc84bd..0000000 --- a/service/log/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -exec multilog t s25000 n4 /var/log/PackageManager - diff --git a/service/run b/service/run index a672d0b..efe8281 100755 --- a/service/run +++ b/service/run @@ -1,4 +1,4 @@ #!/bin/sh -exec 2>&1 -exec /data/SetupHelper/PackageManager.py +#exec 2>&1 +exec /data/SetupHelper/packageAutoUpdater > /dev/null diff --git a/setup b/setup index 3c8f030..710a11c 100755 --- a/setup +++ b/setup @@ -13,25 +13,17 @@ source "/data/SetupHelper/CommonResources" # remove settings no longer used -cleanup () +cleanupDbusSettings () { - # cleanup from older versions - restoreActiveFile "$qmlDir/PageSettingsPackageControl.qml" - dbus-send --system --print-reply=literal --dest=com.victronenergy.settings /Settings/GuiMods/GitHubAutoUpdate\ com.victronenergy.BusItem.GetValue &> /dev/null if (( $? == 0 )); then - removeDbusSettings /Settings/GuiMods/PackageVersions/GeneratorConnector /Settings/GuiMods/PackageVersions/GuiMods\ - /Settings/GuiMods/PackageVersions/GuiMods /Settings/GuiMods/PackageVersions/RpiDisplaySetup\ - /Settings/GuiMods/PackageVersions/RpiGpioSetup /Settings/GuiMods/PackageVersions/RpiTemp\ - /Settings/GuiMods/PackageVersions/SetupHelper /Settings/GuiMods/PackageVersions/TankRepeater,\ - /Settings/GuiMods/PackageVersions/VeCanSetup /Settings/GuiMods/PackageVersions/ShutdownMonitor\ - /Settings/GuiMods/CheckingPackage /Settings/GuiMods/GitHubAutoUpdate > /dev/null - removeDbusSettings /Settings/PackageVersion/Count /Settings/PackageVersion/New/GitHubUser \ - /Settings/PackageVersion/New/GitHubBranch /Settings/PackageVersion/New/InstalledVersion \ - /Settings/PackageVersion/New/PackageName /Settings/PackageVersion/New/PackageVersion \ - /Settings/PackageVersion/GitHubAutoUpdate /Settings/PackageVersion/EditAction \ - /Settings/PackageVersion/CheckingPackage > /dev/null + removeDbusSettings "/Settings/GuiMods/PackageVersions/GeneratorConnector" "/Settings/GuiMods/PackageVersions/GuiMods"\ + "/Settings/GuiMods/PackageVersions/GuiMods" "/Settings/GuiMods/PackageVersions/RpiDisplaySetup"\ + "/Settings/GuiMods/PackageVersions/RpiGpioSetup" "/Settings/GuiMods/PackageVersions/RpiTemp"\ + "/Settings/GuiMods/PackageVersions/SetupHelper" "/Settings/GuiMods/PackageVersions/TankRepeater",\ + "/Settings/GuiMods/PackageVersions/VeCanSetup" "/Settings/GuiMods/PackageVersions/ShutdownMonitor"\ + "/Settings/GuiMods/CheckingPackage" "/Settings/GuiMods/GitHubAutoUpdate" fi } @@ -46,37 +38,165 @@ if [ $scriptAction == 'NONE' ] ; then echo echo "This package provides support functions and utilities for Venus modification packages" echo "Packages are automatically reinstalled following a Venus OS update" - echo "All actions are controlled via /Settings/PackageMonitor in the GUI" - echo " Packages may be automatically updated from GitHub or a USB stick" - echo " Previously uninstalled packages can also be downloaded an installed" + echo "Packages may also be automatically updated from GitHub" + echo " or a USB stick" + echo "Previously uninstalled packages can also be installed and configured" + echo " as an option from the menu either from GitHub or from a USB stick" + echo + echo "If internet access is not available, you can manually update/install from a USB stick or SD card" echo - standardActionPrompt + if [ -f "$setupOptionsDir/optionsSet" ]; then + enableReinstall=true + else + enableReinstall=false + fi + + response='' + fullPrompt=true + while true; do + if $fullPrompt ; then + echo + echo "Available actions:" + echo " Install and activate (i)" + if $enableReinstall ; then + echo " Reinstall (r) based on options provided at last install" + fi + echo " Uninstall (u) and restores all files to stock" + echo " Quit (q) without further action" + echo " Display setup log (s) outputs the last 100 lines of the log" + echo + echo " Enable/disable automatic GitHub package updates (g)" + echo " Manually install packages from GitHub or USB stick (p)" + echo + fullPrompt=false + fi + /bin/echo -n "Choose an action from the list above: " + read response + case $response in + [iI]*) + scriptAction='INSTALL' + break;; + [rR]*) + if $enableReinstall ; then + scriptAction='INSTALL' + break + fi + ;; + [uU]*) + scriptAction='UNINSTALL' + break + ;; + [qQ]*) + exit + ;; + [sS]*) + displayLog $setupLogFile + ;; + [gG]*) + autoUpdate=$(dbus-send --system --print-reply=literal --dest=com.victronenergy.settings\ + /Settings/PackageVersion/GitHubAutoUpdate\ + com.victronenergy.BusItem.GetValue 2> /dev/null | awk '{print $3}') + case $autoUpdate in + 1) + echo "Automatic GitHub updates are at normal rate" + ;; + 2) + echo "Automatic GitHub updates are at fast rate" + ;; + 3) + echo "Automatic GitHub updates will occur only once" + ;; + *) + echo "Automatic GitHub updates are currently disabled" + ;; + esac + echo "Available modes:" + echo " Normal (n) - one package is checked once every 10 minutes" + echo " Fast (f) - one package is checked once every 10 seconds" + echo " Once (o) - each package is checked once then auto updates are turned off" + echo " Disable (d) - auto updates are disabled" + + read -p "Choose a mode from the above list: (cr for no change): " response + if [ ! -z $response ]; then + case $response in + [nN]* | [eE]*) + setSetting 1 /Settings/PackageVersion/GitHubAutoUpdate + ;; + [fF]*) + setSetting 2 /Settings/PackageVersion/GitHubAutoUpdate + ;; + [oO]*) + setSetting 3 /Settings/PackageVersion/GitHubAutoUpdate + ;; + [dD]*) + setSetting 0 /Settings/PackageVersion/GitHubAutoUpdate + ;; + *) + esac + fi + ;; + [pP]*) + "$scriptDir/packageInstaller" + fullPrompt=true + ;; + *) + esac + done fi if [ $scriptAction == 'INSTALL' ] ; then + if [ ! -f "$packageListFile" ]; then + logMessage "installing default packge list" + cp "$scriptDir/defaultPackageList" "$packageListFile" + fi + + # check both parameters to insure they are both created + dbus-send --system --print-reply=literal --dest=com.victronenergy.settings /Settings/PackageVersion/CheckingPackage\ + com.victronenergy.BusItem.GetValue &> /dev/null + if (( $? != 0 )); then + settingsInstalled=false + # first setting exists, check the second + else + dbus-send --system --print-reply=literal --dest=com.victronenergy.settings /Settings/PackageVersion/GitHubAutoUpdate\ + com.victronenergy.BusItem.GetValue &> /dev/null + if (( $? != 0 )); then + settingsInstalled=false + else + settingsInstalled=true + fi + fi + if ! $settingsInstalled ; then + logMessage "creating SetupHelper Settings" + dbus -y com.victronenergy.settings /Settings AddSettings\ + '%[ {"path": "/PackageVersion/GitHubAutoUpdate", "default":0},\ + {"path": "/PackageVersion/CheckingPackage", "default":""},]' > /dev/null + # relocate options and current values + moveSetting "$setupOptionsDir/autoGitHubUpdate" "/Settings/GuiMods/GitHubAutoUpdate" "/Settings/PackageVersion/GitHubAutoUpdate" + rm -f "$setupOptionsDir/autoGitHubUpdate" + moveSetting "" "/Settings/GuiMods/CheckingPackage" "/Settings/PackageVersion/CheckingPackage" + fi + updateActiveFile "$qmlDir/PageSettings.qml" - updateActiveFile "$qmlDir/PageSettingsPackageManager.qml" + updateActiveFile "$qmlDir/PageSettingsPackageControl.qml" updateActiveFile "$qmlDir/PageSettingsPackageVersions.qml" - updateActiveFile "$qmlDir/PageSettingsPackageEdit.qml" updateActiveFile "$qmlDir/MbDisplayPackageVersion.qml" - installService PackageManager + installService $packageName - cleanup + cleanupDbusSettings fi if [ $scriptAction == 'UNINSTALL' ] ; then restoreActiveFile "$qmlDir/PageSettings.qml" - restoreActiveFile "$qmlDir/PageSettingsPackageManager.qml" + restoreActiveFile "$qmlDir/PageSettingsPackageControl.qml" restoreActiveFile "$qmlDir/PageSettingsPackageVersions.qml" - restoreActiveFile "$qmlDir/PageSettingsPackageEdit.qml" restoreActiveFile "$qmlDir/MbDisplayPackageVersion.qml" - removeService PackageManager + removeService $packageName - cleanup + cleanupDbusSettings fi if $filesUpdated ; then diff --git a/updatePackageVersions b/updatePackageVersions new file mode 100755 index 0000000..0e6d2a3 --- /dev/null +++ b/updatePackageVersions @@ -0,0 +1,119 @@ +#!/bin/bash + +# This script keeps creates a list of installed package versions +# and updates dBus /Settings/PackageVersion/... +# +# this script should be called whenever a package is installed or uninstalled +# this is part of endScript () if used +# it can take time, so launch it as a background task: +# +# nohup /data/updatePackageVersions > /dev/null & +# +# dbus access is slow, so this script builds a file with the known values +# only changes are sent to dbus + +source "/data/SetupHelper/EssentialResources" +source "/data/SetupHelper/LogHandler" +source "/data/SetupHelper/DbusSettingsResources" + +versionsFile="/data/packageVersions" + +# this flag is tested by LogHandler to determine if messages should be output to the console +logToConsole=false + +# wait until dbus settings are active +while [ $(dbus -y | grep -c "com.victronenergy.settings") == 0 ]; do + logMessage "waiting for dBus settings" + sleep 1 +done + +newNames=() +newVersions=() +oldNames=() +oldVersions=() +# read in old values +if [ -f "$versionsFile" ]; then + index=0 + while read -u 8 oldPackage oldVersion; do + # skip comments + if [[ ${oldPackage:0:1} == "#" ]] ; then + continue + # skip blank/incomplete lines + elif [ -z $oldPackage ] ; then + continue + fi + + oldNames[$index]=$oldPackage + oldVersions[$index]=$oldVersion + ((index++)) + done 8< "$versionsFile" +fi +oldCount=${#oldNames[@]} + +# loop through packages from package list +index=0 +echo "# AUTO GENERATED DO NOT MODIFY" > "$versionsFile" +while read -u 9 package gitHubUser gitHubBranch; do + + # skip comments + if [[ ${package:0:1} == "#" ]] ; then + continue + # skip blank/incomplete lines + elif [ -z $package ] ; then + continue + fi + # skip uninstalled packages + if [ ! -f "$installedFlagPrefix"$package ]; then + continue + fi + + newNames[$index]=$package + if [ -f "/data/$package/version" ]; then + packageVersion=$(cat "/data/$package/version") + else + packageVersion="?" + fi + newVersions[$index]=$packageVersion + + # save in file for later comparison + echo $package $packageVersion >> "$versionsFile" + + ((index++)) + +done 9< "$packageListFile" +newCount=${#newNames[@]} + +if (( $oldCount > $newCount )); then + minCount=$newCount + maxCount=$oldCount +else + minCount=$oldCount + maxCount=$newCount +fi + +# compare new and old and update dbus Settings as needed +index=0 +while ((index < newCount )); do + if (( index > oldCount )); then + updateDbusStringSetting "/Settings/PackageVersion/$index/PackageName" ${newNames[$index]} + updateDbusStringSetting "/Settings/PackageVersion/$index/PackageVersion" ${newVersions[$index]} + else + if [ "${newNames[$index]}" != "${oldNames[$index]}" ]; then + updateDbusStringSetting "/Settings/PackageVersion/$index/PackageName" ${newNames[$index]} + fi + if [ "${newVersions[$index]}" != "${oldVersions[$index]}" ]; then + updateDbusStringSetting "/Settings/PackageVersion/$index/PackageVersion" ${newVersions[$index]} + fi + fi + ((index++)) +done + +index=$newCount +while (( $index < $oldCount )); do + removeDbusSettings "/Settings/PackageVersion/$index/PackageName"\ + "/Settings/PackageVersion/$index/PackageVersion" + ((index++)) +done + +updateDbusIntSetting "/Settings/PackageVersion/Count" $newCount + diff --git a/venus-data.tgz b/venus-data.tgz index a311ce4..86845de 100644 Binary files a/venus-data.tgz and b/venus-data.tgz differ diff --git a/version b/version index ee26037..588b5b7 100644 --- a/version +++ b/version @@ -1 +1 @@ -v4.0~6 +v3.13