pyqt application performing updates

拈花ヽ惹草 提交于 2019-12-21 15:45:31

问题


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.


回答1:


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.




回答2:


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)[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.

    3. 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.

    4. 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.

Hope this helps someone.




回答3:


This is why so many companies install seperate update service apps on your computer. Adobe do it, Google does it, it seems like everyone is doing it. One way to avoid that is to have you app started by a 'launcher' app that first checks for updates to the main app, if there's no update it launches the main app, but if there is an update it applies it first and then it launches the main app.

Since you're using pyqt, another thing you can do is provide some of the functionality of your app in python script files your app loads dynamically. This is fairly easy to do with py2exe. Treat the python script files as bundled data files as far as py2exe is concerned ,in a 'scripts' or 'plugins' folder, and import them from the folder at runtime. Your app can then check for updated versions, download them and update the scripts before loading them.



来源:https://stackoverflow.com/questions/16549331/pyqt-application-performing-updates

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!