Actually usable????????//

This commit is contained in:
Gvidas Juknevičius 2024-11-21 05:02:30 +02:00
parent b1c8417b0f
commit 4ee4ca1add
Signed by: MCorange
GPG Key ID: 12B1346D720B7FBB
26 changed files with 2339 additions and 356 deletions

492
Cargo.lock generated
View File

@ -63,7 +63,7 @@ dependencies = [
"futures-lite 1.13.0",
"once_cell",
"serde",
"zbus",
"zbus 3.15.2",
]
[[package]]
@ -93,15 +93,6 @@ dependencies = [
"winit",
]
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
@ -292,6 +283,28 @@ dependencies = [
"libloading 0.7.4",
]
[[package]]
name = "ashpd"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3"
dependencies = [
"async-fs 2.1.2",
"async-net",
"enumflags2",
"futures-channel",
"futures-util",
"rand",
"raw-window-handle 0.6.2",
"serde",
"serde_repr",
"url",
"wayland-backend",
"wayland-client",
"wayland-protocols 0.32.5",
"zbus 5.1.1",
]
[[package]]
name = "async-broadcast"
version = "0.5.1"
@ -302,6 +315,18 @@ dependencies = [
"futures-core",
]
[[package]]
name = "async-broadcast"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
dependencies = [
"event-listener 5.3.1",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-channel"
version = "2.3.1"
@ -339,6 +364,17 @@ dependencies = [
"futures-lite 1.13.0",
]
[[package]]
name = "async-fs"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
dependencies = [
"async-lock 3.4.0",
"blocking",
"futures-lite 2.4.0",
]
[[package]]
name = "async-io"
version = "1.13.0"
@ -398,6 +434,17 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-net"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
dependencies = [
"async-io 2.3.4",
"blocking",
"futures-lite 2.4.0",
]
[[package]]
name = "async-once-cell"
version = "0.5.4"
@ -421,6 +468,25 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "async-process"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
dependencies = [
"async-channel",
"async-io 2.3.4",
"async-lock 3.4.0",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener 5.3.1",
"futures-lite 2.4.0",
"rustix 0.38.39",
"tracing",
]
[[package]]
name = "async-recursion"
version = "1.1.1"
@ -493,9 +559,9 @@ dependencies = [
"enumflags2",
"serde",
"static_assertions",
"zbus",
"zbus_names",
"zvariant",
"zbus 3.15.2",
"zbus_names 2.6.1",
"zvariant 3.15.2",
]
[[package]]
@ -507,7 +573,7 @@ dependencies = [
"atspi-common",
"atspi-proxies",
"futures-lite 1.13.0",
"zbus",
"zbus 3.15.2",
]
[[package]]
@ -518,7 +584,7 @@ checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52"
dependencies = [
"atspi-common",
"serde",
"zbus",
"zbus 3.15.2",
]
[[package]]
@ -527,21 +593,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
name = "base64"
version = "0.21.7"
@ -811,6 +862,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cgl"
version = "0.3.2"
@ -1212,15 +1269,6 @@ dependencies = [
"serde",
]
[[package]]
name = "ecolor"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e6b451ff1143f6de0f33fc7f1b68fecfd2c7de06e104de96c4514de3f5396f8"
dependencies = [
"emath 0.28.1",
]
[[package]]
name = "eframe"
version = "0.27.2"
@ -1230,7 +1278,7 @@ dependencies = [
"bytemuck",
"cocoa",
"document-features",
"egui 0.27.2",
"egui",
"egui-wgpu",
"egui-winit",
"egui_glow",
@ -1263,33 +1311,12 @@ checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a"
dependencies = [
"accesskit",
"ahash",
"epaint 0.27.2",
"epaint",
"log",
"nohash-hasher",
"serde",
]
[[package]]
name = "egui"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c97e70a2768de630f161bb5392cbd3874fcf72868f14df0e002e82e06cb798"
dependencies = [
"ahash",
"emath 0.28.1",
"epaint 0.28.1",
"nohash-hasher",
]
[[package]]
name = "egui-aesthetix"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b91e1b07193fa9bc71849e39a76d30ffd6a2f57bfc54552e3a2df263da577b"
dependencies = [
"egui 0.28.1",
]
[[package]]
name = "egui-wgpu"
version = "0.27.2"
@ -1298,8 +1325,8 @@ checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37"
dependencies = [
"bytemuck",
"document-features",
"egui 0.27.2",
"epaint 0.27.2",
"egui",
"epaint",
"log",
"thiserror",
"type-map",
@ -1316,7 +1343,7 @@ checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609"
dependencies = [
"accesskit_winit",
"arboard",
"egui 0.27.2",
"egui",
"log",
"raw-window-handle 0.6.2",
"smithay-clipboard",
@ -1331,7 +1358,7 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b78779f35ded1a853786c9ce0b43fe1053e10a21ea3b23ebea411805ce41593"
dependencies = [
"egui 0.27.2",
"egui",
"ehttp",
"enum-map",
"image",
@ -1348,7 +1375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca"
dependencies = [
"bytemuck",
"egui 0.27.2",
"egui",
"glow",
"log",
"memoffset 0.9.1",
@ -1387,12 +1414,6 @@ dependencies = [
"serde",
]
[[package]]
name = "emath"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6a21708405ea88f63d8309650b4d77431f4bc28fb9d8e6f77d3963b51249e6"
[[package]]
name = "encoding_rs"
version = "0.8.35"
@ -1402,6 +1423,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "endi"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "enum-map"
version = "2.7.3"
@ -1487,28 +1514,14 @@ dependencies = [
"ab_glyph",
"ahash",
"bytemuck",
"ecolor 0.27.2",
"emath 0.27.2",
"ecolor",
"emath",
"log",
"nohash-hasher",
"parking_lot",
"serde",
]
[[package]]
name = "epaint"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f0dcc0a0771e7500e94cd1cb797bd13c9f23b9409bdc3c824e2cbc562b7fa01"
dependencies = [
"ab_glyph",
"ahash",
"ecolor 0.28.1",
"emath 0.28.1",
"nohash-hasher",
"parking_lot",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -1651,6 +1664,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.31"
@ -1691,6 +1713,17 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
@ -1711,6 +1744,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
@ -1750,12 +1784,6 @@ dependencies = [
"wasi",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gl_generator"
version = "0.14.0"
@ -1792,7 +1820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746"
dependencies = [
"bitflags 2.6.0",
"cfg_aliases",
"cfg_aliases 0.1.1",
"cgl",
"core-foundation",
"dispatch",
@ -1815,7 +1843,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735"
dependencies = [
"cfg_aliases",
"cfg_aliases 0.1.1",
"glutin",
"raw-window-handle 0.5.2",
"winit",
@ -2327,18 +2355,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"wasi",
"windows-sys 0.52.0",
]
[[package]]
name = "naga"
version = "0.19.2"
@ -2402,6 +2418,19 @@ dependencies = [
"memoffset 0.7.1",
]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases 0.2.1",
"libc",
"memoffset 0.9.1",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
@ -2581,6 +2610,7 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.6.0",
"block2 0.5.1",
"dispatch",
"libc",
"objc2 0.5.2",
]
@ -2619,15 +2649,6 @@ dependencies = [
"cc",
]
[[package]]
name = "object"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
[[package]]
name = "oboe"
version = "0.6.1"
@ -2820,6 +2841,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "pollster"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
@ -3013,6 +3040,28 @@ dependencies = [
"usvg",
]
[[package]]
name = "rfd"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8"
dependencies = [
"ashpd",
"block2 0.5.1",
"js-sys",
"log",
"objc2 0.5.2",
"objc2-app-kit",
"objc2-foundation",
"pollster",
"raw-window-handle 0.6.2",
"urlencoding",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "rgb"
version = "0.8.50"
@ -3056,12 +3105,6 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -3673,30 +3716,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.41.0"
name = "toml"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.19.15",
]
[[package]]
@ -3727,6 +3755,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.5.40",
]
@ -3886,6 +3916,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "usvg"
version = "0.37.0"
@ -4229,7 +4265,7 @@ checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01"
dependencies = [
"arrayvec",
"cfg-if",
"cfg_aliases",
"cfg_aliases 0.1.1",
"js-sys",
"log",
"parking_lot",
@ -4254,7 +4290,7 @@ dependencies = [
"arrayvec",
"bit-vec",
"bitflags 2.6.0",
"cfg_aliases",
"cfg_aliases 0.1.1",
"codespan-reporting",
"indexmap",
"log",
@ -4281,7 +4317,7 @@ dependencies = [
"arrayvec",
"ash",
"bitflags 2.6.0",
"cfg_aliases",
"cfg_aliases 0.1.1",
"core-graphics-types",
"glow",
"glutin_wgl_sys",
@ -4667,7 +4703,7 @@ dependencies = [
"bitflags 2.6.0",
"bytemuck",
"calloop 0.12.4",
"cfg_aliases",
"cfg_aliases 0.1.1",
"core-foundation",
"core-graphics",
"cursor-icon",
@ -4722,6 +4758,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winresource"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e2aaaf8cfa92078c0c0375423d631f82f2f57979c2884fdd5f604a11e45329"
dependencies = [
"toml 0.7.8",
"version_check",
]
[[package]]
name = "x11-dl"
version = "2.21.0"
@ -4834,6 +4880,7 @@ dependencies = [
"clap",
"env_logger",
"log",
"winresource",
"xmpd-cliargs",
"xmpd-gui",
"xmpd-manifest",
@ -4846,17 +4893,18 @@ version = "2.0.0"
dependencies = [
"anyhow",
"camino",
"dirs",
"eframe",
"egui 0.27.2",
"egui-aesthetix",
"egui",
"egui_extras",
"lazy_static",
"log",
"tokio",
"rfd",
"uuid",
"xmpd-cache",
"xmpd-cliargs",
"xmpd-manifest",
"xmpd-player",
"xmpd-settings",
]
@ -4867,14 +4915,20 @@ dependencies = [
"anyhow",
"serde",
"serde_json",
"toml 0.8.19",
"url",
"uuid",
"xmpd-cliargs",
"xmpd-settings",
]
[[package]]
name = "xmpd-player"
version = "2.0.0"
dependencies = [
"anyhow",
"lazy_static",
"log",
"rodio",
]
@ -4884,10 +4938,10 @@ version = "2.0.0"
dependencies = [
"anyhow",
"camino",
"egui 0.27.2",
"egui",
"lazy_static",
"serde",
"toml",
"toml 0.8.19",
]
[[package]]
@ -4900,12 +4954,12 @@ version = "3.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6"
dependencies = [
"async-broadcast",
"async-broadcast 0.5.1",
"async-executor",
"async-fs",
"async-fs 1.6.0",
"async-io 1.13.0",
"async-lock 2.8.0",
"async-process",
"async-process 1.8.1",
"async-recursion",
"async-task",
"async-trait",
@ -4918,7 +4972,7 @@ dependencies = [
"futures-sink",
"futures-util",
"hex",
"nix",
"nix 0.26.4",
"once_cell",
"ordered-stream",
"rand",
@ -4930,9 +4984,45 @@ dependencies = [
"uds_windows",
"winapi",
"xdg-home",
"zbus_macros",
"zbus_names",
"zvariant",
"zbus_macros 3.15.2",
"zbus_names 2.6.1",
"zvariant 3.15.2",
]
[[package]]
name = "zbus"
version = "5.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608"
dependencies = [
"async-broadcast 0.7.1",
"async-executor",
"async-fs 2.1.2",
"async-io 2.3.4",
"async-lock 3.4.0",
"async-process 2.3.0",
"async-recursion",
"async-task",
"async-trait",
"blocking",
"enumflags2",
"event-listener 5.3.1",
"futures-core",
"futures-util",
"hex",
"nix 0.29.0",
"ordered-stream",
"serde",
"serde_repr",
"static_assertions",
"tracing",
"uds_windows",
"windows-sys 0.59.0",
"winnow 0.6.20",
"xdg-home",
"zbus_macros 5.1.1",
"zbus_names 4.1.0",
"zvariant 5.1.0",
]
[[package]]
@ -4946,7 +5036,22 @@ dependencies = [
"quote",
"regex",
"syn 1.0.109",
"zvariant_utils",
"zvariant_utils 1.0.1",
]
[[package]]
name = "zbus_macros"
version = "5.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d"
dependencies = [
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.87",
"zbus_names 4.1.0",
"zvariant 5.1.0",
"zvariant_utils 3.0.2",
]
[[package]]
@ -4957,7 +5062,19 @@ checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d"
dependencies = [
"serde",
"static_assertions",
"zvariant",
"zvariant 3.15.2",
]
[[package]]
name = "zbus_names"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b"
dependencies = [
"serde",
"static_assertions",
"winnow 0.6.20",
"zvariant 5.1.0",
]
[[package]]
@ -4998,7 +5115,23 @@ dependencies = [
"libc",
"serde",
"static_assertions",
"zvariant_derive",
"zvariant_derive 3.15.2",
]
[[package]]
name = "zvariant"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f"
dependencies = [
"endi",
"enumflags2",
"serde",
"static_assertions",
"url",
"winnow 0.6.20",
"zvariant_derive 5.1.0",
"zvariant_utils 3.0.2",
]
[[package]]
@ -5011,7 +5144,20 @@ dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"zvariant_utils",
"zvariant_utils 1.0.1",
]
[[package]]
name = "zvariant_derive"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916"
dependencies = [
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.87",
"zvariant_utils 3.0.2",
]
[[package]]
@ -5024,3 +5170,17 @@ dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "zvariant_utils"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6"
dependencies = [
"proc-macro2",
"quote",
"serde",
"static_assertions",
"syn 2.0.87",
"winnow 0.6.20",
]

View File

@ -25,28 +25,25 @@ authors=[
[workspace.dependencies]
anstyle = "1.0.6"
anyhow = "1.0.81"
bitflags = { version = "2.6.0", features = ["serde"] }
camino = { version="1.1.6", features = ["serde1"] }
clap = { version = "4.5.4", features = ["derive"] }
eframe = "0.27.2"
egui = { version = "0.27.2", features = ["color-hex", "serde"] }
egui_extras = { version = "0.27.2", features = ["all_loaders"] }
env_logger = "0.11.3"
futures = "0.3.30"
html-escape = "0.2.13"
lazy_static = "1.4.0"
libc = "0.2.153"
log = "0.4.21"
notify-rust = "4.11.3"
open = "5.3.0"
regex = "1.11.0"
reqwest = { version = "0.12.3", features = ["blocking", "h2", "http2", "rustls-tls"], default-features = false }
# notify-rust = "4.11.3"
# open = "5.3.0"
# reqwest = { version = "0.12.3", features = ["blocking", "h2", "http2", "rustls-tls"], default-features = false }
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
# serde_traitobject = "0.2.8"
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "process", "sync"] }
url = { version = "2.5.0", features = ["serde"] }
uuid = { version = "1.11.0", features = ["serde", "v4"] }
windows = { version = "0.56.0", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_Console"] }
zip-extensions = "0.6.20"
# zip-extensions = "0.6.20"
dirs="5.0.1"
winresource = "0.1.17"
toml = "0.8.19"
rfd = "0.15.1"
rodio = { version = "0.20.1", features = ["symphonia-all"] }

BIN
assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 -960 960 960" width="16px" fill="#FFFFFF"><path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

Before

Width:  |  Height:  |  Size: 536 B

After

Width:  |  Height:  |  Size: 536 B

1373
manifest.toml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -108,11 +108,12 @@ impl SongCacheDl {
}
let mut from = song_p.clone();
from.pop();
from.push("{song_format}.{song_format}");
from.push(format!("{song_format}.{song_format}"));
let mut to = song_p.clone();
to.pop();
to.set_extension(&song_format);
log::debug!("from: {from:?} to: {to:?}");
std::fs::copy(&from, &to).unwrap();
from.pop();
std::fs::remove_dir_all(from).unwrap();

View File

@ -1,6 +1,5 @@
use std::{collections::HashMap, str::FromStr, sync::{mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard}, time::Duration};
use downloader::song::SongStatus;
use log::warn;
use xmpd_manifest::song::Song;
pub mod downloader;
@ -106,7 +105,6 @@ fn start_cache_mv_thread(tx: Sender<Message>) {
song_p.push("songs");
song_p.push(sid.clone().to_string());
let song_p = song_p.with_extension(&song_format);
log::debug!("Found done: {:?}: {}", song_p, song_p.exists());
if song_p.exists() {
let _ = tx.send(Message::DownloadDone(sid.clone()));
cache.song_cache.insert(sid.clone(), DlStatus::Done(song_p));

View File

@ -30,3 +30,6 @@ camino.workspace = true
anyhow.workspace = true
log.workspace = true
env_logger.workspace = true
[build-dependencies]
winresource.workspace = true

11
xmpd-core/build.rs Normal file
View File

@ -0,0 +1,11 @@
use winresource::WindowsResource;
fn main() -> std::io::Result<()> {
if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
WindowsResource::new()
// This path can be absolute, or relative to your crate root.
.set_icon("../assets/icon.ico")
.compile()?;
}
Ok(())
}

View File

@ -22,13 +22,14 @@ xmpd-manifest.path = "../xmpd-manifest"
xmpd-settings.path = "../xmpd-settings"
xmpd-cliargs.path = "../xmpd-cliargs"
xmpd-cache.path = "../xmpd-cache"
xmpd-player.path = "../xmpd-player"
egui.workspace = true
eframe.workspace = true
tokio.workspace = true
anyhow.workspace = true
lazy_static.workspace = true
log.workspace = true
egui_extras.workspace = true
egui-aesthetix = "0.2.4"
uuid.workspace = true
camino.workspace = true
rfd.workspace = true
dirs.workspace = true

View File

@ -1,4 +1,4 @@
use egui::RichText;
use egui::{CursorIcon, RichText, Sense};
use xmpd_manifest::store::BaseStore;
use super::{CompGetter, CompUi};
@ -50,6 +50,7 @@ fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option<uuid::Uuid>, title: &str, au
ui.add(
egui::Image::new(crate::data::NOTE_ICON)
.tint(theme.accent_color)
.sense(Sense::click())
.fit_to_exact_size(egui::Vec2::new(32.0, 32.0))
);
ui.vertical(|ui| {
@ -83,9 +84,13 @@ fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option<uuid::Uuid>, title: &str, au
});
}).response.rect;
if ui.interact(wdg_rect, format!("left_nav_playlist_{pid:?}").into(), egui::Sense::click()).clicked() {
let blob = ui.interact(wdg_rect, format!("left_nav_playlist_{pid:?}").into(), egui::Sense::click());
if blob.clicked() {
handle_error_ui!(LeftNav::get()).selected_playlist_id = pid.clone();
}
if blob.hovered() {
ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand);
}
ui.separator();
}

View File

@ -1,19 +1,30 @@
use egui::{Sense, Stroke, Vec2};
use super::{CompGetter, CompUi};
use super::{song_list::SongList, CompGetter, CompUi};
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct Player {
slider_progress: usize,
is_playing: bool,
old_slider_progress: usize,
volume_slider: f64,
}
impl Default for Player {
fn default() -> Self {
Self {
volume_slider: 1.0,
old_slider_progress: 0,
slider_progress: 0
}
}
}
component_register!(Player);
impl CompUi for Player {
fn draw(ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
let theme = xmpd_settings::Settings::get()?.theme.clone();
let avail = ui.available_size();
ui.vertical_centered_justified(|ui| {
@ -21,10 +32,7 @@ impl CompUi for Player {
ui.horizontal(|ui| {
{
ui.add_space(avail.x * 0.05 / 2.0);
let mut slf = handle_error_ui!(Player::get());
let slider = egui::Slider::new(&mut slf.slider_progress, 0..=100)
.show_value(false);
ui.style_mut().spacing.slider_width = avail.x * 0.90;
ui.style_mut().spacing.slider_width = avail.x * 0.87;
let s = Stroke {
color: theme.accent_color,
width: 2.0
@ -32,19 +40,35 @@ impl CompUi for Player {
ui.style_mut().visuals.widgets.inactive.fg_stroke = s;
ui.style_mut().visuals.widgets.active.fg_stroke = s;
ui.style_mut().visuals.widgets.hovered.fg_stroke = s;
ui.add(slider);
ui.label("00:00");
let mut slf = handle_error_ui!(Player::get());
ui.add(
egui::Slider::new(&mut slf.slider_progress, 0..=100)
.show_value(false)
);
if slf.slider_progress == slf.old_slider_progress {
slf.slider_progress = (state.player.get_played_f() * 100.0) as usize;
slf.old_slider_progress = slf.slider_progress;
} else {
handle_error_ui!(state.player.seek_to_f(slf.slider_progress as f64 / 100.0 ));
slf.old_slider_progress = slf.slider_progress;
}
let secs_left = state.player.get_ms_left() as f64 / 1000.0;
let h = (secs_left/60.0/60.0).floor();
let m = ((secs_left - h * 60.0)/60.0).floor();
let s = (secs_left - m * 60.0).floor();
ui.label(format!("{h:02}:{m:02}:{s:02}"));
}
});
ui.horizontal(|ui| {
ui.add_space((avail.x / 2.0) - 16.0 - 8.0 - ui.spacing().item_spacing.x);
let pp = if handle_error_ui!(Player::get()).is_playing {
crate::data::PAUSE_ICON
} else {
let pp = if state.player.is_paused() {
crate::data::PLAY_ICON
} else {
crate::data::PAUSE_ICON
};
let prev = egui::Image::new(crate::data::PREV_ICON)
.tint(theme.accent_color)
.sense(Sense::click())
@ -57,12 +81,43 @@ impl CompUi for Player {
.tint(theme.accent_color)
.sense(Sense::click())
.max_size(Vec2::new(16.0, 16.0));
if ui.add(prev).clicked() {}
if ui.add(pp).clicked() {
let mut slf = handle_error_ui!(Player::get());
slf.is_playing = !slf.is_playing;
if ui.add(prev).clicked() {
handle_error_ui!(handle_error_ui!(SongList::get()).play_prev(state));
}
if ui.add(next).clicked() {}
if ui.add(pp).clicked() {
if state.player.is_paused() {
state.player.play();
} else {
state.player.pause();
}
}
if ui.add(next).clicked() || state.player.just_stopped() {
handle_error_ui!(handle_error_ui!(SongList::get()).play_next(state));
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
ui.add_space(15.0);
ui.style_mut().spacing.slider_width = avail.x * 0.15;
let s = Stroke {
color: theme.accent_color,
width: 1.0
};
ui.style_mut().visuals.widgets.inactive.fg_stroke = s;
ui.style_mut().visuals.widgets.active.fg_stroke = s;
ui.style_mut().visuals.widgets.hovered.fg_stroke = s;
let mut slf = handle_error_ui!(Player::get());
let slider =ui.add(
egui::Slider::new(&mut slf.volume_slider, 0.0..=1.0)
.show_value(false)
);
if slider.changed() {
state.player.set_volume(slf.volume_slider);
}
});
});
ui.add_space(3.0);
});

View File

@ -1,6 +1,4 @@
use std::fmt::write;
use egui::{Color32, RichText, Sense, Vec2};
use egui::{Color32, CursorIcon, RichText, Sense, Vec2};
use song_list_nav::SearchType;
use xmpd_cache::DlStatus;
use xmpd_manifest::{song::Song, store::BaseStore};
@ -10,104 +8,155 @@ pub mod song_list_nav;
#[derive(Debug, Default)]
pub struct SongList {
selected_song_id: uuid::Uuid
selected_sid: uuid::Uuid,
playable_songs: Vec<uuid::Uuid>,
}
component_register!(SongList);
impl CompUi for SongList {
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
let songs = Self::get_and_sort_songs(state)?;
let disp_songs = Self::get_songs_to_display(&songs)?;
{
let mut sl = SongList::get()?;
sl.playable_songs = Self::get_playable_songs(&songs)?;
if let Some((sid, _)) = songs.first() {
if sl.selected_sid == Default::default() {
sl.selected_sid = sid.clone();
}
}
}
egui::ScrollArea::vertical()
.id_source("song_list")
.drag_to_scroll(false)
.show(ui, |ui| {
ui.vertical(|ui| {
ui.add_space(3.0);
let pid = {handle_error_ui!(super::left_nav::LeftNav::get()).selected_playlist_id.clone()};
match pid {
None => {
let mut songs: Vec<_> = state.manifest.store().get_songs().iter().collect();
songs.sort_by(|a, b| {
let a = a.1.name().to_lowercase();
let b = b.1.name().to_lowercase();
a.cmp(&b)
});
for (sid, song) in songs {
let query = {handle_error_ui!(song_list_nav::SongListNav::get()).parse_search()}.clone();
let should_display = match query {
SearchType::Source(s) if !s.is_empty() => format!("{:?}", &song.source_type()).to_lowercase().contains(&s),
SearchType::Author(s) if !s.is_empty() => song.author().to_lowercase().contains(&s),
SearchType::Name(s) if !s.is_empty() => song.name().to_owned().contains(&s),
_ => true
};
if should_display {
display_song_tab(ui, sid, song);
for (sid, song) in disp_songs {
handle_error_ui!(Self::display_song_tab(ui, state, &sid, &song));
}
}
}
Some(pid) => {
if let Some(playlist) = state.manifest.store().get_playlist(&pid) {
let mut songs = Vec::new();
for sid in playlist.songs() {
if let Some(song) = state.manifest.store().get_song(&sid) {
songs.push((sid, song));
}
}
songs.sort_by(|a, b| {
let a = a.1.name().to_lowercase();
let b = b.1.name().to_lowercase();
a.cmp(&b)
});
let query = {handle_error_ui!(song_list_nav::SongListNav::get()).parse_search()}.clone();
for (sid, song) in songs {
let should_display = match query {
SearchType::Source(ref s) if !s.is_empty() => format!("{:?}", &song.source_type()).to_lowercase().contains(s),
SearchType::Author(ref s) if !s.is_empty() => song.author().to_lowercase().contains(s),
SearchType::Name(ref s) if !s.is_empty() => song.name().to_owned().contains(s),
_ => true
};
if should_display {
display_song_tab(ui, sid, song);
}
}
}
}
}
});
});
Ok(())
}
}
fn display_song_tab(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) {
let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone();
impl SongList {
fn get_and_sort_songs(state: &mut crate::GuiState) -> crate::Result<Vec<(uuid::Uuid, Song)>> {
let pid = super::left_nav::LeftNav::get()?.selected_playlist_id.clone();
match pid {
None => {
let songs = state.manifest.store().get_songs().clone().into_iter();
let mut songs: Vec<_> = songs.collect();
songs.sort_by(|a, b| {
let a = a.1.name().to_lowercase();
let b = b.1.name().to_lowercase();
a.cmp(&b)
});
Ok(songs)
}
Some(pid) => {
let Some(playlist) = state.manifest.store().get_playlist(&pid) else {
anyhow::bail!("Couldnt find playlist (corruption?)");
};
let mut songs = Vec::new();
for sid in playlist.songs() {
if let Some(song) = state.manifest.store().get_song(&sid) {
songs.push((sid.clone(), song.clone()));
}
}
songs.sort_by(|a, b| {
let a = a.1.name().to_lowercase();
let b = b.1.name().to_lowercase();
a.cmp(&b)
});
Ok(songs)
}
}
}
fn get_songs_to_display(songs: &Vec<(uuid::Uuid, Song)>) -> crate::Result<Vec<(uuid::Uuid, Song)>>{
let mut to_display = Vec::new();
let query = {song_list_nav::SongListNav::get()?.parse_search()}.clone();
for (sid, song) in songs {
let should_display = match &query {
SearchType::Name(s) |
SearchType::Author(s) |
SearchType::Source(s) if s.is_empty() => true,
SearchType::Source(s) => {
song.source_type().to_string()
.to_lowercase()
.contains(s)
},
SearchType::Author(s) => {
song.author()
.to_lowercase()
.contains(s)
},
SearchType::Name(s) => {
song.name()
.to_lowercase()
.contains(s)
},
};
if should_display {
to_display.push((sid.clone(), song.clone()));
}
}
Ok(to_display)
}
fn display_song_tab(ui: &mut egui::Ui, state: &mut crate::GuiState, sid: &uuid::Uuid, song: &Song) -> crate::Result<()> {
let mut clicked = false;
ui.horizontal(|ui| {
let mut clicked = ui.add(
let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone();
let img = ui.add(
egui::Image::new(crate::data::NOTE_ICON)
.tint(theme.accent_color)
.sense(Sense::click())
.fit_to_exact_size(Vec2::new(32.0, 32.0))
).clicked();
);
let status = {
handle_error_ui!(xmpd_cache::Cache::get()).get_cached_song_status(&sid).clone()
};
if img.clicked() {
clicked = true;
}
if img.hovered() {
if matches!(status, Some(DlStatus::Done(_))) {
ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand);
} else {
ui.output_mut(|o| o.cursor_icon = CursorIcon::Default);
}
}
ui.vertical(|ui| {
let selected_song_id = {handle_error_ui!(SongList::get()).selected_song_id};
let label = if selected_song_id == *sid {
ui.label(
let slf = handle_error_ui!(SongList::get());
let label = if slf.selected_sid == *sid {
RichText::new(song.name())
.color(theme.accent_color)
)
} else {
ui.label(
} else if matches!(status, Some(DlStatus::Done(_))) {
RichText::new(song.name())
.color(theme.text_color)
)
} else {
RichText::new(song.name())
.color(theme.dim_text_color)
};
let label = ui.label(label);
if label.clicked() {
clicked = true;
}
if label.hovered() {
if matches!(status, Some(DlStatus::Done(_))) {
ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand);
} else {
ui.output_mut(|o| o.cursor_icon = CursorIcon::Default);
}
}
ui.monospace(
RichText::new(format!("By {}", song.author()))
.color(theme.dim_text_color)
@ -118,48 +167,41 @@ fn display_song_tab(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) {
ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
ui.add_space(3.0);
let status = {
handle_error_ui!(xmpd_cache::Cache::get()).get_cached_song_status(&sid).clone()
};
match status {
Some(DlStatus::Done(p)) => {
let img = ui.add(
egui::Image::new(crate::data::CHECK_ICON)
.tint(Color32::LIGHT_GREEN)
.sense(Sense::hover())
.fit_to_exact_size(Vec2::new(16.0, 16.0))
);
img.on_hover_ui(|ui| {
ui.label(format!("Path: {p}"));
});
match status {
Some(DlStatus::Done(_p)) => {
//let img = egui::Image::new(crate::data::CHECK_ICON)
// .tint(Color32::LIGHT_GREEN)
// .sense(Sense::hover())
// .fit_to_exact_size(Vec2::new(16.0, 16.0));
//ui.add(img).on_hover_ui(|ui| {
// ui.label(format!("Path: {p}"));
//});
}
Some(DlStatus::Downloading) => {
ui.add(
egui::Spinner::new()
let spinner = egui::Spinner::new()
.color(theme.accent_color)
.size(16.0)
);
.size(16.0);
ui.add(spinner);
}
Some(DlStatus::Error(e)) => {
let img = ui.add(
egui::Image::new(crate::data::WARN_ICON)
let img = egui::Image::new(crate::data::WARN_ICON)
.tint(Color32::LIGHT_YELLOW)
.sense(Sense::hover())
.fit_to_exact_size(Vec2::new(16.0, 16.0))
);
img.on_hover_ui(|ui| {
.fit_to_exact_size(Vec2::new(16.0, 16.0));
ui.add(img).on_hover_ui(|ui| {
ui.label(e);
});
}
None => {
let img = ui.add(
egui::Image::new(crate::data::DL_ICON)
let img = egui::Image::new(crate::data::DL_ICON)
.tint(theme.accent_color)
.sense(Sense::click())
.fit_to_exact_size(Vec2::new(16.0, 16.0))
);
if img.clicked() {
.fit_to_exact_size(Vec2::new(16.0, 16.0));
if ui.add(img).clicked() {
handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone());
let mut toast = handle_error_ui!(crate::components::toast::Toast::get());
toast.show_toast(
@ -171,10 +213,59 @@ fn display_song_tab(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) {
}
}
});
if clicked {
handle_error_ui!(SongList::get()).selected_song_id = sid.clone();
}
});
ui.separator();
if clicked {
let mut sl = SongList::get()?;
sl.play_song(sid.clone(), state)?;
}
Ok(())
}
pub fn play_song(&mut self, sid: uuid::Uuid, state: &mut crate::GuiState) -> crate::Result<()> {
if self.playable_songs.contains(&sid) {
self.selected_sid = sid.clone();
let path = state.manifest.get_song_as_path(sid)?;
state.player.play_song(&path)?;
}
Ok(())
}
pub fn play_prev(&mut self, state: &mut crate::GuiState) -> crate::Result<()> {
let Some(mut prev) = self.playable_songs.last().cloned() else {
anyhow::bail!("Trying to play a song in an empty playlist (impossible)")
};
for sid in self.playable_songs.clone() {
if sid == self.selected_sid {
self.play_song(prev, state)?;
}
prev = sid;
}
Ok(())
}
pub fn play_next(&mut self, state: &mut crate::GuiState) -> crate::Result<()> {
let Some(mut next) = self.playable_songs.first().cloned() else {
anyhow::bail!("Trying to play a song in an empty playlist (impossible)")
};
let mut found = false;
for sid in self.playable_songs.clone() {
if sid == self.selected_sid {
found = true;
} else if found {
next = sid;
break;
}
}
self.play_song(next, state)?;
Ok(())
}
fn get_playable_songs(songs: &Vec<(uuid::Uuid, Song)>) -> crate::Result<Vec<uuid::Uuid>> {
let mut playable_songs = Vec::new();
for (sid, _) in songs {
if let Some(DlStatus::Done(_)) = xmpd_cache::Cache::get()?.get_cached_song_status(&sid) {
playable_songs.push(sid.clone());
}
}
Ok(playable_songs)
}
}

View File

@ -1,8 +1,11 @@
use uuid::Uuid;
use xmpd_manifest::store::BaseStore;
use xmpd_cache::DlStatus;
use xmpd_manifest::{song::Song, store::BaseStore};
use crate::components::{left_nav::LeftNav, toast::ToastType, CompGetter, CompUi};
use super::SongList;
#[derive(Debug, Clone)]
pub enum SearchType {
Name(String),
@ -53,8 +56,9 @@ impl CompUi for SongListNav {
None => {
songs = state.manifest.store().get_songs().keys().cloned().collect();
}
}
for sid in &songs {
for sid in handle_error_ui!(Self::get_songs_to_download(&songs)) {
if let Some(song) = state.manifest.store().get_song(&sid) {
handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
}
@ -86,6 +90,16 @@ impl SongListNav {
i @ _ => SearchType::Name(i.to_string().to_lowercase())
}
}
fn get_songs_to_download(songs: &Vec<uuid::Uuid>) -> crate::Result<Vec<uuid::Uuid>> {
let mut songs2 = Vec::new();
for sid in songs {
if let None = xmpd_cache::Cache::get()?.get_cached_song_status(&sid) {
songs2.push(sid.clone());
}
}
Ok(songs2)
}
}

View File

@ -1,7 +1,7 @@
use std::{collections::VecDeque, time::SystemTime};
use egui::{epaint::Shadow, load::TexturePoll, Align2, Color32, Frame, Image, Margin, Pos2, Rect, RichText, Rounding, Stroke, Style, Vec2};
use egui::{epaint::Shadow, load::TexturePoll, Align2, Color32, Frame, Image, ImageSource, Margin, Pos2, Rect, RichText, Rounding, Stroke, Style, TextureFilter, TextureOptions, TextureWrapMode, Vec2};
use super::{CompGetter, CompUi};
@ -43,19 +43,24 @@ impl CompUi for Toast {
ToastType::Info => {
color = theme.accent_color;
img = Image::new(crate::data::INFO_ICON)
.max_size(Vec2::new(16.0, 16.0))
.fit_to_exact_size(Vec2::new(16.0, 16.0))
.tint(color);
}
ToastType::Warn => {
color = crate::data::C_WARN;
img = Image::new(crate::data::WARN_ICON)
.max_size(Vec2::new(16.0, 16.0))
.fit_to_exact_size(Vec2::new(16.0, 16.0))
.texture_options(TextureOptions {
magnification: TextureFilter::Linear,
minification: TextureFilter::Linear,
wrap_mode: TextureWrapMode::ClampToEdge,
})
.tint(color);
}
ToastType::Error => {
color = Color32::LIGHT_RED;
img = Image::new(crate::data::ERROR_ICON)
.max_size(Vec2::new(16.0, 16.0))
.fit_to_exact_size(Vec2::new(16.0, 16.0))
.tint(color);
}
}

View File

@ -1,9 +1,19 @@
use std::path::PathBuf;
use egui::TextBuffer;
use xmpd_manifest::store::{JsonStore, TomlStore};
use crate::windows::WindowId;
use super::CompUi;
use super::{CompGetter, CompUi};
#[derive(Debug, Default)]
pub struct TopNav;
pub struct TopNav {
// for dialog
manifest_path: Option<(PathBuf, String)>
}
component_register!(TopNav);
impl CompUi for TopNav {
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
@ -22,6 +32,25 @@ impl CompUi for TopNav {
handle_error_ui!(state.manifest.save());
ui.close_menu();
}
if ui.button("Save As").clicked() {
std::thread::spawn(|| -> crate::Result<()> {
let mut dialog = rfd::FileDialog::new()
.add_filter("Json", &["json"])
.add_filter("Toml", &["toml"])
.set_title("Save Manifest As")
.set_can_create_directories(true)
.set_file_name("manifest");
if let Some(home_dir) = dirs::home_dir() {
dialog = dialog.set_directory(home_dir);
}
if let Some(path) = dialog.save_file() {
if let Some(ext) = path.extension() {
TopNav::get()?.manifest_path = Some((path.clone(), ext.to_string_lossy().to_string()))
}
}
Ok(())
});
}
});
ui.menu_button("Help", |ui| {
if ui.button("Source").clicked() {
@ -39,7 +68,20 @@ impl CompUi for TopNav {
});
});
let mut used = false;
if let Some((path, ext)) = &TopNav::get()?.manifest_path {
match ext.as_str() {
"json" => state.manifest.convert_and_save_to::<JsonStore>(&path)?,
"toml" => state.manifest.convert_and_save_to::<TomlStore>(&path)?,
_ => ()
}
used = true;
}
if used {
TopNav::get()?.manifest_path = None;
}
Ok(())
}
}

View File

@ -36,11 +36,13 @@ pub fn start() -> Result<()> {
pub struct GuiState {
pub manifest: Manifest<JsonStore>,
pub windows: windows::Windows,
pub player: xmpd_player::Player,
}
impl GuiState {
pub fn new() -> Result<Self> {
Ok(Self {
player: xmpd_player::Player::new(),
manifest: Manifest::new(&xmpd_cliargs::CLIARGS.manifest_path())?,
windows: windows::Windows::new(),
})

View File

@ -35,3 +35,14 @@ macro_rules! handle_error_ui {
}
};
}
macro_rules! handle_option {
($reason:expr, $val:expr) => {
if let Some(v) = $val {
v
} else {
handle_error_ui!(Err(anyhow::anyhow!($reason)));
return;
}
};
}

View File

@ -18,8 +18,11 @@ crate-type = ["rlib"]
bench = false
[dependencies]
xmpd-cliargs.path = "../xmpd-cliargs"
xmpd-settings.path = "../xmpd-settings"
anyhow.workspace = true
uuid.workspace = true
serde.workspace = true
serde_json.workspace = true
url.workspace = true
toml.workspace = true

View File

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};
#[cfg(test)]
@ -43,6 +43,29 @@ impl<ST: store::BaseStore + Clone> Manifest<ST> {
self.store_mut().load_from(&p)?;
Ok(())
}
pub fn convert_to<ST2: store::BaseStore>(&self) -> ST2 {
let songs = self.store().get_songs().clone();
let playlists = self.store().get_playlists().clone();
let mut st2 = ST2::empty();
*st2.get_songs_mut() = songs;
*st2.get_playlists_mut() = playlists;
st2
}
pub fn convert_and_save_to<ST2: store::BaseStore>(&self, path: &Path) -> Result<()> {
let st2 = self.convert_to::<ST2>();
std::fs::write(path, st2.to_bytes()?)?;
Ok(())
}
pub fn get_song_as_path(&self, sid: uuid::Uuid) -> Result<PathBuf> {
let ext = &xmpd_settings::Settings::get()?.tooling.song_format;
let mut p = xmpd_cliargs::CLIARGS.cache_path().into_std_path_buf();
p.push("songs");
p.push(sid.to_string());
p.set_extension(ext);
Ok(p)
}
}

View File

@ -1,4 +1,4 @@
use std::{path::PathBuf, str::FromStr};
use std::{fmt::Display, path::PathBuf, str::FromStr};
@ -72,3 +72,14 @@ impl SourceType {
}
}
impl Display for SourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Youtube => write!(f, "YouTube"),
Self::Soundcloud => write!(f, "SoundCloud"),
Self::Spotify => write!(f, "Spotify"),
s => write!(f, "{s:?}"),
}
}
}

View File

@ -5,7 +5,9 @@ use uuid::Uuid;
use crate::{playlist::Playlist, song::Song};
mod json;
mod toml;
pub use json::JsonStore;
pub use toml::TomlStore;
pub trait BaseStore {
fn get_songs(&self) -> &HashMap<Uuid, Song>;

View File

@ -0,0 +1,71 @@
use std::{collections::HashMap, path::PathBuf};
use uuid::Uuid;
use crate::{playlist::Playlist, song::Song};
const DEFAULT_TEXT: &str = r#"{
"songs": {},
"playlists": {}
}"#;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct TomlStore {
#[serde(skip)]
original_path: PathBuf,
songs: HashMap<Uuid, Song>,
playlists: HashMap<Uuid, Playlist>
}
impl super::BaseStore for TomlStore {
fn get_default_file_contents() -> &'static str {
&DEFAULT_TEXT
}
fn get_file_extension() -> &'static str {
"toml"
}
fn empty() -> Self where Self: Sized {
Self {
original_path: PathBuf::new(),
songs: HashMap::default(),
playlists: HashMap::default(),
}
}
fn to_bytes(&self) -> crate::Result<Vec<u8>> {
let s: Vec<u8> = toml::to_string_pretty(self)?.chars().map(|c| c as u8).collect();
Ok(s)
}
fn from_bytes(s: &[u8]) -> crate::Result<Self> where Self: Sized {
let s: Self = toml::from_str(&String::from_utf8(s.to_vec())?)?;
Ok(s)
}
fn get_songs(&self) -> &HashMap<Uuid, Song> {
&self.songs
}
fn get_songs_mut(&mut self) -> &mut HashMap<Uuid, Song> {
&mut self.songs
}
fn get_song(&self, id: &Uuid) -> Option<&Song> {
self.songs.get(id)
}
fn get_song_mut(&mut self, id: &Uuid) -> Option<&mut Song> {
self.songs.get_mut(id)
}
fn get_playlists(&self) -> &HashMap<Uuid, Playlist> {
&self.playlists
}
fn get_playlists_mut(&mut self) -> &mut HashMap<Uuid, Playlist> {
&mut self.playlists
}
fn get_playlist(&self, id: &Uuid) -> Option<&Playlist> {
self.playlists.get(id)
}
fn get_playlist_mut(&mut self, id: &Uuid) -> Option<&mut Playlist> {
self.playlists.get_mut(id)
}
fn save_original_path(&mut self, p: &std::path::Path) {
self.original_path = p.to_path_buf();
}
fn get_original_path(&self) -> &std::path::Path {
&self.original_path
}
}

View File

@ -7,4 +7,7 @@ license.workspace = true
authors.workspace = true
[dependencies]
rodio = { version = "0.20.1", features = ["symphonia-all"] }
rodio.workspace = true
lazy_static.workspace = true
anyhow.workspace = true
log.workspace = true

View File

@ -0,0 +1,101 @@
use std::{path::Path, time::Duration};
use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source};
type Result<T> = anyhow::Result<T>;
pub struct Player {
_stream: OutputStream,
_stream_handle: OutputStreamHandle,
sink: Sink,
source_len: Duration,
tested_for_song_end: bool,
}
impl Player {
pub fn new() -> Self {
let (_stream, _stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&_stream_handle).unwrap();
sink.pause();
Self {
_stream,
_stream_handle,
sink,
source_len: Default::default(),
tested_for_song_end: true,
}
}
pub fn play_song(&mut self, path: &Path) -> Result<()> {
let file = std::io::BufReader::new(std::fs::File::open(path)?);
let source = Decoder::new(file)?;
self.tested_for_song_end = false;
self.source_len = source.total_duration().unwrap();
self.sink.clear();
self.sink.append(source);
self.sink.play();
Ok(())
}
pub fn get_played_f(&self) -> f64 {
let source_len = self.source_len.as_millis();
let curr_len = self.sink.get_pos().as_millis();
if source_len == 0 || curr_len == 0 {
return 0.0;
}
curr_len as f64 / source_len as f64
}
pub fn get_played_len(&self) -> Duration {
self.sink.get_pos()
}
pub fn get_ms_left(&self) -> u64 {
let source_len = self.source_len.as_millis();
let curr_len = self.sink.get_pos().as_millis();
if source_len < curr_len {
return 0;
}
(source_len - curr_len) as u64
}
pub fn just_stopped(&mut self) -> bool {
if self.sink.len() == 0 && !self.tested_for_song_end {
self.tested_for_song_end = true;
true
} else {
false
}
}
pub fn play(&self) {
self.sink.play()
}
pub fn pause(&self) {
self.sink.pause()
}
pub fn seek_to_f(&self, f: f64) -> Result<()> {
let dur = self.source_len.as_millis() as f64 * f.clamp(0.0, 1.0);
self.seek(Duration::from_millis(dur as u64))?;
Ok(())
}
pub fn seek(&self, d: Duration) -> Result<()> {
if let Err(e) = self.sink.try_seek(d) {
anyhow::bail!("{e:?}");
}
Ok(())
}
pub fn is_paused(&self) -> bool {
self.sink.is_paused()
}
pub fn set_volume(&self, vol: f64) {
// clamped for the safety of my, and your ears
self.sink.set_volume(vol.clamp(0.0, 1.0) as f32);
}
}

View File

@ -12,4 +12,4 @@ camino.workspace = true
egui.workspace = true
lazy_static.workspace = true
serde.workspace = true
toml = "0.8.19"
toml.workspace = true