Compare commits
4 Commits
main
...
8aa698e65e
| Author | SHA1 | Date | |
|---|---|---|---|
| 8aa698e65e | |||
| 542679e386 | |||
| 599d91700e | |||
| c8c371230f |
370
Cargo.lock
generated
370
Cargo.lock
generated
@@ -212,7 +212,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"iref",
|
||||
"jiff",
|
||||
"reqwest 0.12.28",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tap",
|
||||
@@ -635,28 +635,6 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
@@ -769,6 +747,18 @@ dependencies = [
|
||||
"core2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.7.0"
|
||||
@@ -888,6 +878,28 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc"
|
||||
|
||||
[[package]]
|
||||
name = "bson"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3f109694c4f45353972af96bf97d8a057f82e2d6e496457f4d135b9867a518c"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64",
|
||||
"bitvec",
|
||||
"getrandom 0.3.4",
|
||||
"hex",
|
||||
"indexmap",
|
||||
"js-sys",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"simdutf8",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
@@ -1129,19 +1141,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
@@ -1290,15 +1289,6 @@ dependencies = [
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.25.0"
|
||||
@@ -1757,6 +1747,15 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@@ -2343,10 +2342,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
@@ -3423,30 +3422,6 @@ dependencies = [
|
||||
"windows-registry 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.14.0"
|
||||
@@ -3458,7 +3433,7 @@ dependencies = [
|
||||
"iced_futures",
|
||||
"iced_renderer",
|
||||
"iced_runtime 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
|
||||
"iced_widget 0.14.2 (git+https://github.com/uttarayan21/iced?branch=0.14)",
|
||||
"iced_widget",
|
||||
"iced_winit 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
|
||||
"image",
|
||||
"thiserror 2.0.18",
|
||||
@@ -3483,22 +3458,6 @@ dependencies = [
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_aw"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cc84cc77dcb1c384c60792de025fb4a72e23c3d8c65c4a34691684875fc5403"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"iced_core",
|
||||
"iced_fonts",
|
||||
"iced_widget 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-format",
|
||||
"num-traits",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_beacon"
|
||||
version = "0.14.0"
|
||||
@@ -3562,33 +3521,10 @@ source = "git+https://github.com/uttarayan21/iced?branch=0.14#6fbe1ec83722c67cf7
|
||||
dependencies = [
|
||||
"iced_debug 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
|
||||
"iced_program 0.14.0 (git+https://github.com/uttarayan21/iced?branch=0.14)",
|
||||
"iced_widget 0.14.2 (git+https://github.com/uttarayan21/iced?branch=0.14)",
|
||||
"iced_widget",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_fonts"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "214cff7c8499e328774216690e58e315a1a5f8f6fdd1035aed6298e62ffc4c1d"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
"iced_fonts_macros",
|
||||
"iced_widget 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_fonts_macros"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef5125e110cb19cd1910a28298661c98c5d9ab02eef43594968352940e8752e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"ttf-parser 0.25.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.14.0"
|
||||
@@ -3736,20 +3672,6 @@ dependencies = [
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab"
|
||||
dependencies = [
|
||||
"iced_renderer",
|
||||
"log",
|
||||
"num-traits",
|
||||
"rustc-hash 2.1.1",
|
||||
"thiserror 2.0.18",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.14.2"
|
||||
@@ -4127,6 +4049,14 @@ dependencies = [
|
||||
"ui-iced",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jello-types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.18"
|
||||
@@ -4903,6 +4833,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
@@ -4914,16 +4850,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-format"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@@ -5807,6 +5733,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -5984,7 +5916,6 @@ version = "0.11.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
@@ -6029,6 +5960,12 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -6205,6 +6142,16 @@ dependencies = [
|
||||
"font-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redb"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
@@ -6347,44 +6294,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.45.1"
|
||||
@@ -6522,7 +6431,6 @@ version = "0.23.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -6562,40 +6470,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework 3.5.1",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
@@ -6723,15 +6603,6 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "secrecy"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
@@ -6794,6 +6665,16 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
@@ -6959,6 +6840,12 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.2"
|
||||
@@ -7233,9 +7120,10 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
name = "store"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bson",
|
||||
"futures",
|
||||
"parking_lot",
|
||||
"secrecy",
|
||||
"redb",
|
||||
"serde",
|
||||
"tokio",
|
||||
"uuid",
|
||||
@@ -7649,6 +7537,37 @@ dependencies = [
|
||||
"zune-jpeg 0.4.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
@@ -8088,10 +8007,9 @@ dependencies = [
|
||||
"gpui_util",
|
||||
"iced",
|
||||
"iced-video",
|
||||
"iced_aw",
|
||||
"iced_wgpu",
|
||||
"iced_winit 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.13.1",
|
||||
"reqwest",
|
||||
"tap",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"tracing",
|
||||
@@ -8669,15 +8587,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
@@ -9592,6 +9501,15 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -1,20 +1,21 @@
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"api",
|
||||
"typegen",
|
||||
"ui-gpui",
|
||||
"ui-iced",
|
||||
"crates/api",
|
||||
"crates/gst",
|
||||
"crates/iced-video",
|
||||
"crates/store",
|
||||
"store",
|
||||
"jello-types",
|
||||
"gst",
|
||||
"examples/hdr-gstreamer-wgpu",
|
||||
"crates/iced-video",
|
||||
]
|
||||
[workspace.dependencies]
|
||||
iced = { version = "0.14.0" }
|
||||
gst = { version = "0.1.0", path = "crates/gst" }
|
||||
gst = { version = "0.1.0", path = "gst" }
|
||||
iced_wgpu = { version = "0.14.0" }
|
||||
iced-video = { version = "0.1.0", path = "crates/iced-video" }
|
||||
api = { version = "0.1.0", path = "crates/api" }
|
||||
|
||||
[patch.crates-io]
|
||||
iced_wgpu = { git = "https://github.com/uttarayan21/iced", branch = "0.14" }
|
||||
@@ -30,7 +31,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
api = { version = "0.1.0", path = "crates/api" }
|
||||
api = { version = "0.1.0", path = "api" }
|
||||
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap-verbosity-flag = { version = "3.0.4", features = ["tracing"] }
|
||||
|
||||
0
crates/api/.gitignore → api/.gitignore
vendored
0
crates/api/.gitignore → api/.gitignore
vendored
@@ -19,3 +19,11 @@ wgpu = { version = "27.0.1", features = ["vulkan"] }
|
||||
[dev-dependencies]
|
||||
iced.workspace = true
|
||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
|
||||
[profile.dev]
|
||||
debug = true
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
# [patch.crates-io]
|
||||
# iced_wgpu = { git = "https://github.com/uttarayan21/iced", branch = "0.14" }
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::{Error, Result, ResultExt};
|
||||
use gst::{
|
||||
Bus, Gst, Sink,
|
||||
Bus, Gst, MessageType, MessageView, Sink, Source,
|
||||
app::AppSink,
|
||||
caps::{Caps, CapsType},
|
||||
element::ElementExt,
|
||||
pipeline::PipelineExt,
|
||||
playback::{PlayFlags, Playbin3},
|
||||
videoconvertscale::VideoConvert,
|
||||
};
|
||||
use std::sync::{Arc, Mutex, atomic::AtomicBool};
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ApiKey {
|
||||
inner: secrecy::SecretBox<String>,
|
||||
}
|
||||
pub struct SecretStore {
|
||||
api_keys: BTreeMap<Uuid, ApiKey>,
|
||||
}
|
||||
@@ -15,3 +15,6 @@ anyhow = "*"
|
||||
pollster = "0.4.0"
|
||||
tracing = { version = "0.1.43", features = ["log"] }
|
||||
tracing-subscriber = "0.3.22"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
11
flake.lock
generated
11
flake.lock
generated
@@ -34,10 +34,10 @@
|
||||
"crates-io-index": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1769614137,
|
||||
"narHash": "sha256-3Td8fiv6iFVxeS0hYq3xdd10ZvUkC9INMAiQx/mECas=",
|
||||
"lastModified": 1763363725,
|
||||
"narHash": "sha256-cxr5xIKZFP45yV1ZHFTB1sHo5YGiR3FA8D9vAfDizMo=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "c7e7d6394bc95555d6acd5c6783855f47d64c90d",
|
||||
"rev": "0382002e816a4cbd17d8d5b172f08b848aa22ff6",
|
||||
"shallow": true,
|
||||
"type": "git",
|
||||
"url": "https://github.com/rust-lang/crates.io-index"
|
||||
@@ -50,9 +50,7 @@
|
||||
},
|
||||
"crates-nix": {
|
||||
"inputs": {
|
||||
"crates-io-index": [
|
||||
"crates-io-index"
|
||||
]
|
||||
"crates-io-index": "crates-io-index"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763364255,
|
||||
@@ -126,7 +124,6 @@
|
||||
"inputs": {
|
||||
"advisory-db": "advisory-db",
|
||||
"crane": "crane",
|
||||
"crates-io-index": "crates-io-index",
|
||||
"crates-nix": "crates-nix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
|
||||
39
flake.nix
39
flake.nix
@@ -9,14 +9,7 @@
|
||||
url = "github:nix-community/nix-github-actions";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
crates-io-index = {
|
||||
url = "git+https://github.com/rust-lang/crates.io-index?shallow=1";
|
||||
flake = false;
|
||||
};
|
||||
crates-nix = {
|
||||
url = "github:uttarayan21/crates.nix";
|
||||
inputs.crates-io-index.follows = "crates-io-index";
|
||||
};
|
||||
crates-nix.url = "github:uttarayan21/crates.nix";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -186,38 +179,28 @@
|
||||
devShells = rec {
|
||||
rust-shell =
|
||||
pkgs.mkShell.override {
|
||||
stdenv = pkgs.clangStdenv;
|
||||
# if pkgs.stdenv.isLinux
|
||||
# then (pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv)
|
||||
# else pkgs.clangStdenv;
|
||||
}
|
||||
(commonArgs
|
||||
stdenv =
|
||||
if pkgs.stdenv.isLinux
|
||||
then (pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv)
|
||||
else pkgs.clangStdenv;
|
||||
} (commonArgs
|
||||
// {
|
||||
# GST_PLUGIN_PATH = "/run/current-system/sw/lib/gstreamer-1.0/";
|
||||
GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules";
|
||||
packages = with pkgs;
|
||||
[
|
||||
toolchainWithRustAnalyzer
|
||||
bacon
|
||||
cargo-audit
|
||||
cargo-nextest
|
||||
cargo-deny
|
||||
cargo-expand
|
||||
cargo-hack
|
||||
bacon
|
||||
cargo-make
|
||||
cargo-nextest
|
||||
cargo-hack
|
||||
cargo-outdated
|
||||
lld
|
||||
lldb
|
||||
cargo-audit
|
||||
(crates.buildCrate "cargo-with" {doCheck = false;})
|
||||
(crates.buildCrate "dioxus-cli" {
|
||||
nativeBuildInputs = with pkgs; [pkg-config];
|
||||
buildInputs = [openssl];
|
||||
doCheck = false;
|
||||
})
|
||||
(crates.buildCrate "cargo-hot" {
|
||||
nativeBuildInputs = with pkgs; [pkg-config];
|
||||
buildInputs = [openssl];
|
||||
})
|
||||
]
|
||||
++ (lib.optionals pkgs.stdenv.isDarwin [
|
||||
apple-sdk_26
|
||||
@@ -228,7 +211,7 @@
|
||||
samply
|
||||
cargo-flamegraph
|
||||
perf
|
||||
# mold
|
||||
mold
|
||||
]);
|
||||
});
|
||||
default = rust-shell;
|
||||
|
||||
62
gst/.github/workflows/build.yaml
vendored
Normal file
62
gst/.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
checks-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- id: set-matrix
|
||||
name: Generate Nix Matrix
|
||||
run: |
|
||||
set -Eeu
|
||||
matrix="$(nix eval --json '.#githubActions.matrix')"
|
||||
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
||||
|
||||
checks-build:
|
||||
needs: checks-matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix: ${{fromJSON(needs.checks-matrix.outputs.matrix)}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- run: nix build -L '.#${{ matrix.attr }}'
|
||||
|
||||
codecov:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
contents: "read"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Run codecov
|
||||
run: nix build .#checks.x86_64-linux.hello-llvm-cov
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4.0.1
|
||||
with:
|
||||
flags: unittests
|
||||
name: codecov-hello
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./result
|
||||
verbose: true
|
||||
|
||||
38
gst/.github/workflows/docs.yaml
vendored
Normal file
38
gst/.github/workflows/docs.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
contents: "read"
|
||||
pages: "write"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- uses: DeterminateSystems/flake-checker-action@main
|
||||
|
||||
- name: Generate docs
|
||||
run: nix build .#checks.x86_64-linux.hello-docs
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: result/share/doc
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
0
crates/gst/.gitignore → gst/.gitignore
vendored
0
crates/gst/.gitignore → gst/.gitignore
vendored
0
crates/gst/Cargo.lock → gst/Cargo.lock
generated
0
crates/gst/Cargo.lock → gst/Cargo.lock
generated
8
jello-types/Cargo.toml
Normal file
8
jello-types/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "jello-types"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
uuid = { version = "1.18.1", features = ["serde"] }
|
||||
6
jello-types/src/lib.rs
Normal file
6
jello-types/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct User {
|
||||
id: uuid::Uuid,
|
||||
name: Option<String>,
|
||||
primary_image_tag: Option<String>,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
pub use error_stack::ResultExt;
|
||||
pub use error_stack::{Report, ResultExt};
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("An error occurred")]
|
||||
pub struct Error;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod cli;
|
||||
mod errors;
|
||||
use api::JellyfinConfig;
|
||||
use errors::*;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
||||
@@ -4,9 +4,10 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bson = { version = "3.1.0", features = ["serde"] }
|
||||
futures = "0.3.31"
|
||||
parking_lot = "0.12.5"
|
||||
secrecy = "0.10.3"
|
||||
redb = { version = "3.1.0", features = ["uuid"] }
|
||||
serde = "1.0.228"
|
||||
tokio = { version = "1.48.0", features = ["rt"] }
|
||||
uuid = { version = "1.18.1", features = ["v4"] }
|
||||
uuid = "1.18.1"
|
||||
10
store/src/lib.rs
Normal file
10
store/src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
pub mod redb;
|
||||
pub mod sqlite;
|
||||
pub mod toml;
|
||||
|
||||
pub trait Store {
|
||||
fn image(&self, id: &str) -> Option<Vec<u8>>;
|
||||
fn save_image(&mut self, id: &str, data: &[u8]);
|
||||
}
|
||||
|
||||
pub struct Settings {}
|
||||
225
store/src/redb.rs
Normal file
225
store/src/redb.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::VecDeque,
|
||||
marker::PhantomData,
|
||||
path::Path,
|
||||
sync::{Arc, RwLock, atomic::AtomicBool},
|
||||
};
|
||||
|
||||
use futures::task::AtomicWaker;
|
||||
use redb::{Error, Key, ReadableDatabase, TableDefinition, Value};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
const USERS: TableDefinition<uuid::Uuid, Vec<u8>> = TableDefinition::new("users");
|
||||
const SERVERS: TableDefinition<uuid::Uuid, Vec<u8>> = TableDefinition::new("servers");
|
||||
const SETTINGS: TableDefinition<uuid::Uuid, Vec<u8>> = TableDefinition::new("settings");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableInner<T> {
|
||||
db: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for TableInner<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
db: Arc::clone(&self.db),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TableInner<T> {
|
||||
fn new(db: Arc<T>) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
}
|
||||
|
||||
impl TableInner<DatabaseHandle> {
|
||||
async fn get<'a, K: Key, V: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
table: TableDefinition<'static, K, Vec<u8>>,
|
||||
key: impl Borrow<K::SelfType<'a>>,
|
||||
) -> Result<Option<V>> {
|
||||
let db: &redb::Database = &self.db.as_ref().database;
|
||||
let db_reader = db.begin_read()?;
|
||||
let table = db_reader.open_table(table)?;
|
||||
table
|
||||
.get(key)?
|
||||
.map(|value| bson::deserialize_from_slice(&value.value()))
|
||||
.transpose()
|
||||
.map_err(|e| redb::Error::Io(std::io::Error::other(e)))
|
||||
}
|
||||
|
||||
async fn insert<
|
||||
'a,
|
||||
'b,
|
||||
K: Key + Send + Sync,
|
||||
V: Serialize + DeserializeOwned + Send + Sync + 'a,
|
||||
>(
|
||||
&'b self,
|
||||
table: TableDefinition<'static, K, Vec<u8>>,
|
||||
key: impl Borrow<K::SelfType<'a>> + Send + 'b,
|
||||
value: V,
|
||||
) -> Result<Option<V>> {
|
||||
let db: &redb::Database = &self.db.as_ref().database;
|
||||
// self.db
|
||||
// .writing
|
||||
// .store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
// let out = tokio::task::spawn_blocking(move || -> Result<Option<V>>
|
||||
|
||||
let out = tokio::task::spawn_blocking(|| -> Result<Option<V>> {
|
||||
let db_writer = db.begin_write()?;
|
||||
let out = {
|
||||
let mut table = db_writer.open_table(table)?;
|
||||
let serialized_value = bson::serialize_to_vec(&value)
|
||||
.map_err(|e| redb::Error::Io(std::io::Error::other(e)))?;
|
||||
let previous = table.insert(key, &serialized_value)?;
|
||||
let out = previous
|
||||
.map(|value| bson::deserialize_from_slice(&value.value()))
|
||||
.transpose()
|
||||
.map_err(|e| redb::Error::Io(std::io::Error::other(e)));
|
||||
out
|
||||
};
|
||||
db_writer.commit()?;
|
||||
out
|
||||
})
|
||||
.await
|
||||
.expect("Task panicked");
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
// impl<K: Key, V: Serialize + DeserializeOwned> Table<K, V> for TableInner {
|
||||
// async fn get(&self, key: K) -> Result<Option<Value>> {}
|
||||
// async fn insert(&self, key: K, value: V) -> Result<Option<Value>> {}
|
||||
// async fn modify(&self, key: K, v: FnOnce(V) -> V) -> Result<bool> {}
|
||||
// async fn remove(&self, key: K) -> Result<Option<Value>> {}
|
||||
// }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Users<T>(TableInner<T>);
|
||||
|
||||
impl<T> Clone for Users<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl<T> Users<T> {
|
||||
const TABLE: TableDefinition<'static, uuid::Uuid, Vec<u8>> = USERS;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Servers<T>(TableInner<T>);
|
||||
impl<T> Clone for Servers<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl<T> Servers<T> {
|
||||
const TABLE: TableDefinition<'static, uuid::Uuid, Vec<u8>> = SERVERS;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings<T>(TableInner<T>);
|
||||
impl<T> Clone for Settings<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl<T> Settings<T> {
|
||||
const TABLE: TableDefinition<'static, uuid::Uuid, Vec<u8>> = SETTINGS;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Database {
|
||||
users: Users<DatabaseHandle>,
|
||||
servers: Servers<DatabaseHandle>,
|
||||
settings: Settings<DatabaseHandle>,
|
||||
handle: Arc<DatabaseHandle>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseHandle {
|
||||
database: redb::Database,
|
||||
writing: AtomicBool,
|
||||
wakers: RwLock<VecDeque<AtomicWaker>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseWriterGuard<'a> {
|
||||
handle: &'a DatabaseHandle,
|
||||
dropper: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
// impl Drop for DatabaseWriterGuard<'_> {
|
||||
// fn drop(&mut self) {
|
||||
// self.handle
|
||||
// .writing
|
||||
// .store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
// let is_panicking = std::thread::panicking();
|
||||
// let Ok(writer) = self.handle.wakers.write() else {
|
||||
// if is_panicking {
|
||||
// return;
|
||||
// } else {
|
||||
// panic!("Wakers lock poisoned");
|
||||
// }
|
||||
// }
|
||||
// if let Some(waker) = (self.handle.wakers.write()).pop() {
|
||||
// waker.wake();
|
||||
// };
|
||||
// // let mut wakers = self.handle.wakers.write().expect();
|
||||
// // if let Some(waker) = self.handle.wakers.write().expect("Wakers lock poisoned").pop_front() {
|
||||
// // waker.wake();
|
||||
// // }
|
||||
// // while let Some(waker) = wakers.pop_front() {
|
||||
// // waker.wake();
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
type Result<O, E = redb::Error> = core::result::Result<O, E>;
|
||||
|
||||
pub trait Table<K: Key> {
|
||||
fn insert<V: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
key: K,
|
||||
value: V,
|
||||
) -> impl Future<Output = Result<Option<V>>> + Send;
|
||||
fn modify<V: Serialize + DeserializeOwned, O: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
key: K,
|
||||
v: impl FnOnce(V) -> O,
|
||||
) -> impl Future<Output = Result<bool>> + Send;
|
||||
fn remove<V: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
key: K,
|
||||
) -> impl Future<Output = Result<Option<V>>> + Send;
|
||||
fn get<V: Serialize + DeserializeOwned>(
|
||||
&self,
|
||||
key: K,
|
||||
) -> impl Future<Output = Result<Option<V>>> + Send;
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn create(path: impl AsRef<Path>) -> Result<Self, Error> {
|
||||
let writing = AtomicBool::new(false);
|
||||
let wakers = RwLock::new(VecDeque::new());
|
||||
let db = redb::Database::create(path)?;
|
||||
let db = Arc::new(DatabaseHandle {
|
||||
database: db,
|
||||
writing,
|
||||
wakers,
|
||||
});
|
||||
let table_inner = TableInner::new(Arc::clone(&db));
|
||||
let users = Users(table_inner.clone());
|
||||
let servers = Servers(table_inner.clone());
|
||||
let settings = Settings(table_inner.clone());
|
||||
Ok(Self {
|
||||
servers,
|
||||
users,
|
||||
settings,
|
||||
handle: db,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
api = { workspace = true }
|
||||
api = { version = "0.1.0", path = "../api" }
|
||||
blurhash = "0.2.3"
|
||||
bytes = "1.11.0"
|
||||
gpui_util = "0.2.2"
|
||||
@@ -21,10 +21,9 @@ iced = { workspace = true, features = [
|
||||
|
||||
|
||||
iced-video = { workspace = true }
|
||||
iced_aw = "0.13.0"
|
||||
iced_wgpu = "0.14.0"
|
||||
iced_winit = "0.14.0"
|
||||
reqwest = "0.13"
|
||||
reqwest = "0.12.24"
|
||||
tap = "1.0.1"
|
||||
toml = "0.9.8"
|
||||
tracing = "0.1.41"
|
||||
|
||||
@@ -4,7 +4,6 @@ mod video;
|
||||
mod shared_string;
|
||||
use iced_video::{Ready, Video, VideoHandle};
|
||||
use shared_string::SharedString;
|
||||
use tap::Pipe as _;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -26,8 +25,6 @@ pub struct ItemCache {
|
||||
pub tree: BTreeMap<Option<uuid::Uuid>, BTreeSet<uuid::Uuid>>,
|
||||
}
|
||||
|
||||
const BACKGROUND_COLOR: iced::Color = iced::Color::from_rgba8(30, 30, 30, 0.7);
|
||||
|
||||
impl ItemCache {
|
||||
pub fn insert(&mut self, parent: impl Into<Option<uuid::Uuid>>, item: Item) {
|
||||
let parent = parent.into();
|
||||
@@ -158,6 +155,8 @@ impl State {
|
||||
query: None,
|
||||
screen: Screen::Home,
|
||||
settings: settings::SettingsState::default(),
|
||||
// username_input: String::new(),
|
||||
// password_input: String::new(),
|
||||
is_authenticated: false,
|
||||
video: None,
|
||||
}
|
||||
@@ -173,8 +172,17 @@ pub enum Message {
|
||||
OpenItem(Option<uuid::Uuid>),
|
||||
LoadedItem(Option<uuid::Uuid>, Vec<Item>),
|
||||
Error(String),
|
||||
SetToken(String),
|
||||
Back,
|
||||
Home,
|
||||
// Login {
|
||||
// username: String,
|
||||
// password: String,
|
||||
// config: api::JellyfinConfig,
|
||||
// },
|
||||
// LoginSuccess(String),
|
||||
// LoadedClient(api::JellyfinClient, bool),
|
||||
// Logout,
|
||||
Video(video::VideoMessage),
|
||||
}
|
||||
|
||||
@@ -241,6 +249,15 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
state.messages.push(err);
|
||||
Task::none()
|
||||
}
|
||||
Message::SetToken(token) => {
|
||||
tracing::info!("Authenticated with token: {}", token);
|
||||
state
|
||||
.jellyfin_client
|
||||
.as_mut()
|
||||
.map(|mut client| client.set_token(token));
|
||||
state.is_authenticated = true;
|
||||
Task::none()
|
||||
}
|
||||
Message::Back => {
|
||||
state.current = state.history.pop().unwrap_or(None);
|
||||
Task::none()
|
||||
@@ -251,6 +268,7 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
}
|
||||
Message::SearchQueryChanged(query) => {
|
||||
state.query = Some(query);
|
||||
// Handle search query change
|
||||
Task::none()
|
||||
}
|
||||
Message::Search => {
|
||||
@@ -270,33 +288,14 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
}
|
||||
}
|
||||
Message::Video(msg) => video::update(state, msg),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(state: &State) -> Element<'_, Message> {
|
||||
let content = home(state);
|
||||
match state.screen {
|
||||
Screen::Settings => {
|
||||
let settings = settings::settings(state);
|
||||
let settings = container(settings)
|
||||
.width(Length::FillPortion(4))
|
||||
.height(Length::FillPortion(4))
|
||||
.style(container::rounded_box)
|
||||
.pipe(mouse_area)
|
||||
.on_press(Message::Refresh)
|
||||
.pipe(|c| iced::widget::column![space::vertical(), c, space::vertical()])
|
||||
.pipe(container)
|
||||
.width(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
.align_y(Alignment::Center)
|
||||
.align_x(Alignment::Center)
|
||||
.style(|_| container::background(BACKGROUND_COLOR))
|
||||
.padding(50)
|
||||
.pipe(mouse_area)
|
||||
.on_press(Message::Settings(settings::SettingsMessage::Close));
|
||||
stack![content, settings].into()
|
||||
}
|
||||
Screen::Home | _ => content,
|
||||
Screen::Settings => settings::settings(state),
|
||||
Screen::Home | _ => home(state),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,34 +310,38 @@ fn body(state: &State) -> Element<'_, Message> {
|
||||
if let Some(ref video) = state.video {
|
||||
video::player(video)
|
||||
} else {
|
||||
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
|
||||
.fluid(400)
|
||||
.spacing(50)
|
||||
.pipe(container)
|
||||
scrollable(
|
||||
container(
|
||||
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
|
||||
.fluid(400)
|
||||
.spacing(50),
|
||||
)
|
||||
.padding(50)
|
||||
.align_x(Alignment::Center)
|
||||
// .align_y(Alignment::Center)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
.pipe(scrollable)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn header(state: &State) -> Element<'_, Message> {
|
||||
row([
|
||||
text(
|
||||
state
|
||||
.jellyfin_client
|
||||
.as_ref()
|
||||
.map(|c| c.config.server_url.as_str())
|
||||
.unwrap_or("No Server"),
|
||||
container(
|
||||
Button::new(
|
||||
Text::new(
|
||||
state
|
||||
.jellyfin_client
|
||||
.as_ref()
|
||||
.map(|c| c.config.server_url.as_str())
|
||||
.unwrap_or("No Server"),
|
||||
)
|
||||
.align_x(Alignment::Start),
|
||||
)
|
||||
.on_press(Message::Home),
|
||||
)
|
||||
.align_x(Alignment::Start)
|
||||
.pipe(button)
|
||||
.on_press(Message::Home)
|
||||
.pipe(container)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
@@ -347,17 +350,18 @@ fn header(state: &State) -> Element<'_, Message> {
|
||||
.style(container::rounded_box)
|
||||
.into(),
|
||||
search(state),
|
||||
row([
|
||||
button("Refresh").on_press(Message::Refresh).into(),
|
||||
button("Settings")
|
||||
.on_press(Message::Settings(settings::SettingsMessage::Open))
|
||||
.into(),
|
||||
button("TestVideo")
|
||||
.on_press(Message::Video(video::VideoMessage::Test))
|
||||
.into(),
|
||||
])
|
||||
.spacing(10)
|
||||
.pipe(container)
|
||||
container(
|
||||
row([
|
||||
button("Refresh").on_press(Message::Refresh).into(),
|
||||
button("Settings")
|
||||
.on_press(Message::Settings(settings::SettingsMessage::Open))
|
||||
.into(),
|
||||
button("TestVideo")
|
||||
.on_press(Message::Video(video::VideoMessage::Test))
|
||||
.into(),
|
||||
])
|
||||
.spacing(10),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
@@ -373,18 +377,19 @@ fn header(state: &State) -> Element<'_, Message> {
|
||||
}
|
||||
|
||||
fn search(state: &State) -> Element<'_, Message> {
|
||||
TextInput::new("Search...", state.query.as_deref().unwrap_or_default())
|
||||
.padding(10)
|
||||
.size(16)
|
||||
.width(Length::Fill)
|
||||
.on_input(Message::SearchQueryChanged)
|
||||
.on_submit(Message::Search)
|
||||
.pipe(container)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(container::rounded_box)
|
||||
.into()
|
||||
container(
|
||||
TextInput::new("Search...", state.query.as_deref().unwrap_or_default())
|
||||
.padding(10)
|
||||
.size(16)
|
||||
.width(Length::Fill)
|
||||
.on_input(Message::SearchQueryChanged)
|
||||
.on_submit(Message::Search),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(container::rounded_box)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn footer(state: &State) -> Element<'_, Message> {
|
||||
@@ -441,7 +446,51 @@ fn card(item: &Item) -> Element<'_, Message> {
|
||||
}
|
||||
|
||||
fn init() -> (State, Task<Message>) {
|
||||
(State::new(), Task::done(Message::Refresh))
|
||||
// Create a default config for initial state
|
||||
|
||||
// let default_config = api::JellyfinConfig {
|
||||
// server_url: "http://localhost:8096".parse().expect("Valid URL"),
|
||||
// device_id: "jello-iced".to_string(),
|
||||
// device_name: "Jello Iced".to_string(),
|
||||
// client_name: "Jello".to_string(),
|
||||
// version: "0.1.0".to_string(),
|
||||
// };
|
||||
// let default_client = api::JellyfinClient::new_with_config(default_config);
|
||||
|
||||
(
|
||||
State::new(),
|
||||
Task::perform(
|
||||
async move {
|
||||
let config_str = std::fs::read_to_string("config.toml")
|
||||
.map_err(|e| api::JellyfinApiError::IoError(e))?;
|
||||
let config: api::JellyfinConfig = toml::from_str(&config_str).map_err(|e| {
|
||||
api::JellyfinApiError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Try to load cached token and authenticate
|
||||
match std::fs::read_to_string(".session") {
|
||||
Ok(token) => {
|
||||
let client = api::JellyfinClient::pre_authenticated(token.trim(), config)?;
|
||||
Ok((client, true))
|
||||
}
|
||||
Err(_) => {
|
||||
// No cached token, create unauthenticated client
|
||||
let client = api::JellyfinClient::new_with_config(config);
|
||||
Ok((client, false))
|
||||
}
|
||||
}
|
||||
},
|
||||
|result: Result<_, api::JellyfinApiError>| match result {
|
||||
// Ok((client, is_authenticated)) => Message::LoadedClient(client, is_authenticated),
|
||||
Err(e) => Message::Error(format!("Initialization failed: {}", e)),
|
||||
_ => Message::Error("Login Unimplemented".to_string()),
|
||||
},
|
||||
)
|
||||
.chain(Task::done(Message::Refresh)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ui() -> iced::Result {
|
||||
|
||||
@@ -18,10 +18,10 @@ pub fn update(state: &mut State, message: SettingsMessage) -> Task<Message> {
|
||||
SettingsMessage::Select(screen) => {
|
||||
tracing::trace!("Switching settings screen to {:?}", screen);
|
||||
state.settings.screen = screen;
|
||||
} //
|
||||
// SettingsMessage::User(user) => state.settings.login_form.update(user),
|
||||
//
|
||||
// SettingsMessage::Server(server) => state.settings.server_page.update(server),
|
||||
}
|
||||
SettingsMessage::User(user) => state.settings.login_form.update(user),
|
||||
|
||||
SettingsMessage::Server(server) => state.settings.server_form.update(server),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -30,16 +30,43 @@ pub fn empty() -> Element<'static, Message> {
|
||||
column([]).into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SettingsState {
|
||||
login_form: LoginForm,
|
||||
server_form: ServerForm,
|
||||
screen: SettingsScreen,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SettingsMessage {
|
||||
Open,
|
||||
Close,
|
||||
Select(SettingsScreen),
|
||||
// User(UserMessage),
|
||||
// Server(ServerMessage),
|
||||
User(UserMessage),
|
||||
Server(ServerMessage),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UserMessage {
|
||||
Add,
|
||||
UsernameChanged(String),
|
||||
PasswordChanged(String),
|
||||
// Edit(uuid::Uuid),
|
||||
// Delete(uuid::Uuid),
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ServerMessage {
|
||||
Add,
|
||||
NameChanged(String),
|
||||
UrlChanged(String),
|
||||
// Edit(uuid::Uuid),
|
||||
// Delete(uuid::Uuid),
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub enum SettingsScreen {
|
||||
#[default]
|
||||
Main,
|
||||
@@ -47,34 +74,130 @@ pub enum SettingsScreen {
|
||||
Servers,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct SettingsState {
|
||||
pub screen: SettingsScreen,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerItem {
|
||||
pub id: uuid::Uuid,
|
||||
pub name: SharedString,
|
||||
pub url: SharedString,
|
||||
pub users: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserItem {
|
||||
pub id: uuid::Uuid,
|
||||
pub name: SharedString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl LoginForm {
|
||||
pub fn update(&mut self, message: UserMessage) {
|
||||
match message {
|
||||
UserMessage::UsernameChanged(data) => {
|
||||
self.username = data;
|
||||
}
|
||||
UserMessage::PasswordChanged(data) => {
|
||||
self.password = data;
|
||||
}
|
||||
UserMessage::Add => {
|
||||
// Handle adding user
|
||||
}
|
||||
UserMessage::Clear => {
|
||||
self.username.clear();
|
||||
self.password.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
iced::widget::column![
|
||||
text("Login Form"),
|
||||
text_input("Enter Username", &self.username).on_input(|data| {
|
||||
Message::Settings(SettingsMessage::User(UserMessage::UsernameChanged(data)))
|
||||
}),
|
||||
text_input("Enter Password", &self.password)
|
||||
.secure(true)
|
||||
.on_input(|data| {
|
||||
Message::Settings(SettingsMessage::User(UserMessage::PasswordChanged(data)))
|
||||
}),
|
||||
row![
|
||||
button(text("Add User")).on_press_maybe(self.validate()),
|
||||
button(text("Cancel"))
|
||||
.on_press(Message::Settings(SettingsMessage::User(UserMessage::Clear))),
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(10)
|
||||
.padding([10, 0])
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Option<Message> {
|
||||
(!self.username.is_empty() && !self.password.is_empty())
|
||||
.then(|| Message::Settings(SettingsMessage::User(UserMessage::Add)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ServerForm {
|
||||
name: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl ServerForm {
|
||||
pub fn update(&mut self, message: ServerMessage) {
|
||||
match message {
|
||||
ServerMessage::NameChanged(data) => {
|
||||
self.name = data;
|
||||
}
|
||||
ServerMessage::UrlChanged(data) => {
|
||||
self.url = data;
|
||||
}
|
||||
ServerMessage::Add => {
|
||||
// Handle adding server
|
||||
}
|
||||
ServerMessage::Clear => {
|
||||
self.name.clear();
|
||||
self.url.clear();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
iced::widget::column![
|
||||
text("Add New Server"),
|
||||
text_input("Enter server name", &self.name).on_input(|data| {
|
||||
Message::Settings(SettingsMessage::Server(ServerMessage::NameChanged(data)))
|
||||
}),
|
||||
text_input("Enter server URL", &self.url).on_input(|data| {
|
||||
Message::Settings(SettingsMessage::Server(ServerMessage::UrlChanged(data)))
|
||||
}),
|
||||
row![
|
||||
button(text("Add Server")).on_press_maybe(self.validate()),
|
||||
button(text("Cancel")).on_press(Message::Settings(SettingsMessage::Server(
|
||||
ServerMessage::Clear
|
||||
))),
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(10)
|
||||
.padding([10, 0])
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Option<Message> {
|
||||
(!self.name.is_empty() && !self.url.is_empty())
|
||||
.then(|| Message::Settings(SettingsMessage::Server(ServerMessage::Add)))
|
||||
}
|
||||
}
|
||||
|
||||
mod screens {
|
||||
use iced_aw::Tabs;
|
||||
|
||||
use super::*;
|
||||
pub fn settings(state: &State) -> Element<'_, Message> {
|
||||
Tabs::new(|f| Message::Settings(SettingsMessage::Select(f)))
|
||||
.push(
|
||||
SettingsScreen::Main,
|
||||
iced_aw::TabLabel::Text("General".into()),
|
||||
main(state),
|
||||
)
|
||||
.push(
|
||||
SettingsScreen::Servers,
|
||||
iced_aw::TabLabel::Text("Servers".into()),
|
||||
server(state),
|
||||
)
|
||||
.push(
|
||||
SettingsScreen::Users,
|
||||
iced_aw::TabLabel::Text("Users".into()),
|
||||
user(state),
|
||||
)
|
||||
.set_active_tab(&state.settings.screen)
|
||||
.into()
|
||||
row([settings_list(state), settings_screen(state)]).into()
|
||||
}
|
||||
|
||||
pub fn settings_screen(state: &State) -> Element<'_, Message> {
|
||||
@@ -84,66 +207,64 @@ mod screens {
|
||||
SettingsScreen::Users => user(state),
|
||||
})
|
||||
.width(Length::FillPortion(10))
|
||||
.height(Length::Fill)
|
||||
.style(|theme| container::background(theme.extended_palette().background.base.color))
|
||||
.pipe(container)
|
||||
.padding(10)
|
||||
.style(|theme| container::background(theme.extended_palette().secondary.base.color))
|
||||
.width(Length::FillPortion(10))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn settings_list(state: &State) -> Element<'_, Message> {
|
||||
column(
|
||||
[
|
||||
button(center_text("General")).on_press(Message::Settings(
|
||||
SettingsMessage::Select(SettingsScreen::Main),
|
||||
)),
|
||||
button(center_text("Servers")).on_press(Message::Settings(
|
||||
SettingsMessage::Select(SettingsScreen::Servers),
|
||||
)),
|
||||
button(center_text("Users")).on_press(Message::Settings(SettingsMessage::Select(
|
||||
SettingsScreen::Users,
|
||||
))),
|
||||
]
|
||||
.map(|p| p.clip(true).width(Length::Fill).into()),
|
||||
scrollable(
|
||||
column(
|
||||
[
|
||||
button(center_text("Main")).on_press(Message::Settings(
|
||||
SettingsMessage::Select(SettingsScreen::Main),
|
||||
)),
|
||||
button(center_text("Servers")).on_press(Message::Settings(
|
||||
SettingsMessage::Select(SettingsScreen::Servers),
|
||||
)),
|
||||
button(center_text("Users")).on_press(Message::Settings(
|
||||
SettingsMessage::Select(SettingsScreen::Users),
|
||||
)),
|
||||
]
|
||||
.map(|p| p.clip(true).width(Length::Fill).into()),
|
||||
)
|
||||
.width(Length::FillPortion(2))
|
||||
.spacing(10)
|
||||
.padding(10),
|
||||
)
|
||||
.width(Length::FillPortion(2))
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
.pipe(scrollable)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn main(state: &State) -> Element<'_, Message> {
|
||||
Column::new()
|
||||
.push(text("Main Settings"))
|
||||
.push(toggler(true).label("HDR"))
|
||||
.push(toggler(true).label("Enable Notifications"))
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.pipe(container)
|
||||
.into()
|
||||
// placeholder for now
|
||||
container(
|
||||
Column::new()
|
||||
.push(text("Main Settings"))
|
||||
.push(toggler(true).label("Foobar"))
|
||||
.spacing(20)
|
||||
.padding(20),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn server(state: &State) -> Element<'_, Message> {
|
||||
Column::new()
|
||||
.push(text("Server Settings"))
|
||||
// .push(ServerPage::view(state))
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.pipe(container)
|
||||
.into()
|
||||
container(
|
||||
Column::new()
|
||||
.push(text("Server Settings"))
|
||||
.push(state.settings.server_form.view())
|
||||
// .push(toggler(false).label("Enable Server"))
|
||||
.spacing(20)
|
||||
.padding(20),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn user(state: &State) -> Element<'_, Message> {
|
||||
Column::new()
|
||||
.push(text("User Settings"))
|
||||
// .push(LoginForm::view(&state.settings.login_form))
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.pipe(container)
|
||||
.into()
|
||||
container(
|
||||
Column::new()
|
||||
.push(text("User Settings"))
|
||||
.push(state.settings.login_form.view())
|
||||
// .push(userlist(&state))
|
||||
.spacing(20)
|
||||
.padding(20),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user