8

我有一个PyQt准备发布的应用程序。一切都很好,我只有一件事要结束。我喜欢自我更新的软件:

  • 检查新版本的网址;
  • 发现新版本;
  • 通知用户更新(单击)→更新

问题是我不知道如何执行此更新。我检查,找到新版本,下载它,然后我必须关闭应用程序并执行新版本的安装程序。如果我关闭它,我将无法执行任何其他操作,如果我执行安装程序,我将无法关闭应用程序。

根据一些用户的选择,我的程序还下载并安装了一些需要相同内容的第三方软件:安装程序前关闭,安装程序后重新启动。

4

3 回答 3

7

下载较新版本的安装程序后,您可以使用atexit.register()withos.exec*()来运行安装程序,例如atexit.register(os.execl, "installer.exe", "installer.exe"). 这将使安装程序在应用程序即将退出时启动。调用后应用程序将立即退出os.exec*(),因此不会发生竞争条件。

于 2013-11-13T09:36:50.357 回答
5

我喜欢接受的答案,但是我有两个建议给你,你可能会考虑使用。有很多文字需要消化,但我希望它会很有趣,而且——最重要的是——有帮助。我要写的基本上是两种更新软件的方法,纯粹基于观察有多少其他应用程序在工作。这两种情况都需要重新启动应用程序

  • 在仍在运行时更换组件- 一旦启动,应用程序就会加载到计算机的内存中并驻留在那里。除非它必须以某种方式与文件系统一起工作(例如打开并更改某些配置文件),否则应该能够在应用程序仍在运行时替换文件。在 Unix/Linux 平台上,就可以更改和不可以更改的内容而言,事情要严格一些。通常在 Unix/Linux 中,除非您取消链接,否则无法更改正在运行的可执行文件(出于安全原因)(请参见此处)。我已经做过几次了,这太重要了。如果您不更新可执行文件,您甚至可以避免所有这些,只需替换其余文件(配置文件、库等)而不会出现任何问题。更新完成后,您可以提示用户重新启动应用程序,以便将新内容加载到内存中。我不确定这是否可能,但也许 Qt 插件基础设施允许在主应用程序仍在运行时添加新组件(没有编写很多 Qt 插件,所以我不知道)。您可以使用接受的答案所描述的事情来重新启动,或者继续阅读并应用第二种更新方法的部分内容。

  • 通过使用专用于更新主应用程序的外部进程进行更新——Qt 有一个不错的基础设施来管理进程。如果你不喜欢它,你总是可以回退到 Python,它也提供了一种非常相似的做事方式。基本上,我们可以将场景的进程类型缩小到两种类型-附加(称为子进程)和分离(称为独立)。在您的情况下,我们可以排除第一种情况,因为 - 正如术语“子进程”可能告诉您的那样 - 一旦主进程退出,所有子进程也退出。我们不希望那样。我们想要的是一个分离的过程(你会明白为什么)。分离进程的问题在于这些是......好吧......分离的。这意味着分离的进程必须能够自行终止,或者(如果需要)您需要恢复对它的控制并自行执行此操作。否则,该过程将继续驻留在您的记忆中,这可能不是我们想要的。在您的情况下,外部进程将是您的更新程序(再次用 PyQt、一些 shell 脚本或任何其他允许生成分离进程的东西编写)。这是你可以做的(我自己做过,而且更重要的是它就像一个魅力):

    1. PyQt 应用程序已完成下载文件夹 X 中的更新(位置应与更新程序应用程序查找新文件的位置一致)

    2. 产生一个分离的进程

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

      工作方式startDetached()是,如果成功启动,它会返回启动的外部进程的 PID。对于我当前正在编写的应用程序,我实际上需要 PID(为了恢复对生成进程的控制,以防我的 PyQt 应用程序死在我身上)所以我将它存储在一个文本文件中,一旦我的主应用程序再次启动,该文件就会被读取(在崩溃或正常退出后)。这是我的场景的要求,因为即使 UI 崩溃,生成的进程也必须继续运行,并且 UI 只需恢复到崩溃前的状态(包括控制生成的进程的 UI 控制实体)。您的更新程序不需要任何这些,因此您只需检查是否res == TrueTrue在进程成功启动的情况下返回)。然而您可能想要存储应用程序本身调用的 PID

      `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. 更新程序更新您的应用程序的文件- 更新程序现在正在愉快地运行。使用我们应用程序存储的 PID,它还确保应用程序的进程不再运行。是时候施展魔法了。此时,您可以做任何您想做的事情。当然请记住,您可能需要访问权限才能更改文件。如果该过程尚未以适当的权限开始,它将无法做任何事情。确保这个部门一切顺利。您可以生成具有提升权限的进程。这可能需要输入一些密码,在这种情况下您也必须处理。

    2. 更新程序已完成更新您的应用程序的文件- 在所有必需的文件都已更改后,我们不再需要更新程序,我们还想再次启动应用程序。如果重新启动应用程序不在菜单上,您也可以跳过此步骤。请记住,尽管许多应用程序确实在更新时提供自动重启,因为它增加了用户体验——用户不必再次手动启动应用程序。您可以将其设为可选(甚至更好),这绝对更灵活。如果需要重新启动,您基本上可以执行与从应用程序启动更新程序完全相同的过程,但这次您以相反的方式执行 - 您将应用程序作为更新程序内的分离进程表单启动,然后简单地退出更新程序。

尽管这是很多文本,但要阅读实际实现(尤其是第二个)并不困难。

由于其依赖于平台的性质,我没有提到的第三个选项是服务。在 Linux、MacOS、Windows 等上,您有服务。您可以为您的应用程序创建一个更新服务(例如,Java 的更新服务,它会定期检查新的更新并在发现更新后提示您)。这也与 Qt 无关,除非您的服务以某种方式使用 Qt。

希望这可以帮助某人。

于 2016-01-19T16:26:52.637 回答
1

这就是为什么这么多公司在您的计算机上安装单独的更新服务应用程序的原因。Adobe 做,Google 做,似乎每个人都在做。避免这种情况的一种方法是让您的应用程序由“启动器”应用程序启动,该应用程序首先检查主应用程序的更新,如果没有更新,它会启动主应用程序,但如果有更新,它会先应用它然后再应用它启动主应用程序。

由于您使用的是 pyqt,您可以做的另一件事是在您的应用程序动态加载的 python 脚本文件中提供您的应用程序的一些功能。使用 py2exe 很容易做到这一点。就 py2exe 而言,将 python 脚本文件视为捆绑的数据文件,位于“脚本”或“插件”文件夹中,并在运行时从该文件夹中导入它们。然后,您的应用可以检查更新版本、下载它们并在加载脚本之前更新它们。

于 2013-11-11T17:33:59.450 回答