Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/gallery_dl/update.py
5457 views
1
# -*- coding: utf-8 -*-
2
3
# Copyright 2024-2025 Mike Fährmann
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License version 2 as
7
# published by the Free Software Foundation.
8
9
import os
10
import sys
11
12
from .extractor.common import Extractor, Message
13
from .job import DownloadJob
14
from . import util, version, output, exception
15
16
REPOS = {
17
"stable" : "mikf/gallery-dl",
18
"dev" : "gdl-org/builds",
19
"nightly": "gdl-org/builds",
20
"master" : "gdl-org/builds",
21
}
22
23
BINARIES_STABLE = {
24
"windows" : "gallery-dl.exe",
25
"windows_x64": "gallery-dl.exe",
26
"windows_x86": "gallery-dl_x86.exe",
27
"linux" : "gallery-dl.bin",
28
}
29
BINARIES_DEV = {
30
"windows" : "gallery-dl_windows.exe",
31
"windows_x64": "gallery-dl_windows.exe",
32
"windows_x86": "gallery-dl_windows_x86.exe",
33
"linux" : "gallery-dl_linux",
34
"macos" : "gallery-dl_macos",
35
}
36
BINARIES = {
37
"stable" : BINARIES_STABLE,
38
"dev" : BINARIES_DEV,
39
"nightly": BINARIES_DEV,
40
"master" : BINARIES_DEV,
41
}
42
43
44
class UpdateJob(DownloadJob):
45
46
def handle_url(self, url, kwdict):
47
if not self._check_update(kwdict):
48
if kwdict["_check"]:
49
self.status |= 1
50
return self.extractor.log.info(
51
"gallery-dl is up to date (%s)", version.__version__)
52
53
if kwdict["_check"]:
54
return self.extractor.log.info(
55
"A new release is available: %s -> %s",
56
version.__version__, kwdict["tag_name"])
57
58
self.extractor.log.info(
59
"Updating from %s to %s",
60
version.__version__, kwdict["tag_name"])
61
62
path_old = sys.executable + ".old"
63
path_new = sys.executable + ".new"
64
directory, filename = os.path.split(sys.executable)
65
66
pathfmt = self.pathfmt
67
pathfmt.extension = "new"
68
pathfmt.filename = filename
69
pathfmt.temppath = path_new
70
pathfmt.realpath = pathfmt.path = sys.executable
71
pathfmt.realdirectory = pathfmt.directory = directory
72
73
self._newline = True
74
if not self.download(url):
75
self.status |= 4
76
return self._error("Failed to download %s", url.rpartition("/")[2])
77
78
if not util.WINDOWS:
79
try:
80
mask = os.stat(sys.executable).st_mode
81
except OSError:
82
mask = 0o755
83
self._warning("Unable to get file permission bits")
84
85
try:
86
os.replace(sys.executable, path_old)
87
except OSError:
88
return self._error("Unable to move current executable")
89
90
try:
91
pathfmt.finalize()
92
except OSError:
93
self._error("Unable to overwrite current executable")
94
return os.replace(path_old, sys.executable)
95
96
if util.WINDOWS:
97
import atexit
98
import subprocess
99
100
cmd = f'ping 127.0.0.1 -n 5 -w 1000 & del /F "{path_old}"'
101
atexit.register(
102
util.Popen, cmd, shell=True,
103
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
104
)
105
106
else:
107
try:
108
os.unlink(path_old)
109
except OSError:
110
self._warning("Unable to delete old executable")
111
112
try:
113
os.chmod(sys.executable, mask)
114
except OSError:
115
self._warning("Unable to restore file permission bits")
116
117
self.out.success(pathfmt.path)
118
119
def _check_update(self, kwdict):
120
if kwdict["_exact"]:
121
return True
122
123
tag = kwdict["tag_name"]
124
125
if tag[0] == "v":
126
kwdict["tag_name"] = tag = tag[1:]
127
ver, _, dev = version.__version__.partition("-")
128
129
version_local = [int(v) for v in ver.split(".")]
130
version_remote = [int(v) for v in tag.split(".")]
131
132
if dev:
133
version_local[-1] -= 0.5
134
if version_local >= version_remote:
135
return False
136
137
elif version.__version__.endswith(":" + tag):
138
return False
139
140
return True
141
142
def _warning(self, msg, *args):
143
if self._newline:
144
self._newline = False
145
output.stderr_write("\n")
146
self.extractor.log.warning(msg, *args)
147
148
def _error(self, msg, *args):
149
if self._newline:
150
self._newline = False
151
output.stderr_write("\n")
152
self.status |= 1
153
self.extractor.log.error(msg, *args)
154
155
156
class UpdateExtractor(Extractor):
157
category = "update"
158
root = "https://github.com"
159
root_api = "https://api.github.com"
160
pattern = r"update(?::(.+))?"
161
162
def items(self):
163
tag = "latest"
164
check = exact = False
165
166
variant = version.__variant__ or "stable/windows"
167
repo, _, binary = variant.partition("/")
168
169
target = self.groups[0]
170
if target == "latest":
171
pass
172
elif target == "check":
173
check = True
174
else:
175
channel, sep, target = target.partition("@")
176
if sep:
177
repo = channel
178
tag = target
179
exact = True
180
elif channel in REPOS:
181
repo = channel
182
else:
183
tag = channel
184
exact = True
185
186
if util.re_compile(r"\d\.\d+\.\d+").match(tag):
187
tag = "v" + tag
188
189
try:
190
path_repo = REPOS[repo or "stable"]
191
except KeyError:
192
raise exception.AbortExtraction(f"Invalid channel '{repo}'")
193
194
path_tag = tag if tag == "latest" else "tags/" + tag
195
url = f"{self.root_api}/repos/{path_repo}/releases/{path_tag}"
196
headers = {
197
"Accept": "application/vnd.github+json",
198
"User-Agent": util.USERAGENT,
199
"X-GitHub-Api-Version": "2022-11-28",
200
}
201
data = self.request(url, headers=headers, notfound="tag").json()
202
data["_check"] = check
203
data["_exact"] = exact
204
205
if binary == "linux" and \
206
repo != "stable" and \
207
data["tag_name"] <= "2024.05.28":
208
binary_name = "gallery-dl_ubuntu"
209
else:
210
binary_name = BINARIES[repo][binary]
211
212
url = (f"{self.root}/{path_repo}/releases/download"
213
f"/{data['tag_name']}/{binary_name}")
214
215
yield Message.Directory, data
216
yield Message.Url, url, data
217
218