diff options
Diffstat (limited to 'gallery_dl/update.py')
| -rw-r--r-- | gallery_dl/update.py | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/gallery_dl/update.py b/gallery_dl/update.py new file mode 100644 index 0000000..b068e37 --- /dev/null +++ b/gallery_dl/update.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- + +# Copyright 2024 Mike Fährmann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. + +import os +import re +import sys + +from .extractor.common import Extractor, Message +from .job import DownloadJob +from . import util, version, exception + +REPOS = { + "stable" : "mikf/gallery-dl", + "dev" : "gdl-org/builds", + "nightly": "gdl-org/builds", + "master" : "gdl-org/builds", +} + +BINARIES_STABLE = { + "windows" : "gallery-dl.exe", + "windows_x86": "gallery-dl.exe", + "windows_x64": "gallery-dl.exe", + "linux" : "gallery-dl.bin", +} +BINARIES_DEV = { + "windows" : "gallery-dl_windows.exe", + "windows_x86": "gallery-dl_windows_x86.exe", + "windows_x64": "gallery-dl_windows.exe", + "linux" : "gallery-dl_linux", + "macos" : "gallery-dl_macos", +} +BINARIES = { + "stable" : BINARIES_STABLE, + "dev" : BINARIES_DEV, + "nightly": BINARIES_DEV, + "master" : BINARIES_DEV, +} + + +class UpdateJob(DownloadJob): + + def handle_url(self, url, kwdict): + if not self._check_update(kwdict): + if kwdict["_check"]: + self.status |= 1 + return self.extractor.log.info( + "gallery-dl is up to date (%s)", version.__version__) + + if kwdict["_check"]: + return self.extractor.log.info( + "A new release is available: %s -> %s", + version.__version__, kwdict["tag_name"]) + + self.extractor.log.info( + "Updating from %s to %s", + version.__version__, kwdict["tag_name"]) + + path_old = sys.executable + ".old" + path_new = sys.executable + ".new" + directory, filename = os.path.split(sys.executable) + + pathfmt = self.pathfmt + pathfmt.extension = "new" + pathfmt.filename = filename + pathfmt.temppath = path_new + pathfmt.realpath = pathfmt.path = sys.executable + pathfmt.realdirectory = pathfmt.directory = directory + + self._newline = True + if not self.download(url): + self.status |= 4 + return self._error("Failed to download %s", url.rpartition("/")[2]) + + if not util.WINDOWS: + try: + mask = os.stat(sys.executable).st_mode + except OSError: + mask = 0o755 + self._warning("Unable to get file permission bits") + + try: + os.replace(sys.executable, path_old) + except OSError: + return self._error("Unable to move current executable") + + try: + pathfmt.finalize() + except OSError: + self._error("Unable to overwrite current executable") + return os.replace(path_old, sys.executable) + + if util.WINDOWS: + import atexit + import subprocess + + cmd = 'ping 127.0.0.1 -n 5 -w 1000 & del /F "{}"'.format(path_old) + atexit.register( + util.Popen, cmd, shell=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + ) + + else: + try: + os.unlink(path_old) + except OSError: + self._warning("Unable to delete old executable") + + try: + os.chmod(sys.executable, mask) + except OSError: + self._warning("Unable to restore file permission bits") + + self.out.success(pathfmt.path) + + def _check_update(self, kwdict): + if kwdict["_exact"]: + return True + + tag = kwdict["tag_name"] + + if tag[0] == "v": + kwdict["tag_name"] = tag = tag[1:] + ver, _, dev = version.__version__.partition("-") + + version_local = [int(v) for v in ver.split(".")] + version_remote = [int(v) for v in tag.split(".")] + + if dev: + version_local[-1] -= 0.5 + if version_local >= version_remote: + return False + + elif version.__version__.endswith(":" + tag): + return False + + return True + + def _warning(self, msg, *args): + if self._newline: + self._newline = False + print() + self.extractor.log.warning(msg, *args) + + def _error(self, msg, *args): + if self._newline: + self._newline = False + print() + self.status |= 1 + self.extractor.log.error(msg, *args) + + +class UpdateExtractor(Extractor): + category = "update" + root = "https://github.com" + root_api = "https://api.github.com" + pattern = r"update(?::(.+))?" + + def items(self): + tag = "latest" + check = exact = False + + variant = version.__variant__ or "stable/windows" + repo, _, binary = variant.partition("/") + + target = self.groups[0] + if target == "latest": + pass + elif target == "check": + check = True + else: + channel, sep, target = target.partition("@") + if sep: + repo = channel + tag = target + exact = True + elif channel in REPOS: + repo = channel + else: + tag = channel + exact = True + + if re.match(r"\d\.\d+\.\d+", tag): + tag = "v" + tag + + try: + path_repo = REPOS[repo or "stable"] + except KeyError: + raise exception.StopExtraction("Invalid channel '%s'", repo) + + path_tag = tag if tag == "latest" else "tags/" + tag + url = "{}/repos/{}/releases/{}".format( + self.root_api, path_repo, path_tag) + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": util.USERAGENT, + "X-GitHub-Api-Version": "2022-11-28", + } + data = self.request(url, headers=headers, notfound="tag").json() + data["_check"] = check + data["_exact"] = exact + + if binary == "linux" and \ + repo != "stable" and \ + data["tag_name"] <= "2024.05.28": + binary_name = "gallery-dl_ubuntu" + else: + binary_name = BINARIES[repo][binary] + + url = "{}/{}/releases/download/{}/{}".format( + self.root, path_repo, data["tag_name"], binary_name) + + yield Message.Directory, data + yield Message.Url, url, data |
