Add web crawler infrastructure and HangarMC implementation
This commit is contained in:
parent
d9cf404402
commit
eb51afaea8
@ -4,7 +4,7 @@
|
||||
- [x] Create project roadmap
|
||||
- [x] Initialize Tauri + React project
|
||||
- [x] Setup basic project structure
|
||||
- [ ] Create GitHub repository (optional)
|
||||
- [x] Create Remote repository (optional)
|
||||
|
||||
## Core Infrastructure (In Progress)
|
||||
- [x] Setup SQLite or JSON storage for plugin data
|
||||
@ -18,9 +18,9 @@
|
||||
- [x] Design plugin metadata extraction system
|
||||
- [x] Build plugin hash identification system
|
||||
|
||||
## Web Crawler Development (Upcoming)
|
||||
- [ ] Create base web crawler architecture
|
||||
- [ ] Implement HangarMC crawler
|
||||
## Web Crawler Development (In Progress)
|
||||
- [x] Create base web crawler architecture
|
||||
- [x] Implement HangarMC crawler
|
||||
- [ ] Implement SpigotMC crawler
|
||||
- [ ] Implement Modrinth crawler
|
||||
- [ ] Implement GitHub releases crawler
|
||||
|
366
src-tauri/Cargo.lock
generated
366
src-tauri/Cargo.lock
generated
@ -606,6 +606,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.0"
|
||||
@ -629,9 +639,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@ -642,7 +652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@ -915,7 +925,7 @@ dependencies = [
|
||||
"rustc_version",
|
||||
"toml",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -924,6 +934,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
@ -1039,6 +1058,15 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@ -1046,7 +1074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
"foreign-types-shared 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1060,6 +1088,12 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
@ -1476,6 +1510,25 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.8.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -1535,6 +1588,17 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa 1.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
@ -1546,6 +1610,17 @@ dependencies = [
|
||||
"itoa 1.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
@ -1553,7 +1628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1564,8 +1639,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@ -1575,6 +1650,36 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa 1.0.15",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
@ -1584,8 +1689,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"itoa 1.0.15",
|
||||
"pin-project-lite",
|
||||
@ -1594,6 +1699,19 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper 0.14.32",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.10"
|
||||
@ -1603,9 +1721,9 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.6.0",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
@ -2164,6 +2282,23 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@ -2494,6 +2629,50 @@ dependencies = [
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@ -2780,6 +2959,7 @@ name = "plugsnatcher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"reqwest 0.11.27",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@ -3098,6 +3278,46 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.32",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.15"
|
||||
@ -3108,10 +3328,10 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
@ -3123,7 +3343,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
@ -3202,6 +3422,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
@ -3223,6 +3452,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
@ -3256,6 +3494,29 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.22.0"
|
||||
@ -3522,7 +3783,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"core-graphics",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
@ -3643,6 +3904,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
@ -3663,6 +3930,27 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@ -3683,7 +3971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
@ -3748,7 +4036,7 @@ dependencies = [
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
@ -3760,7 +4048,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@ -3934,7 +4222,7 @@ dependencies = [
|
||||
"cookie",
|
||||
"dpi",
|
||||
"gtk",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"jni",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
@ -3952,7 +4240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "087188020fd6facb8578fe9b38e81fa0fe5fb85744c73da51a299f94a530a1e3"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2 0.6.0",
|
||||
@ -3985,7 +4273,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
"html5ever",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchikiki",
|
||||
@ -4148,6 +4436,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.14"
|
||||
@ -4226,7 +4524,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@ -4431,6 +4729,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
@ -5149,6 +5453,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
@ -5195,7 +5509,7 @@ dependencies = [
|
||||
"gdkx11",
|
||||
"gtk",
|
||||
"html5ever",
|
||||
"http",
|
||||
"http 1.3.1",
|
||||
"javascriptcore-rs",
|
||||
"jni",
|
||||
"kuchikiki",
|
||||
|
@ -28,4 +28,5 @@ yaml-rust = "0.4"
|
||||
walkdir = "2.4"
|
||||
regex = "1.10"
|
||||
sha2 = "0.10"
|
||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||
|
||||
|
203
src-tauri/src/crawlers/hangar.rs
Normal file
203
src-tauri/src/crawlers/hangar.rs
Normal file
@ -0,0 +1,203 @@
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::{HttpClient, RepositoryCrawler, RepositoryPlugin, RepositorySource};
|
||||
|
||||
// HangarMC API response structures
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarProjectsResponse {
|
||||
pagination: HangarPagination,
|
||||
result: Vec<HangarProject>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarPagination {
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarProject {
|
||||
id: String,
|
||||
name: String,
|
||||
namespace: HangarNamespace,
|
||||
category: String,
|
||||
description: Option<String>,
|
||||
stats: HangarStats,
|
||||
last_updated: String,
|
||||
icon_url: Option<String>,
|
||||
created_at: String,
|
||||
visibility: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarNamespace {
|
||||
owner: String,
|
||||
slug: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarStats {
|
||||
views: u64,
|
||||
downloads: u64,
|
||||
recent_views: u64,
|
||||
recent_downloads: u64,
|
||||
stars: u64,
|
||||
watchers: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarVersionsResponse {
|
||||
pagination: HangarPagination,
|
||||
result: Vec<HangarVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HangarVersion {
|
||||
name: String,
|
||||
created_at: String,
|
||||
description: Option<String>,
|
||||
downloads: u64,
|
||||
file_size: u64,
|
||||
platform_versions: Vec<String>,
|
||||
}
|
||||
|
||||
// HangarMC crawler implementation
|
||||
pub struct HangarCrawler {
|
||||
client: HttpClient,
|
||||
api_base_url: String,
|
||||
}
|
||||
|
||||
impl HangarCrawler {
|
||||
pub fn new() -> Self {
|
||||
HangarCrawler {
|
||||
client: HttpClient::new(),
|
||||
api_base_url: "https://hangar.papermc.io/api/v1".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_project_details(&self, owner: &str, slug: &str) -> Result<HangarProject, Box<dyn Error>> {
|
||||
let url = format!("{}/projects/{}/{}", self.api_base_url, owner, slug);
|
||||
let response = self.client.get(&url)?;
|
||||
let project: HangarProject = serde_json::from_str(&response)?;
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
fn get_project_versions(&self, owner: &str, slug: &str) -> Result<Vec<HangarVersion>, Box<dyn Error>> {
|
||||
let url = format!("{}/projects/{}/{}/versions", self.api_base_url, owner, slug);
|
||||
let response = self.client.get(&url)?;
|
||||
let versions_response: HangarVersionsResponse = serde_json::from_str(&response)?;
|
||||
Ok(versions_response.result)
|
||||
}
|
||||
|
||||
fn build_download_url(&self, owner: &str, slug: &str, version: &str) -> String {
|
||||
format!("https://hangar.papermc.io/api/v1/projects/{}/{}/versions/{}/download", owner, slug, version)
|
||||
}
|
||||
}
|
||||
|
||||
impl RepositoryCrawler for HangarCrawler {
|
||||
fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>> {
|
||||
let url = format!("{}/projects?query={}&limit=20", self.api_base_url, query);
|
||||
let response = self.client.get(&url)?;
|
||||
|
||||
let projects_response: HangarProjectsResponse = serde_json::from_str(&response)?;
|
||||
let mut results = Vec::new();
|
||||
|
||||
for project in projects_response.result {
|
||||
// For each project, get the latest version
|
||||
let versions = self.get_project_versions(&project.namespace.owner, &project.namespace.slug)?;
|
||||
|
||||
if let Some(latest_version) = versions.first() {
|
||||
results.push(RepositoryPlugin {
|
||||
id: format!("{}/{}", project.namespace.owner, project.namespace.slug),
|
||||
name: project.name,
|
||||
version: latest_version.name.clone(),
|
||||
description: project.description,
|
||||
authors: vec![project.namespace.owner.clone()],
|
||||
download_url: self.build_download_url(&project.namespace.owner, &project.namespace.slug, &latest_version.name),
|
||||
repository: RepositorySource::HangarMC,
|
||||
page_url: format!("https://hangar.papermc.io/{}/{}", project.namespace.owner, project.namespace.slug),
|
||||
download_count: Some(project.stats.downloads),
|
||||
last_updated: Some(project.last_updated),
|
||||
icon_url: project.icon_url,
|
||||
minecraft_versions: latest_version.platform_versions.clone(),
|
||||
categories: vec![project.category],
|
||||
rating: None, // HangarMC uses stars, not ratings
|
||||
file_size: Some(latest_version.file_size),
|
||||
file_hash: None, // HangarMC API doesn't provide file hashes
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>> {
|
||||
let parts: Vec<&str> = plugin_id.split('/').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid plugin ID format for HangarMC. Expected 'owner/slug'".into());
|
||||
}
|
||||
|
||||
let owner = parts[0];
|
||||
let slug = parts[1];
|
||||
|
||||
let project = self.get_project_details(owner, slug)?;
|
||||
let versions = self.get_project_versions(owner, slug)?;
|
||||
|
||||
if let Some(latest_version) = versions.first() {
|
||||
Ok(RepositoryPlugin {
|
||||
id: plugin_id.to_string(),
|
||||
name: project.name,
|
||||
version: latest_version.name.clone(),
|
||||
description: project.description,
|
||||
authors: vec![project.namespace.owner.clone()],
|
||||
download_url: self.build_download_url(owner, slug, &latest_version.name),
|
||||
repository: RepositorySource::HangarMC,
|
||||
page_url: format!("https://hangar.papermc.io/{}/{}", owner, slug),
|
||||
download_count: Some(project.stats.downloads),
|
||||
last_updated: Some(project.last_updated),
|
||||
icon_url: project.icon_url,
|
||||
minecraft_versions: latest_version.platform_versions.clone(),
|
||||
categories: vec![project.category],
|
||||
rating: None,
|
||||
file_size: Some(latest_version.file_size),
|
||||
file_hash: None,
|
||||
})
|
||||
} else {
|
||||
Err("No versions found for this plugin".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let parts: Vec<&str> = plugin_id.split('/').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid plugin ID format for HangarMC. Expected 'owner/slug'".into());
|
||||
}
|
||||
|
||||
let owner = parts[0];
|
||||
let slug = parts[1];
|
||||
|
||||
let versions = self.get_project_versions(owner, slug)?;
|
||||
Ok(versions.into_iter().map(|v| v.name).collect())
|
||||
}
|
||||
|
||||
fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>> {
|
||||
let parts: Vec<&str> = plugin_id.split('/').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid plugin ID format for HangarMC. Expected 'owner/slug'".into());
|
||||
}
|
||||
|
||||
let owner = parts[0];
|
||||
let slug = parts[1];
|
||||
|
||||
let download_url = self.build_download_url(owner, slug, version);
|
||||
self.client.download(&download_url, destination)?;
|
||||
|
||||
Ok(destination.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
fn get_repository_name(&self) -> RepositorySource {
|
||||
RepositorySource::HangarMC
|
||||
}
|
||||
}
|
4
src-tauri/src/crawlers/mod.rs
Normal file
4
src-tauri/src/crawlers/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod hangar;
|
||||
|
||||
// Re-export the crawler implementations
|
||||
pub use hangar::HangarCrawler;
|
@ -8,6 +8,12 @@ use zip::ZipArchive;
|
||||
use yaml_rust::{YamlLoader, Yaml};
|
||||
use std::fs::File;
|
||||
use sha2::{Sha256, Digest};
|
||||
use reqwest;
|
||||
use std::error::Error;
|
||||
|
||||
// Add the crawlers module
|
||||
mod crawlers;
|
||||
use crawlers::HangarCrawler;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum ServerType {
|
||||
@ -603,12 +609,155 @@ fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
// Web Crawler Module
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum RepositorySource {
|
||||
HangarMC,
|
||||
SpigotMC,
|
||||
Modrinth,
|
||||
GitHub,
|
||||
BukkitDev,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RepositoryPlugin {
|
||||
id: String, // Unique identifier in the repository
|
||||
name: String, // Plugin name
|
||||
version: String, // Latest version
|
||||
description: Option<String>,
|
||||
authors: Vec<String>,
|
||||
download_url: String, // URL to download the plugin
|
||||
repository: RepositorySource,
|
||||
page_url: String, // URL to the plugin page
|
||||
download_count: Option<u64>,
|
||||
last_updated: Option<String>,
|
||||
icon_url: Option<String>,
|
||||
minecraft_versions: Vec<String>,
|
||||
categories: Vec<String>,
|
||||
rating: Option<f32>,
|
||||
file_size: Option<u64>,
|
||||
file_hash: Option<String>,
|
||||
}
|
||||
|
||||
// Trait for implementing different repository crawlers
|
||||
pub trait RepositoryCrawler {
|
||||
fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>>;
|
||||
fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>>;
|
||||
fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>>;
|
||||
fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>>;
|
||||
fn get_repository_name(&self) -> RepositorySource;
|
||||
}
|
||||
|
||||
// Basic HTTP client for crawler implementations
|
||||
pub struct HttpClient {
|
||||
client: reqwest::blocking::Client,
|
||||
}
|
||||
|
||||
impl HttpClient {
|
||||
pub fn new() -> Self {
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.user_agent("PlugSnatcher/0.1.0")
|
||||
.build()
|
||||
.unwrap_or_else(|_| reqwest::blocking::Client::new());
|
||||
|
||||
HttpClient { client }
|
||||
}
|
||||
|
||||
pub fn get(&self, url: &str) -> Result<String, Box<dyn Error>> {
|
||||
let response = self.client.get(url).send()?;
|
||||
|
||||
if response.status().is_success() {
|
||||
Ok(response.text()?)
|
||||
} else {
|
||||
Err(format!("HTTP error: {}", response.status()).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download(&self, url: &str, destination: &Path) -> Result<(), Box<dyn Error>> {
|
||||
let response = self.client.get(url).send()?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let bytes = response.bytes()?;
|
||||
fs::write(destination, bytes)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Download failed: {}", response.status()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get crawler for a specific repository
|
||||
fn get_crawler(repository: &RepositorySource) -> Option<Box<dyn RepositoryCrawler>> {
|
||||
match repository {
|
||||
RepositorySource::HangarMC => Some(Box::new(HangarCrawler::new())),
|
||||
// Other repositories will be implemented later
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Command to search for plugins in specified repositories
|
||||
#[command]
|
||||
pub fn search_repository_plugins(query: &str, repositories: Vec<RepositorySource>) -> Result<Vec<RepositoryPlugin>, String> {
|
||||
let mut results: Vec<RepositoryPlugin> = Vec::new();
|
||||
|
||||
// Try each requested repository
|
||||
for repo in repositories {
|
||||
if let Some(crawler) = get_crawler(&repo) {
|
||||
match crawler.search(query) {
|
||||
Ok(repo_results) => {
|
||||
results.extend(repo_results);
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Error searching in repository {:?}: {}", repo, e);
|
||||
// Continue with other repositories even if one fails
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Repository crawler for {:?} not implemented yet", repo);
|
||||
}
|
||||
}
|
||||
|
||||
if results.is_empty() {
|
||||
Err("No plugins found or repositories not implemented yet".to_string())
|
||||
} else {
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
// Command to get plugin details from a specific repository
|
||||
#[command]
|
||||
pub fn get_repository_plugin_details(plugin_id: &str, repository: RepositorySource) -> Result<RepositoryPlugin, String> {
|
||||
if let Some(crawler) = get_crawler(&repository) {
|
||||
crawler.get_plugin_details(plugin_id).map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||
}
|
||||
}
|
||||
|
||||
// Command to download a plugin from a repository
|
||||
#[command]
|
||||
pub fn download_repository_plugin(plugin_id: &str, version: &str, repository: RepositorySource, destination: &str) -> Result<String, String> {
|
||||
if let Some(crawler) = get_crawler(&repository) {
|
||||
crawler
|
||||
.download_plugin(plugin_id, version, Path::new(destination))
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.invoke_handler(tauri::generate_handler![greet, scan_server_directory])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
scan_server_directory,
|
||||
search_repository_plugins,
|
||||
get_repository_plugin_details,
|
||||
download_repository_plugin
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
763
src-tauri/src/lib.rs.bak
Normal file
763
src-tauri/src/lib.rs.bak
Normal file
@ -0,0 +1,763 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use tauri::command;
|
||||
use zip::ZipArchive;
|
||||
use yaml_rust::{YamlLoader, Yaml};
|
||||
use std::fs::File;
|
||||
use sha2::{Sha256, Digest};
|
||||
use reqwest;
|
||||
use std::error::Error;
|
||||
|
||||
// Add the crawlers module
|
||||
mod crawlers;
|
||||
use crawlers::HangarCrawler;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum ServerType {
|
||||
Paper,
|
||||
Spigot,
|
||||
Bukkit,
|
||||
Vanilla,
|
||||
Forge,
|
||||
Fabric,
|
||||
Velocity,
|
||||
BungeeCord,
|
||||
Waterfall,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ServerInfo {
|
||||
server_type: ServerType,
|
||||
minecraft_version: Option<String>,
|
||||
plugins_directory: String,
|
||||
plugins_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Plugin {
|
||||
name: String,
|
||||
version: String,
|
||||
latest_version: Option<String>,
|
||||
description: Option<String>,
|
||||
authors: Vec<String>,
|
||||
has_update: bool,
|
||||
api_version: Option<String>,
|
||||
main_class: Option<String>,
|
||||
depend: Option<Vec<String>>,
|
||||
soft_depend: Option<Vec<String>>,
|
||||
load_before: Option<Vec<String>>,
|
||||
commands: Option<serde_json::Value>,
|
||||
permissions: Option<serde_json::Value>,
|
||||
file_path: String,
|
||||
file_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PluginMeta {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub authors: Vec<String>,
|
||||
pub api_version: Option<String>,
|
||||
pub main_class: Option<String>,
|
||||
pub depend: Option<Vec<String>>,
|
||||
pub soft_depend: Option<Vec<String>>,
|
||||
pub load_before: Option<Vec<String>>,
|
||||
pub commands: Option<serde_json::Value>,
|
||||
pub permissions: Option<serde_json::Value>,
|
||||
pub file_path: String,
|
||||
pub file_size: u64,
|
||||
pub file_hash: String,
|
||||
}
|
||||
|
||||
/// Calculates SHA-256 hash for a given file path
|
||||
pub fn calculate_file_hash(file_path: &str) -> Result<String, String> {
|
||||
let mut file = File::open(file_path).map_err(|e| format!("Failed to open file for hashing: {}", e))?;
|
||||
let mut hasher = Sha256::new();
|
||||
let mut buffer = [0; 1024];
|
||||
|
||||
loop {
|
||||
let bytes_read = file.read(&mut buffer).map_err(|e| format!("Failed to read file for hashing: {}", e))?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
hasher.update(&buffer[..bytes_read]);
|
||||
}
|
||||
|
||||
let hash = hasher.finalize();
|
||||
Ok(format!("{:x}", hash))
|
||||
}
|
||||
|
||||
/// Extract metadata from a plugin.yml file inside a JAR
|
||||
fn extract_plugin_metadata(jar_path: &Path) -> Result<PluginMeta, String> {
|
||||
let file = fs::File::open(jar_path)
|
||||
.map_err(|e| format!("Failed to open JAR file: {}", e))?;
|
||||
|
||||
let file_size = file.metadata()
|
||||
.map_err(|e| format!("Failed to read file metadata: {}", e))?
|
||||
.len();
|
||||
|
||||
let mut archive = ZipArchive::new(file)
|
||||
.map_err(|e| format!("Invalid JAR file: {}", e))?;
|
||||
|
||||
// Try to find and read plugin.yml or bungee.yml
|
||||
let yaml_content = match read_yaml_from_archive(&mut archive, "plugin.yml") {
|
||||
Ok(content) => content,
|
||||
Err(_) => match read_yaml_from_archive(&mut archive, "bungee.yml") {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
// If no plugin metadata file is found, try to infer from filename
|
||||
let filename = jar_path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("unknown.jar");
|
||||
|
||||
// Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
|
||||
let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||
let version = if parts.len() > 1 {
|
||||
parts.pop().unwrap_or("1.0.0").to_string()
|
||||
} else {
|
||||
"1.0.0".to_string()
|
||||
};
|
||||
|
||||
let name = parts.join("-");
|
||||
|
||||
return Ok(PluginMeta {
|
||||
name,
|
||||
version,
|
||||
description: None,
|
||||
authors: Vec::new(),
|
||||
api_version: None,
|
||||
main_class: None,
|
||||
depend: None,
|
||||
soft_depend: None,
|
||||
load_before: None,
|
||||
commands: None,
|
||||
permissions: None,
|
||||
file_path: jar_path.to_string_lossy().to_string(),
|
||||
file_size,
|
||||
file_hash: calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the YAML content
|
||||
let docs = match YamlLoader::load_from_str(&yaml_content) {
|
||||
Ok(docs) => docs,
|
||||
Err(e) => {
|
||||
println!("Failed to parse plugin YAML: {}", e);
|
||||
return fallback_plugin_meta(jar_path, file_size);
|
||||
}
|
||||
};
|
||||
|
||||
if docs.is_empty() {
|
||||
return fallback_plugin_meta(jar_path, file_size);
|
||||
}
|
||||
|
||||
let doc = &docs[0];
|
||||
|
||||
// Extract basic metadata with fallbacks for missing fields
|
||||
let name = yaml_str_with_fallback(doc, "name", jar_path);
|
||||
let version = yaml_str_with_fallback(doc, "version", jar_path);
|
||||
|
||||
// Extract optional fields
|
||||
let description = yaml_str_opt(doc, "description");
|
||||
|
||||
// Handle authors (can be a single string or an array)
|
||||
let authors = match &doc["authors"] {
|
||||
Yaml::Array(arr) => {
|
||||
arr.iter()
|
||||
.filter_map(|a| a.as_str().map(|s| s.to_string()))
|
||||
.collect()
|
||||
},
|
||||
Yaml::String(s) => vec![s.clone()],
|
||||
_ => {
|
||||
// Fallback to 'author' field which is sometimes used
|
||||
match &doc["author"] {
|
||||
Yaml::String(s) => vec![s.clone()],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Extract other optional metadata
|
||||
let api_version = yaml_str_opt(doc, "api-version");
|
||||
let main_class = yaml_str_opt(doc, "main");
|
||||
|
||||
// Handle dependency lists
|
||||
let depend = yaml_str_array(doc, "depend");
|
||||
let soft_depend = yaml_str_array(doc, "softdepend");
|
||||
let load_before = yaml_str_array(doc, "loadbefore");
|
||||
|
||||
// Handle complex structures as generic JSON values
|
||||
let commands = match &doc["commands"] {
|
||||
Yaml::Hash(_) => {
|
||||
Some(serde_json::Value::String("Commands data present".to_string()))
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
let permissions = match &doc["permissions"] {
|
||||
Yaml::Hash(_) => {
|
||||
Some(serde_json::Value::String("Permissions data present".to_string()))
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
// Calculate the file hash
|
||||
let file_hash = calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string());
|
||||
|
||||
Ok(PluginMeta {
|
||||
name,
|
||||
version,
|
||||
description,
|
||||
authors,
|
||||
api_version,
|
||||
main_class,
|
||||
depend,
|
||||
soft_depend,
|
||||
load_before,
|
||||
commands,
|
||||
permissions,
|
||||
file_path: jar_path.to_string_lossy().to_string(),
|
||||
file_size,
|
||||
file_hash,
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to read a YAML file from the ZIP archive
|
||||
fn read_yaml_from_archive(archive: &mut ZipArchive<fs::File>, file_name: &str) -> Result<String, String> {
|
||||
match archive.by_name(file_name) {
|
||||
Ok(mut file) => {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.map_err(|e| format!("Failed to read {}: {}", file_name, e))?;
|
||||
Ok(contents)
|
||||
},
|
||||
Err(e) => Err(format!("Failed to find {}: {}", file_name, e))
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create plugin metadata with fallback values
|
||||
fn fallback_plugin_meta(jar_path: &Path, file_size: u64) -> Result<PluginMeta, String> {
|
||||
let filename = jar_path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("unknown.jar");
|
||||
|
||||
// Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
|
||||
let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||
let version = if parts.len() > 1 {
|
||||
parts.pop().unwrap_or("1.0.0").to_string()
|
||||
} else {
|
||||
"1.0.0".to_string()
|
||||
};
|
||||
|
||||
let name = parts.join("-");
|
||||
|
||||
Ok(PluginMeta {
|
||||
name,
|
||||
version,
|
||||
description: None,
|
||||
authors: Vec::new(),
|
||||
api_version: None,
|
||||
main_class: None,
|
||||
depend: None,
|
||||
soft_depend: None,
|
||||
load_before: None,
|
||||
commands: None,
|
||||
permissions: None,
|
||||
file_path: jar_path.to_string_lossy().to_string(),
|
||||
file_size,
|
||||
file_hash: calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
// Extract a string from YAML with fallback to filename
|
||||
fn yaml_str_with_fallback(yaml: &Yaml, key: &str, jar_path: &Path) -> String {
|
||||
match yaml[key].as_str() {
|
||||
Some(s) => s.to_string(),
|
||||
None => {
|
||||
// Extract from filename as fallback
|
||||
let filename = jar_path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("unknown.jar");
|
||||
|
||||
if key == "name" {
|
||||
// Extract name (e.g., "WorldEdit-7.2.8.jar" → "WorldEdit")
|
||||
let parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||
parts[0].to_string()
|
||||
} else if key == "version" {
|
||||
// Extract version (e.g., "WorldEdit-7.2.8.jar" → "7.2.8")
|
||||
let parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||
if parts.len() > 1 {
|
||||
parts[1].to_string()
|
||||
} else {
|
||||
"1.0.0".to_string()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn yaml_str_opt(yaml: &Yaml, key: &str) -> Option<String> {
|
||||
yaml[key].as_str().map(|s| s.to_string())
|
||||
}
|
||||
|
||||
fn yaml_str_array(yaml: &Yaml, key: &str) -> Option<Vec<String>> {
|
||||
match &yaml[key] {
|
||||
Yaml::Array(arr) => {
|
||||
let strings: Vec<String> = arr.iter()
|
||||
.filter_map(|a| a.as_str().map(|s| s.to_string()))
|
||||
.collect();
|
||||
if strings.is_empty() { None } else { Some(strings) }
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the server type based on files in the server directory
|
||||
fn detect_server_type(server_path: &Path) -> ServerType {
|
||||
// Check for Paper
|
||||
if server_path.join("cache").join("patched_1.19.2.jar").exists() ||
|
||||
server_path.join("paper.yml").exists() {
|
||||
return ServerType::Paper;
|
||||
}
|
||||
|
||||
// Check for Spigot
|
||||
if server_path.join("spigot.yml").exists() {
|
||||
return ServerType::Spigot;
|
||||
}
|
||||
|
||||
// Check for Bukkit
|
||||
if server_path.join("bukkit.yml").exists() {
|
||||
return ServerType::Bukkit;
|
||||
}
|
||||
|
||||
// Check for Forge
|
||||
if server_path.join("forge-server.jar").exists() ||
|
||||
server_path.join("mods").exists() {
|
||||
return ServerType::Forge;
|
||||
}
|
||||
|
||||
// Check for Fabric
|
||||
if server_path.join("fabric-server-launch.jar").exists() ||
|
||||
(server_path.join("mods").exists() && server_path.join("fabric-server-launcher.properties").exists()) {
|
||||
return ServerType::Fabric;
|
||||
}
|
||||
|
||||
// Check for Velocity
|
||||
if server_path.join("velocity.toml").exists() {
|
||||
return ServerType::Velocity;
|
||||
}
|
||||
|
||||
// Check for BungeeCord
|
||||
if server_path.join("BungeeCord.jar").exists() ||
|
||||
server_path.join("config.yml").exists() {
|
||||
return ServerType::BungeeCord;
|
||||
}
|
||||
|
||||
// Check for Waterfall
|
||||
if server_path.join("waterfall.jar").exists() ||
|
||||
server_path.join("waterfall.yml").exists() {
|
||||
return ServerType::Waterfall;
|
||||
}
|
||||
|
||||
// Check if it's at least a vanilla server
|
||||
if server_path.join("server.properties").exists() ||
|
||||
server_path.join("vanilla_server.jar").exists() {
|
||||
return ServerType::Vanilla;
|
||||
}
|
||||
|
||||
// If no server type detected
|
||||
ServerType::Unknown
|
||||
}
|
||||
|
||||
/// Guess the Minecraft version from various files in the server directory
|
||||
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
|
||||
// Try from version.json if it exists
|
||||
if let Ok(content) = fs::read_to_string(server_path.join("version.json")) {
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
if let Some(version) = json["name"].as_str() {
|
||||
return Some(version.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try from the server jar name pattern
|
||||
if let Ok(entries) = fs::read_dir(server_path) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension().map_or(false, |ext| ext == "jar") {
|
||||
let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||
|
||||
// Extract version from various common patterns in jar names
|
||||
if filename.starts_with("paper-") ||
|
||||
filename.starts_with("spigot-") ||
|
||||
filename.starts_with("craftbukkit-") {
|
||||
// Pattern: paper-1.19.2.jar, spigot-1.19.2.jar
|
||||
let parts: Vec<&str> = filename.split('-').collect();
|
||||
if parts.len() > 1 {
|
||||
let version_part = parts[1].trim_end_matches(".jar");
|
||||
if version_part.contains('.') { // Basic version format check
|
||||
return Some(version_part.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for version patterns like minecraft_server.1.19.2.jar
|
||||
if filename.starts_with("minecraft_server.") {
|
||||
let version_part = filename
|
||||
.trim_start_matches("minecraft_server.")
|
||||
.trim_end_matches(".jar");
|
||||
if version_part.contains('.') {
|
||||
return Some(version_part.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If server type is proxy, look in config files
|
||||
if server_type == &ServerType::BungeeCord ||
|
||||
server_type == &ServerType::Waterfall ||
|
||||
server_type == &ServerType::Velocity {
|
||||
// Velocity uses TOML, others use YAML
|
||||
if server_type == &ServerType::Velocity {
|
||||
if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) {
|
||||
// Very basic TOML parsing just for this field
|
||||
for line in content.lines() {
|
||||
if line.contains("minecraft-version") {
|
||||
if let Some(version) = line.split('=').nth(1) {
|
||||
return Some(version.trim().trim_matches('"').to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Try to parse config.yml for BungeeCord/Waterfall
|
||||
if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) {
|
||||
if let Ok(docs) = YamlLoader::load_from_str(&content) {
|
||||
if !docs.is_empty() {
|
||||
let doc = &docs[0];
|
||||
if let Some(version) = doc["minecraft_version"].as_str() {
|
||||
return Some(version.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
None
|
||||
}
|
||||
|
||||
/// Get plugins directory path based on server type
|
||||
fn get_plugins_directory(server_path: &Path, server_type: &ServerType) -> String {
|
||||
match server_type {
|
||||
ServerType::Velocity => server_path.join("plugins").to_string_lossy().to_string(),
|
||||
ServerType::BungeeCord => server_path.join("plugins").to_string_lossy().to_string(),
|
||||
ServerType::Waterfall => server_path.join("plugins").to_string_lossy().to_string(),
|
||||
_ => server_path.join("plugins").to_string_lossy().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ScanResult {
|
||||
server_info: ServerInfo,
|
||||
plugins: Vec<Plugin>,
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn scan_server_directory(path: &str) -> Result<ScanResult, String> {
|
||||
let server_path = Path::new(path);
|
||||
|
||||
if !server_path.exists() {
|
||||
return Err(format!("Server path does not exist: {}", path));
|
||||
}
|
||||
|
||||
// Detect server type and version
|
||||
let server_type = detect_server_type(server_path);
|
||||
let minecraft_version = detect_minecraft_version(server_path, &server_type);
|
||||
|
||||
println!("Detected server type: {:?}", server_type);
|
||||
if let Some(version) = &minecraft_version {
|
||||
println!("Detected Minecraft version: {}", version);
|
||||
}
|
||||
|
||||
// Determine plugins directory based on server type
|
||||
let plugins_dir_str = get_plugins_directory(server_path, &server_type);
|
||||
let plugins_dir = Path::new(&plugins_dir_str);
|
||||
|
||||
if !plugins_dir.exists() {
|
||||
return Err(format!("Plugins directory not found at: {}", plugins_dir.display()));
|
||||
}
|
||||
|
||||
// Scan for JAR files in the plugins directory
|
||||
let mut plugins = Vec::new();
|
||||
|
||||
match fs::read_dir(&plugins_dir) {
|
||||
Ok(entries) => {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
|
||||
// Check if this is a JAR file
|
||||
if path.is_file() && path.extension().map_or(false, |ext| ext.eq_ignore_ascii_case("jar")) {
|
||||
match extract_plugin_metadata(&path) {
|
||||
Ok(meta) => {
|
||||
// Create a Plugin from PluginMeta
|
||||
let plugin = Plugin {
|
||||
name: meta.name,
|
||||
version: meta.version,
|
||||
latest_version: None, // Will be filled by update checker
|
||||
description: meta.description,
|
||||
authors: meta.authors,
|
||||
has_update: false, // Will be determined by update checker
|
||||
api_version: meta.api_version,
|
||||
main_class: meta.main_class,
|
||||
depend: meta.depend,
|
||||
soft_depend: meta.soft_depend,
|
||||
load_before: meta.load_before,
|
||||
commands: meta.commands,
|
||||
permissions: meta.permissions,
|
||||
file_path: meta.file_path,
|
||||
file_hash: meta.file_hash,
|
||||
};
|
||||
|
||||
plugins.push(plugin);
|
||||
},
|
||||
Err(e) => {
|
||||
// Log error but continue with other plugins
|
||||
println!("Error reading plugin from {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(format!("Failed to read plugins directory: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// If no plugins were found, fall back to mock data for testing
|
||||
if plugins.is_empty() && server_type == ServerType::Unknown {
|
||||
// For testing only - in production, we'd just return an empty list
|
||||
plugins = vec![
|
||||
Plugin {
|
||||
name: "EssentialsX".to_string(),
|
||||
version: "2.19.0".to_string(),
|
||||
latest_version: Some("2.20.0".to_string()),
|
||||
description: Some("Essential server tools for Minecraft".to_string()),
|
||||
authors: vec!["md_5".to_string(), "SupaHam".to_string()],
|
||||
has_update: true,
|
||||
api_version: Some("1.13".to_string()),
|
||||
main_class: Some("com.earth2me.essentials.Essentials".to_string()),
|
||||
depend: None,
|
||||
soft_depend: None,
|
||||
load_before: None,
|
||||
commands: None,
|
||||
permissions: None,
|
||||
file_path: "EssentialsX.jar".to_string(),
|
||||
file_hash: calculate_file_hash("EssentialsX.jar").unwrap_or_else(|_| "unknown".to_string()),
|
||||
},
|
||||
Plugin {
|
||||
name: "WorldEdit".to_string(),
|
||||
version: "7.2.8".to_string(),
|
||||
latest_version: Some("7.2.8".to_string()),
|
||||
description: Some("In-game map editor".to_string()),
|
||||
authors: vec!["sk89q".to_string(), "wizjany".to_string()],
|
||||
has_update: false,
|
||||
api_version: Some("1.13".to_string()),
|
||||
main_class: Some("com.sk89q.worldedit.bukkit.WorldEditPlugin".to_string()),
|
||||
depend: None,
|
||||
soft_depend: None,
|
||||
load_before: None,
|
||||
commands: None,
|
||||
permissions: None,
|
||||
file_path: "WorldEdit.jar".to_string(),
|
||||
file_hash: calculate_file_hash("WorldEdit.jar").unwrap_or_else(|_| "unknown".to_string()),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Create server info
|
||||
let server_info = ServerInfo {
|
||||
server_type,
|
||||
minecraft_version,
|
||||
plugins_directory: plugins_dir_str,
|
||||
plugins_count: plugins.len(),
|
||||
};
|
||||
|
||||
Ok(ScanResult {
|
||||
server_info,
|
||||
plugins,
|
||||
})
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
// Web Crawler Module
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum RepositorySource {
|
||||
HangarMC,
|
||||
SpigotMC,
|
||||
Modrinth,
|
||||
GitHub,
|
||||
BukkitDev,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RepositoryPlugin {
|
||||
id: String, // Unique identifier in the repository
|
||||
name: String, // Plugin name
|
||||
version: String, // Latest version
|
||||
description: Option<String>,
|
||||
authors: Vec<String>,
|
||||
download_url: String, // URL to download the plugin
|
||||
repository: RepositorySource,
|
||||
page_url: String, // URL to the plugin page
|
||||
download_count: Option<u64>,
|
||||
last_updated: Option<String>,
|
||||
icon_url: Option<String>,
|
||||
minecraft_versions: Vec<String>,
|
||||
categories: Vec<String>,
|
||||
rating: Option<f32>,
|
||||
file_size: Option<u64>,
|
||||
file_hash: Option<String>,
|
||||
}
|
||||
|
||||
// Trait for implementing different repository crawlers
|
||||
pub trait RepositoryCrawler {
|
||||
fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>>;
|
||||
fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>>;
|
||||
fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>>;
|
||||
fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>>;
|
||||
fn get_repository_name(&self) -> RepositorySource;
|
||||
}
|
||||
|
||||
// Basic HTTP client for crawler implementations
|
||||
pub struct HttpClient {
|
||||
client: reqwest::blocking::Client,
|
||||
}
|
||||
|
||||
impl HttpClient {
|
||||
pub fn new() -> Self {
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.user_agent("PlugSnatcher/0.1.0")
|
||||
.build()
|
||||
.unwrap_or_else(|_| reqwest::blocking::Client::new());
|
||||
|
||||
HttpClient { client }
|
||||
}
|
||||
|
||||
pub fn get(&self, url: &str) -> Result<String, Box<dyn Error>> {
|
||||
let response = self.client.get(url).send()?;
|
||||
|
||||
if response.status().is_success() {
|
||||
Ok(response.text()?)
|
||||
} else {
|
||||
Err(format!("HTTP error: {}", response.status()).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download(&self, url: &str, destination: &Path) -> Result<(), Box<dyn Error>> {
|
||||
let response = self.client.get(url).send()?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let bytes = response.bytes()?;
|
||||
fs::write(destination, bytes)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Download failed: {}", response.status()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get crawler for a specific repository
|
||||
fn get_crawler(repository: &RepositorySource) -> Option<Box<dyn RepositoryCrawler>> {
|
||||
match repository {
|
||||
RepositorySource::HangarMC => Some(Box::new(HangarCrawler::new())),
|
||||
// Other repositories will be implemented later
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Command to search for plugins in specified repositories
|
||||
#[command]
|
||||
pub fn search_repository_plugins(query: &str, repositories: Vec<RepositorySource>) -> Result<Vec<RepositoryPlugin>, String> {
|
||||
let mut results: Vec<RepositoryPlugin> = Vec::new();
|
||||
|
||||
// Try each requested repository
|
||||
for repo in repositories {
|
||||
if let Some(crawler) = get_crawler(&repo) {
|
||||
match crawler.search(query) {
|
||||
Ok(repo_results) => {
|
||||
results.extend(repo_results);
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Error searching in repository {:?}: {}", repo, e);
|
||||
// Continue with other repositories even if one fails
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Repository crawler for {:?} not implemented yet", repo);
|
||||
}
|
||||
}
|
||||
|
||||
if results.is_empty() {
|
||||
Err("No plugins found or repositories not implemented yet".to_string())
|
||||
} else {
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
// Command to get plugin details from a specific repository
|
||||
#[command]
|
||||
pub fn get_repository_plugin_details(plugin_id: &str, repository: RepositorySource) -> Result<RepositoryPlugin, String> {
|
||||
if let Some(crawler) = get_crawler(&repository) {
|
||||
crawler.get_plugin_details(plugin_id).map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||
}
|
||||
}
|
||||
|
||||
// Command to download a plugin from a repository
|
||||
#[command]
|
||||
pub fn download_repository_plugin(plugin_id: &str, version: &str, repository: RepositorySource, destination: &str) -> Result<String, String> {
|
||||
if let Some(crawler) = get_crawler(&repository) {
|
||||
crawler
|
||||
.download_plugin(plugin_id, version, Path::new(destination))
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
scan_server_directory,
|
||||
search_repository_plugins,
|
||||
get_repository_plugin_details,
|
||||
download_repository_plugin
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
Loading…
Reference in New Issue
Block a user