1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
use crate::{Player, Result};
use dbus::{
    arg::PropMap,
    nonblock::{Proxy, SyncConnection},
};
use dbus_tokio::connection;
use std::{sync::Arc, time::Duration};

const MPRIS_PREFIX: &str = "org.mpris.MediaPlayer2.";

pub async fn validate(player_name: &str, conn: &SyncConnection) -> Result<bool> {
    Ok(get_all_names(&conn)
        .await?
        .contains(&player_name.to_string()))
}

async fn get_all_names(conn: &SyncConnection) -> Result<Vec<String>> {
    let proxy = Proxy::new("org.freedesktop.DBus", "/", Duration::from_secs(1), conn);
    let (services,): (Vec<String>,) = proxy
        .method_call("org.freedesktop.DBus", "ListNames", ())
        .await?;

    let active_players: Vec<String> = services
        .into_iter()
        .filter_map(|name| {
            name.strip_prefix(&MPRIS_PREFIX)
                .map_or_else(|| None, |s| Some(s.to_string()))
        })
        .collect();
    Ok(active_players)
}

/// Establishes a connection to the `DBus`.
/// Use this to create a connection to pass into `Player`.
pub fn get_connection() -> Arc<SyncConnection> {
    let (resource, conn) = connection::new_session_sync().unwrap();

    tokio::spawn(async {
        let err = resource.await;
        panic!("Lost connection to D-Bus: {}", err);
    });

    conn
}

/// Gets a `Vec` of `Player`s from all active
/// MPRIS players found on the `DBus`.
///
/// # Errors
/// May return an `Err` variant if there was a failure in
/// getting a list of names from `DBus`.
pub async fn get_all_players(conn: &SyncConnection) -> Result<Vec<Player<'_>>> {
    let mut players: Vec<Player<'_>> = Vec::new();

    for name in get_all_names(&conn).await? {
        match Player::try_new(name, &conn).await {
            Ok(player) => players.push(player),
            Err(_) => continue,
        };
    }

    Ok(players)
}

/// Gets a value from a HashMap, and casts it to the
/// type provided.
///
/// # Example
/// ```
/// let metadata = player.get_metadata().await?;
/// let title = match prop_cast::<String>(&metadata, "xesam:title") {
///     Some(t) => t.to_string(),
///     None => "Unknown title".to_string()
/// };
/// ```
pub fn prop_cast<'a, T>(map: &'a PropMap, key: &str) -> Option<&'a T>
where
    T: 'static,
{
    map.get(key).and_then(|v| v.0.as_any().downcast_ref())
}