Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pyqt application performing updates

Tags:

updates

pyqt

I have a PyQt application ready to release. Everything works pretty well and I only have one more thing to wrap up. I love software that updates itself:

  • check url for new version;
  • new version found;
  • notify user of updates (click) → update.

The problem is that I don't know how to perform this update. I check, I find new version, I download it and then I must close the application and execute the installer of the new version. If I close it then I can't execute anything else, If I execute the installer I can't close the application.

Based on some user choices my program also downloads and installs some third party software which needs the same thing: close before install program, restart after install program.

like image 304
Romeo Mihalcea Avatar asked May 14 '13 17:05

Romeo Mihalcea


People also ask

Is PyQt good for desktop application?

Compared to Electron and JavaScript, PyQt5 might not be the popular tool to build desktop apps, but it's very effective. Web apps are very popular, but there are still times when only a desktop app can deliver a great user experience.

Is PyQt multithreaded?

Multithreading in PyQt With QThread. Qt, and therefore PyQt, provides its own infrastructure to create multithreaded applications using QThread . PyQt applications can have two different kinds of threads: Main thread.

Is PyQt good for GUI?

PyQt5 is a very well-known GUI framework used by both Python coders and UI designers. One of its components, the PyQt package, is built around the Qt framework, which is a leading cross-platform GUI design tool for just about any kind of application.

Is PyQt6 better than PyQt5?

As we've discovered, there are no major differences between PyQt5 and PyQt6. The changes that are there can be easily worked around. If you are new to Python GUI programming with Qt you may find it easier to start with PyQt5 still, but for any new project I'd suggest starting with PyQt6.


2 Answers

After downloading the installer for the newer version, you can use atexit.register() with os.exec*() to run the installer, e.g. atexit.register(os.execl, "installer.exe", "installer.exe"). This will make the installer start when the application is about to exit. The application will immediately exit after the os.exec*() call, so no race condition will occur.

like image 160
Ramchandra Apte Avatar answered Oct 11 '22 08:10

Ramchandra Apte


I like the accepted answer however I have two suggestions for you that you might want to consider using. It's a lot of text to digest but I hope it will be interesting and - most importantly - helpful. What I am about to write about is basically two ways of updating software purely based on observations how many other applications out there work. Both cases require restart of the application

  • Replacing components while still running - once started an application is loaded into the memory of the computer and resides there. Unless it has to work with the filesystem in some way (for example open and change some configuration file) one should be able to replace the files while the application is still running. On Unix/Linux platforms things are a little bit tighter in terms of what you can change and what not. Generally in Unix/Linux an executable that is running cannot be changed (for security reasons) unless you unlink it (see here). I've done it a couple of times and it's too much of a big deal. If you do not update the executable you can avoid even all that and simply replace the rest of the files (configuration files, libraries etc.) without any issues. After the update is completed you can prompt the user to restart the application in order for the new content to be loaded into memory. I'm not sure whether it is possible or not but maybe the Qt plugin infrastructure allows adding new components while the main application is still running (haven't written many Qt plugins so I don't know). The restart you can do using the things described by the accepted answer or continue reading and apply parts of the second method of doing an update.

  • Update by using an external process dedicated to updating the main application - Qt has a decent infrastructure for managing processes. In case you don't like it you can always fall back to Python, which also provides a very similar way of doing things. Basically we can narrow down the types of processes to two types for our scenario - attached (referred to as child processes) and detached (referred to as standalone). In your case we can exclude the first case since - as the term "child process" probably tells you - once the main process exits all children exit too. We don't want that. What we want is a detached process (you will see in a bit why). The problem with detached processes is that these are...well..detached. This means that the detached process has to be able to die on its own or (if required) you need to restore control over it and do that by yourself. Otherwise the process will continue residing in your memory which is probably not what we want. In your case the external process will be your updater (written again in PyQt, some shell script or anything else that allows spawning detached processes). Here is what you can do (I have done it myself and even more on top of that and it works like a charm):

    1. PyQt application has finished downloading the updates in folder X (location should be consistent with where the updater application will be looking for the new files)

    2. Spawn a detached process with

      res, pid = QtCore.QProcess.startDetached('YOUR_EXTERNAL_UPDATING_PROGRAM')
      

      The way startDetached() works is that it returns a PID of the started external process if it was successfully launched. For the application I'm currently writing I actually need the PID (in order to restore the control over the spawned process in case my PyQt application dies on me) so I store it in a text file that is read once my main application launches again (after a crash or normal exit). This is a requirement for my scenario since the spawned processes HAVE to keep running even if the UI crashes and the UI simply has to be restored to its stated before the crash (including UI control entities that control the spawned processes). Your updater doesn't need any of that so you can just check whether res == True or not (True is returned in case the process was successfully started). However you might want to store the PID the application itself calling

      `QtCore.QCoreApplication.applicationPid()`
      
    You may ask why? Well, because you want to know WHEN your application is no longer running and THEN start the updater (this is a solid insurance that no conflicts will occur when the updater overwrites/remove/renames the application's files including the executable). The easiest but very unreliable way of checking that is to write your updater in such a way that it *waits* for a specific time before starting to tinker with the application's files. The big problem here is that it is not easy to predict how long your application will require to quit and for its data to be flushed from the system's memory. So the other way (there are others but not very reliable) is to store the PID of the application you want to update. Once the updater process has started it will just run a check (in a simple `while` loop) whether the process with PID == 1234 (for example) is still running or not. For that you have plenty of tools including those provided by your platform (see (here)[https://stackoverflow.com/questions/3043978/bash-how-to-check-if-a-process-id-pid-exists] for an example using a shell command). Once the updater makes sure that your application is not running (if the OS lies about it there is nothing we can do about it ;)) it can exit the loop and start the actual update procedure. At this point we can notify the user with a dialog window such as "Your application needs to restart in order to complete an update? [yes]/[no]". If the user choses NO, we can kill the updater process that is running in the background. Otherwise we can quit the application and let the updater do its thing.
    
    1. Updater updates the files of your application - the updater is now happily running. Using the stored PID of our application it has also made sure that the application's process is no longer running. Time to do the magic. At this point you can do whatever you want to. Of course bare in mind that you may need access rights to change files. If the process hasn't been started with the appropriate rights it will not be able to do a thing. Make sure all is good in this department. You can spawn a processes with elevated privileges. This may require entering some password in which case you have to handle this too.

    2. Updater has finished updating the files of your application - after all required files have been changed we no longer need the updater AND we also want to start the application again. You can also skip this step if restarting of the application is not on the menu. Bare in mind though that many applications do offer automatic restart upon update because it adds to the user experience - the user doesn't have to manually launch the application again. You can make it optional (even better) which is definitely more flexible. If restarts is required you can basically do the exact same procedure you used to start the updater from your application but this time you do it the other way around - you start your application as a detached process form inside the updater and simply quit the updater.

Even though this is a lot of text to read the actual implementation (especially of the second one) is not difficult.

One third option that I didn't mention due to its platform-dependent nature - is services. On Linux, MacOS, Windows etc. you have services. You can create an updater service for your application (e.g. Java's Updater service that regularly checks for new updates and prompts you once such has been found). Also this has nothing to do with Qt unless your service employs Qt in some way.

Hope this helps someone.

like image 30
rbaleksandar Avatar answered Oct 11 '22 08:10

rbaleksandar