mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-03 12:36:57 +00:00
sync with dev-16
This commit is contained in:
@@ -14,6 +14,7 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
||||
- heatpump reset [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
|
||||
- e-mail notification using ReadyMail Client
|
||||
- 2.nd freshwater module (dhw4, dhw5) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991)
|
||||
- full system backup and restore
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -34,3 +35,4 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
||||
- secure mqtt uses ESP_SSLClient
|
||||
- fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017)
|
||||
- move http client from stack to heap
|
||||
- heap optimizations [#3021](https://github.com/emsesp/EMS-ESP32/discussions/3021)
|
||||
|
||||
4
Makefile
4
Makefile
@@ -47,8 +47,8 @@ MAKEFLAGS += -j$(JOBS) -l$(shell echo $$(($(JOBS) * 2)))
|
||||
#----------------------------------------------------------------------
|
||||
TARGET := emsesp
|
||||
BUILD := build
|
||||
SOURCES := src/core src/devices src/web src/test lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
|
||||
INCLUDES := src/core src/devices src/web src/test lib_standalone lib/* lib/semver lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
|
||||
SOURCES := src/core src/devices src/web src/test lib_standalone lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
|
||||
INCLUDES := src/core src/devices src/web src/test lib_standalone lib/* lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
|
||||
LIBRARIES :=
|
||||
|
||||
CPPCHECK = cppcheck
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"vite.config.ts",
|
||||
"lib/esp32-psram/**",
|
||||
"test/test_api/test_api.h",
|
||||
"lib_standalone/**"
|
||||
"lib_standalone/**",
|
||||
"**/*.js"
|
||||
]
|
||||
}
|
||||
@@ -57,7 +57,7 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"axe-core": "^4.11.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^10.2.0",
|
||||
"eslint": "^10.2.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.8.3",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
|
||||
111
interface/pnpm-lock.yaml
generated
111
interface/pnpm-lock.yaml
generated
@@ -80,7 +80,7 @@ importers:
|
||||
version: 7.29.0
|
||||
'@eslint/js':
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1(eslint@10.2.0)
|
||||
version: 10.0.1(eslint@10.2.1)
|
||||
'@preact/preset-vite':
|
||||
specifier: ^2.10.5
|
||||
version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1))
|
||||
@@ -103,11 +103,11 @@ importers:
|
||||
specifier: ^9.2.1
|
||||
version: 9.2.1
|
||||
eslint:
|
||||
specifier: ^10.2.0
|
||||
version: 10.2.0
|
||||
specifier: ^10.2.1
|
||||
version: 10.2.1
|
||||
eslint-config-prettier:
|
||||
specifier: ^10.1.8
|
||||
version: 10.1.8(eslint@10.2.0)
|
||||
version: 10.1.8(eslint@10.2.1)
|
||||
prettier:
|
||||
specifier: ^3.8.3
|
||||
version: 3.8.3
|
||||
@@ -119,7 +119,7 @@ importers:
|
||||
version: 5.46.1
|
||||
typescript-eslint:
|
||||
specifier: ^8.58.2
|
||||
version: 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
version: 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
vite:
|
||||
specifier: ^8.0.8
|
||||
version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)
|
||||
@@ -498,12 +498,16 @@ packages:
|
||||
resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
'@humanfs/core@0.19.2':
|
||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
'@humanfs/node@0.16.7':
|
||||
resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
|
||||
'@humanfs/node@0.16.8':
|
||||
resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
'@humanfs/types@0.15.0':
|
||||
resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1':
|
||||
@@ -1181,8 +1185,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.19:
|
||||
resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==}
|
||||
baseline-browser-mapping@2.10.20:
|
||||
resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1710,8 +1714,8 @@ packages:
|
||||
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
eslint@10.2.0:
|
||||
resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==}
|
||||
eslint@10.2.1:
|
||||
resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -2019,8 +2023,8 @@ packages:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
hasown@2.0.3:
|
||||
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
he@1.2.0:
|
||||
@@ -3693,9 +3697,9 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.27.4':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)':
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@10.2.1)':
|
||||
dependencies:
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
@@ -3716,9 +3720,9 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@eslint/js@10.0.1(eslint@10.2.0)':
|
||||
'@eslint/js@10.0.1(eslint@10.2.1)':
|
||||
optionalDependencies:
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
|
||||
'@eslint/object-schema@3.0.5': {}
|
||||
|
||||
@@ -3727,13 +3731,18 @@ snapshots:
|
||||
'@eslint/core': 1.2.1
|
||||
levn: 0.4.1
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.7':
|
||||
'@humanfs/core@0.19.2':
|
||||
dependencies:
|
||||
'@humanfs/core': 0.19.1
|
||||
'@humanfs/types': 0.15.0
|
||||
|
||||
'@humanfs/node@0.16.8':
|
||||
dependencies:
|
||||
'@humanfs/core': 0.19.2
|
||||
'@humanfs/types': 0.15.0
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
|
||||
'@humanfs/types@0.15.0': {}
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1': {}
|
||||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
@@ -4166,15 +4175,15 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.3))(eslint@10.2.0)(typescript@6.0.3)':
|
||||
'@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/parser': 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
'@typescript-eslint/scope-manager': 8.58.2
|
||||
'@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
'@typescript-eslint/visitor-keys': 8.58.2
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||
@@ -4182,14 +4191,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.3)':
|
||||
'@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.58.2
|
||||
'@typescript-eslint/types': 8.58.2
|
||||
'@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
|
||||
'@typescript-eslint/visitor-keys': 8.58.2
|
||||
debug: 4.4.3
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -4212,13 +4221,13 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.58.2(eslint@10.2.0)(typescript@6.0.3)':
|
||||
'@typescript-eslint/type-utils@8.58.2(eslint@10.2.1)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.58.2
|
||||
'@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
debug: 4.4.3
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
@@ -4241,13 +4250,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.58.2(eslint@10.2.0)(typescript@6.0.3)':
|
||||
'@typescript-eslint/utils@8.58.2(eslint@10.2.1)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1)
|
||||
'@typescript-eslint/scope-manager': 8.58.2
|
||||
'@typescript-eslint/types': 8.58.2
|
||||
'@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -4325,7 +4334,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.19: {}
|
||||
baseline-browser-mapping@2.10.20: {}
|
||||
|
||||
bin-build@3.0.0:
|
||||
dependencies:
|
||||
@@ -4386,7 +4395,7 @@ snapshots:
|
||||
|
||||
browserslist@4.28.2:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.19
|
||||
baseline-browser-mapping: 2.10.20
|
||||
caniuse-lite: 1.0.30001788
|
||||
electron-to-chromium: 1.5.340
|
||||
node-releases: 2.0.37
|
||||
@@ -4898,9 +4907,9 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-config-prettier@10.1.8(eslint@10.2.0):
|
||||
eslint-config-prettier@10.1.8(eslint@10.2.1):
|
||||
dependencies:
|
||||
eslint: 10.2.0
|
||||
eslint: 10.2.1
|
||||
|
||||
eslint-scope@9.1.2:
|
||||
dependencies:
|
||||
@@ -4913,15 +4922,15 @@ snapshots:
|
||||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@10.2.0:
|
||||
eslint@10.2.1:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1)
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.23.5
|
||||
'@eslint/config-helpers': 0.5.5
|
||||
'@eslint/core': 1.2.1
|
||||
'@eslint/plugin-kit': 0.7.1
|
||||
'@humanfs/node': 0.16.7
|
||||
'@humanfs/node': 0.16.8
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
@@ -5169,7 +5178,7 @@ snapshots:
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
hasown: 2.0.3
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-proto@1.0.1:
|
||||
@@ -5302,7 +5311,7 @@ snapshots:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hasown@2.0.2:
|
||||
hasown@2.0.3:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
@@ -5412,7 +5421,7 @@ snapshots:
|
||||
|
||||
is-core-module@2.16.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
hasown: 2.0.3
|
||||
|
||||
is-cwebp-readable@3.0.0:
|
||||
dependencies:
|
||||
@@ -6390,13 +6399,13 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
typescript-eslint@8.58.2(eslint@10.2.0)(typescript@6.0.3):
|
||||
typescript-eslint@8.58.2(eslint@10.2.1)(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.3))(eslint@10.2.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
'@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3)
|
||||
'@typescript-eslint/parser': 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
'@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3)
|
||||
'@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.3)
|
||||
eslint: 10.2.0
|
||||
'@typescript-eslint/utils': 8.58.2(eslint@10.2.1)(typescript@6.0.3)
|
||||
eslint: 10.2.1
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -24,20 +24,26 @@ let bundleStats = {
|
||||
other: { count: 0, uncompressed: 0, compressed: 0 }
|
||||
};
|
||||
|
||||
const generateWWWClass =
|
||||
() => `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
||||
// Bundle Statistics:
|
||||
// AsyncWebHandler that performs the lookup.
|
||||
const generateWWWClass = () => `// Bundle Statistics:
|
||||
// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB
|
||||
// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB
|
||||
// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}%
|
||||
// - Generated on: ${new Date().toISOString()}
|
||||
|
||||
class WWWData {
|
||||
${INDENT}public:
|
||||
${INDENT.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
||||
${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, ${f.hash});`).join('\n')}
|
||||
${INDENT.repeat(2)}}
|
||||
struct WWWAsset {
|
||||
${INDENT}const char * uri;
|
||||
${INDENT}const char * contentType;
|
||||
${INDENT}const uint8_t * content;
|
||||
${INDENT}size_t len;
|
||||
${INDENT}const char * etag; // already includes enclosing double quotes
|
||||
};
|
||||
|
||||
static const WWWAsset WWW_ASSETS[] = {
|
||||
${fileInfo.map((f) => `${INDENT}{"${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "\\"${f.rawHash}\\""},`).join('\n')}
|
||||
};
|
||||
|
||||
static constexpr size_t WWW_ASSETS_COUNT = sizeof(WWW_ASSETS) / sizeof(WWW_ASSETS[0]);
|
||||
`;
|
||||
|
||||
const getFilesSync = (dir, files = []) => {
|
||||
@@ -72,6 +78,7 @@ const writeFile = (relativeFilePath, buffer) => {
|
||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||
// const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
||||
const hash = etag(zipBuffer); // use smaller md5 instead of sha256
|
||||
const rawHash = hash.replace(/^"|"$/g, '');
|
||||
|
||||
zipBuffer.forEach((b) => {
|
||||
if (!(size % bytesPerLine)) {
|
||||
@@ -94,7 +101,8 @@ const writeFile = (relativeFilePath, buffer) => {
|
||||
mimeType,
|
||||
variable,
|
||||
size,
|
||||
hash
|
||||
hash,
|
||||
rawHash
|
||||
});
|
||||
|
||||
totalSize += size;
|
||||
|
||||
@@ -166,6 +166,51 @@ const createManualChunks = (detailed = false) => {
|
||||
};
|
||||
};
|
||||
|
||||
// Rolldown-native codeSplitting groups for the production build.
|
||||
// See: https://rolldown.rs/reference/outputoptions.codesplitting
|
||||
const createRolldownCodeSplitting = () => ({
|
||||
groups: [
|
||||
{ name: '@preact', test: /[\\/]node_modules[\\/].*preact/, priority: 100 },
|
||||
{
|
||||
name: '@react-router',
|
||||
test: /[\\/]node_modules[\\/].*react-router/,
|
||||
priority: 95
|
||||
},
|
||||
{
|
||||
name: '@mui-material',
|
||||
test: /[\\/]node_modules[\\/]@mui[\\/]material/,
|
||||
priority: 95
|
||||
},
|
||||
{
|
||||
name: '@mui-icons',
|
||||
test: /[\\/]node_modules[\\/]@mui[\\/]icons-material/,
|
||||
priority: 95
|
||||
},
|
||||
{ name: '@alova', test: /[\\/]node_modules[\\/].*alova/, priority: 90 },
|
||||
{ name: '@i18n', test: /[\\/]node_modules[\\/].*typesafe-i18n/, priority: 90 },
|
||||
{
|
||||
name: '@toastify',
|
||||
test: /[\\/]node_modules[\\/].*react-toastify/,
|
||||
priority: 90
|
||||
},
|
||||
{
|
||||
name: '@table-library',
|
||||
test: /[\\/]node_modules[\\/]@table-library/,
|
||||
priority: 90
|
||||
},
|
||||
{ name: 'vendor', test: /[\\/]node_modules[\\/]/, priority: 10 },
|
||||
// Collapse the lazy-loaded route stubs + shared app/components/utils
|
||||
// code into one chunk. This cuts firmware-side route count roughly in
|
||||
// half and speeds up route-matching on every incoming HTTP request.
|
||||
{
|
||||
name: 'app',
|
||||
test: /[\\/](app|components|utils)[\\/]/,
|
||||
priority: 5
|
||||
},
|
||||
{ name: 'api', test: /[\\/]api[\\/]/, priority: 5 }
|
||||
]
|
||||
});
|
||||
|
||||
// Common build base configuration
|
||||
const createBaseBuildConfig = () => ({
|
||||
target: ES_TARGET,
|
||||
@@ -330,10 +375,17 @@ export default defineConfig(
|
||||
chunkFileNames: 'assets/[name]-[hash].js',
|
||||
entryFileNames: 'assets/[name]-[hash].js',
|
||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||
// Kept as a no-op for documentation/fallback. With Vite 8
|
||||
manualChunks: createManualChunks(true),
|
||||
sourcemap: false
|
||||
}
|
||||
}
|
||||
},
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
codeSplitting: createRolldownCodeSplitting()
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marko Živanović
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# About
|
||||
|
||||
This project is MIT-licensed, C++14 implementation of [semantic versioning](http://semver.org) parser and comparator with support for modifying parsed version strings. Semantic versioning 2.0.0 specification is supported out-of-the-box and the code should be flexible-enough to support future revisions or other similar versioning schemes.
|
||||
|
||||
Copyright (c) 2015 Marko Zivanovic
|
||||
|
||||
Based on https://github.com/zmarko/semver
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marko Zivanovic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include "semver200.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace version {
|
||||
|
||||
namespace {
|
||||
|
||||
// Compare normal version identifiers.
|
||||
int compare_normal(const Version_data & l, const Version_data & r) {
|
||||
if (l.major > r.major)
|
||||
return 1;
|
||||
if (l.major < r.major)
|
||||
return -1;
|
||||
if (l.minor > r.minor)
|
||||
return 1;
|
||||
if (l.minor < r.minor)
|
||||
return -1;
|
||||
if (l.patch > r.patch)
|
||||
return 1;
|
||||
if (l.patch < r.patch)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Compare alphanumeric prerelease identifiers.
|
||||
inline int cmp_alnum_prerel_ids(const string & l, const string & r) {
|
||||
auto cmp = l.compare(r);
|
||||
if (cmp == 0) {
|
||||
return cmp;
|
||||
} else {
|
||||
return cmp > 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare numeric prerelease identifiers.
|
||||
inline int cmp_num_prerel_ids(const string & l, const string & r) {
|
||||
long long li = stoll(l);
|
||||
long long ri = stoll(r);
|
||||
if (li == ri)
|
||||
return 0;
|
||||
return li > ri ? 1 : -1;
|
||||
}
|
||||
|
||||
using Prerel_type_pair = pair<Id_type, Id_type>;
|
||||
using Prerel_id_comparator = function<int(const string &, const string &)>;
|
||||
const map<Prerel_type_pair, Prerel_id_comparator> comparators = {{{Id_type::alnum, Id_type::alnum}, cmp_alnum_prerel_ids},
|
||||
{{Id_type::alnum, Id_type::num}, [](const string &, const string &) { return 1; }},
|
||||
{{Id_type::num, Id_type::alnum}, [](const string &, const string &) { return -1; }},
|
||||
{{Id_type::num, Id_type::num}, cmp_num_prerel_ids}};
|
||||
|
||||
// Compare prerelease identifiers based on their types.
|
||||
inline int compare_prerel_identifiers(const Prerelease_identifier & l, const Prerelease_identifier & r) {
|
||||
auto cmp = comparators.at({l.second, r.second});
|
||||
return cmp(l.first, r.first);
|
||||
}
|
||||
|
||||
inline int cmp_rel_prerel(const Prerelease_identifiers & l, const Prerelease_identifiers & r) {
|
||||
if (l.empty() && !r.empty())
|
||||
return 1;
|
||||
if (r.empty() && !l.empty())
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Semver200_comparator::compare(const Version_data & l, const Version_data & r) const {
|
||||
// Compare normal version components.
|
||||
int cmp = compare_normal(l, r);
|
||||
if (cmp != 0)
|
||||
return cmp;
|
||||
|
||||
// Compare if one version is release and the other prerelease - release is always higher.
|
||||
cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids);
|
||||
if (cmp != 0)
|
||||
return cmp;
|
||||
|
||||
// Compare prerelease by looking at each identifier: numeric ones are compared as numbers,
|
||||
// alphanum as ASCII strings.
|
||||
auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size());
|
||||
for (size_t i = 0; i < shorter; i++) {
|
||||
cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]);
|
||||
if (cmp != 0)
|
||||
return cmp;
|
||||
}
|
||||
|
||||
// Prerelease identifiers are the same, to the length of the shorter version string;
|
||||
// if they are the same length, then versions are equal, otherwise, longer one wins.
|
||||
if (l.prerelease_ids.size() == r.prerelease_ids.size())
|
||||
return 0;
|
||||
return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1;
|
||||
}
|
||||
|
||||
} // namespace version
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marko Zivanovic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include "semver200.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace version {
|
||||
|
||||
namespace {
|
||||
enum class Parser_state { major, minor, patch, prerelease, build };
|
||||
|
||||
using Validator = function<void(const string &, const char)>;
|
||||
using State_transition_hook = function<void(string &)>;
|
||||
/// State transition is described by a character that triggers it, a state to transition to and
|
||||
/// optional hook to be invoked on transition.
|
||||
using Transition = tuple<const char, Parser_state, State_transition_hook>;
|
||||
using Transitions = vector<Transition>;
|
||||
using State = tuple<Transitions, string &, Validator>;
|
||||
using State_machine = std::map<Parser_state, State>;
|
||||
|
||||
// Ranges of characters allowed in prerelease and build identifiers.
|
||||
const vector<pair<char, char>> allowed_prerel_id_chars = {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {'-', '-'}};
|
||||
|
||||
inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) {
|
||||
return make_tuple(c, p, pth);
|
||||
}
|
||||
|
||||
inline void Parse_error(const std::string & s) {
|
||||
// EMSESP::logger().err("parse error: %s", s.c_str());
|
||||
}
|
||||
|
||||
/// Advance parser state machine by a single step.
|
||||
/**
|
||||
Perform single step of parser state machine: if character matches one from transition tables -
|
||||
trigger transition to next state; otherwise, validate if current token is in legal state
|
||||
(throw Parse_error if not) and then add character to current token; State transition includes
|
||||
preparing various vars for next state and invoking state transition hook (if specified) which is
|
||||
where whole tokens are validated.
|
||||
*/
|
||||
inline void process_char(const char c, Parser_state & cstate, Parser_state & pstate, const Transitions & transitions, string & target, Validator validate) {
|
||||
for (const auto & transition : transitions) {
|
||||
if (c == get<0>(transition)) {
|
||||
if (get<2>(transition))
|
||||
get<2>(transition)(target);
|
||||
pstate = cstate;
|
||||
cstate = get<1>(transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
validate(target, c);
|
||||
target.push_back(c);
|
||||
}
|
||||
|
||||
/// Validate normal (major, minor, patch) version components.
|
||||
inline void normal_version_validator(const string & tgt, const char c) {
|
||||
if (c < '0' || c > '9')
|
||||
Parse_error("invalid character encountered: " + string(1, c));
|
||||
if (tgt.compare(0, 1, "0") == 0)
|
||||
Parse_error("leading 0 not allowed");
|
||||
}
|
||||
|
||||
/// Validate that prerelease and build version identifiers are comprised of allowed chars only.
|
||||
inline void prerelease_version_validator(const string &, const char c) {
|
||||
bool res = false;
|
||||
for (const auto & r : allowed_prerel_id_chars) {
|
||||
res |= (c >= r.first && c <= r.second);
|
||||
}
|
||||
if (!res)
|
||||
Parse_error("invalid character encountered: " + string(1, c));
|
||||
}
|
||||
|
||||
inline bool is_identifier_numeric(const string & id) {
|
||||
return id.find_first_not_of("0123456789") == string::npos;
|
||||
}
|
||||
|
||||
inline bool check_for_leading_0(const string & str) {
|
||||
return str.length() > 1 && str[0] == '0';
|
||||
}
|
||||
|
||||
/// Validate every individual prerelease identifier, determine it's type and add it to collection.
|
||||
void prerelease_hook_impl(string & id, Prerelease_identifiers & prerelease) {
|
||||
if (id.empty())
|
||||
Parse_error("version identifier cannot be empty");
|
||||
Id_type t = Id_type::alnum;
|
||||
if (is_identifier_numeric(id)) {
|
||||
t = Id_type::num;
|
||||
if (check_for_leading_0(id)) {
|
||||
Parse_error("numeric identifiers cannot have leading 0");
|
||||
}
|
||||
}
|
||||
prerelease.push_back(Prerelease_identifier(id, t));
|
||||
id.clear();
|
||||
}
|
||||
|
||||
/// Validate every individual build identifier and add it to collection.
|
||||
void build_hook_impl(string & id, Parser_state & pstate, Build_identifiers & build, std::string & prerelease_id, Prerelease_identifiers & prerelease) {
|
||||
// process last token left from parsing prerelease data
|
||||
if (pstate == Parser_state::prerelease)
|
||||
prerelease_hook_impl(prerelease_id, prerelease);
|
||||
if (id.empty())
|
||||
Parse_error("version identifier cannot be empty");
|
||||
build.push_back(id);
|
||||
id.clear();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Parse semver 2.0.0-compatible string to Version_data structure.
|
||||
/**
|
||||
Version text parser is implemented as a state machine. In each step one successive character from version
|
||||
string is consumed and is either added to current token or triggers state transition. Hooks can be
|
||||
injected into state transitions for validation/customization purposes.
|
||||
*/
|
||||
Version_data Semver200_parser::parse(const string & s) const {
|
||||
string major;
|
||||
string minor;
|
||||
string patch;
|
||||
string prerelease_id;
|
||||
string build_id;
|
||||
Prerelease_identifiers prerelease;
|
||||
Build_identifiers build;
|
||||
Parser_state cstate{Parser_state::major};
|
||||
Parser_state pstate;
|
||||
|
||||
auto prerelease_hook = [&](string & id) { prerelease_hook_impl(id, prerelease); };
|
||||
|
||||
auto build_hook = [&](string & id) { build_hook_impl(id, pstate, build, prerelease_id, prerelease); };
|
||||
|
||||
// State transition tables
|
||||
auto major_trans = {mkx('.', Parser_state::minor, {})};
|
||||
auto minor_trans = {mkx('.', Parser_state::patch, {})};
|
||||
auto patch_trans = {mkx('-', Parser_state::prerelease, {}), mkx('+', Parser_state::build, {})};
|
||||
auto prerelease_trans = {// When identifier separator (.) is found, stay in the same state but invoke hook
|
||||
// in order to process each individual identifier separately.
|
||||
mkx('.', Parser_state::prerelease, prerelease_hook),
|
||||
mkx('+', Parser_state::build, {})};
|
||||
auto build_trans = {// Same stay-in-the-same-state-but-invoke-hook trick from above.
|
||||
mkx('.', Parser_state::build, build_hook)};
|
||||
|
||||
State_machine state_machine = {{Parser_state::major, State{major_trans, major, normal_version_validator}},
|
||||
{Parser_state::minor, State{minor_trans, minor, normal_version_validator}},
|
||||
{Parser_state::patch, State{patch_trans, patch, normal_version_validator}},
|
||||
{Parser_state::prerelease, State{prerelease_trans, prerelease_id, prerelease_version_validator}},
|
||||
{Parser_state::build, State{build_trans, build_id, prerelease_version_validator}}};
|
||||
|
||||
// Main loop.
|
||||
for (const auto & c : s) {
|
||||
auto state = state_machine.at(cstate);
|
||||
process_char(c, cstate, pstate, get<0>(state), get<1>(state), get<2>(state));
|
||||
}
|
||||
|
||||
// Trigger appropriate hooks in order to process last token, because no state transition was
|
||||
// triggered for it.
|
||||
if (cstate == Parser_state::prerelease) {
|
||||
prerelease_hook(prerelease_id);
|
||||
} else if (cstate == Parser_state::build) {
|
||||
build_hook(build_id);
|
||||
}
|
||||
|
||||
return Version_data{stoi(major), stoi(minor), stoi(patch), prerelease, build};
|
||||
}
|
||||
|
||||
} // namespace version
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marko Zivanovic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "version.h"
|
||||
|
||||
namespace version {
|
||||
|
||||
/// Parse string into Version_data structure according to semantic versioning 2.0.0 rules.
|
||||
struct Semver200_parser {
|
||||
Version_data parse(const std::string &) const;
|
||||
};
|
||||
|
||||
/// Compare Version_data to another using semantic versioning 2.0.0 rules.
|
||||
struct Semver200_comparator {
|
||||
int compare(const Version_data &, const Version_data &) const;
|
||||
};
|
||||
|
||||
/// Concrete version class that binds all semver 2.0.0 functionality together.
|
||||
class Semver200_version : public Basic_version<Semver200_parser, Semver200_comparator> {
|
||||
public:
|
||||
Semver200_version()
|
||||
: Basic_version{Semver200_parser(), Semver200_comparator()} {
|
||||
}
|
||||
|
||||
Semver200_version(const std::string & v)
|
||||
: Basic_version{v, Semver200_parser(), Semver200_comparator()} {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace version
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marko Zivanovic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace version {
|
||||
|
||||
/// Type of prerelease identifier: alphanumeric or numeric.
|
||||
/**
|
||||
Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while
|
||||
numeric identifiers are compared as numbers.
|
||||
*/
|
||||
enum class Id_type {
|
||||
alnum, ///< Identifier is alphanumerical
|
||||
num ///< Identifier is numeric
|
||||
};
|
||||
|
||||
/// Container for prerelease identifier value and it's type.
|
||||
/**
|
||||
Prerelease version string consist of an optional series of dot-separated identifiers.
|
||||
These identifiers can be either numerical or alphanumerical.
|
||||
This structure describes one such identifier.
|
||||
*/
|
||||
using Prerelease_identifier = std::pair<std::string, Id_type>;
|
||||
|
||||
/// Container for all prerelease identifiers for a given version string.
|
||||
using Prerelease_identifiers = std::vector<Prerelease_identifier>;
|
||||
|
||||
/// Build identifier is arbitrary string with no special meaning with regards to version precedence.
|
||||
using Build_identifier = std::string;
|
||||
|
||||
/// Container for all build identifiers of a given version string.
|
||||
using Build_identifiers = std::vector<Build_identifier>;
|
||||
|
||||
/// Description of version broken into parts, as per semantic versioning specification.
|
||||
struct Version_data {
|
||||
Version_data(const int M, const int m, const int p, const Prerelease_identifiers & pr, const Build_identifiers & b)
|
||||
: major{M}
|
||||
, minor{m}
|
||||
, patch{p}
|
||||
, prerelease_ids{pr}
|
||||
, build_ids{b} {
|
||||
}
|
||||
|
||||
int major; ///< Major version, change only on incompatible API modifications.
|
||||
int minor; ///< Minor version, change on backwards-compatible API modifications.
|
||||
int patch; ///< Patch version, change only on bugfixes.
|
||||
|
||||
/// Optional series of prerelease identifiers.
|
||||
Prerelease_identifiers prerelease_ids;
|
||||
|
||||
/// Optional series of build identifiers.
|
||||
Build_identifiers build_ids;
|
||||
};
|
||||
|
||||
// Forward declaration required for operators' template declarations.
|
||||
template <typename Parser, typename Comparator>
|
||||
class Basic_version;
|
||||
|
||||
/// Test if left-hand version operand is of lower precedence than the right-hand version.
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator<(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
||||
|
||||
/// Test if left-hand version operand if of equal precedence as the right-hand version.
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator==(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
||||
|
||||
/// Test if left-hand version and right-hand version are of different precedence.
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator!=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
||||
|
||||
/// Test if left-hand version operand is of higher precedence than the right-hand version.
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator>(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
||||
|
||||
/// Test if left-hand version operand is of higher or equal precedence as the right-hand version.
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator>=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
||||
|
||||
/// Test if left-hand version operand is of lower or equal precedence as the right-hand version.
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator<=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
||||
|
||||
/// Base class for various version parsing, precedence ordering and data manipulation schemes.
|
||||
/**
|
||||
Basic_version class describes general version object without prescribing parsing,
|
||||
validation, comparison and modification rules. These rules are implemented by supplied Parser, Comparator
|
||||
and Modifier objects.
|
||||
*/
|
||||
template <typename Parser, typename Comparator>
|
||||
class Basic_version {
|
||||
public:
|
||||
/// Construct Basic_version object using Parser object to parse default ("0.0.0") version string, Comparator for comparison and Modifier for modification.
|
||||
Basic_version(Parser, Comparator);
|
||||
|
||||
/// Construct Basic_version object using Parser to parse supplied version string, Comparator for comparison and Modifier for modification.
|
||||
Basic_version(const std::string &, Parser, Comparator);
|
||||
|
||||
/// Construct Basic_version object using supplied Version_data, Parser, Comparator and Modifier objects.
|
||||
Basic_version(const Version_data &, Parser, Comparator);
|
||||
|
||||
/// Construct Basic_version by copying data from another one.
|
||||
Basic_version(const Basic_version &);
|
||||
|
||||
/// Copy version data from another Basic_version to this one.
|
||||
Basic_version & operator=(const Basic_version &);
|
||||
|
||||
int major() const; ///< Get major version.
|
||||
int minor() const; ///< Get minor version.
|
||||
int patch() const; ///< Get patch version.
|
||||
const std::string prerelease() const; ///< Get prerelease version string.
|
||||
const std::string build() const; ///< Get build version string.
|
||||
|
||||
friend bool operator< <>(const Basic_version &, const Basic_version &);
|
||||
friend bool operator==<>(const Basic_version &, const Basic_version &);
|
||||
|
||||
private:
|
||||
Parser parser_;
|
||||
Comparator comparator_;
|
||||
Version_data ver_;
|
||||
};
|
||||
|
||||
} // namespace version
|
||||
|
||||
#include "version.inl"
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marko Zivanovic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "version.h"
|
||||
|
||||
namespace version {
|
||||
|
||||
namespace {
|
||||
|
||||
/// Utility function to splice all vector elements to output stream, using designated separator
|
||||
/// between elements and function object for getting values from vector elements.
|
||||
template <typename T, typename F>
|
||||
std::string & splice(std::string & ss, const std::vector<T> & vec, const std::string & sep, F read) {
|
||||
if (!vec.empty()) {
|
||||
for (auto it = vec.cbegin(); it < vec.cend() - 1; ++it) {
|
||||
ss += read(*it) + sep;
|
||||
}
|
||||
ss += read(*vec.crbegin());
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
Basic_version<Parser, Comparator>::Basic_version(Parser p, Comparator c)
|
||||
: parser_(p)
|
||||
, comparator_(c)
|
||||
, ver_(parser_.parse("0.0.0")) {
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
Basic_version<Parser, Comparator>::Basic_version(const std::string & v, Parser p, Comparator c)
|
||||
: parser_(p)
|
||||
, comparator_(c)
|
||||
, ver_(parser_.parse(v)) {
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
Basic_version<Parser, Comparator>::Basic_version(const Version_data & v, Parser p, Comparator c)
|
||||
: parser_(p)
|
||||
, comparator_(c)
|
||||
, ver_(v) {
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
Basic_version<Parser, Comparator>::Basic_version(const Basic_version<Parser, Comparator> &) = default;
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
Basic_version<Parser, Comparator> & Basic_version<Parser, Comparator>::operator=(const Basic_version<Parser, Comparator> &) = default;
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
int Basic_version<Parser, Comparator>::major() const {
|
||||
return ver_.major;
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
int Basic_version<Parser, Comparator>::minor() const {
|
||||
return ver_.minor;
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
int Basic_version<Parser, Comparator>::patch() const {
|
||||
return ver_.patch;
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
const std::string Basic_version<Parser, Comparator>::prerelease() const {
|
||||
std::string ss;
|
||||
return splice(ss, ver_.prerelease_ids, ".", [](const Prerelease_identifier & id) { return id.first; });
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
const std::string Basic_version<Parser, Comparator>::build() const {
|
||||
std::string ss;
|
||||
return splice(ss, ver_.build_ids, ".", [](const std::string & id) { return id; });
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator<(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
||||
return l.comparator_.compare(l.ver_, r.ver_) == -1;
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
bool operator==(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
||||
return l.comparator_.compare(l.ver_, r.ver_) == 0;
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
inline bool operator!=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
||||
return !(l == r);
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
inline bool operator>(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
||||
return r < l;
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
inline bool operator>=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
||||
return !(l < r);
|
||||
}
|
||||
|
||||
template <typename Parser, typename Comparator>
|
||||
inline bool operator<=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
||||
return !(l > r);
|
||||
}
|
||||
|
||||
} // namespace version
|
||||
@@ -21,8 +21,8 @@ extra_configs =
|
||||
pio_local.ini
|
||||
|
||||
[common]
|
||||
core_build_flags = -std=c++17 -std=gnu++17 -O3 -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
|
||||
core_unbuild_flags = -std=gnu++11
|
||||
core_build_flags = -std=gnu++20 -O3 -flto=auto -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
|
||||
core_unbuild_flags = -std=gnu++11 -std=gnu++14 -std=gnu++17 -fno-lto
|
||||
|
||||
my_build_flags =
|
||||
|
||||
@@ -169,7 +169,7 @@ build_src_flags =
|
||||
-DEMSESP_STANDALONE -DEMSESP_TEST
|
||||
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
||||
-DNO_TLS_SUPPORT
|
||||
-std=gnu++17 -Og -ggdb
|
||||
-std=gnu++20 -Og -ggdb
|
||||
-Wall -Wextra
|
||||
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
||||
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
||||
@@ -210,7 +210,7 @@ build_src_flags =
|
||||
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
||||
-DNO_TLS_SUPPORT
|
||||
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||
-std=gnu++17 -Og -ggdb
|
||||
-std=gnu++20 -Og -ggdb
|
||||
-Wall -Wextra
|
||||
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
||||
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
||||
|
||||
@@ -70,12 +70,16 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
|
||||
*/
|
||||
String ArduinoJsonJWT::sign(String & payload) {
|
||||
std::array<unsigned char, 32> hmacResult{};
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
|
||||
reinterpret_cast<const unsigned char *>(_secret.c_str()),
|
||||
_secret.length(),
|
||||
reinterpret_cast<const unsigned char *>(payload.c_str()),
|
||||
payload.length(),
|
||||
hmacResult.data());
|
||||
{
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
mbedtls_md_init(&ctx);
|
||||
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
|
||||
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
|
||||
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
|
||||
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
|
||||
mbedtls_md_free(&ctx);
|
||||
}
|
||||
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,75 @@
|
||||
|
||||
#include "WWWData.h" // include auto-generated static web resources
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
|
||||
|
||||
// Single static-content handler serving all assets embedded in WWWData.h.
|
||||
class StaticContentHandler : public AsyncWebHandler {
|
||||
public:
|
||||
bool canHandle(AsyncWebServerRequest * request) const override {
|
||||
const auto method = request->method();
|
||||
return method == HTTP_GET || method == HTTP_HEAD || method == HTTP_OPTIONS;
|
||||
}
|
||||
|
||||
void handleRequest(AsyncWebServerRequest * request) override {
|
||||
// OPTIONS is handled generically - the server-level CORS headers are
|
||||
// attached via DefaultHeaders in ESP32React::begin().
|
||||
if (request->method() == HTTP_OPTIONS) {
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
|
||||
const char * url = request->url().c_str();
|
||||
const WWWAsset * found = lookup(url);
|
||||
const WWWAsset * asset = found ? found : index_asset();
|
||||
|
||||
if (asset == nullptr) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the client already has this exact ETag, respond 304 Not Modified without sending the body.
|
||||
const String & inm = request->header(asyncsrv::T_INM);
|
||||
if (inm.length() != 0 && strcmp(inm.c_str(), asset->etag) == 0) {
|
||||
request->send(304);
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(200, asset->contentType, asset->content, asset->len);
|
||||
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
|
||||
response->addHeader(asyncsrv::T_ETag, asset->etag, false);
|
||||
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
private:
|
||||
// Exact-match lookup in the asset table
|
||||
static const WWWAsset * lookup(const char * url) {
|
||||
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
||||
if (strcmp(WWW_ASSETS[i].uri, url) == 0) {
|
||||
return &WWW_ASSETS[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Returns the /index.html asset, used as the SPA fallback for any GET
|
||||
// that didn't match an embedded asset (React Router handles routing on
|
||||
// the client side).
|
||||
static const WWWAsset * index_asset() {
|
||||
static const WWWAsset * cached = nullptr;
|
||||
if (cached == nullptr) {
|
||||
cached = lookup("/index.html");
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
};
|
||||
|
||||
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
||||
: _securitySettingsService(server, fs)
|
||||
: _server(server)
|
||||
, _securitySettingsService(server, fs)
|
||||
, _networkSettingsService(server, fs, &_securitySettingsService)
|
||||
, _wifiScanner(server, &_securitySettingsService)
|
||||
, _networkStatus(server, &_securitySettingsService)
|
||||
@@ -17,50 +82,6 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
||||
, _mqttSettingsService(server, fs, &_securitySettingsService)
|
||||
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
|
||||
, _authenticationService(server, &_securitySettingsService) {
|
||||
//
|
||||
// Serve static web resources
|
||||
//
|
||||
|
||||
ArRequestHandlerFunction indexHtmlHandler = nullptr;
|
||||
|
||||
WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
||||
String etag = "\"" + hash + "\""; // RFC9110: ETag must be enclosed in double quotes
|
||||
|
||||
ArRequestHandlerFunction requestHandler = [contentType, content, len, etag](AsyncWebServerRequest * request) {
|
||||
if (request->header(asyncsrv::T_INM) == etag) {
|
||||
request->send(304);
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len);
|
||||
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
|
||||
response->addHeader(asyncsrv::T_ETag, etag, false);
|
||||
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
|
||||
request->send(response);
|
||||
};
|
||||
|
||||
server->on(uri, HTTP_GET, requestHandler);
|
||||
|
||||
// Capture index.html handler to set onNotFound once after all routes are registered
|
||||
if (strcmp(uri, "/index.html") == 0) {
|
||||
indexHtmlHandler = requestHandler;
|
||||
}
|
||||
});
|
||||
|
||||
// Set onNotFound handler once after all routes are registered
|
||||
// Serving non matching get requests with "/index.html"
|
||||
// OPTIONS get a straight up 200 response
|
||||
if (indexHtmlHandler != nullptr) {
|
||||
server->onNotFound([indexHtmlHandler](AsyncWebServerRequest * request) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
indexHtmlHandler(request);
|
||||
} else if (request->method() == HTTP_OPTIONS) {
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404); // not found
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32React::begin() {
|
||||
@@ -78,11 +99,11 @@ void ESP32React::begin() {
|
||||
_ntpSettingsService.begin();
|
||||
_mqttSettingsService.begin();
|
||||
_securitySettingsService.begin();
|
||||
_server->addHandler(new StaticContentHandler());
|
||||
}
|
||||
|
||||
void ESP32React::loop() {
|
||||
_networkSettingsService.loop();
|
||||
_apSettingsService.loop();
|
||||
_mqttSettingsService.loop();
|
||||
_ntpSettingsService.loop();
|
||||
}
|
||||
@@ -68,6 +68,7 @@ class ESP32React {
|
||||
}
|
||||
|
||||
private:
|
||||
AsyncWebServer * _server;
|
||||
SecuritySettingsService _securitySettingsService;
|
||||
NetworkSettingsService _networkSettingsService;
|
||||
WiFiScanner _wifiScanner;
|
||||
|
||||
124
src/core/EMSESP_Version.h
Normal file
124
src/core/EMSESP_Version.h
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020-2026 emsesp.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_Version_H
|
||||
#define EMSESP_Version_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
// Drop-in lightweight replacement for the subset of the semver library actually used by EMS-ESP.
|
||||
// The previous semver library (lib/semver) builds a std::map + std::function-based state machine on
|
||||
// every parse, which fragments the internal heap on the ESP32. This replacement does no heap
|
||||
// allocation beyond the std::string member for the prerelease tag, and matches the API surface
|
||||
// we consume: construction from string, major()/minor()/patch()/prerelease(), and operator>/</==.
|
||||
//
|
||||
// Only strict numeric precedence (major.minor.patch) is used for comparison in EMS-ESP, so we
|
||||
// intentionally ignore prerelease tags during comparison rather than implement the full semver
|
||||
// ordering rules. This is consistent with how the old code was used (callers only check major/
|
||||
// minor/patch numerically; prerelease() is only read for logging).
|
||||
|
||||
namespace version {
|
||||
|
||||
class EMSESP_Version {
|
||||
public:
|
||||
EMSESP_Version() = default;
|
||||
|
||||
// Construct from a version string like "3.9.0-dev.14" or "3.9.0".
|
||||
// Anything past a '-' or '+' is kept as the prerelease string and not interpreted.
|
||||
explicit EMSESP_Version(const std::string & s) {
|
||||
parse(s.c_str());
|
||||
}
|
||||
|
||||
explicit EMSESP_Version(const char * s) {
|
||||
parse(s ? s : "");
|
||||
}
|
||||
|
||||
int major() const {
|
||||
return major_;
|
||||
}
|
||||
int minor() const {
|
||||
return minor_;
|
||||
}
|
||||
int patch() const {
|
||||
return patch_;
|
||||
}
|
||||
const std::string & prerelease() const {
|
||||
return prerelease_;
|
||||
}
|
||||
|
||||
// Numeric-only comparison (major.minor.patch). Prerelease tags are ignored on purpose.
|
||||
friend bool operator<(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||
if (a.major_ != b.major_)
|
||||
return a.major_ < b.major_;
|
||||
if (a.minor_ != b.minor_)
|
||||
return a.minor_ < b.minor_;
|
||||
return a.patch_ < b.patch_;
|
||||
}
|
||||
|
||||
friend bool operator>(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
friend bool operator==(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||
return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_;
|
||||
}
|
||||
|
||||
friend bool operator!=(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
friend bool operator>=(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
friend bool operator<=(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
private:
|
||||
int major_ = 0;
|
||||
int minor_ = 0;
|
||||
int patch_ = 0;
|
||||
std::string prerelease_;
|
||||
|
||||
void parse(const char * s) {
|
||||
major_ = minor_ = patch_ = 0;
|
||||
prerelease_.clear();
|
||||
if (s == nullptr || *s == '\0') {
|
||||
return;
|
||||
}
|
||||
// parse numeric major.minor.patch; accept partial ("3", "3.9", "3.9.0")
|
||||
sscanf(s, "%d.%d.%d", &major_, &minor_, &patch_);
|
||||
// capture prerelease tag after '-' if present (stop at '+' which is build metadata)
|
||||
const char * dash = strchr(s, '-');
|
||||
if (dash != nullptr) {
|
||||
const char * plus = strchr(dash, '+');
|
||||
if (plus != nullptr) {
|
||||
prerelease_.assign(dash + 1, plus - dash - 1);
|
||||
} else {
|
||||
prerelease_.assign(dash + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace version
|
||||
|
||||
#endif
|
||||
@@ -19,6 +19,7 @@
|
||||
#ifndef EMSESP_EMSFACTORY_H_
|
||||
#define EMSESP_EMSFACTORY_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory> // for unique_ptr
|
||||
#include <map>
|
||||
|
||||
|
||||
@@ -26,13 +26,12 @@
|
||||
#include <esp_mac.h>
|
||||
#include "esp_efuse.h"
|
||||
#include <nvs.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#endif
|
||||
|
||||
#include <HTTPClient.h>
|
||||
#include <map>
|
||||
|
||||
#include <semver200.h>
|
||||
#include "EMSESP_Version.h"
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
#include "../test/test.h"
|
||||
@@ -457,15 +456,16 @@ void System::get_partition_info() {
|
||||
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d));
|
||||
p_info.install_date = d > 1500000000L ? time_string : "";
|
||||
|
||||
esp_image_metadata_t meta = {};
|
||||
esp_partition_pos_t part_pos = {.offset = part->address, .size = part->size};
|
||||
if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &part_pos, &meta) == ESP_OK) {
|
||||
p_info.size = meta.image_len / 1024; // actual firmware size in KB
|
||||
} else {
|
||||
p_info.size = 0;
|
||||
if (!p_info.version.empty()) {
|
||||
esp_image_metadata_t meta = {};
|
||||
esp_partition_pos_t part_pos = {.offset = part->address, .size = part->size};
|
||||
if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &part_pos, &meta) == ESP_OK) {
|
||||
p_info.size = meta.image_len / 1024; // actual firmware size in KB
|
||||
} else {
|
||||
p_info.size = 0;
|
||||
}
|
||||
partition_info_[part->label] = p_info;
|
||||
}
|
||||
|
||||
partition_info_[part->label] = p_info;
|
||||
}
|
||||
|
||||
it = esp_partition_next(it); // loop to next partition
|
||||
@@ -1613,8 +1613,8 @@ bool System::check_upgrade() {
|
||||
settingsVersion = "3.5.0"; // this was the last stable version without version info
|
||||
}
|
||||
|
||||
version::Semver200_version settings_version(settingsVersion);
|
||||
version::Semver200_version this_version(EMSESP_APP_VERSION);
|
||||
version::EMSESP_Version settings_version(settingsVersion);
|
||||
version::EMSESP_Version this_version(EMSESP_APP_VERSION);
|
||||
|
||||
std::string settings_version_type = settings_version.prerelease().empty() ? "" : ("-" + settings_version.prerelease());
|
||||
std::string this_version_type = this_version.prerelease().empty() ? "" : ("-" + this_version.prerelease());
|
||||
@@ -1765,18 +1765,17 @@ void System::exportSettings(const std::string & type, const char * filename, Jso
|
||||
|
||||
File settingsFile = LittleFS.open(filename);
|
||||
if (settingsFile) {
|
||||
JsonDocument jsonDocument;
|
||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
|
||||
JsonObject node = output[section].to<JsonObject>();
|
||||
for (JsonPair kvp : jsonDocument.as<JsonObject>()) {
|
||||
node[kvp.key()] = kvp.value();
|
||||
{
|
||||
JsonDocument jsonDocument;
|
||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||
settingsFile.close(); // close early, we no longer need the file
|
||||
if (error || !jsonDocument.is<JsonObject>()) {
|
||||
LOG_ERROR("Failed to deserialize settings file %s", filename);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to deserialize settings file %s", filename);
|
||||
output[section].set(jsonDocument.as<JsonObject>());
|
||||
}
|
||||
LOG_DEBUG("Exported %s settings from file %s", section, filename);
|
||||
settingsFile.close();
|
||||
} else {
|
||||
LOG_ERROR("No settings file for %s found", filename);
|
||||
}
|
||||
@@ -1828,13 +1827,15 @@ void System::exportSystemBackup(JsonObject output) {
|
||||
if (file) {
|
||||
JsonDocument jsonDocument;
|
||||
DeserializationError error = deserializeJson(jsonDocument, file);
|
||||
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
|
||||
JsonObject node = nodes.add<JsonObject>();
|
||||
node["type"] = "customSupport";
|
||||
node["data"] = jsonDocument.as<JsonObject>();
|
||||
file.close(); // close early, we no longer need the file
|
||||
if (!error && jsonDocument.is<JsonObject>()) {
|
||||
JsonObject support_node = nodes.add<JsonObject>();
|
||||
support_node["type"] = "customSupport";
|
||||
support_node["data"].set(jsonDocument.as<JsonObject>());
|
||||
LOG_DEBUG("Exported custom support file %s", EMSESP_CUSTOMSUPPORT_FILE);
|
||||
} else {
|
||||
LOG_ERROR("Failed to deserialize custom support file");
|
||||
}
|
||||
file.close();
|
||||
LOG_DEBUG("Exported custom support file %s", EMSESP_CUSTOMSUPPORT_FILE);
|
||||
}
|
||||
|
||||
// Backup NVS values
|
||||
|
||||
@@ -253,7 +253,7 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
|
||||
// it's a filename with a .bin or .md extension, try and extract the version from it
|
||||
// e.g. EMS-ESP-3_8_2-dev_13-ESP32-16MB+.bin -> major=3 minor=8 patch=2
|
||||
version::Semver200_version latest_version;
|
||||
version::EMSESP_Version latest_version;
|
||||
if ((version.find(".bin") != std::string::npos) || (version.find(".md") != std::string::npos)) {
|
||||
std::string filename = version;
|
||||
auto pos = filename.find("EMS-ESP-");
|
||||
@@ -274,18 +274,18 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
std::string major_version = filename.substr(pos, underscore1 - pos);
|
||||
std::string minor_version = filename.substr(underscore1 + 1, underscore2 - underscore1 - 1);
|
||||
std::string patch_version = filename.substr(underscore2 + 1, dash - underscore2 - 1);
|
||||
latest_version = version::Semver200_version(major_version + "." + minor_version + "." + patch_version);
|
||||
latest_version = version::EMSESP_Version(major_version + "." + minor_version + "." + patch_version);
|
||||
} else {
|
||||
// if it's .json file exit
|
||||
if (version.find(".json") != std::string::npos) {
|
||||
return 0;
|
||||
} else {
|
||||
// treat it like a version string like "3.9.0"
|
||||
latest_version = version::Semver200_version(version);
|
||||
latest_version = version::EMSESP_Version(version);
|
||||
}
|
||||
}
|
||||
|
||||
version::Semver200_version current_version(current_version_s); // get current version
|
||||
version::EMSESP_Version current_version(current_version_s); // get current version
|
||||
|
||||
if (latest_version > current_version && current_version.minor() < latest_version.minor()) {
|
||||
return 0; // if it's just a minor version upgrade return 0
|
||||
@@ -306,9 +306,9 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
// versions holds the latest development version and stable version in one string, comma separated
|
||||
bool WebStatusService::checkUpgrade(JsonObject root, std::string & version) {
|
||||
if (!version.empty()) {
|
||||
version::Semver200_version current_version(current_version_s);
|
||||
version::Semver200_version latest_dev_version(version.substr(0, version.find(',')));
|
||||
version::Semver200_version latest_stable_version(version.substr(version.find(',') + 1));
|
||||
version::EMSESP_Version current_version(current_version_s);
|
||||
version::EMSESP_Version latest_dev_version(version.substr(0, version.find(',')));
|
||||
version::EMSESP_Version latest_stable_version(version.substr(version.find(',') + 1));
|
||||
|
||||
bool dev_upgradeable = latest_dev_version > current_version;
|
||||
bool stable_upgradeable = latest_stable_version > current_version;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
|
||||
#define EMSESP_ACTION_SERVICE_PATH "/rest/action"
|
||||
|
||||
#include <semver200.h> // for version checking
|
||||
#include "../core/EMSESP_Version.h"
|
||||
#include "../emsesp_version.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
Reference in New Issue
Block a user