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, 'b>(&'a self, url: &'b 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
83            .access_token
84            .as_ref()
85            .map(|v| v.expose_secret().as_str())
86    }
87
88    /// Connect to the device.
89    #[tracing::instrument(level = "debug", skip(self), fields(device_name = self.inner.name))]
90    pub async fn connect(&self) -> Result<DeviceConnection> {
91        if !self.is_server() && !self.is_controller() {
92            error!("Device must provide Server or Controller");
93            return Err(Error::DeviceConnectionNotSupported);
94        }
95
96        if !self.inner.connections.is_empty() {
97            let mut client = self.client.clone();
98            if let Some(access_token) = self.inner.access_token.as_ref() {
99                let access_token = access_token.expose_secret();
100                if access_token != client.x_plex_token() {
101                    debug!("Connecting using access token for the device");
102                    client = client.set_x_plex_token(access_token.to_owned());
103                }
104            }
105
106            if self.is_server() {
107                trace!(
108                    "Connecting to server {id}",
109                    id = self.inner.client_identifier,
110                );
111                let futures = self
112                    .inner
113                    .connections
114                    .iter()
115                    .map(|connection| {
116                        trace!("Trying {address}", address = connection.uri);
117                        crate::Server::new(&connection.uri, client.clone()).boxed()
118                    })
119                    .collect::<Vec<_>>();
120
121                let (server, _) = select_ok(futures).await?;
122                trace!("Connected via {address}", address = server.client().api_url);
123                Ok(DeviceConnection::Server(Box::new(server)))
124            } else {
125                trace!(
126                    "Connecting to player {id}",
127                    id = self.inner.client_identifier,
128                );
129                client.x_plex_target_client_identifier = self.inner.client_identifier.clone();
130
131                let futures = self
132                    .inner
133                    .connections
134                    .iter()
135                    .map(|connection| {
136                        trace!("Trying {address}", address = connection.uri);
137                        crate::Player::new(&connection.uri, client.clone()).boxed()
138                    })
139                    .collect::<Vec<_>>();
140
141                let (player, _) = select_ok(futures).await?;
142                trace!("Connected via {address}", address = player.client().api_url);
143                Ok(DeviceConnection::Player(Box::new(player)))
144            }
145        } else {
146            Err(Error::DeviceConnectionsIsEmpty)
147        }
148    }
149
150    /// Establish a connection to the device using server as a proxy.
151    #[tracing::instrument(level = "debug", skip(self, server), fields(device_name = self.inner.name))]
152    pub async fn connect_via(&self, server: &Server) -> Result<DeviceConnection> {
153        if !self.is_controller() {
154            error!("Only devices providing Controller can be connected via proxy");
155            return Err(Error::DeviceConnectionNotSupported);
156        }
157
158        let futures = self
159            .inner
160            .connections
161            .iter()
162            .map(|connection| {
163                trace!("Trying {address}", address = connection.uri);
164                crate::Player::via_proxy(&connection.uri, server).boxed()
165            })
166            .collect::<Vec<_>>();
167
168        let (player, _) = select_ok(futures).await?;
169        Ok(DeviceConnection::Player(Box::new(player)))
170    }
171}
172
173#[derive(Debug, Clone)]
174pub enum DeviceConnection {
175    Server(Box<Server>),
176    Player(Box<Player>),
177}