plex_api/myplex/
device.rs

1use crate::{
2    http_client::HttpClient,
3    media_container::devices::{DevicesMediaContainer, Feature},
4    url::{MYPLEX_DEVICES, MYPLEX_RESOURCES},
5    Error, Player, Result, Server,
6};
7use futures::{future::select_ok, FutureExt};
8use secrecy::ExposeSecret;
9use tracing::{debug, error, trace};
10
11pub struct DeviceManager {
12    pub client: HttpClient,
13}
14
15impl DeviceManager {
16    pub fn new(client: HttpClient) -> Self {
17        Self { client }
18    }
19
20    async fn devices_internal<'a>(&'a self, url: &str) -> Result<Vec<Device<'a>>> {
21        let container: DevicesMediaContainer = self
22            .client
23            .get(url)
24            .header("Accept", "application/xml")
25            .xml()
26            .await?;
27
28        Ok(container
29            .devices
30            .into_iter()
31            .map(|device| Device {
32                inner: device,
33                client: &self.client,
34            })
35            .collect())
36    }
37
38    #[tracing::instrument(level = "debug", skip(self))]
39    pub async fn devices(&self) -> Result<Vec<Device<'_>>> {
40        self.devices_internal(MYPLEX_DEVICES).await
41    }
42
43    #[tracing::instrument(level = "debug", skip(self))]
44    pub async fn resources(&self) -> Result<Vec<Device<'_>>> {
45        self.devices_internal(MYPLEX_RESOURCES).await
46    }
47}
48
49#[derive(Debug, Clone)]
50pub struct Device<'a> {
51    inner: crate::media_container::devices::Device,
52    client: &'a HttpClient,
53}
54
55impl Device<'_> {
56    /// Returns the list of features supported by the device.
57    pub fn provides(&self, feature: Feature) -> bool {
58        self.inner.provides.contains(&feature)
59    }
60
61    pub fn identifier(&self) -> &str {
62        &self.inner.client_identifier
63    }
64
65    pub fn name(&self) -> &str {
66        &self.inner.name
67    }
68
69    /// Syntax sugar method for checking if the current device provides [`Feature::Server`]
70    pub fn is_server(&self) -> bool {
71        self.provides(Feature::Server)
72    }
73
74    /// Syntax sugar method for checking if the current device provides [`Feature::Controller`]
75    pub fn is_controller(&self) -> bool {
76        self.provides(Feature::Controller)
77    }
78
79    /// Returns the authentication token that should be used when connecting to the device.
80    /// If it's a shared device, the main authentication token will no be accepted.
81    pub fn access_token(&self) -> Option<&str> {
82        self.inner.access_token.as_ref().map(|v| v.expose_secret())
83    }
84
85    /// Connect to the device.
86    #[tracing::instrument(level = "debug", skip(self), fields(device_name = self.inner.name))]
87    pub async fn connect(&self) -> Result<DeviceConnection> {
88        if !self.is_server() && !self.is_controller() {
89            error!("Device must provide Server or Controller");
90            return Err(Error::DeviceConnectionNotSupported);
91        }
92
93        if !self.inner.connections.is_empty() {
94            let mut client = self.client.clone();
95            if let Some(access_token) = self.inner.access_token.as_ref() {
96                let access_token = access_token.expose_secret();
97                if access_token != client.x_plex_token() {
98                    debug!("Connecting using access token for the device");
99                    client = client.set_x_plex_token(access_token.to_owned());
100                }
101            }
102
103            if self.is_server() {
104                trace!(
105                    "Connecting to server {id}",
106                    id = self.inner.client_identifier,
107                );
108                let futures = self
109                    .inner
110                    .connections
111                    .iter()
112                    .map(|connection| {
113                        trace!("Trying {address}", address = connection.uri);
114                        crate::Server::new(&connection.uri, client.clone()).boxed()
115                    })
116                    .collect::<Vec<_>>();
117
118                let (server, _) = select_ok(futures).await?;
119                trace!("Connected via {address}", address = server.client().api_url);
120                Ok(DeviceConnection::Server(Box::new(server)))
121            } else {
122                trace!(
123                    "Connecting to player {id}",
124                    id = self.inner.client_identifier,
125                );
126                client
127                    .x_plex_target_client_identifier
128                    .clone_from(&self.inner.client_identifier);
129
130                let futures = self
131                    .inner
132                    .connections
133                    .iter()
134                    .map(|connection| {
135                        trace!("Trying {address}", address = connection.uri);
136                        crate::Player::new(&connection.uri, client.clone()).boxed()
137                    })
138                    .collect::<Vec<_>>();
139
140                let (player, _) = select_ok(futures).await?;
141                trace!("Connected via {address}", address = player.client().api_url);
142                Ok(DeviceConnection::Player(Box::new(player)))
143            }
144        } else {
145            Err(Error::DeviceConnectionsIsEmpty)
146        }
147    }
148
149    /// Establish a connection to the device using server as a proxy.
150    #[tracing::instrument(level = "debug", skip(self, server), fields(device_name = self.inner.name))]
151    pub async fn connect_via(&self, server: &Server) -> Result<DeviceConnection> {
152        if !self.is_controller() {
153            error!("Only devices providing Controller can be connected via proxy");
154            return Err(Error::DeviceConnectionNotSupported);
155        }
156
157        let futures = self
158            .inner
159            .connections
160            .iter()
161            .map(|connection| {
162                trace!("Trying {address}", address = connection.uri);
163                crate::Player::via_proxy(&connection.uri, server).boxed()
164            })
165            .collect::<Vec<_>>();
166
167        let (player, _) = select_ok(futures).await?;
168        Ok(DeviceConnection::Player(Box::new(player)))
169    }
170}
171
172#[derive(Debug, Clone)]
173pub enum DeviceConnection {
174    Server(Box<Server>),
175    Player(Box<Player>),
176}