Fix local mirror selection (#2789)
This commit is contained in:
parent
fbc9431697
commit
9951c90bf9
|
|
@ -1,6 +1,6 @@
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import pathlib
|
from pathlib import Path
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Any, List, Optional, TYPE_CHECKING
|
from typing import Dict, Any, List, Optional, TYPE_CHECKING
|
||||||
|
|
@ -257,29 +257,51 @@ def select_mirror_regions(preset_values: Dict[str, List[str]] = {}) -> Dict[str,
|
||||||
else:
|
else:
|
||||||
preselected = list(preset_values.keys())
|
preselected = list(preset_values.keys())
|
||||||
|
|
||||||
mirrors = list_mirrors()
|
remote_mirrors = list_mirrors_from_remote()
|
||||||
|
mirrors: Dict[str, list[str]] = {}
|
||||||
|
|
||||||
choice = Menu(
|
if remote_mirrors:
|
||||||
_('Select one of the regions to download packages from'),
|
choice = Menu(
|
||||||
list(mirrors.keys()),
|
_('Select one of the regions to download packages from'),
|
||||||
preset_values=preselected,
|
list(remote_mirrors.keys()),
|
||||||
multi=True,
|
preset_values=preselected,
|
||||||
allow_reset=True
|
multi=True,
|
||||||
).run()
|
allow_reset=True
|
||||||
|
).run()
|
||||||
|
|
||||||
match choice.type_:
|
match choice.type_:
|
||||||
case MenuSelectionType.Reset:
|
case MenuSelectionType.Reset:
|
||||||
return {}
|
return {}
|
||||||
case MenuSelectionType.Skip:
|
case MenuSelectionType.Skip:
|
||||||
return preset_values
|
return preset_values
|
||||||
case MenuSelectionType.Selection:
|
case MenuSelectionType.Selection:
|
||||||
return {
|
for region in choice.multi_value:
|
||||||
selected: [
|
mirrors.setdefault(region, [])
|
||||||
f"{mirror.url}$repo/os/$arch" for mirror in sort_mirrors_by_performance(mirrors[selected])
|
for mirror in _sort_mirrors_by_performance(remote_mirrors[region]):
|
||||||
] for selected in choice.multi_value
|
mirrors[region].append(mirror.server_url)
|
||||||
}
|
return mirrors
|
||||||
|
else:
|
||||||
|
local_mirrors = list_mirrors_from_local()
|
||||||
|
|
||||||
return {}
|
choice = Menu(
|
||||||
|
_('Select one of the regions to download packages from'),
|
||||||
|
list(local_mirrors.keys()),
|
||||||
|
preset_values=preselected,
|
||||||
|
multi=True,
|
||||||
|
allow_reset=True
|
||||||
|
).run()
|
||||||
|
|
||||||
|
match choice.type_:
|
||||||
|
case MenuSelectionType.Reset:
|
||||||
|
return {}
|
||||||
|
case MenuSelectionType.Skip:
|
||||||
|
return preset_values
|
||||||
|
case MenuSelectionType.Selection:
|
||||||
|
for region in choice.multi_value:
|
||||||
|
mirrors[region] = local_mirrors[region]
|
||||||
|
return mirrors
|
||||||
|
|
||||||
|
return mirrors
|
||||||
|
|
||||||
|
|
||||||
def select_custom_mirror(prompt: str = '', preset: List[CustomMirror] = []) -> list[CustomMirror]:
|
def select_custom_mirror(prompt: str = '', preset: List[CustomMirror] = []) -> list[CustomMirror]:
|
||||||
|
|
@ -287,11 +309,35 @@ def select_custom_mirror(prompt: str = '', preset: List[CustomMirror] = []) -> l
|
||||||
return custom_mirrors
|
return custom_mirrors
|
||||||
|
|
||||||
|
|
||||||
def sort_mirrors_by_performance(mirror_list: List[MirrorStatusEntryV3]) -> List[MirrorStatusEntryV3]:
|
def list_mirrors_from_remote() -> Optional[Dict[str, List[MirrorStatusEntryV3]]]:
|
||||||
|
if not storage['arguments']['offline']:
|
||||||
|
url = "https://archlinux.org/mirrors/status/json/"
|
||||||
|
attempts = 3
|
||||||
|
|
||||||
|
for attempt_nr in range(attempts):
|
||||||
|
try:
|
||||||
|
mirrorlist = fetch_data_from_url(url)
|
||||||
|
return _parse_remote_mirror_list(mirrorlist)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'Error while fetching mirror list: {e}')
|
||||||
|
time.sleep(attempt_nr + 1)
|
||||||
|
|
||||||
|
debug('Unable to fetch mirror list remotely, falling back to local mirror list')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def list_mirrors_from_local() -> Dict[str, list[str]]:
|
||||||
|
with Path('/etc/pacman.d/mirrorlist').open('r') as fp:
|
||||||
|
mirrorlist = fp.read()
|
||||||
|
return _parse_locale_mirrors(mirrorlist)
|
||||||
|
|
||||||
|
|
||||||
|
def _sort_mirrors_by_performance(mirror_list: List[MirrorStatusEntryV3]) -> List[MirrorStatusEntryV3]:
|
||||||
return sorted(mirror_list, key=lambda mirror: (mirror.score, mirror.speed))
|
return sorted(mirror_list, key=lambda mirror: (mirror.score, mirror.speed))
|
||||||
|
|
||||||
|
|
||||||
def _parse_mirror_list(mirrorlist: str) -> Dict[str, List[MirrorStatusEntryV3]]:
|
def _parse_remote_mirror_list(mirrorlist: str) -> Dict[str, List[MirrorStatusEntryV3]]:
|
||||||
mirror_status = MirrorStatusListV3(**json.loads(mirrorlist))
|
mirror_status = MirrorStatusListV3(**json.loads(mirrorlist))
|
||||||
|
|
||||||
sorting_placeholder: Dict[str, List[MirrorStatusEntryV3]] = {}
|
sorting_placeholder: Dict[str, List[MirrorStatusEntryV3]] = {}
|
||||||
|
|
@ -324,23 +370,27 @@ def _parse_mirror_list(mirrorlist: str) -> Dict[str, List[MirrorStatusEntryV3]]:
|
||||||
return sorted_by_regions
|
return sorted_by_regions
|
||||||
|
|
||||||
|
|
||||||
def list_mirrors() -> Dict[str, List[MirrorStatusEntryV3]]:
|
def _parse_locale_mirrors(mirrorlist: str) -> Dict[str, List[str]]:
|
||||||
if not storage['arguments']['offline']:
|
lines = mirrorlist.splitlines()
|
||||||
url = "https://archlinux.org/mirrors/status/json/"
|
|
||||||
attempts = 3
|
|
||||||
|
|
||||||
for attempt_nr in range(attempts):
|
# remove empty lines
|
||||||
try:
|
lines = [line for line in lines if line]
|
||||||
mirrorlist = fetch_data_from_url(url)
|
|
||||||
return _parse_mirror_list(mirrorlist)
|
|
||||||
except Exception as e:
|
|
||||||
debug(f'Error while fetching mirror list: {e}')
|
|
||||||
time.sleep(attempt_nr + 1)
|
|
||||||
|
|
||||||
debug('Unable to fetch mirror list remotely, falling back to local mirror list')
|
mirror_list: Dict[str, List[str]] = {}
|
||||||
|
|
||||||
# we'll use the local mirror list if the offline flag is set
|
current_region = ''
|
||||||
# or if fetching the mirror list remotely failed
|
for idx, line in enumerate(lines):
|
||||||
with pathlib.Path('/etc/pacman.d/mirrorlist').open('r') as fp:
|
line = line.strip()
|
||||||
mirrorlist = fp.read()
|
|
||||||
return _parse_mirror_list(mirrorlist)
|
if line.lower().startswith('server'):
|
||||||
|
if not current_region:
|
||||||
|
for i in range(idx - 1, 0, -1):
|
||||||
|
if lines[i].startswith('##'):
|
||||||
|
current_region = lines[i].replace('#', '').strip()
|
||||||
|
mirror_list.setdefault(current_region, [])
|
||||||
|
break
|
||||||
|
|
||||||
|
url = line.removeprefix('Server = ')
|
||||||
|
mirror_list[current_region].append(url)
|
||||||
|
|
||||||
|
return mirror_list
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from typing import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..networking import ping, DownloadTimer
|
from ..networking import ping, DownloadTimer
|
||||||
from ..output import info, debug
|
from ..output import debug
|
||||||
|
|
||||||
|
|
||||||
class MirrorStatusEntryV3(pydantic.BaseModel):
|
class MirrorStatusEntryV3(pydantic.BaseModel):
|
||||||
|
|
@ -35,6 +35,10 @@ class MirrorStatusEntryV3(pydantic.BaseModel):
|
||||||
_port: int | None = None
|
_port: int | None = None
|
||||||
_speedtest_retries: int | None = None
|
_speedtest_retries: int | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def server_url(self) -> str:
|
||||||
|
return f'{self.url}$repo/os/$arch'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed(self) -> float:
|
def speed(self) -> float:
|
||||||
if self._speed is None:
|
if self._speed is None:
|
||||||
|
|
@ -45,7 +49,7 @@ class MirrorStatusEntryV3(pydantic.BaseModel):
|
||||||
|
|
||||||
_retry = 0
|
_retry = 0
|
||||||
while _retry < self._speedtest_retries and self._speed is None:
|
while _retry < self._speedtest_retries and self._speed is None:
|
||||||
info(f"Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db")
|
debug(f"Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db")
|
||||||
req = urllib.request.Request(url=f"{self.url}core/os/x86_64/core.db")
|
req = urllib.request.Request(url=f"{self.url}core/os/x86_64/core.db")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -81,7 +85,7 @@ class MirrorStatusEntryV3(pydantic.BaseModel):
|
||||||
We do this because some hosts blocks ICMP so we'll have to rely on .speed() instead which is slower.
|
We do this because some hosts blocks ICMP so we'll have to rely on .speed() instead which is slower.
|
||||||
"""
|
"""
|
||||||
if self._latency is None:
|
if self._latency is None:
|
||||||
info(f"Checking latency for {self.url}")
|
debug(f"Checking latency for {self.url}")
|
||||||
self._latency = ping(self._hostname, timeout=2)
|
self._latency = ping(self._hostname, timeout=2)
|
||||||
debug(f" latency: {self._latency}")
|
debug(f" latency: {self._latency}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue