|
1 | | -import time |
2 | | -import urllib.parse |
3 | | -from pathlib import Path |
4 | 1 | from typing import override |
5 | 2 |
|
6 | 3 | from archinstall.lib.menu.abstract_menu import AbstractSubMenu |
7 | 4 | from archinstall.lib.menu.helpers import Input, Loading, Selection |
8 | 5 | from archinstall.lib.menu.list_manager import ListManager |
| 6 | +from archinstall.lib.mirror.mirror_handler import MirrorListHandler |
9 | 7 | from archinstall.lib.models.mirrors import ( |
10 | 8 | CustomRepository, |
11 | 9 | CustomServer, |
12 | 10 | MirrorConfiguration, |
13 | 11 | MirrorRegion, |
14 | | - MirrorStatusEntryV3, |
15 | | - MirrorStatusListV3, |
16 | 12 | SignCheck, |
17 | 13 | SignOption, |
18 | 14 | ) |
19 | 15 | from archinstall.lib.models.packages import Repository |
20 | | -from archinstall.lib.networking import fetch_data_from_url |
21 | | -from archinstall.lib.output import FormattedOutput, debug, info |
| 16 | +from archinstall.lib.output import FormattedOutput |
22 | 17 | from archinstall.lib.translationhandler import tr |
23 | 18 | from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup |
24 | 19 | from archinstall.tui.ui.result import ResultType |
@@ -206,161 +201,6 @@ async def _add_custom_server(self, preset: CustomServer | None = None) -> Custom |
206 | 201 | return None |
207 | 202 |
|
208 | 203 |
|
209 | | -class MirrorListHandler: |
210 | | - def __init__( |
211 | | - self, |
212 | | - local_mirrorlist: Path = Path('/etc/pacman.d/mirrorlist'), |
213 | | - offline: bool = False, |
214 | | - verbose: bool = False, |
215 | | - ) -> None: |
216 | | - self._local_mirrorlist = local_mirrorlist |
217 | | - self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None |
218 | | - self._fetched_remote: bool = False |
219 | | - self.offline = offline |
220 | | - self.verbose = verbose |
221 | | - |
222 | | - def _mappings(self) -> dict[str, list[MirrorStatusEntryV3]]: |
223 | | - if self._status_mappings is None: |
224 | | - self.load_mirrors() |
225 | | - |
226 | | - assert self._status_mappings is not None |
227 | | - return self._status_mappings |
228 | | - |
229 | | - def get_mirror_regions(self) -> list[MirrorRegion]: |
230 | | - available_mirrors = [] |
231 | | - mappings = self._mappings() |
232 | | - |
233 | | - for region_name, status_entry in mappings.items(): |
234 | | - urls = [entry.server_url for entry in status_entry] |
235 | | - region = MirrorRegion(region_name, urls) |
236 | | - available_mirrors.append(region) |
237 | | - |
238 | | - return available_mirrors |
239 | | - |
240 | | - def load_mirrors(self) -> None: |
241 | | - if self.offline: |
242 | | - self._fetched_remote = False |
243 | | - self.load_local_mirrors() |
244 | | - else: |
245 | | - self._fetched_remote = self.load_remote_mirrors() |
246 | | - debug(f'load mirrors: {self._fetched_remote}') |
247 | | - if not self._fetched_remote: |
248 | | - self.load_local_mirrors() |
249 | | - |
250 | | - def load_remote_mirrors(self) -> bool: |
251 | | - url = 'https://archlinux.org/mirrors/status/json/' |
252 | | - attempts = 3 |
253 | | - |
254 | | - for attempt_nr in range(attempts): |
255 | | - try: |
256 | | - mirrorlist = fetch_data_from_url(url) |
257 | | - self._status_mappings = self._parse_remote_mirror_list(mirrorlist) |
258 | | - return True |
259 | | - except Exception as e: |
260 | | - debug(f'Error while fetching mirror list: {e}') |
261 | | - time.sleep(attempt_nr + 1) |
262 | | - |
263 | | - debug('Unable to fetch mirror list remotely, falling back to local mirror list') |
264 | | - return False |
265 | | - |
266 | | - def load_local_mirrors(self) -> None: |
267 | | - with self._local_mirrorlist.open('r') as fp: |
268 | | - mirrorlist = fp.read() |
269 | | - self._status_mappings = self._parse_local_mirrors(mirrorlist) |
270 | | - |
271 | | - def get_status_by_region(self, region: str, speed_sort: bool) -> list[MirrorStatusEntryV3]: |
272 | | - mappings = self._mappings() |
273 | | - region_list = mappings[region] |
274 | | - |
275 | | - # Only sort if we have remote mirror data with score/speed info |
276 | | - # Local mirrors lack this data and can be modified manually before-hand |
277 | | - # Or reflector potentially ran already |
278 | | - if self._fetched_remote and speed_sort: |
279 | | - info('Sorting your selected mirror list based on the speed between you and the individual mirrors (this might take a while)') |
280 | | - # Sort by speed descending (higher is better in bitrate form core.db download) |
281 | | - return sorted(region_list, key=lambda mirror: -mirror.speed) |
282 | | - # just return as-is without sorting? |
283 | | - return region_list |
284 | | - |
285 | | - def _parse_remote_mirror_list(self, mirrorlist: str) -> dict[str, list[MirrorStatusEntryV3]]: |
286 | | - context = {'verbose': self.verbose} |
287 | | - mirror_status = MirrorStatusListV3.model_validate_json(mirrorlist, context=context) |
288 | | - |
289 | | - sorting_placeholder: dict[str, list[MirrorStatusEntryV3]] = {} |
290 | | - |
291 | | - for mirror in mirror_status.urls: |
292 | | - # We filter out mirrors that have bad criteria values |
293 | | - if any( |
294 | | - [ |
295 | | - mirror.active is False, # Disabled by mirror-list admins |
296 | | - mirror.last_sync is None, # Has not synced recently |
297 | | - # mirror.score (error rate) over time reported from backend: |
298 | | - # https://github.com/archlinux/archweb/blob/31333d3516c91db9a2f2d12260bd61656c011fd1/mirrors/utils.py#L111C22-L111C66 |
299 | | - (mirror.score is None or mirror.score >= 100), |
300 | | - ] |
301 | | - ): |
302 | | - continue |
303 | | - |
304 | | - if mirror.country == '': |
305 | | - # TODO: This should be removed once RFC!29 is merged and completed |
306 | | - # Until then, there are mirrors which lacks data in the backend |
307 | | - # and there is no way of knowing where they're located. |
308 | | - # So we have to assume world-wide |
309 | | - mirror.country = 'Worldwide' |
310 | | - |
311 | | - if mirror.url.startswith('http'): |
312 | | - sorting_placeholder.setdefault(mirror.country, []).append(mirror) |
313 | | - |
314 | | - sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict( |
315 | | - {region: unsorted_mirrors for region, unsorted_mirrors in sorted(sorting_placeholder.items(), key=lambda item: item[0])} |
316 | | - ) |
317 | | - |
318 | | - return sorted_by_regions |
319 | | - |
320 | | - def _parse_local_mirrors(self, mirrorlist: str) -> dict[str, list[MirrorStatusEntryV3]]: |
321 | | - lines = mirrorlist.splitlines() |
322 | | - |
323 | | - # remove empty lines |
324 | | - # lines = [line for line in lines if line] |
325 | | - |
326 | | - mirror_list: dict[str, list[MirrorStatusEntryV3]] = {} |
327 | | - |
328 | | - current_region = '' |
329 | | - |
330 | | - for line in lines: |
331 | | - line = line.strip() |
332 | | - |
333 | | - if line.startswith('## '): |
334 | | - current_region = line.replace('## ', '').strip() |
335 | | - mirror_list.setdefault(current_region, []) |
336 | | - |
337 | | - if line.startswith('Server = '): |
338 | | - if not current_region: |
339 | | - current_region = 'Local' |
340 | | - mirror_list.setdefault(current_region, []) |
341 | | - |
342 | | - url = line.removeprefix('Server = ') |
343 | | - |
344 | | - mirror_entry = MirrorStatusEntryV3( |
345 | | - url=url.removesuffix('$repo/os/$arch'), |
346 | | - protocol=urllib.parse.urlparse(url).scheme, |
347 | | - active=True, |
348 | | - country=current_region or 'Worldwide', |
349 | | - # The following values are normally populated by |
350 | | - # archlinux.org mirror-list endpoint, and can't be known |
351 | | - # from just the local mirror-list file. |
352 | | - country_code='WW', |
353 | | - isos=True, |
354 | | - ipv4=True, |
355 | | - ipv6=True, |
356 | | - details='Locally defined mirror', |
357 | | - ) |
358 | | - |
359 | | - mirror_list[current_region].append(mirror_entry) |
360 | | - |
361 | | - return mirror_list |
362 | | - |
363 | | - |
364 | 204 | class MirrorMenu(AbstractSubMenu[MirrorConfiguration]): |
365 | 205 | def __init__( |
366 | 206 | self, |
|
0 commit comments