[cleanup] Refactor updater

The updater now uses `.update.run_update` and not `.update.update_self`.
Although I don't expect anyone to be using the updater via API, a wrapper `update_self` is provided for compatibility just in case
This commit is contained in:
pukkandan 2021-05-26 01:13:08 +05:30
parent 5435dcf96e
commit c19bc311cb
No known key found for this signature in database
GPG Key ID: 0F00D95A001F4698
3 changed files with 75 additions and 70 deletions

View File

@ -734,6 +734,7 @@ class YoutubeDL(object):
else: else:
tb_data = traceback.format_list(traceback.extract_stack()) tb_data = traceback.format_list(traceback.extract_stack())
tb = ''.join(tb_data) tb = ''.join(tb_data)
if tb:
self.to_stderr(tb) self.to_stderr(tb)
if not self.params.get('ignoreerrors', False): if not self.params.get('ignoreerrors', False):
if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]: if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:

View File

@ -37,7 +37,7 @@ from .utils import (
std_headers, std_headers,
write_string, write_string,
) )
from .update import update_self from .update import run_update
from .downloader import ( from .downloader import (
FileDownloader, FileDownloader,
) )
@ -663,7 +663,7 @@ def _real_main(argv=None):
# Update version # Update version
if opts.update_self: if opts.update_self:
# If updater returns True, exit. Required for windows # If updater returns True, exit. Required for windows
if update_self(ydl.to_screen, opts.verbose, ydl._opener): if run_update(ydl):
if actual_use: if actual_use:
sys.exit('ERROR: The program must exit for the update to complete') sys.exit('ERROR: The program must exit for the update to complete')
sys.exit() sys.exit()

View File

@ -1,13 +1,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import hashlib
import io import io
import json import json
import traceback
import hashlib
import os import os
import platform import platform
import subprocess import subprocess
import sys import sys
import traceback
from zipimport import zipimporter from zipimport import zipimporter
from .compat import compat_realpath from .compat import compat_realpath
@ -33,6 +33,40 @@ def rsa_verify(message, signature, key):
def update_self(to_screen, verbose, opener): def update_self(to_screen, verbose, opener):
''' Exists for backward compatibility. Use run_update(ydl) instead '''
printfn = to_screen
class FakeYDL():
_opener = opener
to_screen = printfn
@staticmethod
def report_warning(msg, *args, **kwargs):
return printfn('WARNING: %s' % msg, *args, **kwargs)
@staticmethod
def report_error(msg, tb=None):
printfn('ERROR: %s' % msg)
if not verbose:
return
if tb is None:
# Copied from YoutubeDl.trouble
if sys.exc_info()[0]:
tb = ''
if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
tb += ''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
tb += encode_compat_str(traceback.format_exc())
else:
tb_data = traceback.format_list(traceback.extract_stack())
tb = ''.join(tb_data)
if tb:
printfn(tb)
return run_update(FakeYDL())
def run_update(ydl):
""" """
Update the program file with the latest version from the repository Update the program file with the latest version from the repository
Returns whether the program should terminate Returns whether the program should terminate
@ -40,6 +74,11 @@ def update_self(to_screen, verbose, opener):
JSON_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest' JSON_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest'
def report_error(msg, network=False, expected=False, delim=';'):
if network:
msg += '%s Visit https://github.com/yt-dlp/yt-dlp/releases/latest' % delim
ydl.report_error(msg, tb='' if network or expected else None)
def calc_sha256sum(path): def calc_sha256sum(path):
h = hashlib.sha256() h = hashlib.sha256()
b = bytearray(128 * 1024) b = bytearray(128 * 1024)
@ -50,112 +89,91 @@ def update_self(to_screen, verbose, opener):
return h.hexdigest() return h.hexdigest()
if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'): if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, 'frozen'):
to_screen('It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball. Please use that to update') return report_error(
return 'It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball. '
'Please use that to update', expected=True)
# sys.executable is set to the full pathname of the exe-file for py2exe # sys.executable is set to the full pathname of the exe-file for py2exe
# though symlinks are not followed so that we need to do this manually # though symlinks are not followed so that we need to do this manually
# with help of realpath # with help of realpath
filename = compat_realpath(sys.executable if hasattr(sys, 'frozen') else sys.argv[0]) filename = compat_realpath(sys.executable if hasattr(sys, 'frozen') else sys.argv[0])
to_screen('Current Build Hash %s' % calc_sha256sum(filename)) ydl.to_screen('Current Build Hash %s' % calc_sha256sum(filename))
# Download and check versions info # Download and check versions info
try: try:
version_info = opener.open(JSON_URL).read().decode('utf-8') version_info = ydl._opener.open(JSON_URL).read().decode('utf-8')
version_info = json.loads(version_info) version_info = json.loads(version_info)
except Exception: except Exception:
if verbose: return report_error('can\'t obtain versions info. Please try again later ', True, delim='or')
to_screen(encode_compat_str(traceback.format_exc()))
to_screen('ERROR: can\'t obtain versions info. Please try again later')
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
return
def version_tuple(version_str): def version_tuple(version_str):
return tuple(map(int, version_str.split('.'))) return tuple(map(int, version_str.split('.')))
version_id = version_info['tag_name'] version_id = version_info['tag_name']
if version_tuple(__version__) >= version_tuple(version_id): if version_tuple(__version__) >= version_tuple(version_id):
to_screen('yt-dlp is up to date (%s)' % __version__) ydl.to_screen('yt-dlp is up to date (%s)' % __version__)
return return
to_screen('Updating to version ' + version_id + ' ...') ydl.to_screen('Updating to version ' + version_id + ' ...')
version_labels = { version_labels = {
'zip_3': '', 'zip_3': '',
'zip_2': '', 'zip_2': '',
# 'zip_2': '_py2',
'exe_64': '.exe', 'exe_64': '.exe',
'exe_32': '_x86.exe', 'exe_32': '_x86.exe',
} }
def get_bin_info(bin_or_exe, version): def get_bin_info(bin_or_exe, version):
label = version_labels['%s_%s' % (bin_or_exe, version)] label = version_labels['%s_%s' % (bin_or_exe, version)]
return next( return next((i for i in version_info['assets'] if i['name'] == 'yt-dlp%s' % label), {})
(i for i in version_info['assets'] if i['name'] == 'yt-dlp%s' % label),
{})
def get_sha256sum(bin_or_exe, version): def get_sha256sum(bin_or_exe, version):
label = version_labels['%s_%s' % (bin_or_exe, version)] label = version_labels['%s_%s' % (bin_or_exe, version)]
urlh = next( urlh = next(
(i for i in version_info['assets'] (i for i in version_info['assets'] if i['name'] in ('SHA2-256SUMS')),
if i['name'] in ('SHA2-256SUMS')), {}).get('browser_download_url') {}).get('browser_download_url')
if not urlh: if not urlh:
return None return None
hash_data = opener.open(urlh).read().decode('utf-8') hash_data = ydl._opener.open(urlh).read().decode('utf-8')
hashes = list(map(lambda x: x.split(':'), hash_data.splitlines())) hashes = list(map(lambda x: x.split(':'), hash_data.splitlines()))
return next( return next((i[1] for i in hashes if i[0] == 'yt-dlp%s' % label), None)
(i[1] for i in hashes if i[0] == 'yt-dlp%s' % label),
None)
if not os.access(filename, os.W_OK): if not os.access(filename, os.W_OK):
to_screen('ERROR: no write permissions on %s' % filename) return report_error('no write permissions on %s' % filename, expected=True)
return
# PyInstaller # PyInstaller
if hasattr(sys, 'frozen'): if hasattr(sys, 'frozen'):
exe = filename exe = filename
directory = os.path.dirname(exe) directory = os.path.dirname(exe)
if not os.access(directory, os.W_OK): if not os.access(directory, os.W_OK):
to_screen('ERROR: no write permissions on %s' % directory) return report_error('no write permissions on %s' % directory, expected=True)
return
try: try:
arch = platform.architecture()[0][:2] arch = platform.architecture()[0][:2]
url = get_bin_info('exe', arch).get('browser_download_url') url = get_bin_info('exe', arch).get('browser_download_url')
if not url: if not url:
to_screen('ERROR: unable to fetch updates') return report_error('unable to fetch updates', True)
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest') urlh = ydl._opener.open(url)
return
urlh = opener.open(url)
newcontent = urlh.read() newcontent = urlh.read()
urlh.close() urlh.close()
except (IOError, OSError, StopIteration): except (IOError, OSError, StopIteration):
if verbose: return report_error('unable to download latest version', True)
to_screen(encode_compat_str(traceback.format_exc()))
to_screen('ERROR: unable to download latest version')
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
return
try: try:
with open(exe + '.new', 'wb') as outf: with open(exe + '.new', 'wb') as outf:
outf.write(newcontent) outf.write(newcontent)
except (IOError, OSError): except (IOError, OSError):
if verbose: return report_error('unable to write the new version')
to_screen(encode_compat_str(traceback.format_exc()))
to_screen('ERROR: unable to write the new version')
return
expected_sum = get_sha256sum('exe', arch) expected_sum = get_sha256sum('exe', arch)
if not expected_sum: if not expected_sum:
to_screen('WARNING: no hash information found for the release') ydl.report_warning('no hash information found for the release')
elif calc_sha256sum(exe + '.new') != expected_sum: elif calc_sha256sum(exe + '.new') != expected_sum:
to_screen('ERROR: unable to verify the new executable') report_error('unable to verify the new executable', True)
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
try: try:
os.remove(exe + '.new') os.remove(exe + '.new')
except OSError: except OSError:
to_screen('ERROR: unable to remove corrupt download') return report_error('unable to remove corrupt download')
return
try: try:
bat = os.path.join(directory, 'yt-dlp-updater.cmd') bat = os.path.join(directory, 'yt-dlp-updater.cmd')
@ -171,12 +189,9 @@ def update_self(to_screen, verbose, opener):
''' % (exe, exe, version_id)) ''' % (exe, exe, version_id))
subprocess.Popen([bat]) # Continues to run in the background subprocess.Popen([bat]) # Continues to run in the background
return True # Exit app
except (IOError, OSError): except (IOError, OSError):
if verbose: report_error('unable to overwrite current version')
to_screen(encode_compat_str(traceback.format_exc())) return True # Exit app
to_screen('ERROR: unable to overwrite current version')
return
# Zip unix package # Zip unix package
elif isinstance(globals().get('__loader__'), zipimporter): elif isinstance(globals().get('__loader__'), zipimporter):
@ -184,35 +199,24 @@ def update_self(to_screen, verbose, opener):
py_ver = platform.python_version()[0] py_ver = platform.python_version()[0]
url = get_bin_info('zip', py_ver).get('browser_download_url') url = get_bin_info('zip', py_ver).get('browser_download_url')
if not url: if not url:
to_screen('ERROR: unable to fetch updates') return report_error('unable to fetch updates', True)
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest') urlh = ydl._opener.open(url)
return
urlh = opener.open(url)
newcontent = urlh.read() newcontent = urlh.read()
urlh.close() urlh.close()
except (IOError, OSError, StopIteration): except (IOError, OSError, StopIteration):
if verbose: return report_error('unable to download latest version', True)
to_screen(encode_compat_str(traceback.format_exc()))
to_screen('ERROR: unable to download latest version')
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
return
expected_sum = get_sha256sum('zip', py_ver) expected_sum = get_sha256sum('zip', py_ver)
if expected_sum and hashlib.sha256(newcontent).hexdigest() != expected_sum: if expected_sum and hashlib.sha256(newcontent).hexdigest() != expected_sum:
to_screen('ERROR: unable to verify the new zip') return report_error('unable to verify the new zip', True)
to_screen('Visit https://github.com/yt-dlp/yt-dlp/releases/latest')
return
try: try:
with open(filename, 'wb') as outf: with open(filename, 'wb') as outf:
outf.write(newcontent) outf.write(newcontent)
except (IOError, OSError): except (IOError, OSError):
if verbose: return report_error('unable to overwrite current version')
to_screen(encode_compat_str(traceback.format_exc()))
to_screen('ERROR: unable to overwrite current version')
return
to_screen('Updated yt-dlp. Restart yt-dlp to use the new version') ydl.to_screen('Updated yt-dlp to version %s; Restart yt-dlp to use the new version' % version_id)
''' # UNUSED ''' # UNUSED