Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Mettre à jour à distance son programme

+8 votes

Disons que j'ai un programme que je commence à distribuer. Pour l'instant, le cercle de personnes l'utilisant et plutôt restreint, donc je fais juste un executable avec py2exe, que je distribue avec une clé usb.

Mais sous peu, je vais le distribuer à grande échelle. Donc je me pose la question, comment font les logiciels comme firefox ou autre pour se mettre à jour ? Quel est le concept ? Je parle de windows ou mac, quand les mises à jour des logiciels ne sont pas gérées par un gestionnaire de paquet.

Parce que c'est facile (quand on sait faire) de pull un repo github, mais pour les autres ?

demandé 28-Mar-2015 par Rififi (500 points)

Si sa réponse te conviens, clic pour l'accepter, ça lui donne des ponits.

1 Réponse

+9 votes
 
Meilleure réponse

Il semble qu'Eksy permette de faire ce que tu demandes. Plus généralement, si tu ne souhaite pas passer par une librairie tierce, je pense que le workflow pour faire réaliser un programme qui se met à jour seul implique de créer un petit programme à côté chargé de réaliser les mises à jour :

  1. Au lancement du programme, faire une requête (par exemple via HTTP) sur un serveur qui va donner le numéro de la dernière version du programme
  2. Si le version lancée est plus ancienne que la dernière version, on lance le programme de mise à jour
  3. Le programme de mise à jour télécharge la version la plus récente
  4. Le programme de mise à jour copie les fichiers de la nouvelle version à la place de ceux de l'actuelle

Note que si ton programme est trop gros, télécharger l'intégralité des fichiers à chaque nouvelle mise à jour peut ne pas être envisageable. Mais pour un petit exe, je pense que ça doit marcher assez facilement.

Par contre, attention à bien séparer le programme à mettre à jour des données utilisateurs, pour ne pas les écraser lors de la mise à jour.

Tu trouveras peut-être d'autres idées sur ce post StackOverflow.

Edit : code d'exemple

Voici un code d'exemple qui pourra te mettre sur la voie. Il est écrit pour Python 3 mais devrait marcher pour Python 2 en quelques modifications au niveau des imports du module urllib.

Tu peux copier ce code dans un fichier et le lancer via python3 launcher.py. Par manque de temps, j'ai basé l'exemple sur l'API GitHub, mais rien n'empèche d'héberger ça ailleurs et de faire pointer le script sur une URL arbitraire, tant qu'elle retourne des données relatives à la dernière version (mon exemple n'a besoin que de l'URL de téléchargement de l'exe et d'un numéro de version).

Tout se passe dans la fonction check_new_version:

  1. récupération du numéro de version actuelle stocké dans un fichier VERSION. Si le fichier n'existe pas, on part du principe que l'exe n'est pas installé donc on le télécharge
  2. récupération des informations (numéro de version et URL de téléchargement de l'exe) sur la dernière release en faisant un appel à l'API GitHub mentionnée par @Arza
  3. On compare le numéro de version récupéré avec celui stocké dans le fichier VERSION. S'ils diffèrent, on télécharge la dernière version
  4. En cas de téléchargement, on remplace le contenu du fichier VERSION (en le créant si nécessaire) par le numéro de la version téléchargée
  5. Enfin, on lance le programme. Mon programme fait juste un print sur ce point, mais tu décommenter la ligne suivante pour lancer l'exe.

Le code:

import os
import urllib.request
import json


API_URL = 'https://api.github.com/repos/Arzaroth/CelestiaSunrise/releases'
APP_EXE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'app.exe')
VERSION_INFO_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VERSION')


def check_new_version():
    """Check if there is new version, download it if necessary then launch the program"""

    current_version = get_current_version()
    latest_version, download_url = get_latest_version_data()

    if current_version is None:
        print('Current installation not found, please wait while downloading latest release...')
        install_new_version(latest_version, download_url)

    elif current_version != latest_version:
        print('A new version ({0}) is available. You are currently using {1}.'.format(latest_version, CURRENT_VERSION))
        install_new_version(latest_version, download_url)

    else:
        print('Current version is up to date !')

    print('Launching {0}'.format(APP_EXE))
    # os.system(APP_EXE)


def get_current_version():
    try:
        with open(VERSION_INFO_FILE) as f:
            return f.read()
    except FileNotFoundError:
        return None


def install_new_version(version_name, download_url):
    print('Updating to version {0}...'.format(version_name))
    download_new_version(download_url)
    update_current_version_name(version_name)


def download_new_version(download_url):
    print('Downloading new version ({0}...'.format(download_url))
    urllib.request.urlretrieve(download_url, APP_EXE)


def update_current_version_name(version_name):
    """Storing the new version name in a file so we can check against it for later updates"""

    with open(VERSION_INFO_FILE, 'w+') as f:
        return f.write(version_name) 


def get_latest_version_data(): 
    """Query the GitHub APi for the latest release and return the tag name and the exe download URL"""

    response = urllib.request.urlopen(API_URL)
    data = json.loads(response.read().decode('utf-8'))
    latest = data[0]
    return latest['tag_name'], latest['assets'][0]['browser_download_url']


if __name__ == '__main__':
    check_new_version()
répondu 28-Mar-2015 par eliotberriot (678 points)
sélectionné 4-Avr-2015 par Rififi

Ok. Est-ce que le serveur peut être un repo github ?

Je pense que c'est tout à fait réalisable. Par exemple, le programme de mise à jour pourrait télécharger un fichier de ton dépôt ou se trouvera le numéro de version, l'extraire, et le comparer avec le numéro de version local. Si les deux ne correspondent pas, l'updater télécharge l'archive zip du dépôt, l'extrait et remplace les fichiers locaux avec.

Plutôt que de télécharger un fichier contenant le numéro de version, autant utiliser les releases (et donc les tags) et l'api github.

Une simple requête HTTP permet d'obtenir les releases d'un projet github. Un exemple:
https://api.github.com/repos/Arzaroth/CelestiaSunrise/releases

On peut voir qu'on a accès au tag_name (le plus souvent, il s'agit du numéro de version), d'url de download des tarballs, mais aussi des assets (qui sont les fichiers liés à une release github).

Dans le cadre d'un exécutable généré avec py2exe ou équivalent, on peut donc imaginer que à chaque release, tu l'ajoutes en asset au tag, que ton programme fasse une requête à l'API github à chaque démarrage, vérifie le numéro de version et le cas échéant, télécharge l'asset, qui sera la nouvelle version de l’exécutable.

Ça implique de ton côté d'utiliser les releases github, le système d'asset, et à fortiori les tags.

Tu as raison, c'est beaucoup plus malin et moins prise de tête d'utiliser l'API faite pour ça !

Salut eliotberriot,
Est ce possible de voir un exemple simple qui utilise cx_freeze et un serveur ou sont hébergées les versions ? Merci.

@lanfyp, je viens de rajouter un code d'exemple (sans cx_freeze, mais ça devrait marcher une fois compilé) :)

J'ai ouvert une nouvelle question, très similaire à celle-ci, avec de nouvelles informations, pour ceux que ça intéresse. En particulier @eliotberriot : http://indexerror.net/4245/mettre-%C3%A0-jour-%C3%A0-distance-%C3%A9craser-fichiers

...