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, '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 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
83 .access_token
84 .as_ref()
85 .map(|v| v.expose_secret().as_str())
86 }
87
88 #[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 #[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}