Version d'un programme AppImage

Bonjour,
je cherche via un script a connaitre la version d’un logiciel externe de type AppImage. Y-aurait-il un moyen pour le faire ?
Par exemple avec BalenaEtcher si je le lance je récupère un résultat (str) dans lequel il y a la version. J’ai essayé avec LibreOffice mais cela ne donne rien.
Une idée serait la bienvenue, …si c’est possible…

Selon AppStream metadata — AppImage documentation ça serait dans usr/share/metainfo/myapp.appdata.xml.

Il doit exister un moyen de l’extraire, mais je l’ignore.

Par exemple avec binwalk on voit bien la structure de l’image :

$ binwalk python3.9.16-cp39-cp39-manylinux_2_24_x86_64.AppImage 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
7024          0x1B70          ESP Image (ESP32): segment count: 10, flash mode: QUIO, flash speed: 40MHz, flash size: 1MB, entry address: 0xc0012, hash: none
7408          0x1CF0          ESP Image (ESP32): segment count: 11, flash mode: QUIO, flash speed: 40MHz, flash size: 1MB, entry address: 0xc0012, hash: none
140480        0x224C0         SHA256 hash constants, little endian
140952        0x22698         xz compressed data
140992        0x226C0         CRC32 polynomial table, little endian
188392        0x2DFE8         Squashfs filesystem, little endian, version 4.0, compression:gzip, size: 20332416 bytes, 3017 inodes, blocksize: 131072 bytes, created: 1970-01-01 00:00:00

Après un petit binwalk -e on trouve en effet le appdata.xml :

$ cat _python3.9.16-cp39-cp39-manylinux_2_24_x86_64.AppImage.extracted/squashfs-root/usr/share/metainfo/python3.9.16.appdata.xml
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
    <id>python3.9.16</id>
    <metadata_license>MIT</metadata_license>
    <project_license>Python-2.0</project_license>
    <name>Python 3.9</name>
    <summary>A Python 3.9 runtime</summary>
    <description>
        <p>  A relocated Python 3.9 installation running from an
             AppImage.
        </p>
    </description>
    <launchable type="desktop-id">python.desktop</launchable>
    <url type="homepage">https://python.org</url>
    <provides>
        <binary>python3.9</binary>
    </provides>
</component>

donc c’est possible, ma solution n’est pas élégante du tout, elle prouve que c’est possible.

Tu peux essayer ma manière avec ton appimage libreoffice pour voir à quoi ça ressemble.

Je parie qu’il existe des libs pour faire ça de manière un peu plus proprement.

Merci pour ta réponse, j’ai essayé mais cela ne donne rien d’intéressant avec binwalk et je n’ai pas de fichier particulier à LibreOffice dans usr/share/metainfo/myapp.appdata.xml.

Ou as-tu récupéré l’appimage ? Si tu peux me donner le lien pour télécharger exactement la même je peux essayer.

voici l’adresse: LibreOffice as AppImage | LibreOffice - Free and private office suite - Based on OpenOffice - Compatible with Microsoft
Le fichier est Fresh full
Merci d’avance

En effet, j’y vois bien des “appdata” :

$ binwalk -e LibreOffice-fresh.full-x86_64.AppImage
$ find _LibreOffice-fresh.full-x86_64.AppImage.extracted -name '*.appdata.xml' 
./libreoffice24.8-base.appdata.xml
./libreoffice24.8-calc.appdata.xml
./libreoffice24.8-draw.appdata.xml
./libreoffice24.8-impress.appdata.xml
./libreoffice24.8-writer.appdata.xml
./squashfs-root/usr/share/metainfo/libreoffice24.8-base.appdata.xml
./squashfs-root/usr/share/metainfo/libreoffice24.8-calc.appdata.xml
./squashfs-root/usr/share/metainfo/libreoffice24.8-draw.appdata.xml
./squashfs-root/usr/share/metainfo/libreoffice24.8-impress.appdata.xml
./squashfs-root/usr/share/metainfo/libreoffice24.8-writer.appdata.xml

Et … pas de version dans les fichiers appdata.xml MAIS MAIS MAIS y’a la version dans le nom du fichier, libreoffice24.8-base.appdata.xml hop, version 24.8. C’est pas élégant (je trouve), c’est pas standard (j’ai pas lu le standard AppImage en entier), mais c’est là.

Je serai toi je leur demanderai poliment de rajouter ça dans le appdata.xml pour la prochaine release :slight_smile:

oui mais ce que voulais c’est la version complète, donc en l’occrence 24.8.2.1…Je n’avais compris comment récupérer l’ appdata.xml.
Avec BalenaEtcher j’avais fait comme cela (surement pas catholique!):

#Cas BalenaEtcher (AppImage)
		fb='b.AppImage'
		result=subprocess.run('/home/bibi/Bureau/'+fb, capture_output=True, text=True)
		if "'balenaEtcher@" in result.stdout:
			i = result.stdout.find("'balenaEtcher@")
			val=result.stdout[i:i+22].replace('@',' ').replace("'",'')
			print(val)

Ohhh :

squashfs-root/startcenter.desktop:X-AppImage-Version=24.8.2.1.full

oui je viens aussi de le trouver et j’ai réussi à récupérer la version. Un grand merci pour ton aide

Ça doit s’automatiser un peu…

$ cat extract_version.py 
import io
import os
from dissect.squashfs import SquashFS

with open("LibreOffice-fresh.full-x86_64.AppImage", mode="rb") as appimage:
    while block := appimage.read(4):
        if block == b'hsqs':
            appimage.seek(-4, os.SEEK_CUR)
            fsbytes = appimage.read()
            fs = SquashFS(io.BytesIO(fsbytes))
            with fs.get("/startcenter.desktop").open() as desktop_file:
                desktop = desktop_file.read().decode("UTF-8")
                for line in desktop.splitlines():
                    if line.startswith("X-AppImage-Version"):
                        print(line)
$ python extract_version.py 
X-AppImage-Version=24.8.2.1.full

je vais regarder ton code, c’est surement plus élégant que le mien

J’ai essayé ton code qui fonctionne parfaitement, mais je n’ai pas le niveau technique pour le comprendre…
Ma version est beaucoup plus simpliste mais je suis obligé d’extraire tout puis de supprimer le-dit répertoire après avoir récupéré l’info de la version.

Bonjour @mdk, j’ai essayé d’analyser ta solution basée sur SquashFS. Vu mon niveau je ne sais pas si cela correspond bien à la réalité. Peux-tu me corriger svp:
ouvrir le fichier AppImage binaire
lire au moins 4 octets ? := affecte valeur et affiche
if block == b’hsqs’: si octets = ???
se positionner depuis la fin à partir du 4ème octet
lecture du fichier AppImage
pour créer une trame d’octets mémorisables
ouvrir le fichier .desktop qui nous intéresse
convertir en UTF-8 = transformer en str
éclater la trame
rechercher la ligne de version qui commence par
formater la sortie à afficher
afficher version de l’AppImage

ouvrir le fichier AppImage binaire

C’est juste. C’est le "rb" qui donne l’indication : read, binary.

lire au moins 4 octets ? := affecte valeur et affiche

Oui pour le read. Le := n’affiche pas, il affecte a block, comme un =. Mais contrairement à un =, l’ensemble block := appimage.read(4) vaut la valeur assignée, donc les 4 octets. Donc le while va prendre en compte ces 4 octets pour savoir s’il faut, ou non, boucler. Si read réussi à lire, le while verra quelque chose qui n’est pas vide. “pas vide” en Python c’est vrai. Et à la toute fin, lorsque le read échouera à lire (car le fichier est entièrement lu), while verra quelque chose de vide, ce qui est faux en Python, et donc s’arrêtera.

J’aurai pu l’écrire :

block = appimage.read(4)
while block:
    ... tout le code du while
    block = appimage.read(4)

mais ça oblige à écrire deux fois le appimage.read, alors j’aime bien :=.

if block == b’hsqs’: si octets = ???

Une chaîne de caractères préfixée par b en Python c’est une séquence d’octets, donc là b'hsqs' c’est les octets (qui correspondent aux caractères ASCII) h, s, q, s, soit les valeurs 104, 115, 113, 115. hsqs c’est les 4 octets qui son toujours au début d’un squashfs, c’est dans la spec de squashfs. Y’a plein de format qui commencent par une suite d’octets « bien connue », ça permet de reconnaître un fichier, ça s’appelle souvent un « magic number ».

se positionner depuis la fin à partir du 4ème octet

Je dirais « reculer de 4 octets », mon but est de revenu au vrai début du squashfs, d’être pile sur le “h” du “hsqs”, car si block == "b’hsqs’ c’est que hsqs a déjà été lu, donc on est plus au début de l’appimage, il faut reculer de 4 octets pour se mettre au début de l’appimage.

lecture du fichier AppImage
pour créer une trame d’octets mémorisables
ouvrir le fichier .desktop qui nous intéresse

Oui, c’est un fichier texte.

convertir en UTF-8 = transformer en str

Exact, le texte est stocké sous forme d’octets (forcément), donc existe un algorithme pour transformer d’octet à texte et vice-versa, ce genre d’algo c’est un “encodage”, et UTF-8 est l’encodage qui marche bien de nos jours, il est top top top :slight_smile:

éclater la trame

Je dirais : “découper le fichier en lignes”.

rechercher la ligne de version qui commence par
formater la sortie à afficher

Oh y’a pas vraiment de formattage ici c’est vraiment juste un print de la ligne SI elle commence par X-AppImage-Version.

merci infiniment pour ces explications, c’est vraiment agréable d’être épaulé ainsi, car je ne fais que bricoler (comme cela peut se supposer).

Je sais que j’abuse… J’ai effectivement trouvé quelque part que les premiers octets devaient être en hexa 68 73 71 73. Du coup j’ai essayé un xxd -l 0x10 lostd.AppImage pour lire en hexa et en ASCII la première ligne du fichier AppImage, mais je trouve en hexa 7f 45 4c 46. Qu’est-ce que j’ai loupé ?..

Alors 68 73 71 73, tu peux repérer les deux 73, comme les deux s dans hsqs, c’est pas un hasard, c’est hsqs :

$ printf hsqs | hexdump -C
00000000  68 73 71 73                                       |hsqs|

Mais c’est les premiers octets du SquashFS pas de l’AppImage. L’AppImage contient des données AVANT le SquashFS.

Dans mon code, rajoute :

print(appimage.tell(0))

juste après le :

appimage.seek(-4, os.SEEK_CUR)

ça te dira à quel octet se situe le début du SquashFS dans l’AppImage.

Mais attention le SquashFS ne commence pas au même octet dans toutes les AppImage. Il doit exister un moyen élégant de trouver le début du SquashFS dans l’AppImage, en lisant bien la spec de AppImage, moi dans mon code j’ai fait ça a la rache en cherchant octet par octet le ‘hsqs’, c’est moche, c’est fragile, mais c’est vite codé :smiley:

Vu :slight_smile: - par contre tell ne prend pas d’argument : appimage.tell() :slight_smile:
Ta solution est efficace et rapide

Exact, tell ne prend pas d’argument, hey tu progresse vite !