mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Sat Feb 1 19:32:25 CET 2025
This commit is contained in:
@@ -900,3 +900,4 @@ Update On Tue Jan 28 19:34:17 CET 2025
|
||||
Update On Wed Jan 29 19:35:42 CET 2025
|
||||
Update On Thu Jan 30 19:32:29 CET 2025
|
||||
Update On Fri Jan 31 19:32:11 CET 2025
|
||||
Update On Sat Feb 1 19:32:16 CET 2025
|
||||
|
||||
@@ -26,6 +26,7 @@ export default {
|
||||
() => 'cargo fmt --manifest-path ./backend/Cargo.toml --all',
|
||||
// () => 'cargo test --manifest-path=./backend/Cargo.toml',
|
||||
// () => "cargo fmt --manifest-path=./backend/Cargo.toml --all",
|
||||
() => 'git add -u',
|
||||
],
|
||||
'*.{html,sass,scss,less}': ['prettier --write', 'stylelint --fix'],
|
||||
'package.json': ['prettier --write'],
|
||||
|
||||
Generated
+111
-230
@@ -62,9 +62,9 @@ checksum = "bfc6c1ecd82053d127961ad80a8beaa6004fb851a3a5b96506d7a6bd462403f6"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
@@ -343,9 +343,9 @@ dependencies = [
|
||||
"core-graphics 0.23.2",
|
||||
"image",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"windows-sys 0.48.0",
|
||||
"x11rb",
|
||||
@@ -944,16 +944,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||
dependencies = [
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1543,9 +1534,9 @@ dependencies = [
|
||||
"nyanpasu-ipc",
|
||||
"nyanpasu-macro",
|
||||
"nyanpasu-utils",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit 0.3.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"open",
|
||||
"openssl",
|
||||
@@ -1568,6 +1559,7 @@ dependencies = [
|
||||
"runas",
|
||||
"rust-i18n",
|
||||
"rustc_version",
|
||||
"seahash",
|
||||
"semver 1.0.24",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2517,9 +2509,9 @@ dependencies = [
|
||||
"image",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
@@ -3466,8 +3458,8 @@ checksum = "b00d88f1be7bf4cd2e61623ce08e84be2dfa4eab458e5d632d3dab95f16c1f64"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"keyboard-types",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
@@ -3550,9 +3542,9 @@ dependencies = [
|
||||
"glutin_glx_sys",
|
||||
"glutin_wgl_sys",
|
||||
"libloading 0.8.6",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"raw-window-handle",
|
||||
"wayland-sys",
|
||||
@@ -5135,9 +5127,9 @@ dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"keyboard-types",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
@@ -5602,15 +5594,6 @@ dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.2.2"
|
||||
@@ -5618,32 +5601,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-data 0.2.2",
|
||||
"objc2-core-image 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.6.0",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
"objc2-cloud-kit 0.3.0",
|
||||
"objc2-core-data 0.3.0",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image 0.3.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-quartz-core 0.3.0",
|
||||
"objc2",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5653,21 +5617,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5676,9 +5629,9 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5688,42 +5641,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"objc2 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5732,32 +5652,22 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-image"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-location"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5773,34 +5683,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"block2",
|
||||
"dispatch",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.6.0",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5809,10 +5695,10 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5822,9 +5708,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5834,31 +5720,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-symbols"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
||||
dependencies = [
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5868,15 +5743,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-cloud-kit 0.2.2",
|
||||
"objc2-core-data 0.2.2",
|
||||
"objc2-core-image 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-foundation",
|
||||
"objc2-link-presentation",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
"objc2-quartz-core",
|
||||
"objc2-symbols",
|
||||
"objc2-uniform-type-identifiers",
|
||||
"objc2-user-notifications",
|
||||
@@ -5888,9 +5763,9 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5900,10 +5775,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5913,10 +5788,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7363,7 +7238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a24763657bff09769a8ccf12c8b8a50416fb035fe199263b4c5071e4e3f006f"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2 0.5.1",
|
||||
"block2",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation-sys",
|
||||
"glib-sys",
|
||||
@@ -7371,9 +7246,9 @@ dependencies = [
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -7678,6 +7553,12 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
@@ -8272,9 +8153,9 @@ dependencies = [
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.5.8",
|
||||
"wasm-bindgen",
|
||||
@@ -8699,9 +8580,9 @@ dependencies = [
|
||||
"log",
|
||||
"mime",
|
||||
"muda",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
@@ -8830,7 +8711,7 @@ dependencies = [
|
||||
"dirs 6.0.0",
|
||||
"interprocess",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2",
|
||||
"once_cell",
|
||||
"tauri-utils",
|
||||
"tokio",
|
||||
@@ -9021,9 +8902,9 @@ dependencies = [
|
||||
"http 1.2.0",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
"softbuffer",
|
||||
@@ -9715,9 +9596,9 @@ dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
@@ -10488,14 +10369,14 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"block2",
|
||||
"core-foundation 0.10.0",
|
||||
"home",
|
||||
"jni",
|
||||
"log",
|
||||
"ndk-context",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"url",
|
||||
"web-sys",
|
||||
]
|
||||
@@ -10792,9 +10673,9 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ea403deff7b51fff19e261330f71608ff2cdef5721d72b64180bb95be7c4150"
|
||||
dependencies = [
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-version",
|
||||
@@ -11382,7 +11263,7 @@ dependencies = [
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.8.0",
|
||||
"block2 0.5.1",
|
||||
"block2",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"cfg_aliases 0.2.1",
|
||||
@@ -11395,9 +11276,9 @@ dependencies = [
|
||||
"libc",
|
||||
"memmap2",
|
||||
"ndk",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"orbclient",
|
||||
"percent-encoding",
|
||||
@@ -11496,7 +11377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2e33c08b174442ff80d5c791020696f9f8b4e4a87b8cfc7494aad6167ec44e1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.5.1",
|
||||
"block2",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
@@ -11510,9 +11391,9 @@ dependencies = [
|
||||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"once_cell",
|
||||
|
||||
@@ -309,7 +309,7 @@ impl Console {
|
||||
console: &Self,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let assertion = args.first().map_or(false, JsValue::to_boolean);
|
||||
let assertion = args.first().is_some_and(JsValue::to_boolean);
|
||||
|
||||
if !assertion {
|
||||
let mut args: Vec<JsValue> = args.iter().skip(1).cloned().collect();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::utils::svg::{render_svg_with_current_color_replace, SvgExt};
|
||||
use eframe::egui::{
|
||||
self, include_image, style::Selection, Color32, Id, Image, Layout, Margin, RichText, Rounding,
|
||||
Sense, Stroke, Style, TextureOptions, Theme, Vec2, ViewportCommand, Visuals, WidgetText,
|
||||
Sense, Stroke, Style, Theme, Vec2, ViewportCommand, Visuals, WidgetText,
|
||||
};
|
||||
|
||||
// Presets
|
||||
|
||||
@@ -27,4 +27,4 @@ winreg = "0.55.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.5.0"
|
||||
objc2 = "0.5.2"
|
||||
|
||||
@@ -52,6 +52,8 @@ rayon = "1.10" # for iterator parallel proc
|
||||
ambassador = "0.4.1" # for trait delegation
|
||||
derive_builder = "0.20" # for builder pattern
|
||||
strum = { version = "0.26", features = ["derive"] } # for enum string conversion
|
||||
atomic_enum = "0.3.0" # for atomic enum
|
||||
enumflags2 = "0.7" # for enum flags
|
||||
|
||||
# Data Structures
|
||||
dashmap = "6"
|
||||
@@ -78,7 +80,7 @@ rust-i18n = "3"
|
||||
axum = "0.8"
|
||||
url = "2"
|
||||
mime = "0.3"
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
|
||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||
port_scanner = "0.1.5"
|
||||
sysproxy = { git = "https://github.com/libnyanpasu/sysproxy-rs.git", version = "0.3" }
|
||||
backon = { version = "1.0.1", features = ["tokio-sleep"] }
|
||||
@@ -89,6 +91,7 @@ serde_json = "1.0"
|
||||
serde_yaml = { version = "0.10", package = "serde_yaml_ng", branch = "feat/specta", git = "https://github.com/libnyanpasu/serde-yaml-ng.git", features = [
|
||||
"specta",
|
||||
] }
|
||||
simd-json = "0.14.1"
|
||||
bincode = "1"
|
||||
bytes = { version = "1", features = ["serde"] }
|
||||
semver = "1.0"
|
||||
@@ -109,9 +112,7 @@ md-5 = "0.10.6"
|
||||
sha2 = "0.10"
|
||||
nanoid = "0.4.0"
|
||||
rs-snowflake = "0.6"
|
||||
simd-json = "0.14.1"
|
||||
atomic_enum = "0.3.0"
|
||||
enumflags2 = "0.7"
|
||||
seahash = "4.1"
|
||||
|
||||
# System Utilities
|
||||
auto-launch = { git = "https://github.com/libnyanpasu/auto-launch.git", version = "0.5" }
|
||||
@@ -202,19 +203,19 @@ specta = { version = "=2.0.0-rc.22", features = [
|
||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||
tauri-plugin-global-shortcut = "2.2.0"
|
||||
|
||||
[target.'cfg(all(target_os = "linux", target_arch = "aarch64"))'.dependencies]
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6.0"
|
||||
objc2-app-kit = { version = "0.3.0", features = [
|
||||
objc2 = "0.5.2"
|
||||
objc2-app-kit = { version = "0.2.2", features = [
|
||||
"NSApplication",
|
||||
"NSResponder",
|
||||
"NSRunningApplication",
|
||||
"NSWindow",
|
||||
"NSView",
|
||||
] }
|
||||
objc2-foundation = { version = "0.3.0", features = ["NSGeometry"] }
|
||||
objc2-foundation = { version = "0.2.2", features = ["NSGeometry"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.29.0", features = ["user", "fs"] }
|
||||
|
||||
@@ -97,7 +97,7 @@ impl IClashTemp {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_external_controller_port(&self) -> u16 {
|
||||
let server = self.get_client_info().server;
|
||||
let port = server.split(':').last().unwrap_or("9090");
|
||||
let port = server.split(':').next_back().unwrap_or("9090");
|
||||
port.parse().unwrap_or(9090)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
item_type::ProfileUid,
|
||||
};
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{bail, Result};
|
||||
use derive_builder::Builder;
|
||||
use indexmap::IndexMap;
|
||||
use nyanpasu_macro::BuilderUpdate;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use tauri::AppHandle;
|
||||
|
||||
#[derive(Debug, serde::Serialize, Clone, specta::Type)]
|
||||
pub struct BuildInfo {
|
||||
@@ -41,3 +42,10 @@ pub static IS_PORTABLE: Lazy<bool> = Lazy::new(|| {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
/// A Tauri AppHandle copy for access from global context,
|
||||
/// maybe only access it from panic handler
|
||||
static APP_HANDLE: OnceCell<AppHandle> = OnceCell::new();
|
||||
pub fn app_handle() -> &'static AppHandle {
|
||||
APP_HANDLE.get().expect("app handle not initialized")
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use crate::{log_err, utils::dirs};
|
||||
use anyhow::Context;
|
||||
use redb::TableDefinition;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
result::Result as StdResult,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use tauri::Emitter;
|
||||
use std::{fs, ops::Deref, result::Result as StdResult, sync::Arc};
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum StorageOperationError {
|
||||
@@ -31,7 +27,29 @@ type Result<T> = StdResult<T, StorageOperationError>;
|
||||
|
||||
/// storage is a wrapper or called a facade for the rocksdb
|
||||
/// Maybe provide a facade for a kv storage is a good idea?
|
||||
#[derive(Clone)]
|
||||
pub struct Storage {
|
||||
inner: Arc<StorageInner>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn try_new(path: &std::path::Path) -> Result<Self> {
|
||||
let inner = StorageInner::try_new(path)?;
|
||||
Ok(Self {
|
||||
inner: Arc::new(inner),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Storage {
|
||||
type Target = Arc<StorageInner>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StorageInner {
|
||||
instance: redb::Database,
|
||||
tx: tokio::sync::broadcast::Sender<(String, Option<Vec<u8>>)>,
|
||||
}
|
||||
@@ -42,30 +60,24 @@ pub trait WebStorage {
|
||||
fn remove_item(&self, key: impl AsRef<str>) -> Result<()>;
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn global() -> &'static Self {
|
||||
static STORAGE: OnceLock<Arc<Storage>> = OnceLock::new();
|
||||
|
||||
STORAGE.get_or_init(|| {
|
||||
let path = dirs::storage_path().unwrap().to_str().unwrap().to_string();
|
||||
let path = PathBuf::from(&path);
|
||||
let instance: redb::Database = if path.exists() && !path.is_dir() {
|
||||
redb::Database::open(&path).unwrap()
|
||||
} else {
|
||||
if path.exists() && path.is_dir() {
|
||||
fs::remove_dir_all(&path).unwrap();
|
||||
}
|
||||
let db = redb::Database::create(&path).unwrap();
|
||||
// Create table
|
||||
let write_txn = db.begin_write().unwrap();
|
||||
write_txn.open_table(NYANPASU_TABLE).unwrap();
|
||||
write_txn.commit().unwrap();
|
||||
db
|
||||
};
|
||||
Arc::new(Storage {
|
||||
instance,
|
||||
tx: tokio::sync::broadcast::channel(16).0,
|
||||
})
|
||||
impl StorageInner {
|
||||
pub fn try_new(path: &std::path::Path) -> Result<Self> {
|
||||
let instance: redb::Database = if path.exists() && !path.is_dir() {
|
||||
redb::Database::open(path).unwrap()
|
||||
} else {
|
||||
if path.exists() && path.is_dir() {
|
||||
fs::remove_dir_all(path).unwrap();
|
||||
}
|
||||
let db = redb::Database::create(path).unwrap();
|
||||
// Create table
|
||||
let write_txn = db.begin_write().unwrap();
|
||||
write_txn.open_table(NYANPASU_TABLE).unwrap();
|
||||
write_txn.commit().unwrap();
|
||||
db
|
||||
};
|
||||
Ok(Self {
|
||||
instance,
|
||||
tx: tokio::sync::broadcast::channel(16).0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,8 +88,9 @@ impl Storage {
|
||||
fn notify_subscribers(&self, key: impl AsRef<str>, value: Option<&[u8]>) {
|
||||
let key = key.as_ref().to_string();
|
||||
let value = value.map(|v| v.to_vec());
|
||||
let tx = self.tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let _ = Self::global().tx.send((key, value));
|
||||
let _ = tx.send((key, value));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,7 +99,7 @@ impl Storage {
|
||||
}
|
||||
}
|
||||
|
||||
impl WebStorage for Storage {
|
||||
impl WebStorage for StorageInner {
|
||||
fn get_item<T: DeserializeOwned>(&self, key: impl AsRef<str>) -> Result<Option<T>> {
|
||||
let key = key.as_ref().as_bytes();
|
||||
let db = self.get_instance();
|
||||
@@ -134,7 +147,8 @@ impl WebStorage for Storage {
|
||||
}
|
||||
|
||||
pub fn register_web_storage_listener(app_handle: &tauri::AppHandle) {
|
||||
let rx = Storage::global().get_rx();
|
||||
let storage = app_handle.state::<Storage>();
|
||||
let rx = storage.get_rx();
|
||||
let app_handle = app_handle.clone();
|
||||
std::thread::spawn(move || {
|
||||
nyanpasu_utils::runtime::block_on(async {
|
||||
@@ -152,3 +166,10 @@ pub fn register_web_storage_listener(app_handle: &tauri::AppHandle) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn setup<R: tauri::Runtime, M: tauri::Manager<R>>(app: &M) -> anyhow::Result<()> {
|
||||
let storage_path = dirs::storage_path().context("failed to get storage path")?;
|
||||
let storage = Storage::try_new(&storage_path)?;
|
||||
app.manage(storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Sysopt {
|
||||
if let Some(mut old) = old_sysproxy.take() {
|
||||
// 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置
|
||||
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
|
||||
let port_same = cur_sysproxy.map_or(true, |cur| old.port == cur.port);
|
||||
let port_same = cur_sysproxy.is_none_or(|cur| old.port == cur.port);
|
||||
|
||||
if old.enable && port_same {
|
||||
old.enable = false;
|
||||
|
||||
@@ -1,51 +1,45 @@
|
||||
use chrono::Utc;
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
storage::EventsGuard,
|
||||
storage::TaskStorage,
|
||||
task::{TaskEventID, TaskID, TaskRunResult, Timestamp},
|
||||
utils::Result,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
pub struct TaskEvents;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
pub struct TaskEvents {
|
||||
storage: Arc<Mutex<TaskStorage>>,
|
||||
}
|
||||
|
||||
pub trait TaskEventsDispatcher {
|
||||
fn new() -> Self;
|
||||
fn new(storage: Arc<Mutex<TaskStorage>>) -> Self;
|
||||
fn new_event(&self, task_id: TaskID, event_id: TaskEventID) -> Result<TaskEventID>;
|
||||
fn dispatch(&self, event_id: TaskEventID, state: TaskEventState) -> Result<()>;
|
||||
}
|
||||
|
||||
impl TaskEvents {
|
||||
pub fn global() -> &'static Arc<Self> {
|
||||
static EVENTS: OnceLock<Arc<TaskEvents>> = OnceLock::new();
|
||||
|
||||
EVENTS.get_or_init(|| Arc::new(Self::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskEventsDispatcher for TaskEvents {
|
||||
fn new() -> Self {
|
||||
TaskEvents {}
|
||||
fn new(storage: Arc<Mutex<TaskStorage>>) -> Self {
|
||||
TaskEvents { storage }
|
||||
}
|
||||
|
||||
fn new_event(&self, task_id: TaskID, event_id: TaskEventID) -> Result<TaskEventID> {
|
||||
let storage = self.storage.lock();
|
||||
let mut event = TaskEvent {
|
||||
id: event_id,
|
||||
task_id,
|
||||
..TaskEvent::default()
|
||||
};
|
||||
event.dispatch(TaskEventState::Pending);
|
||||
EventsGuard::global().add_event(&event)?;
|
||||
storage.add_event(&event)?;
|
||||
Ok(event_id)
|
||||
}
|
||||
|
||||
fn dispatch(&self, event_id: TaskEventID, state: TaskEventState) -> Result<()> {
|
||||
let mut event = EventsGuard::global().get_event(event_id).unwrap().unwrap(); // unwrap because it should be exist here, if not, it's a bug
|
||||
let storage = self.storage.lock();
|
||||
let mut event = storage.get_event(event_id).unwrap().unwrap(); // unwrap because it should be exist here, if not, it's a bug
|
||||
event.dispatch(state);
|
||||
EventsGuard::global().update_event(&event)?;
|
||||
storage.update_event(&event)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -56,6 +50,7 @@ pub struct TaskEvent {
|
||||
pub task_id: TaskID,
|
||||
pub state: TaskEventState,
|
||||
pub timeline: HashMap<String, Timestamp>,
|
||||
pub updated_at: Timestamp,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -84,14 +79,16 @@ impl Default for TaskEvent {
|
||||
task_id: 0,
|
||||
state: TaskEventState::Pending,
|
||||
timeline: HashMap::with_capacity(4), // 4 states
|
||||
updated_at: Utc::now().timestamp_millis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskEvent {
|
||||
fn dispatch(&mut self, state: TaskEventState) {
|
||||
let now = Utc::now().timestamp_millis();
|
||||
self.state = state;
|
||||
self.timeline
|
||||
.insert(self.state.fmt().into(), Utc::now().timestamp_millis());
|
||||
self.timeline.insert(self.state.fmt().into(), now);
|
||||
self.updated_at = now;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use crate::core::tasks::{
|
||||
executor::{AsyncJobExecutor, TaskExecutor},
|
||||
storage::TaskStorage,
|
||||
task::TaskSchedule,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::JobExt;
|
||||
|
||||
const CLEAR_EVENTS_TASK_NAME: &str = "Task Events Rotate";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventsRotateJob {
|
||||
task_storage: Arc<Mutex<TaskStorage>>,
|
||||
}
|
||||
|
||||
impl EventsRotateJob {
|
||||
pub fn new(task_storage: Arc<Mutex<TaskStorage>>) -> Self {
|
||||
Self { task_storage }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AsyncJobExecutor for EventsRotateJob {
|
||||
// TODO: optimize performance if we got reported that this job is slow
|
||||
async fn execute(&self) -> anyhow::Result<()> {
|
||||
let storage = self.task_storage.lock();
|
||||
let task_ids = storage.list_tasks().context("failed to list tasks")?;
|
||||
for task_id in task_ids {
|
||||
let event_ids = storage
|
||||
.get_event_ids(task_id)
|
||||
.context(format!("failed to get event ids for task {}", task_id))?
|
||||
.unwrap_or_default();
|
||||
let mut events_to_remove = Vec::new();
|
||||
let mut events = event_ids
|
||||
.into_iter()
|
||||
.filter_map(|id| {
|
||||
let event = storage.get_event(id).ok().flatten();
|
||||
if event.is_none() {
|
||||
events_to_remove.push(id);
|
||||
}
|
||||
event
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// DESC sort events by updated_at
|
||||
events.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
|
||||
// keep max 10 events
|
||||
let events = events
|
||||
.into_iter()
|
||||
.skip(10)
|
||||
.map(|e| e.id)
|
||||
.collect::<Vec<_>>();
|
||||
events_to_remove.extend(events);
|
||||
// remove events
|
||||
for event_id in events_to_remove {
|
||||
log::debug!("removing event {} for task {}", event_id, task_id);
|
||||
storage
|
||||
.remove_event(event_id, task_id)
|
||||
.context(format!("failed to remove event {}", event_id))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JobExt for EventsRotateJob {
|
||||
fn name(&self) -> &'static str {
|
||||
CLEAR_EVENTS_TASK_NAME
|
||||
}
|
||||
|
||||
fn setup(&self) -> Option<crate::core::tasks::task::Task> {
|
||||
Some(crate::core::tasks::task::Task {
|
||||
name: CLEAR_EVENTS_TASK_NAME.to_string(),
|
||||
// 12:00 every day
|
||||
schedule: TaskSchedule::Cron("0 12 * * *".to_string()),
|
||||
executor: TaskExecutor::Async(Box::new(self.clone())),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
mod events_rotate;
|
||||
mod logger;
|
||||
mod profiles;
|
||||
|
||||
use super::{
|
||||
task::Task,
|
||||
task::{Task, TaskManager},
|
||||
utils::{ConfigChangedNotifier, Result},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::RwLock;
|
||||
pub use profiles::ProfilesJobGuard;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::Arc;
|
||||
pub trait JobExt {
|
||||
fn name(&self) -> &'static str;
|
||||
fn setup(&self) -> Option<Task>; // called when the app starts or the config changed
|
||||
@@ -16,24 +17,27 @@ pub trait JobExt {
|
||||
|
||||
pub struct JobsManager {
|
||||
jobs: Vec<Box<dyn JobExt + Send + Sync>>,
|
||||
task_manager: Arc<RwLock<TaskManager>>,
|
||||
}
|
||||
|
||||
impl JobsManager {
|
||||
pub fn global() -> &'static Arc<Mutex<Self>> {
|
||||
static JOBS: OnceLock<Arc<Mutex<JobsManager>>> = OnceLock::new();
|
||||
JOBS.get_or_init(|| Arc::new(Mutex::new(Self { jobs: Vec::new() })))
|
||||
pub fn new(task_manager: Arc<RwLock<TaskManager>>) -> Self {
|
||||
Self {
|
||||
jobs: Vec::new(),
|
||||
task_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_register() -> Result<()> {
|
||||
let jobs: Vec<Box<dyn JobExt + Send + Sync>> = vec![
|
||||
// Box::<logger::ClearLogsJob>::default() as Box<dyn JobExt + Send + Sync>
|
||||
];
|
||||
pub fn setup(&mut self) -> anyhow::Result<()> {
|
||||
let jobs: Vec<Box<dyn JobExt + Send + Sync>> = vec![Box::new(
|
||||
events_rotate::EventsRotateJob::new(self.task_manager.read().get_inner_task_storage()),
|
||||
)];
|
||||
for job in jobs {
|
||||
let task = job.setup();
|
||||
if let Some(task) = task {
|
||||
super::task::TaskManager::global().write().add_task(task)?;
|
||||
self.task_manager.write().add_task(task)?;
|
||||
}
|
||||
JobsManager::global().lock().jobs.push(job);
|
||||
self.jobs.push(job);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -48,7 +52,7 @@ impl ConfigChangedNotifier for JobsManager {
|
||||
.ok_or(anyhow!("job not exist"))?;
|
||||
let task = job.setup();
|
||||
if let Some(task) = task {
|
||||
let mut task_manager = super::task::TaskManager::global().write();
|
||||
let mut task_manager = self.task_manager.write();
|
||||
task_manager.remove_task(task.id)?;
|
||||
task_manager.add_task(task)?;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,8 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
collections::{hash_map::DefaultHasher, HashMap},
|
||||
hash::{Hash, Hasher},
|
||||
sync::{Arc, OnceLock},
|
||||
time::Duration,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration};
|
||||
|
||||
const INITIAL_TASK_ID: TaskID = 10000000; // 留一个初始的 TaskID,避免和其他任务的 ID 冲突
|
||||
|
||||
@@ -51,21 +46,38 @@ enum ProfileTaskOp {
|
||||
Update(TaskID, Minutes),
|
||||
}
|
||||
|
||||
pub struct ProfilesJobGuard {
|
||||
pub struct ProfilesJob {
|
||||
task_map: HashMap<ProfileUID, (TaskID, u64)>,
|
||||
task_manager: Arc<RwLock<TaskManager>>,
|
||||
// next_id: TaskID,
|
||||
}
|
||||
|
||||
impl ProfilesJobGuard {
|
||||
pub fn global() -> &'static Arc<Mutex<Self>> {
|
||||
static GUARD: OnceLock<Arc<Mutex<ProfilesJobGuard>>> = OnceLock::new();
|
||||
pub struct ProfilesJobGuard {
|
||||
job: Arc<RwLock<ProfilesJob>>,
|
||||
}
|
||||
|
||||
GUARD.get_or_init(|| {
|
||||
Arc::new(Mutex::new(Self {
|
||||
task_map: HashMap::new(),
|
||||
// next_id: INITIAL_TASK_ID,
|
||||
}))
|
||||
})
|
||||
impl ProfilesJobGuard {
|
||||
pub fn new(task_manager: Arc<RwLock<TaskManager>>) -> Self {
|
||||
Self {
|
||||
job: Arc::new(RwLock::new(ProfilesJob::new(task_manager))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ProfilesJobGuard {
|
||||
type Target = Arc<RwLock<ProfilesJob>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.job
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfilesJob {
|
||||
pub fn new(task_manager: Arc<RwLock<TaskManager>>) -> Self {
|
||||
Self {
|
||||
task_map: HashMap::new(),
|
||||
task_manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// restore timer
|
||||
@@ -95,7 +107,7 @@ impl ProfilesJobGuard {
|
||||
})
|
||||
.for_each(|item| {
|
||||
if let Some((task_id, _)) = task_map.get(item.uid()) {
|
||||
crate::log_err!(TaskManager::global().write().advance_task(*task_id));
|
||||
crate::log_err!(self.task_manager.write().advance_task(*task_id));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,18 +121,17 @@ impl ProfilesJobGuard {
|
||||
match diff {
|
||||
ProfileTaskOp::Add(task_id, interval) => {
|
||||
let task = new_task(task_id, &uid, interval);
|
||||
crate::log_err!(TaskManager::global().write().add_task(task));
|
||||
crate::log_err!(self.task_manager.write().add_task(task));
|
||||
self.task_map.insert(uid, (task_id, interval));
|
||||
}
|
||||
ProfileTaskOp::Remove(task_id) => {
|
||||
crate::log_err!(TaskManager::global().write().remove_task(task_id));
|
||||
crate::log_err!(self.task_manager.write().remove_task(task_id));
|
||||
self.task_map.remove(&uid);
|
||||
}
|
||||
ProfileTaskOp::Update(task_id, interval) => {
|
||||
let mut task_manager = TaskManager::global().write();
|
||||
crate::log_err!(task_manager.remove_task(task_id));
|
||||
crate::log_err!(self.task_manager.write().remove_task(task_id));
|
||||
let task = new_task(task_id, &uid, interval);
|
||||
crate::log_err!(task_manager.add_task(task));
|
||||
crate::log_err!(self.task_manager.write().add_task(task));
|
||||
self.task_map.insert(uid, (task_id, interval));
|
||||
}
|
||||
}
|
||||
@@ -182,9 +193,7 @@ fn gen_map() -> HashMap<ProfileUID, Minutes> {
|
||||
|
||||
/// get_task_id Get a u64 task id by profile uid
|
||||
fn get_task_id(uid: &str) -> TaskID {
|
||||
let mut hash = DefaultHasher::new();
|
||||
uid.hash(&mut hash);
|
||||
let task_id = hash.finish();
|
||||
let task_id = seahash::hash(uid.as_bytes());
|
||||
if task_id < INITIAL_TASK_ID {
|
||||
INITIAL_TASK_ID + task_id
|
||||
} else {
|
||||
|
||||
@@ -5,4 +5,29 @@ mod storage;
|
||||
pub mod task;
|
||||
mod utils;
|
||||
|
||||
pub use jobs::JobsManager;
|
||||
pub fn setup<R: tauri::Runtime, M: tauri::Manager<R>>(
|
||||
app: &M,
|
||||
storage: super::storage::Storage,
|
||||
) -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
let task_storage = storage::TaskStorage::new(storage);
|
||||
let task_manager = task::TaskManager::new(task_storage);
|
||||
let task_manager = std::sync::Arc::new(RwLock::new(task_manager));
|
||||
|
||||
// job manager
|
||||
let mut job_manager = jobs::JobsManager::new(task_manager.clone());
|
||||
job_manager.setup().context("failed to setup job manager")?;
|
||||
let job_manager = std::sync::Arc::new(RwLock::new(job_manager));
|
||||
app.manage(job_manager);
|
||||
// profiles job
|
||||
let profiles_job = jobs::ProfilesJobGuard::new(task_manager.clone());
|
||||
{
|
||||
let mut profiles_job = profiles_job.write();
|
||||
profiles_job.init()?;
|
||||
}
|
||||
app.manage(profiles_job);
|
||||
app.manage(task_manager);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// store is a interface to save and restore task states
|
||||
//! store is a interface to save and restore task states
|
||||
use super::{
|
||||
events::TaskEvent,
|
||||
task::{TaskEventID, TaskID, TaskManager},
|
||||
@@ -10,24 +10,75 @@ use crate::core::{
|
||||
};
|
||||
use log::debug;
|
||||
use redb::ReadableTable;
|
||||
use std::{
|
||||
str,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use std::{collections::HashSet, str};
|
||||
|
||||
pub struct EventsGuard;
|
||||
pub struct TaskStorage {
|
||||
// TODO: hold storage instance, and better concurrency safety
|
||||
storage: Storage,
|
||||
}
|
||||
|
||||
/// EventsGuard is a bridge between the task events and the storage
|
||||
impl EventsGuard {
|
||||
pub fn global() -> &'static Arc<EventsGuard> {
|
||||
static EVENTS: OnceLock<Arc<EventsGuard>> = OnceLock::new();
|
||||
/// TaskStorage is a bridge between the task events and the storage
|
||||
impl TaskStorage {
|
||||
const TASKS_KEY: &str = "tasks";
|
||||
|
||||
EVENTS.get_or_init(|| Arc::new(EventsGuard))
|
||||
pub fn new(storage: Storage) -> Self {
|
||||
Self { storage }
|
||||
}
|
||||
|
||||
/// list_tasks list all tasks, for reduce the number of read operations
|
||||
pub fn list_tasks(&self) -> Result<Vec<TaskID>> {
|
||||
let db = self.storage.get_instance();
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(NYANPASU_TABLE)?;
|
||||
let value = table.get(Self::TASKS_KEY.as_bytes())?;
|
||||
match value {
|
||||
Some(value) => {
|
||||
let mut value = value.value().to_owned();
|
||||
let tasks: Vec<TaskID> = simd_json::from_slice(value.as_mut_slice())?;
|
||||
Ok(tasks)
|
||||
}
|
||||
None => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// add_task add a task id to the storage
|
||||
pub fn add_task(&self, task_id: TaskID) -> Result<()> {
|
||||
let db = self.storage.get_instance();
|
||||
let write_txn = db.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(NYANPASU_TABLE)?;
|
||||
let mut tasks = table
|
||||
.get(Self::TASKS_KEY.as_bytes())?
|
||||
.and_then(|val| {
|
||||
let mut value = val.value().to_owned();
|
||||
let tasks: HashSet<TaskID> =
|
||||
simd_json::from_slice(value.as_mut_slice()).ok()?;
|
||||
Some(tasks)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
tasks.insert(task_id);
|
||||
let value = simd_json::to_vec(&tasks)?;
|
||||
table.insert(Self::TASKS_KEY.as_bytes(), value.as_slice())?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// remove_task remove a task id from the storage
|
||||
pub fn remove_task(&self, task_id: TaskID) -> Result<()> {
|
||||
let db = self.storage.get_instance();
|
||||
let write_txn = db.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(NYANPASU_TABLE)?;
|
||||
table.remove(Self::TASKS_KEY.as_bytes())?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get_event get a task event by event id
|
||||
pub fn get_event(&self, event_id: TaskEventID) -> Result<Option<TaskEvent>> {
|
||||
let db = Storage::global().get_instance();
|
||||
let db = self.storage.get_instance();
|
||||
let key = format!("task:event:id:{}", event_id);
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(NYANPASU_TABLE)?;
|
||||
@@ -59,7 +110,7 @@ impl EventsGuard {
|
||||
}
|
||||
|
||||
pub fn get_event_ids(&self, task_id: TaskID) -> Result<Option<Vec<TaskEventID>>> {
|
||||
let db = Storage::global().get_instance();
|
||||
let db = self.storage.get_instance();
|
||||
let key = format!("task:events:task_id:{}", task_id);
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(NYANPASU_TABLE)?;
|
||||
@@ -79,7 +130,7 @@ impl EventsGuard {
|
||||
let mut event_ids = (self.get_event_ids(event.task_id)?).unwrap_or_default();
|
||||
event_ids.push(event.id);
|
||||
|
||||
let db = Storage::global().get_instance();
|
||||
let db = self.storage.get_instance();
|
||||
let event_key = format!("task:event:id:{}", event.id);
|
||||
let event_ids_key = format!("task:events:task_id:{}", event.task_id);
|
||||
let event_value = simd_json::to_vec(event)?;
|
||||
@@ -96,7 +147,7 @@ impl EventsGuard {
|
||||
|
||||
/// update_event update a event in the storage
|
||||
pub fn update_event(&self, event: &TaskEvent) -> Result<()> {
|
||||
let db = Storage::global().get_instance();
|
||||
let db = self.storage.get_instance();
|
||||
let event_key = format!("task:event:id:{}", event.id);
|
||||
let event_value = simd_json::to_vec(event)?;
|
||||
let write_txn = db.begin_write()?;
|
||||
@@ -115,7 +166,7 @@ impl EventsGuard {
|
||||
Some(value) => value.into_iter().filter(|v| v != &event_id).collect(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
let db = Storage::global().get_instance();
|
||||
let db = self.storage.get_instance();
|
||||
let event_key = format!("task:event:id:{}", event_id);
|
||||
let event_ids_key = format!("task:events:task_id:{}", event_id);
|
||||
let write_txn = db.begin_write()?;
|
||||
@@ -132,6 +183,11 @@ impl EventsGuard {
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get_instance get the raw storage instance
|
||||
fn get_instance(&self) -> &redb::Database {
|
||||
self.storage.get_instance()
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct TaskGuard;
|
||||
@@ -143,32 +199,37 @@ pub trait TaskGuard {
|
||||
/// TaskGuard is a bridge between the tasks and the storage
|
||||
impl TaskGuard for TaskManager {
|
||||
fn restore(&mut self) -> Result<()> {
|
||||
let db = Storage::global().get_instance();
|
||||
let mut tasks = Vec::new();
|
||||
let tasks = {
|
||||
let db = self.storage.lock();
|
||||
let instance = db.get_instance();
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
let read_txn = db.begin_read()?;
|
||||
let table = read_txn.open_table(NYANPASU_TABLE)?;
|
||||
for item in table.iter()? {
|
||||
let (key, value) = item?;
|
||||
let key = key.value();
|
||||
let mut value = value.value().to_owned();
|
||||
if key.starts_with(b"task:id:") {
|
||||
let task = simd_json::from_slice::<Task>(value.as_mut_slice())?;
|
||||
debug!(
|
||||
"restore task: {:?} {:?}",
|
||||
str::from_utf8(key).unwrap(),
|
||||
str::from_utf8(value.as_slice()).unwrap()
|
||||
);
|
||||
tasks.push(task);
|
||||
let read_txn = instance.begin_read()?;
|
||||
let table = read_txn.open_table(NYANPASU_TABLE)?;
|
||||
for item in table.iter()? {
|
||||
let (key, value) = item?;
|
||||
let key = key.value();
|
||||
let mut value = value.value().to_owned();
|
||||
if key.starts_with(b"task:id:") {
|
||||
let task = simd_json::from_slice::<Task>(value.as_mut_slice())?;
|
||||
debug!(
|
||||
"restore task: {:?} {:?}",
|
||||
str::from_utf8(key).unwrap(),
|
||||
str::from_utf8(value.as_slice()).unwrap()
|
||||
);
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks
|
||||
};
|
||||
self.restore_tasks(tasks);
|
||||
Ok(())
|
||||
}
|
||||
fn dump(&self) -> Result<()> {
|
||||
let tasks = self.list();
|
||||
let db = Storage::global().get_instance();
|
||||
let write_txn = db.begin_write()?;
|
||||
let db = self.storage.lock();
|
||||
let instance = db.get_instance();
|
||||
let write_txn = instance.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(NYANPASU_TABLE)?;
|
||||
for task in tasks {
|
||||
@@ -181,3 +242,18 @@ impl TaskGuard for TaskManager {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_hashset_eq_vec() {
|
||||
let json = r#"
|
||||
[1, 2, 3]
|
||||
"#
|
||||
.trim();
|
||||
let hashset: HashSet<i32> = serde_json::from_str(json).unwrap();
|
||||
let new_json = serde_json::to_string(&hashset).unwrap();
|
||||
println!("{}", new_json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{
|
||||
events::{TaskEventState, TaskEvents, TaskEventsDispatcher},
|
||||
executor::{AsyncJob, Job, TaskExecutor},
|
||||
storage::TaskGuard,
|
||||
executor::{Job, TaskExecutor},
|
||||
storage::TaskStorage,
|
||||
utils::{Error, Result, TaskCreationError},
|
||||
};
|
||||
use crate::error;
|
||||
@@ -14,10 +14,7 @@ use delay_timer::{
|
||||
use parking_lot::{Mutex, RwLock as RW};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snowflake::SnowflakeIdGenerator;
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
pub type TaskID = u64;
|
||||
pub type TaskEventID = i64; // 任务事件 ID,适用于任务并发执行,区分不同的执行事件
|
||||
@@ -173,52 +170,18 @@ fn build_task<'a>(task: Task, len: usize) -> (Task, TimerTaskBuilder<'a>) {
|
||||
(task, builder)
|
||||
}
|
||||
|
||||
// TODO: 改成使用宏生成
|
||||
fn wrap_job(list: TaskList, mut id_generator: SnowflakeIdGenerator, task_id: TaskID, job: Job) {
|
||||
let event_id = id_generator.generate();
|
||||
{
|
||||
let _ = list.set_task_state(task_id, TaskState::Running(event_id), None);
|
||||
TaskEvents::global().new_event(task_id, event_id).unwrap(); // TODO: error handling
|
||||
TaskEvents::global()
|
||||
.dispatch(event_id, TaskEventState::Running)
|
||||
.unwrap();
|
||||
};
|
||||
let res = job.execute();
|
||||
{
|
||||
let res = match res {
|
||||
Ok(_) => TaskRunResult::Ok,
|
||||
Err(e) => {
|
||||
error!(format!("task error: {}", e.to_string()));
|
||||
TaskRunResult::Err(e.to_string())
|
||||
}
|
||||
};
|
||||
if let TaskState::Running(latest_event_id) = list.get_task_state(task_id).unwrap() {
|
||||
if latest_event_id == event_id {
|
||||
let _ = list.set_task_state(task_id, TaskState::Idle, Some(res.clone()));
|
||||
}
|
||||
}
|
||||
TaskEvents::global()
|
||||
.dispatch(event_id, TaskEventState::Finished(res))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
macro_rules! wrap_job {
|
||||
($exec:expr, $list:expr, $id_generator:expr, $task_id:expr, $task_events:expr) => {{
|
||||
let event_id = $id_generator.generate();
|
||||
|
||||
async fn wrap_async_job(
|
||||
list: TaskList,
|
||||
mut id_generator: SnowflakeIdGenerator,
|
||||
task_id: TaskID,
|
||||
async_job: AsyncJob,
|
||||
) {
|
||||
let event_id = id_generator.generate();
|
||||
{
|
||||
let _ = list.set_task_state(task_id, TaskState::Running(event_id), None);
|
||||
TaskEvents::global().new_event(task_id, event_id).unwrap(); // TODO: error handling
|
||||
TaskEvents::global()
|
||||
let _ = $list.set_task_state($task_id, TaskState::Running(event_id), None);
|
||||
$task_events.new_event($task_id, event_id).unwrap();
|
||||
$task_events
|
||||
.dispatch(event_id, TaskEventState::Running)
|
||||
.unwrap();
|
||||
};
|
||||
let res = async_job.execute().await;
|
||||
{
|
||||
|
||||
let res = $exec;
|
||||
|
||||
let res = match res {
|
||||
Ok(_) => TaskRunResult::Ok,
|
||||
Err(e) => {
|
||||
@@ -226,15 +189,16 @@ async fn wrap_async_job(
|
||||
TaskRunResult::Err(e.to_string())
|
||||
}
|
||||
};
|
||||
if let TaskState::Running(latest_event_id) = list.get_task_state(task_id).unwrap() {
|
||||
|
||||
if let TaskState::Running(latest_event_id) = $list.get_task_state($task_id).unwrap() {
|
||||
if latest_event_id == event_id {
|
||||
let _ = list.set_task_state(task_id, TaskState::Idle, Some(res.clone()));
|
||||
let _ = $list.set_task_state($task_id, TaskState::Idle, Some(res.clone()));
|
||||
}
|
||||
}
|
||||
TaskEvents::global()
|
||||
$task_events
|
||||
.dispatch(event_id, TaskEventState::Finished(res))
|
||||
.unwrap();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// TaskList 语法糖
|
||||
@@ -293,7 +257,9 @@ impl TaskListOps for TaskList {
|
||||
pub struct TaskManager {
|
||||
/// cron manager
|
||||
timer: Arc<Mutex<DelayTimer>>,
|
||||
|
||||
// Add a mutex to protect the concurrency of the storage
|
||||
pub(super) storage: Arc<Mutex<TaskStorage>>,
|
||||
task_events: Arc<TaskEvents>,
|
||||
/// task list
|
||||
list: TaskList,
|
||||
restore_list: TaskList,
|
||||
@@ -301,23 +267,24 @@ pub struct TaskManager {
|
||||
}
|
||||
|
||||
impl TaskManager {
|
||||
pub fn global() -> &'static Arc<RW<Self>> {
|
||||
static TASK_MANAGER: OnceLock<Arc<RW<TaskManager>>> = OnceLock::new();
|
||||
pub fn new(storage: TaskStorage) -> Self {
|
||||
let storage = Arc::new(Mutex::new(storage));
|
||||
let task_events = TaskEvents::new(storage.clone());
|
||||
Self {
|
||||
timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
|
||||
storage,
|
||||
task_events: Arc::new(task_events),
|
||||
restore_list: Arc::new(RW::new(Vec::new())),
|
||||
list: Arc::new(RW::new(Vec::new())),
|
||||
id_generator: SnowflakeIdGenerator::new(1, 1),
|
||||
}
|
||||
}
|
||||
|
||||
TASK_MANAGER.get_or_init(|| {
|
||||
let mut task_manager = TaskManager {
|
||||
timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
|
||||
restore_list: Arc::new(RW::new(Vec::new())),
|
||||
list: Arc::new(RW::new(Vec::new())),
|
||||
id_generator: SnowflakeIdGenerator::new(1, 1),
|
||||
};
|
||||
task_manager.restore().unwrap();
|
||||
std::thread::spawn(move || loop {
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
let _ = TaskManager::global().write().dump();
|
||||
});
|
||||
Arc::new(RW::new(task_manager))
|
||||
})
|
||||
#[doc(hidden)]
|
||||
/// a hidden method to get the inner task storage
|
||||
/// just for internal jobs to use
|
||||
pub(super) fn get_inner_task_storage(&self) -> Arc<Mutex<TaskStorage>> {
|
||||
self.storage.clone()
|
||||
}
|
||||
|
||||
pub fn restore_tasks(&mut self, tasks: Vec<Task>) {
|
||||
@@ -358,11 +325,13 @@ impl TaskManager {
|
||||
let id_generator = self.id_generator;
|
||||
let list_ref = self.list.clone();
|
||||
let executor = task.executor.clone();
|
||||
let task_events = self.task_events.clone();
|
||||
let timer_task = match executor {
|
||||
TaskExecutor::Sync(job) => {
|
||||
let body = move || {
|
||||
let list = list_ref.clone();
|
||||
wrap_job(list, id_generator, task_id, job.clone())
|
||||
let mut id_generator = id_generator;
|
||||
wrap_job!(job.execute(), list, id_generator, task_id, task_events);
|
||||
};
|
||||
builder.spawn_routine(body)
|
||||
}
|
||||
@@ -370,7 +339,17 @@ impl TaskManager {
|
||||
let body = move || {
|
||||
let list = list_ref.clone();
|
||||
let async_job = async_job.clone();
|
||||
async move { wrap_async_job(list, id_generator, task_id, async_job).await }
|
||||
let mut id_generator = id_generator;
|
||||
let task_events = task_events.clone();
|
||||
async move {
|
||||
wrap_job!(
|
||||
async_job.execute().await,
|
||||
list,
|
||||
id_generator,
|
||||
task_id,
|
||||
task_events
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
builder.spawn_async_routine(body)
|
||||
@@ -390,7 +369,10 @@ impl TaskManager {
|
||||
.map_err(|e| {
|
||||
Error::new_task_error("failed to add a task to scheduler".to_string(), e)
|
||||
})?;
|
||||
let storage = self.storage.lock();
|
||||
let task_id = task.id;
|
||||
list.push(task);
|
||||
storage.add_task(task_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -432,6 +414,8 @@ impl TaskManager {
|
||||
.remove_task(task_id)
|
||||
.map_err(|e| Error::new_task_error("failed to remove task".to_string(), e))?;
|
||||
list.remove(index);
|
||||
let storage = self.storage.lock();
|
||||
storage.remove_task(task_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -493,7 +493,7 @@ pub fn on_system_tray_event(event: &str) {
|
||||
if !event.starts_with("proxy_node_") {
|
||||
return; // bypass non-select event
|
||||
}
|
||||
let node_id = event.split('_').last().unwrap(); // safe to unwrap
|
||||
let node_id = event.split('_').next_back().unwrap(); // safe to unwrap
|
||||
let node_id = match node_id.parse::<usize>() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
|
||||
@@ -252,7 +252,7 @@ impl UpdaterManager {
|
||||
let version = res
|
||||
.trim()
|
||||
.split(' ')
|
||||
.last()
|
||||
.next_back()
|
||||
.ok_or(anyhow!("no version found"))?;
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
@@ -194,7 +194,6 @@ mod tests {
|
||||
#[test]
|
||||
fn test_correct_original_mapping_order() {
|
||||
use super::*;
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
let mut target = serde_yaml::from_str::<Value>(
|
||||
r#" ######### 锚点 start #######
|
||||
|
||||
@@ -298,6 +298,7 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
|
||||
if tun_mode.is_some() {
|
||||
log::debug!(target: "app", "toggle tun mode");
|
||||
#[allow(unused_mut)]
|
||||
let mut flag = false;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
config::*,
|
||||
core::{tasks::jobs::ProfilesJobGuard, updater::ManifestVersionLatest, *},
|
||||
core::{storage::Storage, tasks::jobs::ProfilesJobGuard, updater::ManifestVersionLatest, *},
|
||||
enhance::PostProcessingOutput,
|
||||
feat,
|
||||
utils::{
|
||||
@@ -15,12 +15,11 @@ use chrono::Local;
|
||||
use log::debug;
|
||||
use nyanpasu_ipc::api::status::CoreState;
|
||||
use profile::item_type::ProfileItemType;
|
||||
use serde::Deserialize;
|
||||
use serde_yaml::{value::TaggedValue, Mapping};
|
||||
use serde_yaml::Mapping;
|
||||
use std::{borrow::Cow, collections::VecDeque, path::PathBuf, result::Result as StdResult};
|
||||
use storage::{StorageOperationError, WebStorage};
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::AppHandle;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tray::icon::TrayIcon;
|
||||
|
||||
use tauri_plugin_dialog::{DialogExt, FileDialogBuilder};
|
||||
@@ -278,13 +277,16 @@ pub async fn patch_profiles_config(profiles: ProfilesBuilder) -> Result {
|
||||
/// update profile by uid
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn patch_profile(uid: String, profile: ProfileKind) -> Result {
|
||||
pub async fn patch_profile(app_handle: AppHandle, uid: String, profile: ProfileKind) -> Result {
|
||||
tracing::debug!("patch profile: {uid} with {profile:?}");
|
||||
{
|
||||
let committer = Config::profiles().auto_commit();
|
||||
(committer.draft().patch_item(uid.clone(), profile))?;
|
||||
}
|
||||
ProfilesJobGuard::global().lock().refresh();
|
||||
{
|
||||
let profiles_jobs = app_handle.state::<ProfilesJobGuard>();
|
||||
profiles_jobs.write().refresh();
|
||||
}
|
||||
let need_update = {
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
@@ -948,21 +950,24 @@ pub fn cleanup_processes(app_handle: AppHandle) -> Result {
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_storage_item(key: String) -> Result<Option<String>> {
|
||||
let value = (crate::core::storage::Storage::global().get_item(&key))?;
|
||||
pub fn get_storage_item(app_handle: AppHandle, key: String) -> Result<Option<String>> {
|
||||
let storage = app_handle.state::<Storage>();
|
||||
let value = (storage.get_item(&key))?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn set_storage_item(key: String, value: String) -> Result {
|
||||
(crate::core::storage::Storage::global().set_item(&key, &value))?;
|
||||
pub fn set_storage_item(app_handle: AppHandle, key: String, value: String) -> Result {
|
||||
let storage = app_handle.state::<Storage>();
|
||||
(storage.set_item(&key, &value))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn remove_storage_item(key: String) -> Result {
|
||||
(crate::core::storage::Storage::global().remove_item(&key))?;
|
||||
pub fn remove_storage_item(app_handle: AppHandle, key: String) -> Result {
|
||||
let storage = app_handle.state::<Storage>();
|
||||
(storage.remove_item(&key))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
};
|
||||
use specta_typescript::{BigIntExportBehavior, Typescript};
|
||||
use tauri::{Emitter, Manager};
|
||||
use tauri_specta::{collect_commands, Builder};
|
||||
use tauri_specta::collect_commands;
|
||||
use utils::resolve::{is_window_opened, reset_window_open_counter};
|
||||
|
||||
rust_i18n::i18n!("../../locales");
|
||||
|
||||
@@ -279,12 +279,12 @@ impl<F: Fn(DownloaderState)> Downloader<F> {
|
||||
.map(|part| {
|
||||
part.trim()
|
||||
.split('=')
|
||||
.last()
|
||||
.next_back()
|
||||
.unwrap()
|
||||
.trim_matches(['"', ';', '\''])
|
||||
})
|
||||
})
|
||||
.unwrap_or(self.url.path_segments().unwrap().last().unwrap());
|
||||
.unwrap_or(self.url.path_segments().unwrap().next_back().unwrap());
|
||||
Ok((filename.to_string(), total_size))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
use super::candy::get_reqwest_client;
|
||||
|
||||
#[tracing_attributes::instrument]
|
||||
|
||||
@@ -3,11 +3,7 @@ use crate::{
|
||||
nyanpasu::{ClashCore, WindowState},
|
||||
Config, IVerge,
|
||||
},
|
||||
core::{
|
||||
tasks::{jobs::ProfilesJobGuard, JobsManager},
|
||||
tray::proxies,
|
||||
*,
|
||||
},
|
||||
core::{storage::Storage, tray::proxies, *},
|
||||
log_err, trace_err,
|
||||
utils::init,
|
||||
};
|
||||
@@ -148,6 +144,9 @@ pub fn resolve_setup(app: &mut App) {
|
||||
log::trace!("init config");
|
||||
log_err!(Config::init_config());
|
||||
|
||||
log::trace!("init storage");
|
||||
log_err!(crate::core::storage::setup(app));
|
||||
|
||||
log::trace!("launch core");
|
||||
log_err!(CoreManager::global().init());
|
||||
|
||||
@@ -179,9 +178,12 @@ pub fn resolve_setup(app: &mut App) {
|
||||
log_err!(hotkey::Hotkey::global().init(app.app_handle().clone()));
|
||||
|
||||
// setup jobs
|
||||
log_err!(JobsManager::global_register());
|
||||
// init task manager
|
||||
log_err!(ProfilesJobGuard::global().lock().init());
|
||||
log::trace!("setup jobs");
|
||||
{
|
||||
let storage = app.state::<Storage>();
|
||||
let storage = (*storage).clone();
|
||||
log_err!(crate::core::tasks::setup(app, storage));
|
||||
}
|
||||
|
||||
// test job
|
||||
proxies::setup_proxies();
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": ["openssl"]
|
||||
"depends": []
|
||||
}
|
||||
},
|
||||
"licenseFile": "../../LICENSE",
|
||||
|
||||
@@ -42,6 +42,22 @@
|
||||
"/lint-staged/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "Tauri packages",
|
||||
"matchPackageNames": ["/tauri/"]
|
||||
},
|
||||
{
|
||||
"groupName": "Windows packages",
|
||||
"matchPackageNames": ["/windows/", "/webview2-com/", "winreg"]
|
||||
},
|
||||
{
|
||||
"groupName": "Object-C packages",
|
||||
"matchPackageNames": ["/objc2/"]
|
||||
},
|
||||
{
|
||||
"groupName": "egui packages",
|
||||
"matchPackageNames": ["/egui/", "/eframe/"]
|
||||
},
|
||||
{
|
||||
"groupName": "Testing packages",
|
||||
"matchPackageNames": ["/vitest/", "/cypress/", "/wdio/"]
|
||||
|
||||
+11
-11
@@ -162,16 +162,16 @@ func getInputForLookup(format, name, uri, dir string) lib.InputConverter {
|
||||
var input lib.InputConverter
|
||||
|
||||
switch strings.ToLower(format) {
|
||||
case strings.ToLower(maxmind.TypeMaxmindMMDBIn):
|
||||
input = &maxmind.MMDBIn{
|
||||
Type: maxmind.TypeMaxmindMMDBIn,
|
||||
case strings.ToLower(maxmind.TypeGeoLite2CountryMMDBIn):
|
||||
input = &maxmind.GeoLite2CountryMMDBIn{
|
||||
Type: maxmind.TypeGeoLite2CountryMMDBIn,
|
||||
Action: lib.ActionAdd,
|
||||
Description: maxmind.DescMaxmindMMDBIn,
|
||||
Description: maxmind.DescGeoLite2CountryMMDBIn,
|
||||
URI: uri,
|
||||
}
|
||||
|
||||
case strings.ToLower(maxmind.TypeDBIPCountryMMDBIn):
|
||||
input = &maxmind.MMDBIn{
|
||||
input = &maxmind.GeoLite2CountryMMDBIn{
|
||||
Type: maxmind.TypeDBIPCountryMMDBIn,
|
||||
Action: lib.ActionAdd,
|
||||
Description: maxmind.DescDBIPCountryMMDBIn,
|
||||
@@ -179,7 +179,7 @@ func getInputForLookup(format, name, uri, dir string) lib.InputConverter {
|
||||
}
|
||||
|
||||
case strings.ToLower(maxmind.TypeIPInfoCountryMMDBIn):
|
||||
input = &maxmind.MMDBIn{
|
||||
input = &maxmind.GeoLite2CountryMMDBIn{
|
||||
Type: maxmind.TypeIPInfoCountryMMDBIn,
|
||||
Action: lib.ActionAdd,
|
||||
Description: maxmind.DescIPInfoCountryMMDBIn,
|
||||
@@ -206,11 +206,11 @@ func getInputForLookup(format, name, uri, dir string) lib.InputConverter {
|
||||
InputDir: dir,
|
||||
}
|
||||
|
||||
case strings.ToLower(v2ray.TypeGeoIPdatIn):
|
||||
case strings.ToLower(v2ray.TypeGeoIPDatIn):
|
||||
input = &v2ray.GeoIPDatIn{
|
||||
Type: v2ray.TypeGeoIPdatIn,
|
||||
Type: v2ray.TypeGeoIPDatIn,
|
||||
Action: lib.ActionAdd,
|
||||
Description: v2ray.DescGeoIPdatIn,
|
||||
Description: v2ray.DescGeoIPDatIn,
|
||||
URI: uri,
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ func getInputForLookup(format, name, uri, dir string) lib.InputConverter {
|
||||
input = &plaintext.TextIn{
|
||||
Type: plaintext.TypeClashRuleSetIPCIDRIn,
|
||||
Action: lib.ActionAdd,
|
||||
Description: plaintext.DescClashRuleSetIn,
|
||||
Description: plaintext.DescClashRuleSetIPCIDRIn,
|
||||
Name: name,
|
||||
URI: uri,
|
||||
InputDir: dir,
|
||||
@@ -238,7 +238,7 @@ func getInputForLookup(format, name, uri, dir string) lib.InputConverter {
|
||||
input = &plaintext.TextIn{
|
||||
Type: plaintext.TypeClashRuleSetClassicalIn,
|
||||
Action: lib.ActionAdd,
|
||||
Description: plaintext.DescClashClassicalIn,
|
||||
Description: plaintext.DescClashRuleSetClassicalIn,
|
||||
Name: name,
|
||||
URI: uri,
|
||||
InputDir: dir,
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGeoLite2MMDBFile = filepath.Join("./", "geolite2", "GeoLite2-Country.mmdb")
|
||||
defaultDBIPCountryMMDBFile = filepath.Join("./", "db-ip", "dbip-country-lite.mmdb")
|
||||
defaultIPInfoCountryMMDBFile = filepath.Join("./", "ipinfo", "country.mmdb")
|
||||
defaultGeoLite2CountryMMDBFile = filepath.Join("./", "geolite2", "GeoLite2-Country.mmdb")
|
||||
defaultDBIPCountryMMDBFile = filepath.Join("./", "db-ip", "dbip-country-lite.mmdb")
|
||||
defaultIPInfoCountryMMDBFile = filepath.Join("./", "ipinfo", "country.mmdb")
|
||||
)
|
||||
|
||||
func newMMDBIn(iType string, iDesc string, action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
func newGeoLite2CountryMMDBIn(iType string, iDesc string, action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
var tmp struct {
|
||||
URI string `json:"uri"`
|
||||
Want []string `json:"wantedList"`
|
||||
@@ -29,8 +29,8 @@ func newMMDBIn(iType string, iDesc string, action lib.Action, data json.RawMessa
|
||||
|
||||
if tmp.URI == "" {
|
||||
switch iType {
|
||||
case TypeMaxmindMMDBIn:
|
||||
tmp.URI = defaultGeoLite2MMDBFile
|
||||
case TypeGeoLite2CountryMMDBIn:
|
||||
tmp.URI = defaultGeoLite2CountryMMDBFile
|
||||
|
||||
case TypeDBIPCountryMMDBIn:
|
||||
tmp.URI = defaultDBIPCountryMMDBFile
|
||||
@@ -48,7 +48,7 @@ func newMMDBIn(iType string, iDesc string, action lib.Action, data json.RawMessa
|
||||
}
|
||||
}
|
||||
|
||||
return &MMDBIn{
|
||||
return &GeoLite2CountryMMDBIn{
|
||||
Type: iType,
|
||||
Action: action,
|
||||
Description: iDesc,
|
||||
|
||||
@@ -13,13 +13,14 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultOutputName = "Country.mmdb"
|
||||
defaultGeoLite2CountryMMDBOutputName = "Country.mmdb"
|
||||
|
||||
defaultMaxmindOutputDir = filepath.Join("./", "output", "maxmind")
|
||||
defaultDBIPOutputDir = filepath.Join("./", "output", "db-ip")
|
||||
defaultIPInfoOutputDir = filepath.Join("./", "output", "ipinfo")
|
||||
)
|
||||
|
||||
func newMMDBOut(iType string, iDesc string, action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
func newGeoLite2CountryMMDBOut(iType string, iDesc string, action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
var tmp struct {
|
||||
OutputName string `json:"outputName"`
|
||||
OutputDir string `json:"outputDir"`
|
||||
@@ -38,12 +39,12 @@ func newMMDBOut(iType string, iDesc string, action lib.Action, data json.RawMess
|
||||
}
|
||||
|
||||
if tmp.OutputName == "" {
|
||||
tmp.OutputName = defaultOutputName
|
||||
tmp.OutputName = defaultGeoLite2CountryMMDBOutputName
|
||||
}
|
||||
|
||||
if tmp.OutputDir == "" {
|
||||
switch iType {
|
||||
case TypeMaxmindMMDBOut:
|
||||
case TypeGeoLite2CountryMMDBOut:
|
||||
tmp.OutputDir = defaultMaxmindOutputDir
|
||||
|
||||
case TypeDBIPCountryMMDBOut:
|
||||
@@ -54,7 +55,7 @@ func newMMDBOut(iType string, iDesc string, action lib.Action, data json.RawMess
|
||||
}
|
||||
}
|
||||
|
||||
return &MMDBOut{
|
||||
return &GeoLite2CountryMMDBOut{
|
||||
Type: iType,
|
||||
Action: action,
|
||||
Description: iDesc,
|
||||
@@ -69,18 +70,18 @@ func newMMDBOut(iType string, iDesc string, action lib.Action, data json.RawMess
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MMDBOut) GetExtraInfo() (map[string]interface{}, error) {
|
||||
if strings.TrimSpace(m.SourceMMDBURI) == "" {
|
||||
func (g *GeoLite2CountryMMDBOut) GetExtraInfo() (map[string]any, error) {
|
||||
if strings.TrimSpace(g.SourceMMDBURI) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var content []byte
|
||||
var err error
|
||||
switch {
|
||||
case strings.HasPrefix(strings.ToLower(m.SourceMMDBURI), "http://"), strings.HasPrefix(strings.ToLower(m.SourceMMDBURI), "https://"):
|
||||
content, err = lib.GetRemoteURLContent(m.SourceMMDBURI)
|
||||
case strings.HasPrefix(strings.ToLower(g.SourceMMDBURI), "http://"), strings.HasPrefix(strings.ToLower(g.SourceMMDBURI), "https://"):
|
||||
content, err = lib.GetRemoteURLContent(g.SourceMMDBURI)
|
||||
default:
|
||||
content, err = os.ReadFile(m.SourceMMDBURI)
|
||||
content, err = os.ReadFile(g.SourceMMDBURI)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -92,11 +93,11 @@ func (m *MMDBOut) GetExtraInfo() (map[string]interface{}, error) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
infoList := make(map[string]interface{})
|
||||
infoList := make(map[string]any)
|
||||
networks := db.Networks(maxminddb.SkipAliasedNetworks)
|
||||
for networks.Next() {
|
||||
switch m.Type {
|
||||
case TypeMaxmindMMDBOut, TypeDBIPCountryMMDBOut:
|
||||
switch g.Type {
|
||||
case TypeGeoLite2CountryMMDBOut, TypeDBIPCountryMMDBOut:
|
||||
var record geoip2.Country
|
||||
_, err := networks.Network(&record)
|
||||
if err != nil {
|
||||
@@ -170,7 +171,7 @@ func (m *MMDBOut) GetExtraInfo() (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
if len(infoList) == 0 {
|
||||
return nil, fmt.Errorf("❌ [type %s | action %s] no extra info found in the source MMDB file: %s", m.Type, m.Action, m.SourceMMDBURI)
|
||||
return nil, fmt.Errorf("❌ [type %s | action %s] no extra info found in the source MMDB file: %s", g.Type, g.Action, g.SourceMMDBURI)
|
||||
}
|
||||
|
||||
return infoList, nil
|
||||
|
||||
@@ -18,9 +18,9 @@ const (
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeDBIPCountryMMDBIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newMMDBIn(TypeDBIPCountryMMDBIn, DescDBIPCountryMMDBIn, action, data)
|
||||
return newGeoLite2CountryMMDBIn(TypeDBIPCountryMMDBIn, DescDBIPCountryMMDBIn, action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeDBIPCountryMMDBIn, &MMDBIn{
|
||||
lib.RegisterInputConverter(TypeDBIPCountryMMDBIn, &GeoLite2CountryMMDBIn{
|
||||
Description: DescDBIPCountryMMDBIn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ const (
|
||||
|
||||
func init() {
|
||||
lib.RegisterOutputConfigCreator(TypeDBIPCountryMMDBOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newMMDBOut(TypeDBIPCountryMMDBOut, DescDBIPCountryMMDBOut, action, data)
|
||||
return newGeoLite2CountryMMDBOut(TypeDBIPCountryMMDBOut, DescDBIPCountryMMDBOut, action, data)
|
||||
})
|
||||
lib.RegisterOutputConverter(TypeDBIPCountryMMDBOut, &MMDBOut{
|
||||
lib.RegisterOutputConverter(TypeDBIPCountryMMDBOut, &GeoLite2CountryMMDBOut{
|
||||
Description: DescDBIPCountryMMDBOut,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ const (
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeIPInfoCountryMMDBIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newMMDBIn(TypeIPInfoCountryMMDBIn, DescIPInfoCountryMMDBIn, action, data)
|
||||
return newGeoLite2CountryMMDBIn(TypeIPInfoCountryMMDBIn, DescIPInfoCountryMMDBIn, action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeIPInfoCountryMMDBIn, &MMDBIn{
|
||||
lib.RegisterInputConverter(TypeIPInfoCountryMMDBIn, &GeoLite2CountryMMDBIn{
|
||||
Description: DescIPInfoCountryMMDBIn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ const (
|
||||
|
||||
func init() {
|
||||
lib.RegisterOutputConfigCreator(TypeIPInfoCountryMMDBOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newMMDBOut(TypeIPInfoCountryMMDBOut, DescIPInfoCountryMMDBOut, action, data)
|
||||
return newGeoLite2CountryMMDBOut(TypeIPInfoCountryMMDBOut, DescIPInfoCountryMMDBOut, action, data)
|
||||
})
|
||||
lib.RegisterOutputConverter(TypeIPInfoCountryMMDBOut, &MMDBOut{
|
||||
lib.RegisterOutputConverter(TypeIPInfoCountryMMDBOut, &GeoLite2CountryMMDBOut{
|
||||
Description: DescIPInfoCountryMMDBOut,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,25 +13,25 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeASNCSV = "maxmindGeoLite2ASNCSV"
|
||||
DescASNCSV = "Convert MaxMind GeoLite2 ASN CSV data to other formats"
|
||||
TypeGeoLite2ASNCSVIn = "maxmindGeoLite2ASNCSV"
|
||||
DescGeoLite2ASNCSVIn = "Convert MaxMind GeoLite2 ASN CSV data to other formats"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultASNIPv4File = filepath.Join("./", "geolite2", "GeoLite2-ASN-Blocks-IPv4.csv")
|
||||
defaultASNIPv6File = filepath.Join("./", "geolite2", "GeoLite2-ASN-Blocks-IPv6.csv")
|
||||
defaultGeoLite2ASNCSVIPv4File = filepath.Join("./", "geolite2", "GeoLite2-ASN-Blocks-IPv4.csv")
|
||||
defaultGeoLite2ASNCSVIPv6File = filepath.Join("./", "geolite2", "GeoLite2-ASN-Blocks-IPv6.csv")
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeASNCSV, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newGeoLite2ASNCSV(action, data)
|
||||
lib.RegisterInputConfigCreator(TypeGeoLite2ASNCSVIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newGeoLite2ASNCSVIn(action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeASNCSV, &GeoLite2ASNCSV{
|
||||
Description: DescASNCSV,
|
||||
lib.RegisterInputConverter(TypeGeoLite2ASNCSVIn, &GeoLite2ASNCSVIn{
|
||||
Description: DescGeoLite2ASNCSVIn,
|
||||
})
|
||||
}
|
||||
|
||||
func newGeoLite2ASNCSV(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
func newGeoLite2ASNCSVIn(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
var tmp struct {
|
||||
IPv4File string `json:"ipv4"`
|
||||
IPv6File string `json:"ipv6"`
|
||||
@@ -48,8 +48,8 @@ func newGeoLite2ASNCSV(action lib.Action, data json.RawMessage) (lib.InputConver
|
||||
// When both of IP files are not specified,
|
||||
// it means user wants to use the default ones
|
||||
if tmp.IPv4File == "" && tmp.IPv6File == "" {
|
||||
tmp.IPv4File = defaultASNIPv4File
|
||||
tmp.IPv6File = defaultASNIPv6File
|
||||
tmp.IPv4File = defaultGeoLite2ASNCSVIPv4File
|
||||
tmp.IPv6File = defaultGeoLite2ASNCSVIPv6File
|
||||
}
|
||||
|
||||
// Filter want list
|
||||
@@ -85,10 +85,10 @@ func newGeoLite2ASNCSV(action lib.Action, data json.RawMessage) (lib.InputConver
|
||||
wantList[asn] = []string{"AS" + asn}
|
||||
}
|
||||
|
||||
return &GeoLite2ASNCSV{
|
||||
Type: TypeASNCSV,
|
||||
return &GeoLite2ASNCSVIn{
|
||||
Type: TypeGeoLite2ASNCSVIn,
|
||||
Action: action,
|
||||
Description: DescASNCSV,
|
||||
Description: DescGeoLite2ASNCSVIn,
|
||||
IPv4File: tmp.IPv4File,
|
||||
IPv6File: tmp.IPv6File,
|
||||
Want: wantList,
|
||||
@@ -96,7 +96,7 @@ func newGeoLite2ASNCSV(action lib.Action, data json.RawMessage) (lib.InputConver
|
||||
}, nil
|
||||
}
|
||||
|
||||
type GeoLite2ASNCSV struct {
|
||||
type GeoLite2ASNCSVIn struct {
|
||||
Type string
|
||||
Action lib.Action
|
||||
Description string
|
||||
@@ -106,19 +106,19 @@ type GeoLite2ASNCSV struct {
|
||||
OnlyIPType lib.IPType
|
||||
}
|
||||
|
||||
func (g *GeoLite2ASNCSV) GetType() string {
|
||||
func (g *GeoLite2ASNCSVIn) GetType() string {
|
||||
return g.Type
|
||||
}
|
||||
|
||||
func (g *GeoLite2ASNCSV) GetAction() lib.Action {
|
||||
func (g *GeoLite2ASNCSVIn) GetAction() lib.Action {
|
||||
return g.Action
|
||||
}
|
||||
|
||||
func (g *GeoLite2ASNCSV) GetDescription() string {
|
||||
func (g *GeoLite2ASNCSVIn) GetDescription() string {
|
||||
return g.Description
|
||||
}
|
||||
|
||||
func (g *GeoLite2ASNCSV) Input(container lib.Container) (lib.Container, error) {
|
||||
func (g *GeoLite2ASNCSVIn) Input(container lib.Container) (lib.Container, error) {
|
||||
entries := make(map[string]*lib.Entry)
|
||||
|
||||
if g.IPv4File != "" {
|
||||
@@ -163,7 +163,7 @@ func (g *GeoLite2ASNCSV) Input(container lib.Container) (lib.Container, error) {
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (g *GeoLite2ASNCSV) process(file string, entries map[string]*lib.Entry) error {
|
||||
func (g *GeoLite2ASNCSVIn) process(file string, entries map[string]*lib.Entry) error {
|
||||
if entries == nil {
|
||||
entries = make(map[string]*lib.Entry)
|
||||
}
|
||||
|
||||
@@ -13,26 +13,26 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeCountryCSV = "maxmindGeoLite2CountryCSV"
|
||||
DescCountryCSV = "Convert MaxMind GeoLite2 country CSV data to other formats"
|
||||
TypeGeoLite2CountryCSVIn = "maxmindGeoLite2CountryCSV"
|
||||
DescGeoLite2CountryCSVIn = "Convert MaxMind GeoLite2 country CSV data to other formats"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultCCFile = filepath.Join("./", "geolite2", "GeoLite2-Country-Locations-en.csv")
|
||||
defaultCountryIPv4File = filepath.Join("./", "geolite2", "GeoLite2-Country-Blocks-IPv4.csv")
|
||||
defaultCountryIPv6File = filepath.Join("./", "geolite2", "GeoLite2-Country-Blocks-IPv6.csv")
|
||||
defaultGeoLite2CountryCodeFile = filepath.Join("./", "geolite2", "GeoLite2-Country-Locations-en.csv")
|
||||
defaultGeoLite2CountryIPv4File = filepath.Join("./", "geolite2", "GeoLite2-Country-Blocks-IPv4.csv")
|
||||
defaultGeoLite2CountryIPv6File = filepath.Join("./", "geolite2", "GeoLite2-Country-Blocks-IPv6.csv")
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeCountryCSV, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newGeoLite2CountryCSV(action, data)
|
||||
lib.RegisterInputConfigCreator(TypeGeoLite2CountryCSVIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newGeoLite2CountryCSVIn(action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeCountryCSV, &GeoLite2CountryCSV{
|
||||
Description: DescCountryCSV,
|
||||
lib.RegisterInputConverter(TypeGeoLite2CountryCSVIn, &GeoLite2CountryCSVIn{
|
||||
Description: DescGeoLite2CountryCSVIn,
|
||||
})
|
||||
}
|
||||
|
||||
func newGeoLite2CountryCSV(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
func newGeoLite2CountryCSVIn(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
var tmp struct {
|
||||
CountryCodeFile string `json:"country"`
|
||||
IPv4File string `json:"ipv4"`
|
||||
@@ -48,14 +48,14 @@ func newGeoLite2CountryCSV(action lib.Action, data json.RawMessage) (lib.InputCo
|
||||
}
|
||||
|
||||
if tmp.CountryCodeFile == "" {
|
||||
tmp.CountryCodeFile = defaultCCFile
|
||||
tmp.CountryCodeFile = defaultGeoLite2CountryCodeFile
|
||||
}
|
||||
|
||||
// When both of IP files are not specified,
|
||||
// it means user wants to use the default ones
|
||||
if tmp.IPv4File == "" && tmp.IPv6File == "" {
|
||||
tmp.IPv4File = defaultCountryIPv4File
|
||||
tmp.IPv6File = defaultCountryIPv6File
|
||||
tmp.IPv4File = defaultGeoLite2CountryIPv4File
|
||||
tmp.IPv6File = defaultGeoLite2CountryIPv6File
|
||||
}
|
||||
|
||||
// Filter want list
|
||||
@@ -66,10 +66,10 @@ func newGeoLite2CountryCSV(action lib.Action, data json.RawMessage) (lib.InputCo
|
||||
}
|
||||
}
|
||||
|
||||
return &GeoLite2CountryCSV{
|
||||
Type: TypeCountryCSV,
|
||||
return &GeoLite2CountryCSVIn{
|
||||
Type: TypeGeoLite2CountryCSVIn,
|
||||
Action: action,
|
||||
Description: DescCountryCSV,
|
||||
Description: DescGeoLite2CountryCSVIn,
|
||||
CountryCodeFile: tmp.CountryCodeFile,
|
||||
IPv4File: tmp.IPv4File,
|
||||
IPv6File: tmp.IPv6File,
|
||||
@@ -78,7 +78,7 @@ func newGeoLite2CountryCSV(action lib.Action, data json.RawMessage) (lib.InputCo
|
||||
}, nil
|
||||
}
|
||||
|
||||
type GeoLite2CountryCSV struct {
|
||||
type GeoLite2CountryCSVIn struct {
|
||||
Type string
|
||||
Action lib.Action
|
||||
Description string
|
||||
@@ -89,19 +89,19 @@ type GeoLite2CountryCSV struct {
|
||||
OnlyIPType lib.IPType
|
||||
}
|
||||
|
||||
func (g *GeoLite2CountryCSV) GetType() string {
|
||||
func (g *GeoLite2CountryCSVIn) GetType() string {
|
||||
return g.Type
|
||||
}
|
||||
|
||||
func (g *GeoLite2CountryCSV) GetAction() lib.Action {
|
||||
func (g *GeoLite2CountryCSVIn) GetAction() lib.Action {
|
||||
return g.Action
|
||||
}
|
||||
|
||||
func (g *GeoLite2CountryCSV) GetDescription() string {
|
||||
func (g *GeoLite2CountryCSVIn) GetDescription() string {
|
||||
return g.Description
|
||||
}
|
||||
|
||||
func (g *GeoLite2CountryCSV) Input(container lib.Container) (lib.Container, error) {
|
||||
func (g *GeoLite2CountryCSVIn) Input(container lib.Container) (lib.Container, error) {
|
||||
ccMap, err := g.getCountryCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -151,7 +151,7 @@ func (g *GeoLite2CountryCSV) Input(container lib.Container) (lib.Container, erro
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (g *GeoLite2CountryCSV) getCountryCode() (map[string]string, error) {
|
||||
func (g *GeoLite2CountryCSVIn) getCountryCode() (map[string]string, error) {
|
||||
var f io.ReadCloser
|
||||
var err error
|
||||
switch {
|
||||
@@ -198,7 +198,7 @@ func (g *GeoLite2CountryCSV) getCountryCode() (map[string]string, error) {
|
||||
return ccMap, nil
|
||||
}
|
||||
|
||||
func (g *GeoLite2CountryCSV) process(file string, ccMap map[string]string, entries map[string]*lib.Entry) error {
|
||||
func (g *GeoLite2CountryCSVIn) process(file string, ccMap map[string]string, entries map[string]*lib.Entry) error {
|
||||
if len(ccMap) == 0 {
|
||||
return fmt.Errorf("❌ [type %s | action %s] invalid country code data", g.Type, g.Action)
|
||||
}
|
||||
|
||||
@@ -13,20 +13,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeMaxmindMMDBIn = "maxmindMMDB"
|
||||
DescMaxmindMMDBIn = "Convert MaxMind mmdb database to other formats"
|
||||
TypeGeoLite2CountryMMDBIn = "maxmindMMDB"
|
||||
DescGeoLite2CountryMMDBIn = "Convert MaxMind mmdb database to other formats"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeMaxmindMMDBIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newMMDBIn(TypeMaxmindMMDBIn, DescMaxmindMMDBIn, action, data)
|
||||
lib.RegisterInputConfigCreator(TypeGeoLite2CountryMMDBIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newGeoLite2CountryMMDBIn(TypeGeoLite2CountryMMDBIn, DescGeoLite2CountryMMDBIn, action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeMaxmindMMDBIn, &MMDBIn{
|
||||
Description: DescMaxmindMMDBIn,
|
||||
lib.RegisterInputConverter(TypeGeoLite2CountryMMDBIn, &GeoLite2CountryMMDBIn{
|
||||
Description: DescGeoLite2CountryMMDBIn,
|
||||
})
|
||||
}
|
||||
|
||||
type MMDBIn struct {
|
||||
type GeoLite2CountryMMDBIn struct {
|
||||
Type string
|
||||
Action lib.Action
|
||||
Description string
|
||||
@@ -35,43 +35,43 @@ type MMDBIn struct {
|
||||
OnlyIPType lib.IPType
|
||||
}
|
||||
|
||||
func (m *MMDBIn) GetType() string {
|
||||
return m.Type
|
||||
func (g *GeoLite2CountryMMDBIn) GetType() string {
|
||||
return g.Type
|
||||
}
|
||||
|
||||
func (m *MMDBIn) GetAction() lib.Action {
|
||||
return m.Action
|
||||
func (g *GeoLite2CountryMMDBIn) GetAction() lib.Action {
|
||||
return g.Action
|
||||
}
|
||||
|
||||
func (m *MMDBIn) GetDescription() string {
|
||||
return m.Description
|
||||
func (g *GeoLite2CountryMMDBIn) GetDescription() string {
|
||||
return g.Description
|
||||
}
|
||||
|
||||
func (m *MMDBIn) Input(container lib.Container) (lib.Container, error) {
|
||||
func (g *GeoLite2CountryMMDBIn) Input(container lib.Container) (lib.Container, error) {
|
||||
var content []byte
|
||||
var err error
|
||||
switch {
|
||||
case strings.HasPrefix(strings.ToLower(m.URI), "http://"), strings.HasPrefix(strings.ToLower(m.URI), "https://"):
|
||||
content, err = lib.GetRemoteURLContent(m.URI)
|
||||
case strings.HasPrefix(strings.ToLower(g.URI), "http://"), strings.HasPrefix(strings.ToLower(g.URI), "https://"):
|
||||
content, err = lib.GetRemoteURLContent(g.URI)
|
||||
default:
|
||||
content, err = os.ReadFile(m.URI)
|
||||
content, err = os.ReadFile(g.URI)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries := make(map[string]*lib.Entry, 300)
|
||||
err = m.generateEntries(content, entries)
|
||||
err = g.generateEntries(content, entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
return nil, fmt.Errorf("❌ [type %s | action %s] no entry is generated", m.Type, m.Action)
|
||||
return nil, fmt.Errorf("❌ [type %s | action %s] no entry is generated", g.Type, g.Action)
|
||||
}
|
||||
|
||||
var ignoreIPType lib.IgnoreIPOption
|
||||
switch m.OnlyIPType {
|
||||
switch g.OnlyIPType {
|
||||
case lib.IPv4:
|
||||
ignoreIPType = lib.IgnoreIPv6
|
||||
case lib.IPv6:
|
||||
@@ -79,7 +79,7 @@ func (m *MMDBIn) Input(container lib.Container) (lib.Container, error) {
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
switch m.Action {
|
||||
switch g.Action {
|
||||
case lib.ActionAdd:
|
||||
if err := container.Add(entry, ignoreIPType); err != nil {
|
||||
return nil, err
|
||||
@@ -96,7 +96,7 @@ func (m *MMDBIn) Input(container lib.Container) (lib.Container, error) {
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (m *MMDBIn) generateEntries(content []byte, entries map[string]*lib.Entry) error {
|
||||
func (g *GeoLite2CountryMMDBIn) generateEntries(content []byte, entries map[string]*lib.Entry) error {
|
||||
db, err := maxminddb.FromBytes(content)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -109,8 +109,8 @@ func (m *MMDBIn) generateEntries(content []byte, entries map[string]*lib.Entry)
|
||||
var subnet *net.IPNet
|
||||
var err error
|
||||
|
||||
switch m.Type {
|
||||
case TypeMaxmindMMDBIn, TypeDBIPCountryMMDBIn:
|
||||
switch g.Type {
|
||||
case TypeGeoLite2CountryMMDBIn, TypeDBIPCountryMMDBIn:
|
||||
var record geoip2.Country
|
||||
subnet, err = networks.Network(&record)
|
||||
if err != nil {
|
||||
@@ -144,7 +144,7 @@ func (m *MMDBIn) generateEntries(content []byte, entries map[string]*lib.Entry)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(m.Want) > 0 && !m.Want[name] {
|
||||
if len(g.Want) > 0 && !g.Want[name] {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -16,20 +16,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeMaxmindMMDBOut = "maxmindMMDB"
|
||||
DescMaxmindMMDBOut = "Convert data to MaxMind mmdb database format"
|
||||
TypeGeoLite2CountryMMDBOut = "maxmindMMDB"
|
||||
DescGeoLite2CountryMMDBOut = "Convert data to MaxMind mmdb database format"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterOutputConfigCreator(TypeMaxmindMMDBOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newMMDBOut(TypeMaxmindMMDBOut, DescMaxmindMMDBOut, action, data)
|
||||
lib.RegisterOutputConfigCreator(TypeGeoLite2CountryMMDBOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newGeoLite2CountryMMDBOut(TypeGeoLite2CountryMMDBOut, DescGeoLite2CountryMMDBOut, action, data)
|
||||
})
|
||||
lib.RegisterOutputConverter(TypeMaxmindMMDBOut, &MMDBOut{
|
||||
Description: DescMaxmindMMDBOut,
|
||||
lib.RegisterOutputConverter(TypeGeoLite2CountryMMDBOut, &GeoLite2CountryMMDBOut{
|
||||
Description: DescGeoLite2CountryMMDBOut,
|
||||
})
|
||||
}
|
||||
|
||||
type MMDBOut struct {
|
||||
type GeoLite2CountryMMDBOut struct {
|
||||
Type string
|
||||
Action lib.Action
|
||||
Description string
|
||||
@@ -43,25 +43,25 @@ type MMDBOut struct {
|
||||
SourceMMDBURI string
|
||||
}
|
||||
|
||||
func (m *MMDBOut) GetType() string {
|
||||
return m.Type
|
||||
func (g *GeoLite2CountryMMDBOut) GetType() string {
|
||||
return g.Type
|
||||
}
|
||||
|
||||
func (m *MMDBOut) GetAction() lib.Action {
|
||||
return m.Action
|
||||
func (g *GeoLite2CountryMMDBOut) GetAction() lib.Action {
|
||||
return g.Action
|
||||
}
|
||||
|
||||
func (m *MMDBOut) GetDescription() string {
|
||||
return m.Description
|
||||
func (g *GeoLite2CountryMMDBOut) GetDescription() string {
|
||||
return g.Description
|
||||
}
|
||||
|
||||
func (m *MMDBOut) Output(container lib.Container) error {
|
||||
func (g *GeoLite2CountryMMDBOut) Output(container lib.Container) error {
|
||||
dbName := ""
|
||||
dbDesc := ""
|
||||
recordSize := 28
|
||||
|
||||
switch m.Type {
|
||||
case TypeMaxmindMMDBOut:
|
||||
switch g.Type {
|
||||
case TypeGeoLite2CountryMMDBOut:
|
||||
dbName = "GeoLite2-Country"
|
||||
dbDesc = "Customized GeoLite2 Country database"
|
||||
|
||||
@@ -88,20 +88,20 @@ func (m *MMDBOut) Output(container lib.Container) error {
|
||||
}
|
||||
|
||||
// Get extra info
|
||||
extraInfo, err := m.GetExtraInfo()
|
||||
extraInfo, err := g.GetExtraInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updated := false
|
||||
for _, name := range m.filterAndSortList(container) {
|
||||
for _, name := range g.filterAndSortList(container) {
|
||||
entry, found := container.GetEntry(name)
|
||||
if !found {
|
||||
log.Printf("❌ entry %s not found\n", name)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := m.marshalData(writer, entry, extraInfo); err != nil {
|
||||
if err := g.marshalData(writer, entry, extraInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -109,13 +109,13 @@ func (m *MMDBOut) Output(container lib.Container) error {
|
||||
}
|
||||
|
||||
if updated {
|
||||
return m.writeFile(m.OutputName, writer)
|
||||
return g.writeFile(g.OutputName, writer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MMDBOut) filterAndSortList(container lib.Container) []string {
|
||||
func (g *GeoLite2CountryMMDBOut) filterAndSortList(container lib.Container) []string {
|
||||
/*
|
||||
Note: The IPs and/or CIDRs of the latter list will overwrite those of the former one
|
||||
when duplicated data found due to MaxMind mmdb file format constraint.
|
||||
@@ -127,14 +127,14 @@ func (m *MMDBOut) filterAndSortList(container lib.Container) []string {
|
||||
*/
|
||||
|
||||
excludeMap := make(map[string]bool)
|
||||
for _, exclude := range m.Exclude {
|
||||
for _, exclude := range g.Exclude {
|
||||
if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" {
|
||||
excludeMap[exclude] = true
|
||||
}
|
||||
}
|
||||
|
||||
wantList := make([]string, 0, len(m.Want))
|
||||
for _, want := range m.Want {
|
||||
wantList := make([]string, 0, len(g.Want))
|
||||
for _, want := range g.Want {
|
||||
if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] {
|
||||
wantList = append(wantList, want)
|
||||
}
|
||||
@@ -144,9 +144,9 @@ func (m *MMDBOut) filterAndSortList(container lib.Container) []string {
|
||||
return wantList
|
||||
}
|
||||
|
||||
overwriteList := make([]string, 0, len(m.Overwrite))
|
||||
overwriteList := make([]string, 0, len(g.Overwrite))
|
||||
overwriteMap := make(map[string]bool)
|
||||
for _, overwrite := range m.Overwrite {
|
||||
for _, overwrite := range g.Overwrite {
|
||||
if overwrite = strings.ToUpper(strings.TrimSpace(overwrite)); overwrite != "" && !excludeMap[overwrite] {
|
||||
overwriteList = append(overwriteList, overwrite)
|
||||
overwriteMap[overwrite] = true
|
||||
@@ -171,10 +171,10 @@ func (m *MMDBOut) filterAndSortList(container lib.Container) []string {
|
||||
return list
|
||||
}
|
||||
|
||||
func (m *MMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraInfo map[string]interface{}) error {
|
||||
func (g *GeoLite2CountryMMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraInfo map[string]any) error {
|
||||
var entryCidr []string
|
||||
var err error
|
||||
switch m.OnlyIPType {
|
||||
switch g.OnlyIPType {
|
||||
case lib.IPv4:
|
||||
entryCidr, err = entry.MarshalText(lib.IgnoreIPv6)
|
||||
case lib.IPv6:
|
||||
@@ -187,10 +187,10 @@ func (m *MMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraIn
|
||||
}
|
||||
|
||||
var record mmdbtype.DataType
|
||||
switch strings.TrimSpace(m.SourceMMDBURI) {
|
||||
switch strings.TrimSpace(g.SourceMMDBURI) {
|
||||
case "": // No need to get extra info
|
||||
switch m.Type {
|
||||
case TypeMaxmindMMDBOut, TypeDBIPCountryMMDBOut:
|
||||
switch g.Type {
|
||||
case TypeGeoLite2CountryMMDBOut, TypeDBIPCountryMMDBOut:
|
||||
record = mmdbtype.Map{
|
||||
"country": mmdbtype.Map{
|
||||
"iso_code": mmdbtype.String(entry.GetName()),
|
||||
@@ -207,11 +207,11 @@ func (m *MMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraIn
|
||||
}
|
||||
|
||||
default: // Get extra info
|
||||
switch m.Type {
|
||||
case TypeMaxmindMMDBOut:
|
||||
switch g.Type {
|
||||
case TypeGeoLite2CountryMMDBOut:
|
||||
info, found := extraInfo[entry.GetName()].(geoip2.Country)
|
||||
if !found {
|
||||
log.Printf("⚠️ [type %s | action %s] not found extra info for list %s\n", m.Type, m.Action, entry.GetName())
|
||||
log.Printf("⚠️ [type %s | action %s] not found extra info for list %s\n", g.Type, g.Action, entry.GetName())
|
||||
|
||||
record = mmdbtype.Map{
|
||||
"country": mmdbtype.Map{
|
||||
@@ -273,7 +273,7 @@ func (m *MMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraIn
|
||||
case TypeDBIPCountryMMDBOut:
|
||||
info, found := extraInfo[entry.GetName()].(geoip2.Country)
|
||||
if !found {
|
||||
log.Printf("⚠️ [type %s | action %s] not found extra info for list %s\n", m.Type, m.Action, entry.GetName())
|
||||
log.Printf("⚠️ [type %s | action %s] not found extra info for list %s\n", g.Type, g.Action, entry.GetName())
|
||||
|
||||
record = mmdbtype.Map{
|
||||
"country": mmdbtype.Map{
|
||||
@@ -347,7 +347,7 @@ func (m *MMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraIn
|
||||
})
|
||||
|
||||
if !found {
|
||||
log.Printf("⚠️ [type %s | action %s] not found extra info for list %s\n", m.Type, m.Action, entry.GetName())
|
||||
log.Printf("⚠️ [type %s | action %s] not found extra info for list %s\n", g.Type, g.Action, entry.GetName())
|
||||
|
||||
record = mmdbtype.Map{
|
||||
"country": mmdbtype.String(entry.GetName()),
|
||||
@@ -379,12 +379,12 @@ func (m *MMDBOut) marshalData(writer *mmdbwriter.Tree, entry *lib.Entry, extraIn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MMDBOut) writeFile(filename string, writer *mmdbwriter.Tree) error {
|
||||
if err := os.MkdirAll(m.OutputDir, 0755); err != nil {
|
||||
func (g *GeoLite2CountryMMDBOut) writeFile(filename string, writer *mmdbwriter.Tree) error {
|
||||
if err := os.MkdirAll(g.OutputDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filepath.Join(m.OutputDir, filename), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
||||
f, err := os.OpenFile(filepath.Join(g.OutputDir, filename), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -394,7 +394,7 @@ func (m *MMDBOut) writeFile(filename string, writer *mmdbwriter.Tree) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("✅ [%s] %s --> %s", m.Type, filename, m.OutputDir)
|
||||
log.Printf("✅ [%s] %s --> %s", g.Type, filename, g.OutputDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,24 +13,24 @@ which make it possible to support more formats for the project.
|
||||
|
||||
const (
|
||||
TypeClashRuleSetClassicalIn = "clashRuleSetClassical"
|
||||
DescClashClassicalIn = "Convert classical type of Clash RuleSet to other formats (just processing IP & CIDR lines)"
|
||||
DescClashRuleSetClassicalIn = "Convert classical type of Clash RuleSet to other formats (just processing IP & CIDR lines)"
|
||||
|
||||
TypeClashRuleSetIPCIDRIn = "clashRuleSet"
|
||||
DescClashRuleSetIn = "Convert ipcidr type of Clash RuleSet to other formats"
|
||||
DescClashRuleSetIPCIDRIn = "Convert ipcidr type of Clash RuleSet to other formats"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeClashRuleSetClassicalIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newTextIn(TypeClashRuleSetClassicalIn, DescClashClassicalIn, action, data)
|
||||
return newTextIn(TypeClashRuleSetClassicalIn, DescClashRuleSetClassicalIn, action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeClashRuleSetClassicalIn, &TextIn{
|
||||
Description: DescClashClassicalIn,
|
||||
Description: DescClashRuleSetClassicalIn,
|
||||
})
|
||||
|
||||
lib.RegisterInputConfigCreator(TypeClashRuleSetIPCIDRIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newTextIn(TypeClashRuleSetIPCIDRIn, DescClashRuleSetIn, action, data)
|
||||
return newTextIn(TypeClashRuleSetIPCIDRIn, DescClashRuleSetIPCIDRIn, action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeClashRuleSetIPCIDRIn, &TextIn{
|
||||
Description: DescClashRuleSetIn,
|
||||
Description: DescClashRuleSetIPCIDRIn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,24 +13,24 @@ which make it possible to support more formats for the project.
|
||||
|
||||
const (
|
||||
TypeClashRuleSetClassicalOut = "clashRuleSetClassical"
|
||||
DescClashClassicalOut = "Convert data to classical type of Clash RuleSet"
|
||||
DescClashRuleSetClassicalOut = "Convert data to classical type of Clash RuleSet"
|
||||
|
||||
TypeClashRuleSetIPCIDROut = "clashRuleSet"
|
||||
DescClashRuleSetOut = "Convert data to ipcidr type of Clash RuleSet"
|
||||
DescClashRuleSetIPCIDROut = "Convert data to ipcidr type of Clash RuleSet"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterOutputConfigCreator(TypeClashRuleSetClassicalOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newTextOut(TypeClashRuleSetClassicalOut, DescClashClassicalOut, action, data)
|
||||
return newTextOut(TypeClashRuleSetClassicalOut, DescClashRuleSetClassicalOut, action, data)
|
||||
})
|
||||
lib.RegisterOutputConverter(TypeClashRuleSetClassicalOut, &TextOut{
|
||||
Description: DescClashClassicalOut,
|
||||
Description: DescClashRuleSetClassicalOut,
|
||||
})
|
||||
|
||||
lib.RegisterOutputConfigCreator(TypeClashRuleSetIPCIDROut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newTextOut(TypeClashRuleSetIPCIDROut, DescClashRuleSetOut, action, data)
|
||||
return newTextOut(TypeClashRuleSetIPCIDROut, DescClashRuleSetIPCIDROut, action, data)
|
||||
})
|
||||
lib.RegisterOutputConverter(TypeClashRuleSetIPCIDROut, &TextOut{
|
||||
Description: DescClashRuleSetOut,
|
||||
Description: DescClashRuleSetIPCIDROut,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,16 +14,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeGeoIPdatIn = "v2rayGeoIPDat"
|
||||
DescGeoIPdatIn = "Convert V2Ray GeoIP dat to other formats"
|
||||
TypeGeoIPDatIn = "v2rayGeoIPDat"
|
||||
DescGeoIPDatIn = "Convert V2Ray GeoIP dat to other formats"
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterInputConfigCreator(TypeGeoIPdatIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
lib.RegisterInputConfigCreator(TypeGeoIPDatIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
|
||||
return newGeoIPDatIn(action, data)
|
||||
})
|
||||
lib.RegisterInputConverter(TypeGeoIPdatIn, &GeoIPDatIn{
|
||||
Description: DescGeoIPdatIn,
|
||||
lib.RegisterInputConverter(TypeGeoIPDatIn, &GeoIPDatIn{
|
||||
Description: DescGeoIPDatIn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func newGeoIPDatIn(action lib.Action, data json.RawMessage) (lib.InputConverter,
|
||||
}
|
||||
|
||||
if tmp.URI == "" {
|
||||
return nil, fmt.Errorf("❌ [type %s | action %s] uri must be specified in config", TypeGeoIPdatIn, action)
|
||||
return nil, fmt.Errorf("❌ [type %s | action %s] uri must be specified in config", TypeGeoIPDatIn, action)
|
||||
}
|
||||
|
||||
// Filter want list
|
||||
@@ -53,9 +53,9 @@ func newGeoIPDatIn(action lib.Action, data json.RawMessage) (lib.InputConverter,
|
||||
}
|
||||
|
||||
return &GeoIPDatIn{
|
||||
Type: TypeGeoIPdatIn,
|
||||
Type: TypeGeoIPDatIn,
|
||||
Action: action,
|
||||
Description: DescGeoIPdatIn,
|
||||
Description: DescGeoIPDatIn,
|
||||
URI: tmp.URI,
|
||||
Want: wantList,
|
||||
OnlyIPType: tmp.OnlyIPType,
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TypeGeoIPdatOut = "v2rayGeoIPDat"
|
||||
DescGeoIPdatOut = "Convert data to V2Ray GeoIP dat format"
|
||||
TypeGeoIPDatOut = "v2rayGeoIPDat"
|
||||
DescGeoIPDatOut = "Convert data to V2Ray GeoIP dat format"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,15 +26,15 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib.RegisterOutputConfigCreator(TypeGeoIPdatOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newGeoIPDat(action, data)
|
||||
lib.RegisterOutputConfigCreator(TypeGeoIPDatOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
return newGeoIPDatOut(action, data)
|
||||
})
|
||||
lib.RegisterOutputConverter(TypeGeoIPdatOut, &GeoIPDatOut{
|
||||
Description: DescGeoIPdatOut,
|
||||
lib.RegisterOutputConverter(TypeGeoIPDatOut, &GeoIPDatOut{
|
||||
Description: DescGeoIPDatOut,
|
||||
})
|
||||
}
|
||||
|
||||
func newGeoIPDat(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
func newGeoIPDatOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
|
||||
var tmp struct {
|
||||
OutputName string `json:"outputName"`
|
||||
OutputDir string `json:"outputDir"`
|
||||
@@ -59,9 +59,9 @@ func newGeoIPDat(action lib.Action, data json.RawMessage) (lib.OutputConverter,
|
||||
}
|
||||
|
||||
return &GeoIPDatOut{
|
||||
Type: TypeGeoIPdatOut,
|
||||
Type: TypeGeoIPDatOut,
|
||||
Action: action,
|
||||
Description: DescGeoIPdatOut,
|
||||
Description: DescGeoIPDatOut,
|
||||
OutputName: tmp.OutputName,
|
||||
OutputDir: tmp.OutputDir,
|
||||
Want: tmp.Want,
|
||||
|
||||
@@ -32,7 +32,7 @@ PKG_CONFIG_DEPENDS:= \
|
||||
LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Tuic/ShadowTLS/Hysteria/Socks5/Tun LuCI interface
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:= \
|
||||
+coreutils +coreutils-base64 +dns2socks +dns2tcp +dnsmasq-full +@PACKAGE_dnsmasq_full_ipset +ipset +kmod-ipt-nat \
|
||||
+coreutils +coreutils-base64 +dns2socks +dns2tcp +dnsmasq-full +@PACKAGE_dnsmasq_full_ipset +ipset +kmod-ipt-nat +jq \
|
||||
+ip-full +iptables +iptables-mod-tproxy +lua +lua-neturl +libuci-lua +microsocks \
|
||||
+tcping +resolveip +shadowsocksr-libev-ssr-check +uclient-fetch \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_libustream-mbedtls:libustream-mbedtls \
|
||||
|
||||
@@ -533,6 +533,31 @@ shunt_dns_command() {
|
||||
esac
|
||||
}
|
||||
|
||||
shunt_dns_config_file_port() {
|
||||
if [ "$LOCAL_SERVER" == "$SHUNT_SERVER" ]; then
|
||||
# NetFlix 和 全局socks 节点相同
|
||||
if [ "$(uci_get_by_type socks5_proxy socks5_auth nil)" != "noauth" ]; then
|
||||
# 全局socks 有密码,NetFlix 不能使用 auth 验证,需更换为新端口并使用无密码的 socks 配置用于分流
|
||||
# 新增NetFlix dns 使用端口
|
||||
local port=$tmp_shunt_local_port
|
||||
jq --arg port "$port" '.inbounds |= .[0:1] + [{"protocol":"socks","port":($port | tonumber),"settings":{"udp":true,"auth":"noauth"}}] + .[1:]' "$shunt_config_file" > "$shunt_config_file.tmp" && mv "$shunt_config_file.tmp" $shunt_config_file
|
||||
echo "$port" # 返回端口号
|
||||
return 0 # 成功返回
|
||||
fi
|
||||
else
|
||||
# NetFlix 和 全局 socks 节点不相同
|
||||
if [ "$(uci_get_by_type socks5_proxy socks5_auth nil)" != "noauth" ]; then
|
||||
# 全局socks 有密码,NetFlix不能使用auth验证,需设置为无密码的socks配置用于分流
|
||||
# 删除 NetFlix dns 端口密码验证
|
||||
sed -i -e 's/"auth"\s*:\s*"password"/\"auth\": \"noauth\"/g' \
|
||||
-e '/"accounts": \[/,/\]/d' $shunt_config_file
|
||||
fi
|
||||
fi
|
||||
# 使用传入的端口
|
||||
echo "$1" # 返回传入的端口号
|
||||
return 0 # 成功返回
|
||||
}
|
||||
|
||||
start_shunt() {
|
||||
local type=$(uci_get_by_name $SHUNT_SERVER type)
|
||||
case "$type" in
|
||||
@@ -552,6 +577,14 @@ start_shunt() {
|
||||
v2ray)
|
||||
local tmp_port=${tmp_local_port:-$tmp_shunt_local_port}
|
||||
gen_config_file $SHUNT_SERVER $type 3 $tmp_shunt_port $tmp_port
|
||||
# 处理配置文件中的 NetFlix 端口
|
||||
if [ "$LOCAL_SERVER" == "$SHUNT_SERVER" ]; then
|
||||
# NetFlix 和 全局socks 节点相同
|
||||
tmp_port=$(shunt_dns_config_file_port "$tmp_port")
|
||||
else
|
||||
# NetFlix 和 全局 socks 节点不相同
|
||||
shunt_dns_config_file_port "$tmp_port"
|
||||
fi
|
||||
ln_start_bin $(first_type xray v2ray) v2ray run -c $shunt_config_file
|
||||
shunt_dns_command $tmp_port
|
||||
echolog "shunt:$($(first_type xray v2ray) version | head -1) Started!"
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ jobs:
|
||||
matrix:
|
||||
configuration: [Release]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -64,7 +64,7 @@ sudo chmod 0755 "${PackagePath}/AppDir/AppRun"
|
||||
|
||||
# desktop && PATH
|
||||
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
wget "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod a+x appimagetool-x86_64.AppImage
|
||||
sudo apt install -y libfuse2
|
||||
sudo ./appimagetool-x86_64.AppImage "${PackagePath}/AppDir"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.7.0</Version>
|
||||
<Version>7.7.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -18,6 +18,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<StackTraceSupport>false</StackTraceSupport>
|
||||
<MetricsSupport>false</MetricsSupport>
|
||||
|
||||
@@ -414,7 +414,7 @@ namespace v2rayN.Desktop.Views
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_config.UiItem.Hide2TrayWhenClose)
|
||||
if (Utils.IsOSX() || _config.UiItem.Hide2TrayWhenClose)
|
||||
{
|
||||
foreach (var ownedWindow in this.OwnedWindows)
|
||||
{
|
||||
|
||||
+3
-1
@@ -24,7 +24,9 @@
|
||||
|
||||
[Project X Channel](https://t.me/projectXtls)
|
||||
|
||||
[Project VLESS](https://t.me/projectVless) (non-Chinese)
|
||||
[Project VLESS](https://t.me/projectVless) (Русский)
|
||||
|
||||
[Project XHTTP](https://t.me/projectXhttp) (Persian)
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
privateKey[0] &= 248
|
||||
privateKey[31] &= 127 | 64
|
||||
privateKey[31] &= 127
|
||||
privateKey[31] |= 64
|
||||
|
||||
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
|
||||
output = err.Error()
|
||||
|
||||
Reference in New Issue
Block a user