AppImage - Include auto-updating logic in to appimage directly?

Have some feature requests, feedback, cool stuff to share, or want to know where FreeCAD is going? This is the place.
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
User avatar
antonyjr
Posts: 45
Joined: Sat Mar 21, 2020 9:35 pm
Contact:

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by antonyjr »

@looo I found the bug, It is specific to FreeCAD's console. It seems that print function does not work with functions invoked by Qt event loop. I think this only happens in FreeCAD's console and normal Python console seems to work good.

To prove my theory try executing this in your FreeCAD console, also calling the start method of the updater will start the update but you don't know that it is updating(i.e no log but it is doing it's job). See the AppImage's directory you should have a .part file when you start the update.

Code: Select all

from PySide.QtCore import QTimer

def printSomething():
	print("Hello World")
	
QTimer.singleShot(1000, printSomething)

# You cannot see anything even after 1 second

# Now Try this

Test = '' # Empty string
def handleTimeout():
	global Test
	Test = 'This was called'

QTimer.singleShot(1000, handleTimeout)

# After 1 second
print(Test) # This should print 'This was called'
I tried everything but I cannot find a way to print from functions executed via a Qt signal. I suspect that the print function only works when executed from the main thread all other print are simply not working, but this only exists in the FreeCAD's console.

Try executing this to see what's the updater is doing if you are using FreeCAD's console.

Code: Select all

import sys
import os
import time
from PySide2.QtCore import QCoreApplication,QObject,QTimer,QPluginLoader

messages = []

def handleLog(msg, appimage):
    global messages
    messages.append(msg)

path = os.path.join(os.environ['PREFIX'], "lib", "libAppImageUpdaterBridge.so")
loader = QPluginLoader()
loader.setFileName(path)
loader.load()

appimageupdaterbridge = loader.instance()

appimageupdaterbridge.setAppImage(os.environ['APPIMAGE'])
appimageupdaterbridge.logger.connect(handleLog)

# Now call any method
appimageupdaterbridge.start()

# Now after sometime, Try printing the messages list
print(messages) # Do not put this in a loop, this will block the main thread.
User avatar
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by looo »

antonyjr wrote: Sat May 23, 2020 4:01 pm @looo I found the bug, It is specific to FreeCAD's console. It seems that print function does not work with functions invoked by Qt event loop. I think this only happens in FreeCAD's console and normal Python console seems to work good.

To prove my theory try executing this in your FreeCAD console, also calling the start method of the updater will start the update but you don't know that it is updating(i.e no log but it is doing it's job). See the AppImage's directory you should have a .part file when you start the update.

Nice, this worked for me now. I started with a simple gui:

Code: Select all

import sys
import os
import time
from PySide import QtGui, QtCore

class FCAppImageUpdateBridge(object):
    def __init__(self, update_foo=None, progress_foo=None, logging_foo=None, finished_foo=None):
        self.logging_foo = logging_foo
        self.loader = QtCore.QPluginLoader()
        self.loader.setFileName("libAppImageUpdaterBridge")
        self.loaded = self.loader.load()
        self.updater = self.loader.instance()
        if update_foo:
            self.updater.updateAvailable.connect(update_foo)
        if logging_foo:
            self.logging_foo("loading the plugin: {}".format(str(self.loaded)), None)
            self.updater.logger.connect(logging_foo)
        if progress_foo:
            self.updater.progress.connect(progress_foo)
        if finished_foo:
            self.updater.finished.connect(finished_foo)
        self.updater.setAppImage(os.environ['APPIMAGE'])
        self.updater.checkForUpdate()


class AppImageUpdaterDialog(QtGui.QDialog):
    def __init__(self, parent=None, callback=None):
        super(AppImageUpdaterDialog, self).__init__(parent=parent)
        self.setWindowTitle("AppImage-Updater-Bridge")
        self.setLayout(QtGui.QVBoxLayout())

        self.log = QtGui.QTextEdit()
        self.log.setReadOnly(True)

        self.update_button = QtGui.QPushButton("check for update")
        self.reject_button = QtGui.QPushButton("Cancel")
        self.button_widget = QtGui.QWidget()
        self.button_widget.setLayout(QtGui.QHBoxLayout())
        self.button_widget.layout().addWidget(self.update_button)
        self.button_widget.layout().addWidget(self.reject_button)
        self.update_button.clicked.connect(self.update)
        self.reject_button.clicked.connect(self.reject)
        self.update_button.setEnabled(False)

        self.progress_bar = QtGui.QProgressBar()

        self.layout().addWidget(self.log)
        self.layout().addWidget(self.progress_bar)
        self.layout().addWidget(self.button_widget)

        self.appimage_update_bridge = FCAppImageUpdateBridge(self.update_foo, 
                                                             self.progress_foo,
                                                             self.logging_foo,
                                                             self.finished_foo)

    def sizeHint(self, *args):
        return QtCore.QSize(1000, 500)

    def update(self):
        self.logging_foo("start with update process")
        self.update_button.setEnabled(False)
        self.appimage_update_bridge.updater.start()

    def progress_foo(self, *args):
        self.progress_bar.setValue(args[0])

    def logging_foo(self, msg, appimage=None):
        self.log.moveCursor(QtGui.QTextCursor.End)
        self.log.insertPlainText(msg)
        self.log.insertPlainText("\n")

    def update_foo(self, available, update_info):
        if available:
            self.update_button.setEnabled(True)
            self.update_button.setText("update")

    def reject(self, *args):
        self.appimage_update_bridge.updater.cancel()
        self.appimage_update_bridge.updater.clear()
        super(AppImageUpdaterDialog, self).reject(*args)

    def finished_foo(self, new, old):
        self.new_file_name = new["AbsolutePath"]
        self.update_button.setText("restart")
        self.update_button.clicked.disconnect()
        self.update_button.setEnabled(True)
        self.update_button.clicked.connect(self.restart)

    def restart(self):
        import FreeCADGui as Gui
        closing = Gui.getMainWindow().close()
        if closing:
            os.system(self.new_file_name);

updater = AppImageUpdaterDialog()
updater.show()
There are still some troubles:
- we need a signal when the update process has finished
- we need to abort the update process somehow
- we need the name of the ew appimage to add the possibility of restarting freecad to the new version
Bildschirmfoto von 2020-05-27 16-28-08.png
Bildschirmfoto von 2020-05-27 16-28-08.png (252.9 KiB) Viewed 1981 times
edit.: Thanks for the good documentation: https://antony-jr.github.io/AppImageUpd ... rface.html
I think most should work. If anyone is able to try and give feedback about the dialog handling, please go ahead.
User avatar
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by looo »

The script is now available from here:
https://github.com/looooo/freecad.appim ... /update.py

This way we can pacage the script and include it easily in the appimage. But there are still some questions:

- Should the script work in no-gui mode? As qapplication must be available I think this is no option
- Should it be possible to update from the freecad-python-console?
- How to integrate the dialog in the freecad-gui? @triplus
User avatar
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by looo »

Is there any way to connect a button in the preferences with a python function? I didn't find any example.
Bildschirmfoto von 2020-05-28 11-52-31.png
Bildschirmfoto von 2020-05-28 11-52-31.png (71.35 KiB) Viewed 1881 times
Syres
Veteran
Posts: 2902
Joined: Thu Aug 09, 2018 11:14 am

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by Syres »

looo wrote: Thu May 28, 2020 11:09 am Is there any way to connect a button in the preferences with a python function? I didn't find any example.
Is the CfdOF Wb Preferences > Run Dependency Checker what you're looking for as an example? https://github.com/jaheyns/CfdOF


CfdOF_Preferences2.jpg
CfdOF_Preferences2.jpg (85.28 KiB) Viewed 1870 times
User avatar
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by looo »

thanks a lot! reference

So this is now included in the appimage. Using the tool is as simple as:
1. Edit->Preferences->AppImage:
2. Press "Check for update" and wait
3.a if no update is available the update button will not be enabled -> cancel
3.b if update is available, press update to update the appimage
4.b once the new appimage was created press the restart button to start the new appimage.

If the "always check for updates at startup" box is checked, startup will be a bit slower and a dialog will pop up once a new appimage is available.

I hope it's clear how it works. If there are any suggestions please give feedback or directly send PR: https://github.com/looooo/freecad.appimage_updater
triplus
Veteran
Posts: 9471
Joined: Mon Dec 12, 2011 4:45 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by triplus »

Hi @looo

I see you have solved the "adding to the interface" part already, by adding the dialog to the Preferences. For now i feel that is the most elegant solution. Around June you should be able to add an entry to the Accessories menu in some straightforward fashion. Most of my free time is currently directed towards achieving that goal (IconThemes).

P.S. As for the work you are doing. It looks good to me but currently i don't have much time for testing. Will for now leave the testing part to people that expressed desire, to have a built in AppImage updater tool.
User avatar
adrianinsaval
Veteran
Posts: 5553
Joined: Thu Apr 05, 2018 5:15 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by adrianinsaval »

I get this message when checking for updates:

Code: Select all

loading the plugin: True
   INFO:   setAppImage :  "/run/user/1000/appimagelauncherfs/0002.AppImage" . 
  FATAL:   getInfo : invalid magic bytes( 127 , 69 ). 
OS: Manjaro Linux (KDE//usr/share/xsessions/plasma)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.21329 (Git) AppImage
Build type: Release
Branch: master
Hash: 2330eef823b32ac412d839031cc174353a76b013
Python version: 3.8.2
Qt version: 5.12.5
Coin version: 4.0.0
OCC version: 7.4.0
Locale: Spanish/Paraguay (es_PY)
TheMarkster
Veteran
Posts: 5513
Joined: Thu Apr 05, 2018 1:53 am

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by TheMarkster »

It looks like it's working, but as I only just downloaded the latest AppImage there is yet no update available. I get these messages:

Code: Select all

loading the plugin: True
   INFO:   setAppImage :  "/home/mwganson/Downloads/FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage" . 
   INFO:   getInfo : AppImage is confirmed to be type 2. 
   INFO:   getInfo : mapping AppImage to memory. 
   INFO:   getInfo : AppImage architecture is x86_64 (64 bits). 
   INFO:   getInfo : updateString( "gh-releases-zsync|FreeCAD|FreeCAD|0.19_pre|FreeCAD*glibc2.12-x86_64.AppImage.zsync" ). 
   INFO:   getInfo : finished. 
   INFO:  setControlFileUrl : using github releases zsync transport. 
   INFO:  setControlFileUrl : github api request( QUrl("https://api.github.com/repos/FreeCAD/FreeCAD/releases/tags/0.19_pre") ). 
   INFO:   handleGithubAPIResponse : starting to parse github api response. 
   INFO:   handleGithubAPIResponse : http response code( 200 ). 
   INFO:  handleGithubAPIResponse : latest version is  "0.19_pre" 
   INFO:  handleGithubAPIResponse : asset required is  "FreeCAD*glibc2.12-x86_64.AppImage.zsync" 
   INFO:  handleGithubAPIResponse : inspecting asset( "FreeCADLibs_12.1.4_x64_VC15.7z" 
   INFO:  handleGithubAPIResponse : inspecting asset( "FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage" 
   INFO:  handleGithubAPIResponse : inspecting asset( "FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage-SHA256.txt" 
   INFO:  handleGithubAPIResponse : inspecting asset( "FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage.zsync" 
   INFO:   setControlFileUrl : using  QUrl("https://github.com/FreeCAD/FreeCAD/releases/download/0.19_pre/FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage.zsync")  as zsync control file. 
   INFO:   handleGithubMarkdownParsed : starting to parse github api response. 
   INFO:   handleGithubMarkdownParsed : http response code( 200 ). 
   INFO:   getControlFile : sending get request to  QUrl("https://github.com/FreeCAD/FreeCAD/releases/download/0.19_pre/FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage.zsync") . 
   INFO:   handleControlFile : starting to parse zsync control file. 
   INFO:   handleControlFile : http response code( 200 ). 
   INFO:   handleControlFile : searching for checksum blocks offset in the zsync control file. 
   INFO:   handleControlFile : found checksum blocks offset( 284 ) in zsync control file. 
   INFO:   handleControlFile : zsync make version confirmed to be  "0.6.2" . 
   INFO:   handleControlFile : zsync target file name confirmed to be  "FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage" . 
   INFO:   handleControlFile : zsync target file MTime confirmed to be  QDateTime(2020-06-05 21:14:04.000 CDT Qt::LocalTime) . 
   INFO:   handleControlFile : zsync target file blocksize confirmed to be  4096  bytes. 
   INFO:   handleControlFile : zysnc target file length confirmed to be  768589864  bytes. 
   INFO:   handleControlFile :  3  bytes of weak checksum is available. 
   INFO:   handleControlFile :  5  bytes of strong checksum is available. 
   INFO:   handleControlFile :  2  consecutive matches is needed. 
   INFO:   handleControlFile : zsync target file url is confirmed to be  QUrl("FreeCAD_0.19-21329-Linux-Conda_glibc2.12-x86_64.AppImage") . 
   INFO:   handleControlFile : zsync target file sha1 hash is confirmed to be  "F6EAED30AE6DA66A158107DCB93CD8AC891E3798" . 
   INFO:   handleControlFile : zsync target file has  187645  number of blocks. 
OS: Ubuntu 18.04.4 LTS (LXDE/LXDE)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.21329 (Git) AppImage
Build type: Release
Branch: master
Hash: 2330eef823b32ac412d839031cc174353a76b013
Python version: 3.8.2
Qt version: 5.12.5
Coin version: 4.0.0
OCC version: 7.4.0
Locale: English/United States (en_US)

This is running inside a VirtualBox VM on Windows 10.

Edit: Suggestion, perhaps there should be some message to the user that the system is already up to date. Great work, by the way.
User avatar
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: AppImage - Include auto-updating logic in to appimage directly?

Post by looo »

TheMarkster wrote: Sat Jun 06, 2020 6:24 pm Edit: Suggestion, perhaps there should be some message to the user that the system is already up to date. Great work, by the way.
Thanks for testing and the feedback. I added a message at the end of the log if no new appimage is available. Should be included in the next available appimage.
Post Reply