]> jfr.im git - yt-dlp.git/blame - yt_dlp/update.py
[Arte] Improve description extraction (#1046)
[yt-dlp.git] / yt_dlp / update.py
CommitLineData
15938ab6
PH
1from __future__ import unicode_literals
2
c19bc311 3import hashlib
d5ed35b6 4import json
ce02ed60 5import os
e5813e53 6import platform
d2790370 7import subprocess
46353f67 8import sys
c19bc311 9import traceback
d5ed35b6
FV
10from zipimport import zipimporter
11
bfe2b8cf 12from .compat import compat_realpath
c0384f22 13from .utils import encode_compat_str
d3d3e2e3 14
d5ed35b6
FV
15from .version import __version__
16
5f6a1245 17
3dd264bf 18''' # Not signed
d5ed35b6 19def rsa_verify(message, signature, key):
d5ed35b6 20 from hashlib import sha256
15938ab6 21 assert isinstance(message, bytes)
4d318be1
FV
22 byte_size = (len(bin(key[0])) - 2 + 8 - 1) // 8
23 signature = ('%x' % pow(int(signature, 16), key[1], key[0])).encode()
24 signature = (byte_size * 2 - len(signature)) * b'0' + signature
25 asn1 = b'3031300d060960864801650304020105000420'
26 asn1 += sha256(message).hexdigest().encode()
27 if byte_size < len(asn1) // 2 + 11:
5f6a1245 28 return False
4d318be1
FV
29 expected = b'0001' + (byte_size - len(asn1) // 2 - 3) * b'ff' + b'00' + asn1
30 return expected == signature
3dd264bf 31'''
d5ed35b6 32
d7386f62 33
0c3e5f49 34def update_self(to_screen, verbose, opener):
c19bc311 35 ''' Exists for backward compatibility. Use run_update(ydl) instead '''
36
37 printfn = to_screen
38
39 class FakeYDL():
40 _opener = opener
41 to_screen = printfn
42
43 @staticmethod
44 def report_warning(msg, *args, **kwargs):
45 return printfn('WARNING: %s' % msg, *args, **kwargs)
46
47 @staticmethod
48 def report_error(msg, tb=None):
49 printfn('ERROR: %s' % msg)
50 if not verbose:
51 return
52 if tb is None:
53 # Copied from YoutubeDl.trouble
54 if sys.exc_info()[0]:
55 tb = ''
56 if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
57 tb += ''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
58 tb += encode_compat_str(traceback.format_exc())
59 else:
60 tb_data = traceback.format_list(traceback.extract_stack())
61 tb = ''.join(tb_data)
62 if tb:
63 printfn(tb)
64
65 return run_update(FakeYDL())
66
67
68def run_update(ydl):
e5813e53 69 """
70 Update the program file with the latest version from the repository
71 Returns whether the program should terminate
72 """
d5ed35b6 73
7a5c1cfe 74 JSON_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest'
d5ed35b6 75
c19bc311 76 def report_error(msg, network=False, expected=False, delim=';'):
77 if network:
78 msg += '%s Visit https://github.com/yt-dlp/yt-dlp/releases/latest' % delim
79 ydl.report_error(msg, tb='' if network or expected else None)
80
44f705d0 81 def calc_sha256sum(path):
fa57af1e
U
82 h = hashlib.sha256()
83 b = bytearray(128 * 1024)
84 mv = memoryview(b)
44f705d0 85 with open(os.path.realpath(path), 'rb', buffering=0) as f:
fa57af1e
U
86 for n in iter(lambda: f.readinto(mv), 0):
87 h.update(mv[:n])
88 return h.hexdigest()
89
4040428e 90 err = None
91 if isinstance(globals().get('__loader__'), zipimporter):
0181adef 92 pass
4040428e 93 elif hasattr(sys, 'frozen'):
0181adef 94 pass
4040428e 95 else:
96 err = 'It looks like you installed yt-dlp with a package manager, pip, setup.py or a tarball. Please use that to update'
97 if err:
98 return report_error(err, expected=True)
d5ed35b6 99
7815e555 100 # sys.executable is set to the full pathname of the exe-file for py2exe
101 # though symlinks are not followed so that we need to do this manually
102 # with help of realpath
103 filename = compat_realpath(sys.executable if hasattr(sys, 'frozen') else sys.argv[0])
c19bc311 104 ydl.to_screen('Current Build Hash %s' % calc_sha256sum(filename))
7815e555 105
d5ed35b6
FV
106 # Download and check versions info
107 try:
c19bc311 108 version_info = ydl._opener.open(JSON_URL).read().decode('utf-8')
3dd264bf 109 version_info = json.loads(version_info)
70a1165b 110 except Exception:
c19bc311 111 return report_error('can\'t obtain versions info. Please try again later ', True, delim='or')
d5ed35b6 112
d7386f62
PH
113 def version_tuple(version_str):
114 return tuple(map(int, version_str.split('.')))
3dd264bf 115
e5813e53 116 version_id = version_info['tag_name']
2e767313 117 if version_tuple(__version__) >= version_tuple(version_id):
c19bc311 118 ydl.to_screen('yt-dlp is up to date (%s)' % __version__)
d7386f62
PH
119 return
120
c19bc311 121 ydl.to_screen('Updating to version ' + version_id + ' ...')
3bf79c75 122
44f705d0 123 version_labels = {
124 'zip_3': '',
44f705d0 125 'exe_64': '.exe',
126 'exe_32': '_x86.exe',
127 }
128
e5813e53 129 def get_bin_info(bin_or_exe, version):
44f705d0 130 label = version_labels['%s_%s' % (bin_or_exe, version)]
c19bc311 131 return next((i for i in version_info['assets'] if i['name'] == 'yt-dlp%s' % label), {})
44f705d0 132
133 def get_sha256sum(bin_or_exe, version):
beb982be 134 filename = 'yt-dlp%s' % version_labels['%s_%s' % (bin_or_exe, version)]
44f705d0 135 urlh = next(
c19bc311 136 (i for i in version_info['assets'] if i['name'] in ('SHA2-256SUMS')),
137 {}).get('browser_download_url')
44f705d0 138 if not urlh:
139 return None
c19bc311 140 hash_data = ydl._opener.open(urlh).read().decode('utf-8')
beb982be
NA
141 if hash_data.startswith('version:'):
142 # Old colon-separated hash file
143 return dict(ln.split(':') for ln in hash_data.splitlines()).get(filename)
144 else:
145 # GNU-style hash file
146 return dict(ln.split()[::-1] for ln in hash_data.splitlines()).get(filename)
d5ed35b6
FV
147
148 if not os.access(filename, os.W_OK):
c19bc311 149 return report_error('no write permissions on %s' % filename, expected=True)
d5ed35b6 150
3dd264bf 151 # PyInstaller
611c1dd9 152 if hasattr(sys, 'frozen'):
e9297256 153 exe = filename
d5ed35b6
FV
154 directory = os.path.dirname(exe)
155 if not os.access(directory, os.W_OK):
c19bc311 156 return report_error('no write permissions on %s' % directory, expected=True)
b25522ba 157 try:
158 if os.path.exists(filename + '.old'):
159 os.remove(filename + '.old')
160 except (IOError, OSError):
161 return report_error('unable to remove the old version')
d5ed35b6
FV
162
163 try:
e5813e53 164 arch = platform.architecture()[0][:2]
44f705d0 165 url = get_bin_info('exe', arch).get('browser_download_url')
166 if not url:
c19bc311 167 return report_error('unable to fetch updates', True)
168 urlh = ydl._opener.open(url)
d5ed35b6
FV
169 newcontent = urlh.read()
170 urlh.close()
e5813e53 171 except (IOError, OSError, StopIteration):
c19bc311 172 return report_error('unable to download latest version', True)
d5ed35b6
FV
173
174 try:
175 with open(exe + '.new', 'wb') as outf:
176 outf.write(newcontent)
0b63aed8 177 except (IOError, OSError):
c19bc311 178 return report_error('unable to write the new version')
d5ed35b6 179
44f705d0 180 expected_sum = get_sha256sum('exe', arch)
181 if not expected_sum:
c19bc311 182 ydl.report_warning('no hash information found for the release')
44f705d0 183 elif calc_sha256sum(exe + '.new') != expected_sum:
c19bc311 184 report_error('unable to verify the new executable', True)
44f705d0 185 try:
186 os.remove(exe + '.new')
187 except OSError:
c19bc311 188 return report_error('unable to remove corrupt download')
44f705d0 189
d5ed35b6 190 try:
b25522ba 191 os.rename(exe, exe + '.old')
192 except (IOError, OSError):
193 return report_error('unable to move current version')
194 try:
195 os.rename(exe + '.new', exe)
0b63aed8 196 except (IOError, OSError):
c19bc311 197 report_error('unable to overwrite current version')
b25522ba 198 os.rename(exe + '.old', exe)
199 return
200 try:
201 # Continues to run in the background
202 subprocess.Popen(
203 'ping 127.0.0.1 -n 5 -w 1000 & del /F "%s.old"' % exe,
204 shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
205 ydl.to_screen('Updated yt-dlp to version %s' % version_id)
206 return True # Exit app
207 except OSError:
208 report_error('unable to delete old version')
d5ed35b6
FV
209
210 # Zip unix package
211 elif isinstance(globals().get('__loader__'), zipimporter):
212 try:
4040428e 213 url = get_bin_info('zip', '3').get('browser_download_url')
44f705d0 214 if not url:
c19bc311 215 return report_error('unable to fetch updates', True)
216 urlh = ydl._opener.open(url)
d5ed35b6
FV
217 newcontent = urlh.read()
218 urlh.close()
e5813e53 219 except (IOError, OSError, StopIteration):
c19bc311 220 return report_error('unable to download latest version', True)
d5ed35b6 221
4040428e 222 expected_sum = get_sha256sum('zip', '3')
beb982be
NA
223 if not expected_sum:
224 ydl.report_warning('no hash information found for the release')
225 elif hashlib.sha256(newcontent).hexdigest() != expected_sum:
c19bc311 226 return report_error('unable to verify the new zip', True)
44f705d0 227
228 try:
c4a508ab 229 with open(filename, 'wb') as outf:
230 outf.write(newcontent)
231 except (IOError, OSError):
c19bc311 232 return report_error('unable to overwrite current version')
d5ed35b6 233
c19bc311 234 ydl.to_screen('Updated yt-dlp to version %s; Restart yt-dlp to use the new version' % version_id)
3bf79c75 235
5f6a1245 236
3dd264bf 237''' # UNUSED
46a127ee 238def get_notes(versions, fromVersion):
3bf79c75 239 notes = []
5f6a1245 240 for v, vdata in sorted(versions.items()):
3bf79c75
PH
241 if v > fromVersion:
242 notes.extend(vdata.get('notes', []))
46a127ee
PH
243 return notes
244
5f6a1245 245
46a127ee
PH
246def print_notes(to_screen, versions, fromVersion=__version__):
247 notes = get_notes(versions, fromVersion)
3bf79c75 248 if notes:
15938ab6 249 to_screen('PLEASE NOTE:')
3bf79c75
PH
250 for note in notes:
251 to_screen(note)
3dd264bf 252'''