Skip to content

Commit a509f80

Browse files
committed
Release 2.3
1 parent 885b4e8 commit a509f80

File tree

97 files changed

+2805
-1087
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+2805
-1087
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

7+
## Version 2.3 - 2025-11-21
8+
9+
### Added
10+
- New `file_checking` configuration option, that will tell Downloader whether to verify the presence of installed files or not. By default it will take the value `balanced`, which is the recommended option. The option `balanced` is a new behavior that skips file presence checking when the free space in the storages (like the SD) haven't been increased, making the updating process much quicker as result. With option `exhaustive` you get the old behavior, it will always check file presence, which is useful for advanced users that usually manipulate installed files. There are more options documented in the [README.md file](README.md#options).
11+
12+
### Changed
13+
- Reworked NTP logic in launcher. It now adds a few extra NTP servers that are helpful to avoid connectivity issues in users from China.
14+
- Adjusted timeouts in HTTP gateway to be more responsive when a HTTP Proxy is not working and in other scenarios.
15+
- Fixed locale errors in native builds.
16+
- Fixed launcher for headless runs.
17+
- Default `downloader_threads_limit` value changed to 6.
18+
- Documentation now reflects the latest architectural changes introduced with the `file_checking` feature.
19+
- Improved logs during connectivity issues.
20+
21+
### Removed
22+
- The legacy *"force check"* flag triggered by removing the file `Scripts/.config/downloader/downloader.last_successful_run` is now **DEPRECATED** and will be removed in **May 2026**. To get the same behavior (verifying all file hashes), use `file_checking` with the option `verify_integrity`.
23+
724
## Version 2.2.1 - 2025-10-30
825

926
### Added

README.md

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ Here you can see the default parameters and the options that you may change:
1616

1717
```ini
1818
[MiSTer]
19+
; verbose: when true, will make Downloader output to display additional debug information
20+
; This is also necessary to be active to display benchmark information.
21+
verbose = false
22+
23+
; file_checking options:
24+
; 'fastest' -> Only check file presences when there are database changes. Fastest.
25+
; 'balanced' -> Check file presences when there are database changes or the free space in the storages have increased noticeably. Recommended for most users.
26+
; 'exhaustive' -> Always check file presences. Option for users who remove/add cores manually.
27+
; 'verify_integrity' -> Always check file presences and hashes (much slower). Useful to verify that installed files are not corrupted (ExFAT partitions can become corrupt in rare cases).
28+
file_checking = 'balanced'
29+
1930
; storage_priority defines how this tool will use external storage during the installation
2031
; of files that are designed for external locations (usually games & docs files).
2132
;
@@ -35,47 +46,45 @@ Here you can see the default parameters and the options that you may change:
3546
; 'off' -> Disables this feature. Affected files will always be installed in your SD.
3647
storage_priority = 'prefer_sd'
3748

38-
; update_linux options:
39-
; true -> Updates Linux when there is a new update (very recommended).
40-
; false -> Doesn't update Linux.
41-
update_linux = true
42-
43-
; allow_reboot options:
44-
; 0 -> Don't allow this tool to ever reboot automatically.
45-
; 1 -> Allow this tool to reboot the system after any system file has been updated.
46-
; 2 -> Allow this tool to reboot the system only after Linux has been updated.
47-
allow_reboot = 1
48-
49-
; allow_delete options:
50-
; 0 -> Don't allow this tool to delete anything at all.
51-
; 1 -> Allow this tool to delete any old file from previous updates.
52-
; 2 -> Allow this tool to delete only old cores that receive a new version.
53-
allow_delete = 1
54-
5549
; minimum_system_free_space_mb: Fee space needed to install files with Downloader
5650
; This minimum applies solely to the system partition (SD card).
57-
; Reducing this value is not advised.
51+
; Reducing this value is highly discouraged.
5852
minimum_system_free_space_mb = 512
5953

6054
; minimum_external_free_space_mb: Free space needed to install files in external storages
6155
; This minimum applies to all external storages (USBs & CIFS).
62-
; Reducing this value is not advised.
56+
; Reducing this value is highly discouraged.
6357
minimum_external_free_space_mb = 128
6458

6559
; downloader_timeout: Can be tweaked to increase the timeout time in seconds
6660
; It is useful to increase this value for users with slow connections.
61+
; Reducing this value is highly discouraged.
6762
downloader_timeout = 300
6863

6964
; downloader_retries: Can be tweaked to increase the retries per failed download
7065
; It is useful to increase this value for users with very unstable connections.
66+
; Reducing this value is highly discouraged.
7167
downloader_retries = 3
7268

73-
; verbose: when true, will make Downloader output to display additional debug information
74-
; This is also necessary to be active to display benchmark information.
75-
verbose = false
69+
; update_linux options:
70+
; true -> Updates Linux when there is a new update.
71+
; false -> Doesn't update Linux (highly discouraged).
72+
update_linux = true
73+
74+
; allow_reboot options:
75+
; 0 -> Don't allow this tool to ever reboot automatically (highly discouraged).
76+
; 1 -> Allow this tool to reboot the system after any system file has been updated.
77+
; 2 -> Allow this tool to reboot the system only after Linux has been updated (highly discouraged).
78+
allow_reboot = 1
79+
80+
; allow_delete options:
81+
; 0 -> Don't allow this tool to delete anything at all (highly discouraged).
82+
; 1 -> Allow this tool to delete any old file from previous updates.
83+
; 2 -> Allow this tool to delete only old cores that receive a new version (highly discouraged).
84+
allow_delete = 1
7685

7786
; http_proxy: Routes all downloads through a proxy server (advanced, rarely needed)
78-
; Format: 'http://proxy-server:port'
87+
; Format: 'http://proxy-server:port' (it also supports basic auth)
7988
http_proxy = ''
8089
```
8190

@@ -87,7 +96,7 @@ http_proxy = ''
8796
- [x] Configurable custom download filters
8897
- [x] Storage Priority Resolution for automatically detecting connected drives
8998
- [x] Free space checks
90-
- [ ] Opt-in parameter to bypass strict file checks
99+
- [x] Opt-in parameter to bypass strict file checks
91100
- [ ] Fast check for update availability (without triggering an actual update)
92101
- [ ] HTTP cookie support
93102
- [ ] Uninstall database feature

docs/custom-databases.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ The `db_url` entry points to a JSON file that a maintainer has published. The `*
1212
The format of the aforementioned JSON file should be as follow:
1313
```js
1414
{
15+
/**
16+
* [Mandatory] Version of the database format (int)
17+
* - Must be set to 1 to match the spec in this document.
18+
* - Versions >1 are rejected; the downloader will prompt a self-update.
19+
* - Omitted defaults to version 0, which still works but is slower.
20+
*/
21+
"v": 1,
1522
/**
1623
* [Mandatory] It should match the corresponding section_id defined in the INI file (string)
1724
* Important: This ID must identify unequivocally your collection in the whole MiSTer ecosystem.

docs/jobs_diagram.png

36.9 KB
Loading
64.1 KB
Loading

docs/jobs_flow_diagram.py

Lines changed: 78 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from graphviz import Digraph
22

3+
BACKUP_COLOR = '#888888'
4+
35
# Initialize the Digraph
46
dot = Digraph(
57
comment='Workflow Diagram',
@@ -10,19 +12,23 @@
1012
'fillcolor': 'lightgrey',
1113
'fontname': 'Helvetica'
1214
},
13-
edge_attr={'color': 'black'}
15+
edge_attr={'color': 'black', 'fontname': 'Helvetica-Narrow', 'fontsize': '10'}
1416
)
1517

1618
# NON-ZIP NODES
1719
dot.node('START', 'START', shape='ellipse', fillcolor='black', fontcolor='white')
1820
dot.node('END', 'END', shape='ellipse', fillcolor='#77DD77')
21+
dot.node('SKIP1', 'SKIP', shape='ellipse', fillcolor='#77DD77')
22+
dot.node('SKIP2', 'SKIP', shape='ellipse', fillcolor='#77DD77')
1923
dot.node('ABORT', 'ABORT', shape='ellipse', fillcolor='#ff8f8f')
2024
dot.node('A', 'FetchDataJob [db]')
21-
dot.node('B', 'LoadLocalStoreJob', fillcolor='#ffefd5')
22-
dot.node('C', 'OpenDbJob', fillcolor='#B0E0E6')
23-
dot.node('D', 'ProcessDbMainJob', fillcolor='#B0E0E6')
24-
dot.node('E', 'ProcessDbIndexJob', fillcolor='#B0E0E6:#ffefd5')
25-
dot.node('M', 'FetchFileJob [file]')
25+
dot.node('B', 'LoadLocalStoreSigsJob', fillcolor='#ffefd5')
26+
dot.node('C', 'LoadLocalStoreJob', fillcolor='#ffefd5')
27+
dot.node('D', 'OpenDbJob', fillcolor='#B0E0E6')
28+
dot.node('E', 'MixStoreAndDbJob', fillcolor='#B0E0E6')
29+
dot.node('F', 'ProcessDbMainJob', fillcolor='#B0E0E6')
30+
dot.node('G', 'ProcessDbIndexJob', fillcolor='#B0E0E6:#ffefd5')
31+
dot.node('H', 'FetchFileJob [file]')
2632

2733
# Example "legend" nodes
2834
dot.node('0', 'CPU', fillcolor='#B0E0E6')
@@ -32,21 +38,46 @@
3238
dot.edge('START', 'A', label='1:N')
3339
dot.edge('START', 'B')
3440

35-
dot.edge('A', 'C', weight='10')
36-
dot.edge('C', 'D', weight='10', label=' db + store')
37-
dot.edge('D', 'E', weight='10', label=' if no zips')
41+
dot.edge('A', 'D', weight='10')
42+
dot.edge('D', 'C', weight='5', constraint='false', label=' N:1, if any db sig != store sig')
43+
dot.edge('D', 'E', weight='10', label=' db')
44+
dot.edge('E', 'F', weight='10', label=' db + store')
45+
dot.edge('F', 'G', weight='10', label=' if no zips')
46+
47+
dot.edge('G', 'H', label=' 1:N')
48+
49+
dot.edge('C', 'E', style='dotted', constraint='true', label='wait\nstore', dir='back')
50+
dot.edge('B', 'D', style='dotted', constraint='true', label='wait\nstore sig', dir='back')
51+
52+
# Position SKIP1 at same rank as OpenDbJob (D)
53+
with dot.subgraph() as s:
54+
s.attr(rank='same')
55+
s.node('SKIP1')
56+
s.node('D')
57+
s.node('C')
3858

39-
dot.edge('E', 'M', label=' 1:N')
59+
dot.edge('SKIP1', 'D', style='invis')
60+
dot.edge('D', 'C', style='invis')
4061

41-
dot.edge('B', 'C', style='dotted', constraint='true', label='wait\nstore', dir='back')
62+
# Position E, SKIP2, and ABORT at same rank (left to right)
63+
with dot.subgraph() as s:
64+
s.attr(rank='same')
65+
s.node('E')
66+
s.node('SKIP2')
67+
s.node('ABORT')
4268

43-
dot.edge('B', 'B', label=' retry', style='dashed', constraint='false')
69+
dot.edge('E', 'SKIP2', style='invis')
70+
dot.edge('SKIP2', 'ABORT', style='invis')
71+
72+
dot.edge('C', 'C', label=' retry', style='dashed', constraint='false')
4473
dot.edge('A', 'A', label=' retry', style='dashed', constraint='false')
45-
dot.edge('C', 'A', label=' retry', style='dashed', constraint='true')
46-
dot.edge('M', 'M', label=' retry', style='dashed', constraint='false')
74+
dot.edge('D', 'A', label=' retry', style='dashed', constraint='true')
75+
dot.edge('H', 'H', label=' retry', style='dashed', constraint='false')
4776

48-
dot.edge('M', 'END', weight='20', constraint='true')
49-
dot.edge('B', 'ABORT', weight='20', constraint='true', label=' if always fails', style='dashed')
77+
dot.edge('H', 'END', weight='20', constraint='true')
78+
dot.edge('C', 'ABORT', weight='20', constraint='true', label=' if always fails', style='dashed', color=BACKUP_COLOR, fontcolor=BACKUP_COLOR)
79+
dot.edge('D', 'SKIP1', weight='1', constraint='false', label=' if db sig == store sig')
80+
dot.edge('E', 'SKIP2', weight='1', constraint='false', label=' if db sig == store sig')
5081

5182
dot.render('jobs_diagram', format='png', cleanup=True)
5283

@@ -55,51 +86,51 @@
5586
c.attr(style='filled', color='#f5f5ff', penwidth='1.0')
5687
c.node('3', 'Zip Feature', style='filled', fillcolor='#8f8fff', fontcolor='white', penwidth='0')
5788

58-
c.node('O', 'WaitDbZipsJob', fillcolor='#B0E0E6')
59-
c.node('F', 'ProcessZipIndexJob', fillcolor='#B0E0E6:#ffefd5')
60-
c.node('G', 'FetchDataJob [zip summary]')
61-
c.node('I', 'OpenZipSummaryJob', fillcolor='#B0E0E6')
89+
c.node('I', 'WaitDbZipsJob', fillcolor='#B0E0E6')
90+
c.node('J', 'ProcessZipIndexJob', fillcolor='#B0E0E6:#ffefd5')
91+
c.node('K', 'FetchDataJob [zip summary]')
92+
c.node('L', 'OpenZipSummaryJob', fillcolor='#B0E0E6')
6293
c.node('DOT0', '', shape='circle', fixedsize='true', width='0.01', fillcolor='#ff8f8f')
63-
c.node('J', 'FetchDataJob [zip contents]')
64-
c.node('L', 'OpenZipContentsJob', fillcolor='#ffefd5')
94+
c.node('M', 'FetchDataJob [zip contents]')
95+
c.node('N', 'OpenZipContentsJob', fillcolor='#ffefd5')
6596
c.node('DOT1', '', shape='circle', fixedsize='true', width='0.01', fillcolor='#ff8f8f')
6697
c.node('DOT2', '', shape='circle', fixedsize='true', width='0.01', fillcolor='#ff8f8f')
6798
#
6899
# EDGES
69100
#
70-
dot.edge('D', 'O', weight='10', label='if zips')
71-
dot.edge('O', 'E', weight='10', label='store w/ deselected\nzip indexes')
72-
dot.edge('D', 'G', label='1:N (missing zips)')
73-
74-
dot.edge('F', 'J', label='if many file installs')
75-
dot.edge('I', 'F')
76-
dot.edge('G', 'I')
77-
dot.edge('J', 'L')
78-
dot.edge('L', 'M', label='1:N\n(invalid files)')
79-
dot.edge('F', 'DOT1', dir='none')
101+
dot.edge('F', 'I', weight='10', label='if zips')
102+
dot.edge('I', 'G', weight='10', label='store w/ deselected\nzip indexes')
103+
dot.edge('F', 'K', label='1:N (missing zips)')
104+
105+
dot.edge('J', 'M', label='if many file installs')
106+
dot.edge('L', 'J')
107+
dot.edge('K', 'L')
108+
dot.edge('M', 'N')
109+
dot.edge('N', 'H', label='1:N\n(invalid files)')
110+
dot.edge('J', 'DOT1', dir='none')
80111
dot.edge('DOT1', 'DOT2', label='if just few\n file installs', dir='none')
81-
dot.edge('DOT2', 'M', label=' 1:N')
82-
#dot.edge('L', 'M', label='1:N')
112+
dot.edge('DOT2', 'H', label=' 1:N')
113+
#dot.edge('N', 'H', label='1:N')
83114

84-
# Wait edges
85-
dot.edge('F', 'O', style='dotted', constraint='true', label='wait\n zip indexes', dir='back')
115+
# "Wait" edges
116+
dot.edge('J', 'I', style='dotted', constraint='true', label='wait\n zip indexes', dir='back')
86117

87-
# Labeled 1:N edges
88-
dot.edge('D', 'F', label='1:N (stored zips)', weight='5')
118+
# Labeled "1:N" edges
119+
dot.edge('F', 'J', label='1:N (stored zips)', weight='5')
89120

90121
# Edges to END
91-
dot.edge('L', 'END', weight='20', constraint='true')
122+
dot.edge('N', 'END', weight='20', constraint='true')
92123

93-
# Retry edges
94-
dot.edge('G', 'G', label=' retry', style='dashed', constraint='false')
95-
dot.edge('I', 'G', label=' retry', style='dashed', constraint='false')
96-
dot.edge('J', 'J', label=' retry', style='dashed', constraint='false')
97-
dot.edge('L', 'J', label=' retry', style='dashed', constraint='false')
124+
# "Retry" edges
125+
dot.edge('K', 'K', label=' retry', style='dashed', constraint='false')
126+
dot.edge('L', 'K', label=' retry', style='dashed', constraint='false')
127+
dot.edge('M', 'M', label=' retry', style='dashed', constraint='false')
128+
dot.edge('N', 'M', label=' retry', style='dashed', constraint='false')
98129

99130
# "Backup" edges
100-
dot.edge('G', 'DOT0', style='dashed', constraint='true', dir='none')
101-
dot.edge('I', 'DOT0', style='dashed', constraint='true', dir='none')
102-
dot.edge('DOT0', 'F', label='backup:\nstored summary', style='dashed', constraint='false')
131+
dot.edge('K', 'DOT0', style='dashed', constraint='true', dir='none', color=BACKUP_COLOR)
132+
dot.edge('L', 'DOT0', style='dashed', constraint='true', dir='none', color=BACKUP_COLOR)
133+
dot.edge('DOT0', 'J', label='backup:\nstored summary', style='dashed', constraint='false', color=BACKUP_COLOR, fontcolor=BACKUP_COLOR)
103134
#dot.edge('H', 'I', label='b', style='dashed', constraint='false', dir='none')
104135
#dot.edge('I', 'F', label='b', style='dashed', constraint='false')
105136
#dot.edge('J', 'E', label='b', style='dashed', constraint='false')

downloader.sh

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,41 @@ LATEST_BIN_PATH="/media/fat/Scripts/.config/downloader/downloader_bin"
2525
CACERT_PEM_0="/etc/ssl/certs/cacert.pem"
2626
CACERT_PEM_1="/media/fat/Scripts/.config/downloader/cacert.pem"
2727

28-
if (( $(date +%Y) < 2000 )) ; then
29-
NTP_SERVER="0.pool.ntp.org"
30-
echo "Syncing date and time with $NTP_SERVER"
31-
echo
32-
if ntpdate -s -b -u $NTP_SERVER ; then
28+
# NTP SETUP
29+
if (( 10#$(date +%Y) < 2000 )) ; then
30+
NTP_SERVERS=(
31+
"time.apple.com"
32+
"time.amazonaws.cn"
33+
"ntp.ntsc.ac.cn"
34+
"cn.pool.ntp.org"
35+
"ntp.aliyun.com"
36+
"ntp.tencent.com"
37+
"ntp.rt.ru"
38+
)
39+
NTP_CONF="/etc/ntp.conf"
40+
for server in "${NTP_SERVERS[@]}"; do
41+
if ! grep -qF "${server}" "${NTP_CONF}"; then
42+
echo "server $server iburst" >> "${NTP_CONF}"
43+
fi
44+
done
45+
NTP_PID="/var/run/ntpd.pid"
46+
start-stop-daemon -K -p "${NTP_PID}"
47+
rm -f "${NTP_PID}"
48+
start-stop-daemon -S -q -p "${NTP_PID}" -x "/usr/sbin/ntpd" -- -g -p "${NTP_PID}"
49+
connected=0
50+
for ((i=1; i<=10; i++)); do
51+
if ntpq -c "rv 0" 2>&1 | grep -qiE "connection refused|sync_unspec" ; then
52+
printf "."
53+
sleep 3
54+
else
55+
connected=1
56+
break
57+
fi
58+
done
59+
printf "\n"
60+
if (( connected )); then
3361
echo "Date and time is:"
34-
echo "$(date)"
62+
date
3563
echo
3664
elif [[ "${CURL_SSL:-}" != "--insecure" ]] ; then
3765
echo "Unable to sync."
@@ -40,6 +68,7 @@ if (( $(date +%Y) < 2000 )) ; then
4068
fi
4169
fi
4270

71+
# CERTS SETUP
4372
if [ -s "${CACERT_PEM_1}" ] ; then
4473
export SSL_CERT_FILE="${CACERT_PEM_1}"
4574
elif [ -s "${CACERT_PEM_0}" ] ; then
@@ -101,6 +130,7 @@ elif [[ "${CURL_SSL:-}" != "--insecure" ]] ; then
101130
esac
102131
fi
103132

133+
# LAUNCHER
104134
download_file() {
105135
local DOWNLOAD_PATH="${1}"
106136
local DOWNLOAD_URL="${2}"

downloader_force_proxy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ set -euo pipefail
2121

2222
your_http_proxy_url="http://your-proxy-server-url-goes-here.com"
2323

24-
rm -f /media/fat/Scripts/.config/downloader/downloader_bin /media/fat/Scripts/.config/downloader/downloader_latest.zip
24+
rm -f /media/fat/Scripts/.config/downloader_bin /media/fat/Scripts/.config/downloader_latest.zip
2525
export http_proxy="${http_proxy:-${your_http_proxy_url}}"
2626
/media/fat/Scripts/downloader.sh

0 commit comments

Comments
 (0)