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