Skip to content

Instantly share code, notes, and snippets.

@ergolyam
Last active July 7, 2026 14:09
Show Gist options
  • Select an option

  • Save ergolyam/9657ccde929cd282df684b7596b3465a to your computer and use it in GitHub Desktop.

Select an option

Save ergolyam/9657ccde929cd282df684b7596b3465a to your computer and use it in GitHub Desktop.
Search for Yggdrasil peers
#!/bin/sh
set -f
URL_DEFAULT='https://peers.yggdrasil.link/publicnodes.json'
url=$URL_DEFAULT
source_file=
sort_mode=ping
records_mode=0
ping_timeout=1
ping_concurrency=8
tmpdir=
ui_started=0
raw_started=0
old_stty=
ping_worker_pid=
ping_status_file=
ping_total=0
die() {
printf 'ygg-peer-picker: %s\n' "$1" >&2
exit 1
}
usage() {
cat <<'EOF'
Usage: ygg-peer-picker.sh [options]
Fetch Yggdrasil public peers, sort them, select peers in a TUI, and print
selected peer addresses one per line after Enter.
Options:
-s, --sort ping|country|proto Initial sort mode (default: ping)
--url URL JSON URL to fetch
--file FILE Read JSON from a local file
--records Print parsed TSV records and exit
--ping-timeout SECONDS Per-peer local ping timeout (default: 1)
--ping-concurrency COUNT Parallel local ping batch size (default: 8)
-h, --help Show this help
TUI keys:
Up/Down or k/j Move cursor
Space Select or unselect peer
p, c, r Sort by ping, country, protocol
Enter Accept selection and print addresses
q or Esc Quit without output
EOF
}
set_sort_mode() {
case $1 in
ping|p|1)
sort_mode=ping
;;
country|c|2)
sort_mode=country
;;
proto|protocol|r|3)
sort_mode=proto
;;
*)
die "unknown sort mode: $1"
;;
esac
}
set_positive_number() {
name=$1
value=$2
case $value in
''|*[!0-9]*|0)
die "$name must be a positive integer"
;;
esac
}
while [ "$#" -gt 0 ]
do
case $1 in
-s|--sort)
shift
[ "$#" -gt 0 ] || die "missing value for --sort"
set_sort_mode "$1"
;;
--url)
shift
[ "$#" -gt 0 ] || die "missing value for --url"
url=$1
;;
--file)
shift
[ "$#" -gt 0 ] || die "missing value for --file"
source_file=$1
;;
--records)
records_mode=1
;;
--ping-timeout)
shift
[ "$#" -gt 0 ] || die "missing value for --ping-timeout"
set_positive_number "--ping-timeout" "$1"
ping_timeout=$1
;;
--ping-concurrency)
shift
[ "$#" -gt 0 ] || die "missing value for --ping-concurrency"
set_positive_number "--ping-concurrency" "$1"
ping_concurrency=$1
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
shift
done
cleanup() {
if [ -n "$ping_worker_pid" ]; then
kill "$ping_worker_pid" 2>/dev/null
wait "$ping_worker_pid" 2>/dev/null
ping_worker_pid=
fi
if [ "$raw_started" -eq 1 ] && [ -n "$old_stty" ]; then
stty "$old_stty" <&4 2>/dev/null
fi
if [ "$ui_started" -eq 1 ]; then
printf '\033[0m\033[?25h\033[?1049l' >&3
fi
if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then
rm -rf "$tmpdir"
fi
}
on_signal() {
exit 130
}
trap cleanup EXIT
trap on_signal HUP INT TERM
make_tmpdir() {
base=${TMPDIR:-/tmp}
base=${base%/}
if command -v mktemp >/dev/null 2>&1; then
tmpdir=$(mktemp -d "$base/ygg-peer-picker.XXXXXX") || exit 1
else
tmpdir=$base/ygg-peer-picker.$$
old_umask=$(umask)
umask 077
mkdir "$tmpdir" || exit 1
umask "$old_umask"
fi
}
fetch_nodes() {
if [ -n "$source_file" ]; then
cat "$source_file"
return $?
fi
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url"
return $?
fi
if command -v wget >/dev/null 2>&1; then
wget -qO- "$url"
return $?
fi
printf 'ygg-peer-picker: need curl or wget to fetch %s\n' "$url" >&2
return 127
}
parse_nodes() {
awk '
BEGIN {
country = ""
addr = ""
proto = ""
host = ""
in_peer = 0
up = 0
}
/^ "[^"]+\.md"[[:space:]]*:[[:space:]]*\{[[:space:]]*,?$/ {
country = $0
sub(/^ "/, "", country)
sub(/\.md"[[:space:]]*:[[:space:]]*\{[[:space:]]*,?$/, "", country)
gsub(/[-_]/, " ", country)
next
}
country != "" && /^ "[^"]+"[[:space:]]*:[[:space:]]*\{[[:space:]]*,?$/ {
addr = $0
sub(/^ "/, "", addr)
sub(/"[[:space:]]*:[[:space:]]*\{[[:space:]]*,?$/, "", addr)
if (addr ~ /^[A-Za-z][A-Za-z0-9+.-]*:\/\//) {
proto = addr
sub(/:\/\/.*/, "", proto)
host = addr
sub(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "", host)
sub(/[\/?].*$/, "", host)
if (host ~ /^\[[^]]+\]/) {
sub(/^\[/, "", host)
sub(/\].*$/, "", host)
} else {
sub(/:.*/, "", host)
}
in_peer = 1
up = 0
}
next
}
in_peer && /^[[:space:]]*"up"[[:space:]]*:[[:space:]]*true/ {
up = 1
next
}
in_peer && /^ \}[,[:space:]]*$/ {
if (up) {
printf "999999\t%s\t%s\t%s\t%s\n", country, proto, addr, host
}
in_peer = 0
addr = ""
proto = ""
host = ""
up = 0
next
}
'
}
require_ping() {
if ! command -v ping >/dev/null 2>&1; then
printf 'ygg-peer-picker: need ping to test latency from this device\n' >&2
return 127
fi
return 0
}
ping_wait_arg() {
case $(uname -s 2>/dev/null) in
Darwin|FreeBSD|OpenBSD|NetBSD)
printf '%s' $((ping_timeout * 1000))
;;
*)
printf '%s' "$ping_timeout"
;;
esac
}
measure_ping_ms() {
host=$1
wait_arg=$(ping_wait_arg)
hard_timeout=$((ping_timeout + 2))
if command -v timeout >/dev/null 2>&1; then
output=$(timeout "$hard_timeout" ping -n -c 1 -W "$wait_arg" "$host" 2>/dev/null)
else
output=$(ping -n -c 1 -W "$wait_arg" "$host" 2>/dev/null)
fi
code=$?
if [ "$code" -ne 0 ]; then
printf '999999'
return
fi
printf '%s\n' "$output" | awk '
BEGIN {
found = 0
}
{
for (i = 1; i <= NF; i++) {
if ($i ~ /^time[=<]/) {
value = $i
sub(/^time[=<]/, "", value)
sub(/[^0-9.].*$/, "", value)
if (value != "") {
print value
found = 1
exit
}
}
}
}
END {
if (!found) {
print "999999"
}
}
'
}
measure_record_line() {
line=$1
tab=$(printf '\t')
old_ifs=$IFS
IFS=$tab
set -- $line
IFS=$old_ifs
country=$2
proto=$3
addr=$4
host=$5
if [ -z "$host" ]; then
ping_ms=999999
else
ping_ms=$(measure_ping_ms "$host")
fi
printf '%s\t%s\t%s\t%s\n' "$ping_ms" "$country" "$proto" "$addr"
}
measure_records_plain() {
input_file=$1
output_file=$2
require_ping || return $?
: > "$output_file"
while IFS= read -r line
do
measure_record_line "$line" >> "$output_file"
done < "$input_file"
}
sort_records() {
file=$1
tab=$(printf '\t')
case $sort_mode in
ping)
sort -t "$tab" -k1,1n -k2,2 -k3,3 -k4,4 "$file"
;;
country)
sort -t "$tab" -k2,2 -k3,3 -k1,1n -k4,4 "$file"
;;
proto)
sort -t "$tab" -k3,3 -k1,1n -k2,2 -k4,4 "$file"
;;
esac
}
build_records_plain() {
json_file=$tmpdir/publicnodes.json
parsed_file=$tmpdir/parsed.tsv
records_file=$tmpdir/records.tsv
fetch_nodes > "$json_file"
code=$?
[ "$code" -eq 0 ] || exit "$code"
parse_nodes < "$json_file" > "$parsed_file"
code=$?
[ "$code" -eq 0 ] || exit "$code"
measure_records_plain "$parsed_file" "$records_file"
code=$?
[ "$code" -eq 0 ] || exit "$code"
}
line_count() {
file=$1
if [ ! -f "$file" ]; then
printf '0'
return
fi
set -- $(wc -l < "$file" 2>/dev/null)
printf '%s' "${1:-0}"
}
file_size() {
file=$1
if [ ! -f "$file" ]; then
printf '0'
return
fi
set -- $(wc -c < "$file" 2>/dev/null)
printf '%s' "${1:-0}"
}
open_tty() {
if ! exec 3>/dev/tty; then
die "cannot open /dev/tty for TUI output"
fi
if ! exec 4</dev/tty; then
die "cannot open /dev/tty for TUI input"
fi
}
enter_ui() {
old_stty=$(stty -g <&4 2>/dev/null)
printf '\033[?1049h\033[?25l\033[H\033[J' >&3
ui_started=1
}
leave_ui() {
if [ "$raw_started" -eq 1 ] && [ -n "$old_stty" ]; then
stty "$old_stty" <&4 2>/dev/null
raw_started=0
fi
if [ "$ui_started" -eq 1 ]; then
printf '\033[0m\033[?25h\033[?1049l' >&3
ui_started=0
fi
}
start_raw_input() {
stty -echo -icanon min 1 time 0 <&4
raw_started=1
}
set_read_timeout() {
stty min 0 time 1 <&4
}
set_read_blocking() {
stty min 1 time 0 <&4
}
read_one() {
dd bs=1 count=1 2>/dev/null <&4
}
read_one_timeout() {
set_read_timeout
ch=$(dd bs=1 count=1 2>/dev/null <&4)
set_read_blocking
printf '%s' "$ch"
}
read_key() {
esc=$(printf '\033')
cr=$(printf '\r')
key=$(read_one)
case $key in
"$esc")
second=$(read_one_timeout)
if [ -z "$second" ]; then
printf 'QUIT\n'
return
fi
if [ "$second" = "[" ]; then
third=$(read_one_timeout)
case $third in
A) printf 'UP\n' ;;
B) printf 'DOWN\n' ;;
*) printf 'NONE\n' ;;
esac
return
fi
printf 'NONE\n'
;;
"")
printf 'ENTER\n'
;;
"$cr")
printf 'ENTER\n'
;;
" ")
printf 'SPACE\n'
;;
j)
printf 'DOWN\n'
;;
k)
printf 'UP\n'
;;
p)
printf 'SORT_PING\n'
;;
c)
printf 'SORT_COUNTRY\n'
;;
r)
printf 'SORT_PROTO\n'
;;
q)
printf 'QUIT\n'
;;
*)
printf 'NONE\n'
;;
esac
}
read_byte_code_timeout() {
timeout_ticks=$1
stty min 0 time "$timeout_ticks" <&4
code=$(dd bs=1 count=1 2>/dev/null <&4 | od -An -tu1 | awk '{ print $1 }')
stty min 1 time 0 <&4
printf '%s' "$code"
}
read_key_poll() {
code=$(read_byte_code_timeout 1)
case $code in
'')
printf 'NONE\n'
;;
27)
second=$(read_byte_code_timeout 1)
if [ -z "$second" ]; then
printf 'QUIT\n'
return
fi
if [ "$second" = 91 ]; then
third=$(read_byte_code_timeout 1)
case $third in
65) printf 'UP\n' ;;
66) printf 'DOWN\n' ;;
*) printf 'NONE\n' ;;
esac
return
fi
printf 'NONE\n'
;;
10|13)
printf 'ENTER\n'
;;
32)
printf 'SPACE\n'
;;
106)
printf 'DOWN\n'
;;
107)
printf 'UP\n'
;;
112)
printf 'SORT_PING\n'
;;
99)
printf 'SORT_COUNTRY\n'
;;
114)
printf 'SORT_PROTO\n'
;;
113)
printf 'QUIT\n'
;;
*)
printf 'NONE\n'
;;
esac
}
draw_loading() {
title=$1
detail=$2
spin=$3
case $spin in
0) mark='|' ;;
1) mark='/' ;;
2) mark='-' ;;
*) mark='\' ;;
esac
printf '\033[H\033[J' >&3
printf '\033[1;36mYggdrasil public peer picker\033[0m\n\n' >&3
printf ' %s %s\n\n' "$mark" "$title" >&3
printf ' %s\n' "$detail" >&3
}
draw_progress_table() {
file=$1
title=$2
detail=$3
spin=$4
progress_view_file=$tmpdir/progress-view.tsv
case $spin in
0) mark='|' ;;
1) mark='/' ;;
2) mark='-' ;;
*) mark='\' ;;
esac
get_size
list_rows=$((rows - 7))
if [ "$list_rows" -lt 1 ]; then
list_rows=1
fi
if [ -s "$file" ]; then
sort_records "$file" > "$progress_view_file"
else
: > "$progress_view_file"
fi
count=$(line_count "$progress_view_file")
printf '\033[H\033[J' >&3
printf '\033[1;36mYggdrasil public peer picker\033[0m\n' >&3
printf ' %s %s\n' "$mark" "$title" >&3
printf ' %s\n\n' "$detail" >&3
printf '\033[2m%-6s %-16s %-7s %s\033[0m\n' 'Ping' 'Country' 'Proto' 'Address' >&3
if [ "$count" -eq 0 ]; then
printf '\nWaiting for first local ping result...\n' >&3
return
fi
awk -F '\t' \
-v rows="$list_rows" \
-v cols="$cols" '
function fit(s, w) {
if (w < 1) {
return ""
}
if (length(s) > w) {
return substr(s, 1, w - 1) "~"
}
return s
}
BEGIN {
country_w = 16
proto_w = 7
addr_w = cols - 33
if (addr_w < 10) {
addr_w = 10
}
}
NR > rows {
exit
}
{
display_ping = $1
if (display_ping == "999999") {
display_ping = "-"
}
printf "%6s %-16s %-7s %s\n", display_ping, fit($2, country_w), fit($3, proto_w), fit($4, addr_w)
}
' "$progress_view_file" >&3
}
measure_records_with_ui() {
input_file=$1
output_file=$2
status_file=$tmpdir/ping.status
progress_file=$tmpdir/ping.progress
total=$(line_count "$input_file")
spin=0
if ! require_ping; then
draw_loading "Cannot test local latency" "ping command was not found" "$spin"
sleep 2
return 127
fi
: > "$output_file"
(
batch=0
batch_files=
idx=0
completed=0
while IFS= read -r line
do
idx=$((idx + 1))
batch=$((batch + 1))
result_file=$tmpdir/ping-result.$idx
measure_record_line "$line" > "$result_file" &
batch_files="$batch_files $result_file"
if [ "$batch" -ge "$ping_concurrency" ]; then
wait
for measured_file in $batch_files
do
cat "$measured_file" >> "$output_file"
rm -f "$measured_file"
completed=$((completed + 1))
done
printf '%s\n' "$completed" > "$progress_file"
batch=0
batch_files=
fi
done < "$input_file"
if [ "$batch" -gt 0 ]; then
wait
for measured_file in $batch_files
do
cat "$measured_file" >> "$output_file"
rm -f "$measured_file"
completed=$((completed + 1))
done
printf '%s\n' "$completed" > "$progress_file"
fi
printf '0\n' > "$status_file"
) &
ping_pid=$!
while [ ! -f "$status_file" ]
do
measured=$(line_count "$output_file")
draw_progress_table "$output_file" "Testing latency from this device" "$measured/$total peers measured by local ping" "$spin"
spin=$(( (spin + 1) % 4 ))
[ -f "$status_file" ] && break
sleep 1
done
wait "$ping_pid" 2>/dev/null
set -- $(cat "$status_file" 2>/dev/null)
code=${1:-1}
if [ "$code" -ne 0 ]; then
draw_loading "Local ping failed" "ping worker exited with status $code" "$spin"
sleep 2
return "$code"
fi
return 0
}
start_measure_records_background() {
input_file=$1
output_file=$2
ping_status_file=$tmpdir/ping.status
ping_total=$(line_count "$input_file")
if ! require_ping; then
return 127
fi
: > "$output_file"
rm -f "$ping_status_file"
(
batch=0
batch_files=
idx=0
completed=0
while IFS= read -r line
do
idx=$((idx + 1))
batch=$((batch + 1))
result_file=$tmpdir/ping-live-result.$idx
measure_record_line "$line" > "$result_file" &
batch_files="$batch_files $result_file"
if [ "$batch" -ge "$ping_concurrency" ]; then
wait
for measured_file in $batch_files
do
cat "$measured_file" >> "$output_file"
rm -f "$measured_file"
completed=$((completed + 1))
done
batch=0
batch_files=
fi
done < "$input_file"
if [ "$batch" -gt 0 ]; then
wait
for measured_file in $batch_files
do
cat "$measured_file" >> "$output_file"
rm -f "$measured_file"
completed=$((completed + 1))
done
fi
printf '0\n' > "$ping_status_file"
) &
ping_worker_pid=$!
return 0
}
stop_ping_worker() {
if [ -n "$ping_worker_pid" ]; then
kill "$ping_worker_pid" 2>/dev/null
wait "$ping_worker_pid" 2>/dev/null
ping_worker_pid=
fi
}
load_records_with_ui() {
json_file=$tmpdir/publicnodes.json
parsed_file=$tmpdir/parsed.tsv
records_file=$tmpdir/records.tsv
fetch_status=$tmpdir/fetch.status
parse_status=$tmpdir/parse.status
spin=0
: > "$json_file"
(
fetch_nodes > "$json_file"
code=$?
printf '%s\n' "$code" > "$fetch_status"
) &
fetch_pid=$!
while [ ! -f "$fetch_status" ]
do
bytes=$(file_size "$json_file")
draw_loading "Downloading publicnodes.json" "$bytes bytes received" "$spin"
spin=$(( (spin + 1) % 4 ))
[ -f "$fetch_status" ] && break
sleep 1
done
wait "$fetch_pid" 2>/dev/null
set -- $(cat "$fetch_status" 2>/dev/null)
code=${1:-1}
if [ "$code" -ne 0 ]; then
draw_loading "Download failed" "curl/wget exited with status $code" "$spin"
sleep 2
return "$code"
fi
: > "$parsed_file"
(
parse_nodes < "$json_file" > "$parsed_file"
code=$?
printf '%s\n' "$code" > "$parse_status"
) &
parse_pid=$!
while [ ! -f "$parse_status" ]
do
peers=$(line_count "$parsed_file")
draw_loading "Parsing reachable peers" "$peers peers parsed" "$spin"
spin=$(( (spin + 1) % 4 ))
[ -f "$parse_status" ] && break
sleep 1
done
wait "$parse_pid" 2>/dev/null
set -- $(cat "$parse_status" 2>/dev/null)
code=${1:-1}
if [ "$code" -ne 0 ]; then
draw_loading "Parse failed" "awk exited with status $code" "$spin"
sleep 2
return "$code"
fi
: > "$records_file"
return 0
}
get_size() {
set -- $(stty size <&4 2>/dev/null)
rows=${1:-24}
cols=${2:-80}
case $rows in
''|*[!0-9]*) rows=24 ;;
esac
case $cols in
''|*[!0-9]*) cols=80 ;;
esac
}
selected_count() {
line_count "$selected_file"
}
refresh_view() {
sort_records "$records_file" > "$view_file"
count=$(line_count "$view_file")
if [ "$count" -eq 0 ]; then
cursor=0
else
cursor=1
fi
top=1
}
refresh_view_keep_cursor() {
old_cursor=$cursor
old_top=$top
sort_records "$records_file" > "$view_file"
count=$(line_count "$view_file")
if [ "$count" -eq 0 ]; then
cursor=0
top=1
return
fi
if [ "$old_cursor" -lt 1 ]; then
cursor=1
elif [ "$old_cursor" -gt "$count" ]; then
cursor=$count
else
cursor=$old_cursor
fi
if [ "$old_top" -lt 1 ]; then
top=1
else
top=$old_top
fi
}
draw_table() {
get_size
count=$(line_count "$view_file")
selected=$(selected_count)
ping_note=
list_rows=$((rows - 6))
if [ "$ping_total" -gt 0 ]; then
measured=$(line_count "$records_file")
if [ -n "$ping_status_file" ] && [ -f "$ping_status_file" ]; then
ping_note=" Pinging: done"
else
ping_note=" Pinging: $measured/$ping_total"
fi
fi
if [ "$list_rows" -lt 1 ]; then
list_rows=1
fi
if [ "$count" -eq 0 ]; then
cursor=0
top=1
else
if [ "$cursor" -lt 1 ]; then
cursor=1
fi
if [ "$cursor" -gt "$count" ]; then
cursor=$count
fi
if [ "$top" -lt 1 ]; then
top=1
fi
if [ "$cursor" -lt "$top" ]; then
top=$cursor
fi
bottom=$((top + list_rows - 1))
if [ "$cursor" -gt "$bottom" ]; then
top=$((cursor - list_rows + 1))
fi
fi
printf '\033[H' >&3
printf '\033[1;36mYggdrasil public peer picker\033[0m\033[K\n' >&3
printf 'Sort: %-8s Peers: %-5s Selected: %-5s%s\033[K\n' "$sort_mode" "$count" "$selected" "$ping_note" >&3
printf 'Keys: j/k or arrows move | Space select | p/c/r sort | Enter accept | q/Esc quit\033[K\n' >&3
printf '\033[2m%-2s %-1s %6s %-16s %-7s %s\033[0m\033[K\n' '' '' 'Ping' 'Country' 'Proto' 'Address' >&3
if [ "$count" -eq 0 ]; then
if [ "$ping_total" -gt 0 ]; then
printf 'Testing local ping from this device...\033[K\n' >&3
else
printf 'No reachable peers found.\033[K\n' >&3
fi
cleared=1
while [ "$cleared" -lt "$list_rows" ]
do
printf '\033[K\n' >&3
cleared=$((cleared + 1))
done
return
fi
awk -F '\t' \
-v top="$top" \
-v rows="$list_rows" \
-v cursor="$cursor" \
-v cols="$cols" \
-v selected_file="$selected_file" '
function fit(s, w) {
if (w < 1) {
return ""
}
if (length(s) > w) {
return substr(s, 1, w - 1) "~"
}
return s
}
BEGIN {
while ((getline s < selected_file) > 0) {
sel[s] = 1
}
close(selected_file)
country_w = 16
proto_w = 7
addr_w = cols - 39
if (addr_w < 10) {
addr_w = 10
}
}
NR < top {
next
}
NR >= top + rows {
exit
}
{
ping = $1
country = fit($2, country_w)
proto = fit($3, proto_w)
addr = fit($4, addr_w)
display_ping = ping
if (display_ping == "999999") {
display_ping = "-"
}
mark = " "
if ($4 in sel) {
mark = "*"
}
pointer = " "
if (NR == cursor) {
pointer = ">"
printf "\033[7m"
}
printf "%-2s %-1s %6s %-16s %-7s %s", pointer, mark, display_ping, country, proto, addr
if (NR == cursor) {
printf "\033[0m"
}
printf "\033[K\n"
printed++
}
END {
while (printed < rows) {
printf "\033[K\n"
printed++
}
}
' "$view_file" >&3
}
current_address() {
awk -F '\t' -v n="$cursor" 'NR == n { print $4; exit }' "$view_file"
}
toggle_current() {
addr=$(current_address)
[ -n "$addr" ] || return
if grep -F -x -e "$addr" "$selected_file" >/dev/null 2>&1; then
grep -F -x -v -e "$addr" "$selected_file" > "$selected_tmp"
mv "$selected_tmp" "$selected_file"
else
printf '%s\n' "$addr" >> "$selected_file"
fi
}
print_selected_addresses() {
awk -F '\t' -v selected_file="$selected_file" '
BEGIN {
while ((getline s < selected_file) > 0) {
sel[s] = 1
}
close(selected_file)
}
$4 in sel {
print $4
}
' "$view_file"
}
run_tui() {
selected_file=$tmpdir/selected.txt
selected_tmp=$tmpdir/selected.tmp
view_file=$tmpdir/view.tsv
cursor=1
top=1
action=quit
: > "$selected_file"
if ! start_measure_records_background "$parsed_file" "$records_file"; then
draw_loading "Cannot test local latency" "ping command was not found" 0
sleep 2
return 1
fi
refresh_view_keep_cursor
start_raw_input
last_records_count=$(line_count "$records_file")
if [ -n "$ping_status_file" ] && [ -f "$ping_status_file" ]; then
last_ping_done=1
else
last_ping_done=0
fi
draw_table
while :
do
key=$(read_key_poll)
redraw=0
records_count=$(line_count "$records_file")
if [ -n "$ping_status_file" ] && [ -f "$ping_status_file" ]; then
ping_done=1
else
ping_done=0
fi
if [ "$records_count" -ne "$last_records_count" ] || [ "$ping_done" -ne "$last_ping_done" ]; then
refresh_view_keep_cursor
last_records_count=$records_count
last_ping_done=$ping_done
redraw=1
fi
if [ "$key" = NONE ]; then
if [ "$redraw" -eq 1 ]; then
draw_table
fi
continue
fi
case $key in
UP)
if [ "$cursor" -gt 1 ]; then
cursor=$((cursor - 1))
redraw=1
fi
;;
DOWN)
count=$(line_count "$view_file")
if [ "$cursor" -lt "$count" ]; then
cursor=$((cursor + 1))
redraw=1
fi
;;
SPACE)
toggle_current
redraw=1
;;
SORT_PING)
sort_mode=ping
refresh_view
redraw=1
;;
SORT_COUNTRY)
sort_mode=country
refresh_view
redraw=1
;;
SORT_PROTO)
sort_mode=proto
refresh_view
redraw=1
;;
ENTER)
action=accept
break
;;
QUIT)
action=quit
break
;;
esac
if [ "$redraw" -eq 1 ]; then
draw_table
fi
done
leave_ui
stop_ping_worker
if [ "$action" = accept ]; then
print_selected_addresses
return 0
fi
return 1
}
make_tmpdir
if [ "$records_mode" -eq 1 ]; then
build_records_plain
sort_records "$records_file"
exit 0
fi
open_tty
enter_ui
load_records_with_ui || exit 1
run_tui
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment