mirror of
https://github.com/figsoda/mmtc
synced 2024-11-22 07:03:05 +00:00
initial commit
This commit is contained in:
parent
63e2fd5e96
commit
446a2edaa2
5 changed files with 846 additions and 2 deletions
453
Cargo.lock
generated
453
Cargo.lock
generated
|
@ -1,5 +1,458 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.17.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f4919d60f26ae233e14233cc39746c8c8bb8cd7b05840ace83604917b51b6c7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.10.2",
|
||||
"signal-hook",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cef9149b29071d44c9fb98fd9c27fcf74405bbdb761889ad6a03f36be93b0b15"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.11.0",
|
||||
"signal-hook",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "expand"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3fc822ad358d15f3cf1127d12f26a0cf0b0dcb26d9f1e33505d80689b7b3f1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8f1c83949125de4a582aa2da15ae6324d91cf6a58a70ea407643941ff98f558"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mmtc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossterm 0.18.1",
|
||||
"expand",
|
||||
"tokio",
|
||||
"tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
|
||||
dependencies = [
|
||||
"lock_api 0.3.4",
|
||||
"parking_lot_core 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api 0.4.1",
|
||||
"parking_lot_core 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cloudabi 0.0.3",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cloudabi 0.1.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71f1b20504fd0aa9dab3ae17e8c4dd9431e5e08fd6921689f9745a4004883a17"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"tokio-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21d30fdbb5dc2d8f91049691aa1a9d4d4ae422a21c334ce8936e5886d30c5c45"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tui"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2eaeee894a1e9b90f80aa466fe59154fdb471980b5e104d8836fcea309ae17e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm 0.17.7",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
|
|
@ -12,6 +12,14 @@ repository = "https://github.com/figsoda/mmtc"
|
|||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.33"
|
||||
crossterm = "0.18.1"
|
||||
expand = "0.1.2"
|
||||
tui = { version = "0.12.0", default-features = false, features = ["crossterm"] }
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "0.3.2"
|
||||
features = ["io-util", "macros", "net", "rt-multi-thread", "sync", "time"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
9
src/fail.rs
Normal file
9
src/fail.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
macro_rules! fail {
|
||||
($n:ident $($a:ident)+ = $m:literal) => {
|
||||
pub fn $n($( $a: impl std::fmt::Display ),+) -> impl FnOnce() -> String {
|
||||
move || format!($m, $( $a ),+)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fail!(connect addr = "Failed to connect to {}");
|
201
src/main.rs
201
src/main.rs
|
@ -1,3 +1,200 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
#![feature(async_closure)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod fail;
|
||||
mod mpd;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use tokio::{
|
||||
sync::Mutex,
|
||||
time::{sleep_until, Duration, Instant},
|
||||
};
|
||||
use tui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::{List, ListItem, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use std::{
|
||||
io::{stdout, Write},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
process::exit,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::mpd::{Song, Status, Track};
|
||||
|
||||
fn cleanup() -> Result<()> {
|
||||
disable_raw_mode().context("Failed to clean up terminal")?;
|
||||
execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)
|
||||
.context("Failed to clean up terminal")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn die<T>(e: impl std::fmt::Display) -> T {
|
||||
if let Err(e) = cleanup() {
|
||||
eprintln!("{}", e);
|
||||
};
|
||||
eprintln!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let res = run().await;
|
||||
cleanup()?;
|
||||
res
|
||||
}
|
||||
|
||||
async fn run() -> Result<()> {
|
||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6600);
|
||||
|
||||
let queue_layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(vec![
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
]);
|
||||
|
||||
let mut idle_cl = mpd::init(addr).await.with_context(fail::connect(addr))?;
|
||||
let mut status_cl = mpd::init(addr).await.with_context(fail::connect(addr))?;
|
||||
|
||||
let queue = Arc::new(Mutex::new(mpd::queue(&mut idle_cl).await?));
|
||||
let queue1 = Arc::clone(&queue);
|
||||
let status = Arc::new(Mutex::new(mpd::status(&mut status_cl).await?));
|
||||
let status1 = Arc::clone(&status);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
mpd::idle_playlist(&mut idle_cl)
|
||||
.await
|
||||
.context("Failed to idle")
|
||||
.unwrap_or_else(die);
|
||||
*queue1.lock().await = mpd::queue(&mut idle_cl)
|
||||
.await
|
||||
.context("Failed to query queue information")
|
||||
.unwrap_or_else(die);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let deadline = Instant::now() + Duration::from_millis(250);
|
||||
*status1.lock().await = mpd::status(&mut status_cl)
|
||||
.await
|
||||
.context("Failed to query status")
|
||||
.unwrap_or_else(die);
|
||||
sleep_until(deadline).await;
|
||||
}
|
||||
});
|
||||
|
||||
let mut stdout = stdout();
|
||||
enable_raw_mode().context("Failed to initialize terminal")?;
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)
|
||||
.context("Failed to initialize terminal")?;
|
||||
let mut term =
|
||||
Terminal::new(CrosstermBackend::new(stdout)).context("Failed to initialize terminal")?;
|
||||
|
||||
loop {
|
||||
let deadline = Instant::now() + Duration::from_secs_f32(1.0 / 30.0);
|
||||
|
||||
let queue = &*queue.lock().await;
|
||||
let status = (*status.lock().await).clone();
|
||||
|
||||
term.draw(|frame| {
|
||||
let len = queue.len();
|
||||
let mut titles = Vec::with_capacity(len);
|
||||
let mut artists = Vec::with_capacity(len);
|
||||
let mut albums = Vec::with_capacity(len);
|
||||
|
||||
for Track {
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
..
|
||||
} in queue
|
||||
{
|
||||
titles.push(ListItem::new(title.clone().unwrap_or_default()));
|
||||
artists.push(ListItem::new(artist.clone().unwrap_or_default()));
|
||||
albums.push(ListItem::new(album.clone().unwrap_or_default()));
|
||||
}
|
||||
|
||||
let Rect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} = frame.size();
|
||||
let chunks = queue_layout.split(Rect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height: y + height - 1,
|
||||
});
|
||||
frame.render_widget(List::new(titles), chunks[0]);
|
||||
frame.render_widget(List::new(artists), chunks[1]);
|
||||
frame.render_widget(List::new(albums), chunks[2]);
|
||||
|
||||
if let Status {
|
||||
song: Some(Song { pos, elapsed }),
|
||||
..
|
||||
} = status
|
||||
{
|
||||
if let Some(Track {
|
||||
file,
|
||||
artist,
|
||||
album,
|
||||
title,
|
||||
time,
|
||||
}) = queue.get(pos)
|
||||
{
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(
|
||||
"[{:02}:{:02}/{:02}:{:02}] {}",
|
||||
elapsed / 60,
|
||||
elapsed % 60,
|
||||
time / 60,
|
||||
time % 60,
|
||||
match (title, artist, album) {
|
||||
(Some(title), Some(artist), Some(album)) =>
|
||||
format!("{} - {} - {}", title, artist, album),
|
||||
(Some(title), Some(artist), _) => format!("{} - {}", title, artist),
|
||||
(Some(title), ..) => title.clone(),
|
||||
_ => file.clone(),
|
||||
}
|
||||
)),
|
||||
Rect {
|
||||
x,
|
||||
y: y + height - 1,
|
||||
width,
|
||||
height: 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.context("Failed to draw to terminal")?;
|
||||
|
||||
while event::poll(Duration::new(0, 0)).context("Failed to poll events")? {
|
||||
match event::read().context("Failed to read events")? {
|
||||
Event::Key(KeyEvent { code, .. }) => match code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => {
|
||||
cleanup()?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
sleep_until(deadline).await;
|
||||
}
|
||||
}
|
||||
|
|
177
src/mpd.rs
Normal file
177
src/mpd.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use expand::expand;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::fail;
|
||||
|
||||
pub type Client = BufReader<TcpStream>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Status {
|
||||
pub repeat: bool,
|
||||
pub random: bool,
|
||||
pub single: Option<bool>, // None: oneshot
|
||||
pub consume: bool,
|
||||
pub song: Option<Song>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Song {
|
||||
pub pos: usize,
|
||||
pub elapsed: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Track {
|
||||
pub file: String,
|
||||
pub artist: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub time: u16,
|
||||
}
|
||||
|
||||
pub async fn init(addr: SocketAddr) -> Result<Client> {
|
||||
let mut cl = BufReader::new(
|
||||
TcpStream::connect(&addr)
|
||||
.await
|
||||
.with_context(fail::connect(addr))?,
|
||||
);
|
||||
|
||||
let mut buf = [0; 7];
|
||||
cl.read(&mut buf).await?;
|
||||
if &buf != b"OK MPD " {
|
||||
bail!("server did not greet with a success");
|
||||
}
|
||||
cl.read_line(&mut String::new()).await?;
|
||||
|
||||
Ok(cl)
|
||||
}
|
||||
|
||||
pub async fn idle_playlist(cl: &mut Client) -> Result<()> {
|
||||
cl.write_all(b"idle playlist\n").await?;
|
||||
let mut lines = cl.lines();
|
||||
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
match line.as_bytes() {
|
||||
b"OK" => break,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn queue(cl: &mut Client) -> Result<Vec<Track>> {
|
||||
let mut first = true;
|
||||
let mut tracks = Vec::new();
|
||||
|
||||
let mut file = None;
|
||||
let mut artist = None;
|
||||
let mut album = None;
|
||||
let mut title = None;
|
||||
let mut time = None;
|
||||
|
||||
cl.write_all(b"playlistinfo\n").await?;
|
||||
let mut lines = cl.lines();
|
||||
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
match line.as_bytes() {
|
||||
b"OK" => break,
|
||||
expand!([@b"file: ", xs @ ..]) => {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
if let (Some(file), Some(time)) = (file, time) {
|
||||
tracks.push(Track {
|
||||
file,
|
||||
artist,
|
||||
album,
|
||||
title,
|
||||
time,
|
||||
});
|
||||
} else {
|
||||
bail!("incomplete playlist response");
|
||||
}
|
||||
}
|
||||
|
||||
file = Some(String::from_utf8_lossy(xs).into());
|
||||
artist = None;
|
||||
album = None;
|
||||
title = None;
|
||||
time = None;
|
||||
}
|
||||
expand!([@b"Artist: ", xs @ ..]) => {
|
||||
artist = Some(String::from_utf8_lossy(xs).into());
|
||||
}
|
||||
expand!([@b"Album: ", xs @ ..]) => {
|
||||
album = Some(String::from_utf8_lossy(xs).into());
|
||||
}
|
||||
expand!([@b"Title: ", xs @ ..]) => {
|
||||
title = Some(String::from_utf8_lossy(xs).into());
|
||||
}
|
||||
expand!([@b"Time: ", xs @ ..]) => {
|
||||
time = Some(String::from_utf8_lossy(xs).parse()?);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
pub async fn status(cl: &mut Client) -> Result<Status> {
|
||||
let mut repeat = None;
|
||||
let mut random = None;
|
||||
let mut single = None;
|
||||
let mut consume = None;
|
||||
let mut pos = None;
|
||||
let mut elapsed = None;
|
||||
|
||||
cl.write_all(b"status\n").await?;
|
||||
let mut lines = cl.lines();
|
||||
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
match line.as_bytes() {
|
||||
b"OK" => break,
|
||||
b"repeat: 0" => repeat = Some(false),
|
||||
b"repeat: 1" => repeat = Some(true),
|
||||
b"random: 0" => random = Some(false),
|
||||
b"random: 1" => random = Some(true),
|
||||
b"single: 0" => single = Some(Some(false)),
|
||||
b"single: 1" => single = Some(Some(true)),
|
||||
b"single: oneshot" => single = Some(None),
|
||||
b"consume: 0" => consume = Some(false),
|
||||
b"consume: 1" => consume = Some(true),
|
||||
expand!([@b"song: ", xs @ ..]) => {
|
||||
pos = Some(String::from_utf8_lossy(xs).parse()?);
|
||||
}
|
||||
expand!([@b"elapsed: ", xs @ ..]) => {
|
||||
elapsed = Some(String::from_utf8_lossy(xs).parse::<f32>()?.round() as u16);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(repeat), Some(random), Some(single), Some(consume)) =
|
||||
(repeat, random, single, consume)
|
||||
{
|
||||
Ok(Status {
|
||||
repeat,
|
||||
random,
|
||||
single,
|
||||
consume,
|
||||
song: if let (Some(pos), Some(elapsed)) = (pos, elapsed) {
|
||||
Some(Song { pos, elapsed })
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
} else {
|
||||
bail!("incomplete status response");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue