Python cross-platform software update mechanism like youtube-dl?

21 views Asked by At

Back in the days when the command line utility youtube-dl still worked, I was quite impressed that:

  • The download was a single executable file (youtube-dl for Linux, youtube-dl.exe for Windows)
  • If you ran the update command, youtube-dl -U - then if there was an update, the actual single file got "replaced", so the update worked even if you did not have the program "installed", and you were running it from an arbitrary user directory instead.

I would like to implement something similar, but I have a difficulty gathering the minimal requirements for how to setup the software, and how to setup the web server side - e.g. if my main update URL is https://example.com/mysoft/update/, what files would I need to have there, so the entire mechanism works?

Here is what I can see so far:

  • If you download the Linux version of youtube-dl, this is what I get when I run file on it (in MINGW64 console):
$ file youtube-dl
youtube-dl: a /usr/bin/env python script executable (Zip archive)

So, it is somehow an "executable zip" file, not sure how that was made; inside it we can see:

$ unzip -l youtube-dl
Archive:  youtube-dl
warning [youtube-dl]:  22 extra bytes at beginning or within zipfile
  (attempting to process anyway)
  Length      Date    Time    Name
---------  ---------- -----   ----
    16130  1999-12-31 19:01   youtube_dl/aes.py
     2981  1999-12-31 19:01   youtube_dl/cache.py
    94259  1999-12-31 19:01   youtube_dl/compat.py
    20585  1999-12-31 19:01   youtube_dl/__init__.py
...
      955  1999-12-31 19:01   youtube_dl/postprocessor/__init__.py
     1652  1999-12-31 19:01   youtube_dl/postprocessor/metadatafromtitle.py
     2926  1999-12-31 19:01   youtube_dl/postprocessor/xattrpp.py
      467  1999-12-31 19:01   __main__.py
---------                     -------
  5436380                     818 files

So apparently:

  • There has to be a __main__.py in the zip (is the __main__.py name required or arbitrary?
  • Rest of the software needs to be a Python module, in its own (sub)directory (youtube-dl/), which has an __init__.py inside it

In the Makefile, I can see a youtube-dl target, which apparently clarifies how the final "executable zip" is created:

youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
    mkdir -p zip
    for d in youtube_dl youtube_dl/downloader youtube_dl/extractor youtube_dl/postprocessor ; do \
      mkdir -p zip/$$d ;\
      cp -pPR $$d/*.py zip/$$d/ ;\
    done
    touch -t 200001010101 zip/youtube_dl/*.py zip/youtube_dl/*/*.py
    mv zip/youtube_dl/__main__.py zip/
    cd zip ; zip -q ../youtube-dl youtube_dl/*.py youtube_dl/*/*.py __main__.py
    rm -rf zip
    echo '#!$(PYTHON)' > youtube-dl
    cat youtube-dl.zip >> youtube-dl
    rm youtube-dl.zip
    chmod a+x youtube-dl

... however, I cannot see in the Makefile how the Windows .exe is created.

However, in setup.py I can see:

# ..
py2exe_console = [{
    'script': './youtube_dl/__main__.py',
    'dest_base': 'youtube-dl',
    'version': __version__,
    'description': DESCRIPTION,
    'comments': LONG_DESCRIPTION,
    'product_name': 'youtube-dl',
    'product_version': __version__,
}]

py2exe_params = {
    'console': py2exe_console,
    'options': {'py2exe': py2exe_options},
    'zipfile': None
}
# ...

... so apparently somehow py2exe is used for the Windows .exe.

If the Makefile does not directly handle the .exe, I guess then devscripts/wine-py2exe.sh does - but I cannot tell how (if at all) it is integrated in the build process.

Handling the update from within the program is probably handled by youtube_dl/update.py, where I can see:

# ...
def update_self(to_screen, verbose, opener):
    """Update the program file with the latest version from the repository"""

    UPDATE_URL = 'https://yt-dl.org/update/'
    VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
    JSON_URL = UPDATE_URL + 'versions.json'
    UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)

    if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'):
        to_screen('It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.')
        return

    # Check if there is a new version
    try:
        newversion = opener.open(VERSION_URL).read().decode('utf-8').strip()
# ...

So, on the web/online side, I need at least a UPDATE_URL/LATEST_VERSION and UPDATE_URL/versions.json; where versions.json looks something like:

{
    "latest": "2013.01.06", 
    "signature": "72158cdba391628569ffdbea259afbcf279bbe3d8aeb7492690735dc1cfa6afa754f55c61196f3871d429599ab22f2667f1fec98865527b32632e7f4b3675a7ef0f0fbe084d359256ae4bba68f0d33854e531a70754712f244be71d4b92e664302aa99653ee4df19800d955b6c4149cd2b3f24288d6e4b40b16126e01f4c8ce6", 
    "versions": {
        "2013.01.02": {
...
        }, 
        "2013.01.06": {
            "bin": [
                "http://youtube-dl.org/downloads/2013.01.06/youtube-dl", 
                "64b6ed8865735c6302e836d4d832577321b4519aa02640dc508580c1ee824049"
            ], 
            "exe": [
                "http://youtube-dl.org/downloads/2013.01.06/youtube-dl.exe", 
                "58609baf91e4389d36e3ba586e21dab882daaaee537e4448b1265392ae86ff84"
            ], 
            "tar": [
                "http://youtube-dl.org/downloads/2013.01.06/youtube-dl-2013.01.06.tar.gz", 
                "fe77ab20a95d980ed17a659aa67e371fdd4d656d19c4c7950e7b720b0c2f1a86"
            ]
        }
...

So, there are separate downloads for the Linux "executable Python zip", and for the Windows .exe file; there seem to be file hashes as well.

Ultimately, on one hand I'd like to avoid reinventing the wheel for the update procedure; on the other hand, I'd like to also avoid learning the youtube-dl code in enough detail, to extract the update procedure for my program. So my questions are:

  • Is there a name for a technique for a similar cross-platform update technique for Python programs, so I can (hopefully) find a simple bare-bones example, that I could build upon?
  • Lacking that, can someone with more experience in this summarize what is necessary (e.g. organization in modules, what the build scripts need to do, what the web server layout should) to implement such an update mechanism?
  • Finally - if I want to update a GUI program, so it "restarts" itself in case there was an update, how would one go about that? (note in the command line -U procedure, the software overwrites the single executable file (zip or exe), and then exits - it does not "restart" after the update is done).
0

There are 0 answers