Update On Sat Feb 1 19:32:25 CET 2025

This commit is contained in:
github-action[bot]
2025-02-01 19:32:25 +01:00
parent d08f1ff5da
commit 5fbdd9228c
53 changed files with 810 additions and 662 deletions
+1
View File
@@ -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
+1
View File
@@ -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'],
+111 -230
View File
@@ -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"
+9 -8
View File
@@ -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;
+9 -1
View File
@@ -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 #######
+1
View File
@@ -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"))]
{
+17 -12
View File
@@ -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(())
}
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -53,7 +53,7 @@
},
"linux": {
"deb": {
"depends": ["openssl"]
"depends": []
}
},
"licenseFile": "../../LICENSE",
+16
View File
@@ -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
View File
@@ -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,
+7 -7
View File
@@ -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,
+15 -14
View File
@@ -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
+2 -2
View File
@@ -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,
})
}
+20 -20
View File
@@ -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)
}
+23 -23
View File
@@ -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)
}
+25 -25
View File
@@ -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
}
+6 -6
View File
@@ -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,
})
}
+6 -6
View File
@@ -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,
})
}
+8 -8
View File
@@ -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,
+9 -9
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -22,7 +22,7 @@ jobs:
matrix:
configuration: [Release]
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
+1 -1
View File
@@ -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"
+2 -1
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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()