-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathflipperdump.py
270 lines (250 loc) · 10.1 KB
/
flipperdump.py
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# awesome-flipperzero-pack gets all resources for a flipper zero,
# from https://github.com/djsime1/awesome-flipperzero
# and downloads them to create a zip file with all the resources
# for easy installation on the SD card.
# The zip file is then uploaded to the releases page of this repo.
import requests
import re
import os
import json
import tempfile
import pathlib
import subprocess
import datetime
import asyncio
import concurrent.futures
import shutil
class Main:
def __init__(self):
self.TMPDIR = pathlib.Path(tempfile.mkdtemp())
self.REGEX_CATEGORY = re.compile(r"^## (.*)")
self.REGEX_LINK = re.compile(r"^- \[(.*)\]\((.*)\)")
self.REGEX_GITHUB_REPO = re.compile(r"^https://github.com/(.*)/(.*)$")
self.NOW = datetime.datetime.now()
self.VERSION = self.NOW.strftime("v0.0.%Y%m%d%H%M")
self.SEMA = asyncio.Semaphore(30)
self.PACKAGES = {
"microsd": [
"databases-dumps",
"applications-plugins",
"graphics-animations",
],
"tweaks": [
"firmwares-tweaks",
"modules-cases",
"off-device-debugging",
"notes-references",
],
"microsd-wavplayer": [],
}
def _urlify(self, string):
# this removes all non-alphanumeric characters from a string to a dash
# and makes it lowercase
# additionally it removes all double dashes and leading and trailing dashes
return re.sub(
r"-+",
"-",
re.sub(r"^-+|-+$", "", re.sub(r"[^a-zA-Z0-9]+", "-", string.lower())),
)
async def _exec_wait(self, *args, **kwargs):
proc = await asyncio.create_subprocess_exec(
*args,
**kwargs,
)
await proc.wait()
return proc.returncode
def _get_awesome_list(self):
r = requests.get(
"https://raw.githubusercontent.com/djsime1/awesome-flipperzero/main/README.md"
)
# We want to parse the markdown with mistletoe,
# then we get a list of links per category.
# We then want to download all the links.
# Most links are github, so we can clone the latest default branch,
# some are some specific path of a github repo, so we can download the latest default branch and only the path.
# Some are just a link to a zip file, or a PDF, so we can download that.
# Let's not use a markdown parser, but just regex.
# Get all item number, names and links per category
categories = []
current_category = None
for i, line in enumerate(r.text.splitlines()):
if category := self.REGEX_CATEGORY.match(line):
current_category = category.group(1)
categories.append(
{
"name": current_category,
"url": self._urlify(current_category),
"line": line,
"items": [],
"_i": str(i + 1).zfill(4),
}
)
elif link := self.REGEX_LINK.match(line):
categories[-1]["items"].append(
{
"name": link.group(1),
"url": self._urlify(link.group(1)),
"line": line,
"link": link.group(2),
"_i": str(i + 1).zfill(4),
}
)
return categories
async def _download_github_repo(self, url, path):
async with self.SEMA:
os.makedirs(path, exist_ok=True)
proc = await asyncio.create_subprocess_exec(
"git",
"clone",
"--depth",
"1",
url,
str(path),
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
# wait until exit
await proc.wait()
print("downloaded", proc.returncode, url, path)
# get the return code
return proc.returncode
async def _safe_download(self, package, category, item):
# mkdir /self.TMPDIR/package/i-category/i-link
# path = pathlib.Path(self.TMPDIR) / package / item["_i"] + "-" + item["url"]
# not work PosixPath + str
path = (
pathlib.Path(self.TMPDIR)
/ package
/ (category["_i"] + "-" + category["url"])
/ (item["_i"] + "-" + item["url"])
)
# if the link is a github repo, clone it with depth 1
if github := self.REGEX_GITHUB_REPO.match(item["link"]):
res = await self._download_github_repo(item["link"], path)
if res == 0:
# success. downloaded.md
with open(self.TMPDIR / package / "downloaded.md", "a") as f:
f.write(item["line"] + "\n")
# remove .git
shutil.rmtree(path / ".git")
else:
# failed. skipped.md
with open(self.TMPDIR / package / "skipped.md", "a") as f:
f.write(item["line"] + " (failed: " + str(res) + ")\n")
print("failed", item["link"], path, res)
async def run(self):
links = self._get_awesome_list()
# now we have categories and links
# let's create a zipfile with all the resources
# print only categories
items = []
for package, categories in self.PACKAGES.items():
for category_filter in categories:
for category in links:
if category["url"] != category_filter:
continue
for item in category["items"]:
items.append((package, category, item))
await asyncio.gather(*[self._safe_download(*item) for item in items])
# move $(find . -type d -name Wav_Player)
# to microsd-wavplayer
dir_wavplayer = subprocess.run(
[
"find",
self.TMPDIR / "microsd",
"-type",
"d",
"-name",
"Wav_Player",
],
capture_output=True,
text=True,
).stdout.strip()
os.rename(
dir_wavplayer,
self.TMPDIR / "microsd-wavplayer",
)
# zip every package
tasks = []
for package in self.PACKAGES.keys():
# with lowest compression
tasks.append(
self._exec_wait(
"zip",
"-r",
package + ".zip",
package,
cwd=self.TMPDIR,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
)
await asyncio.gather(*tasks)
for package in self.PACKAGES.keys():
# move the file to proper awesome-flipperzero-pack release name
new_name = self.TMPDIR / (
"awesome-flipperzero-pack-" + self.VERSION + "-" + package + ".zip"
)
os.rename(
self.TMPDIR / (package + ".zip"),
new_name,
)
# we want a file tree that shows
# the full size but not
# the individual file lines.
file_tree = subprocess.run(
[
"tree",
"-h",
"--du",
"-L",
"3",
self.TMPDIR / package,
],
capture_output=True,
text=True,
).stdout
# write it to files.txt
with open(self.TMPDIR / package / "files.txt", "w") as f:
f.write(file_tree)
body = "\n".join(
[
f"# awesome-flipperzero-pack {self.VERSION}\n",
"awesome-flipperzero-pack is a downloadable toolkit of [the awesome-flipperzero resources](https://github.com/djsime1/awesome-flipperzero).",
"These zip files are available:",
f"1. awesome-flipperzero-pack-{self.VERSION}-microsd.zip: for the microsd card (firmwares, databases, plugins, animations)",
f"2. awesome-flipperzero-pack-{self.VERSION}-tweaks.zip: for the tweaks (firmwares, modules, cases, notes)",
f"3. awesome-flipperzero-pack-{self.VERSION}-microsd-wavplayer.zip: for the microsd card (wavplayer, part of [UberGuidoZ/Flipper](https://github.com/UberGuidoZ/Flipper))",
"\n## microsd",
"\n### downloaded",
open(self.TMPDIR / "microsd" / "downloaded.md").read(),
"\n### skipped",
open(self.TMPDIR / "microsd" / "skipped.md").read(),
"\n### files",
# spoiler it
"<details><summary>click to expand</summary>\n\n```\n\n",
open(self.TMPDIR / "microsd" / "files.txt").read(),
"\n\n```\n\n</details>\n",
"\n## tweaks",
"\n### downloaded",
open(self.TMPDIR / "tweaks" / "downloaded.md").read(),
"\n### skipped",
open(self.TMPDIR / "tweaks" / "skipped.md").read(),
"\n### files",
"<details><summary>click to expand</summary>\n\n```\n\n",
open(self.TMPDIR / "tweaks" / "files.txt").read(),
"\n\n```\n\n</details>\n",
"\n## microsd-wavplayer",
"\npart of [UberGuidoZ/Flipper](https://github.com/UberGuidoZ/Flipper), but very big, so it's in a separate zip file",
"\n### files",
"\nSee <https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player>",
]
)
# write body.txt to tmpdir/body.txt
with open(self.TMPDIR / "body.md", "w") as f:
f.write(body)
# move tmpdir to cwd/files
shutil.move(self.TMPDIR, "files")
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(Main().run())