-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathget-git-releases.py
More file actions
executable file
·161 lines (138 loc) · 5.5 KB
/
get-git-releases.py
File metadata and controls
executable file
·161 lines (138 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env python3
import json
import logging
import os
import re
import requests
import subprocess
import threading
from concurrent.futures import ThreadPoolExecutor, wait
# 全局配置变量
CONFIG = {
"data_dir": "data",
"deb_dir": "deb",
"packages_dir": "packages",
"thread": 5,
"dry_run": False,
}
tag_lock = threading.Lock()
# 日志等级,若需要展示每次请求结果请使用 INFO 等级
logging.basicConfig(level=logging.INFO)
# 读取 JSON 文件
def read_json(filename: str) -> dict:
try:
with open(os.path.join(CONFIG["data_dir"], filename), "r") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
logging.error(e)
return {}
# 获取最新标签
def latest_releases_tag(repo_url: str) -> str:
url = f"{repo_url}/releases/latest"
try:
location = requests.head(url).headers.get("Location", "")
match = re.search(r".*releases/tag/([^/]+)", location)
return match.group(1) if match else ""
except requests.RequestException as e:
logging.error(e)
return ""
def format_release_filename(template: str, releases_tag: str) -> str:
# https://www.debian.org/doc/manuals/debmake-doc/ch06.zh-cn.html#name-version
# 参考文档中 Upstream version 正则表达式获取版本号,即从第一个数字开始。
vpattern = "[0-9][-+.:~a-z0-9A-Z]*"
version = match.group() if (match := re.search(vpattern, releases_tag)) else ""
return template.format(
releases_tag=releases_tag, # 若存在则用完整 tag 替换
version=version, # 若存在则用 version 替换
)
# 下载文件
def download(url: str, file_path: str) -> bool:
# 检查是否为 dry-run 模式
method = requests.head if CONFIG["dry_run"] else requests.get
response = method(url, stream=True, allow_redirects=True)
if response.status_code != 200:
logging.error(f"Can't download {url} because received {response.status_code}")
return False
if CONFIG["dry_run"]:
logging.info(f"Dry-run download: {url}")
else:
logging.info(f"Downloading: {url}")
with open(file_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return True
def scan(name, arch, url, file_path) -> bool:
scan_process = subprocess.run(
["apt-ftparchive", "packages", file_path], capture_output=True
)
package = scan_process.stdout.decode()
package = re.sub(
r"^(Filename: ).*", f"\\1{url}", package, flags=re.MULTILINE
) # 替换 Filename 开头的行为完整下载路径
package_file_path = os.path.join(CONFIG["packages_dir"], arch, f"{name}.package")
try:
with open(package_file_path, "w") as f:
f.write(package)
return True
except IOError as e:
logging.error(f"Failed to write package file for {name}: {e}")
return False
# 检查版本并下载新版本文件
def check(name: str, repo: dict, tag_list: dict) -> None:
if "site" in repo:
repo_url = os.path.join(repo["site"], repo["repo"])
else:
# 默认认为是 GitHub 仓库地址
repo_url = os.path.join("https://github.com", repo["repo"])
releases_tag = latest_releases_tag(repo_url)
if not releases_tag:
logging.error(f"Can't get latest releases tag of {name}")
return
logging.info(f"{name} = {releases_tag}")
# 判断是否需要更新
local_tag = tag_list.get(name, "")
if not releases_tag or local_tag == releases_tag:
return
name = repo["package_name"] if "package_name" in repo else name
for arch, template in repo["file_list"].items():
# 确定本地文件目录并确保目录存在
app_dir = os.path.join(CONFIG["deb_dir"], name)
os.makedirs(app_dir, exist_ok=True)
# 得到 Releases 中的文件名
release_filename = format_release_filename(template, releases_tag)
url = f"{repo_url}/releases/download/{releases_tag}/{release_filename}"
file_path = os.path.join(app_dir, release_filename)
# download and scan
logging.info(f"Downloading {name}:{arch} ({releases_tag})")
if not download(url, file_path):
continue
logging.info(f"Downloaded {name}:{arch} ({releases_tag})")
os.makedirs(os.path.join(CONFIG["packages_dir"], arch), exist_ok=True)
if not scan(name, arch, url, file_path):
continue
# 判断是否是新添加应用
if local_tag == "":
print(f"AddNew: {name}:{arch} ({releases_tag})")
else:
print(f"Update: {name}:{arch} ({local_tag} -> {releases_tag})")
# 删除旧版本文件
old_file_path = os.path.join(
app_dir, format_release_filename(template, local_tag)
)
if old_file_path != file_path and os.path.exists(old_file_path):
os.remove(old_file_path)
# 更新版本号
with tag_lock:
tag_list[name] = releases_tag
if __name__ == "__main__":
git_repo_list = read_json("git-repo.json")
tag_list = read_json("git-tag.json")
with ThreadPoolExecutor(max_workers=CONFIG["thread"]) as executor:
tasks = [
executor.submit(check, name, repo, tag_list)
for name, repo in git_repo_list.items()
]
wait(tasks)
# 保存到 git-tag.json
with open(os.path.join(CONFIG["data_dir"], "git-tag.json"), "w") as f:
json.dump(tag_list, f, indent=4)