plex_api/myplex/
device.rs1use 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 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 pub fn is_server(&self) -> bool {
71 self.provides(Feature::Server)
72 }
73
74 pub fn is_controller(&self) -> bool {
76 self.provides(Feature::Controller)
77 }
78
79 pub fn access_token(&self) -> Option<&str> {
82 self.inner.access_token.as_ref().map(|v| v.expose_secret())
83 }
84
85 #[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 #[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}