use crate::Error;
use crate::frame_tv_api::FrameTvApi;
use crate::frame_tv_transport::FrameTvTransport;
use crate::protocol::art_request::{ArtRequest, ArtRequestParams, ContentIdEntry};
use crate::protocol::art_response::{ArtResponse, ContentListItem, FilterItem, MatteTypeItem};
use crate::transport::persistent_ws_transport::PersistentWsTransport;
use crate::transport::rest_client::RestClient;
use crate::transport::ws_transport::WsTransport;
use crate::types::{
ArtImage, ArtImageThumbnail, DeviceInfo, DisplayBrightness, DisplayColorTemp, DisplayRotation,
Matte, MotionSensitivity, MotionTimer, PhotoFilter, SlideshowCategory, SlideshowConfig,
UploadedArtImage,
};
pub(crate) enum Transport {
OneShot(WsTransport),
Persistent(Box<PersistentWsTransport>),
}
impl FrameTvTransport for Transport {
async fn send_art_request(
&self,
request_json: &str,
wait_for_event: Option<&str>,
) -> Result<ArtResponse, Error> {
match self {
Self::OneShot(t) => t.send_art_request(request_json, wait_for_event).await,
Self::Persistent(t) => t.send_art_request(request_json, wait_for_event).await,
}
}
async fn send_remote_key(&self, key_code: &str) -> Result<(), Error> {
match self {
Self::OneShot(t) => t.send_remote_key(key_code).await,
Self::Persistent(t) => t.send_remote_key(key_code).await,
}
}
async fn upload_image(
&self,
image_data: &[u8],
file_type: &str,
matte_id: Option<&str>,
) -> Result<ArtResponse, Error> {
match self {
Self::OneShot(t) => t.upload_image(image_data, file_type, matte_id).await,
Self::Persistent(t) => t.upload_image(image_data, file_type, matte_id).await,
}
}
async fn download_thumbnail(&self, content_id: &str) -> Result<Vec<u8>, Error> {
match self {
Self::OneShot(t) => t.download_thumbnail(content_id).await,
Self::Persistent(t) => t.download_thumbnail(content_id).await,
}
}
async fn tv_display_size(&self) -> Result<(u32, u32), Error> {
match self {
Self::OneShot(t) => t.tv_display_size().await,
Self::Persistent(t) => t.tv_display_size().await,
}
}
}
impl Transport {
pub(crate) fn is_persistent(&self) -> bool {
matches!(self, Self::Persistent(_))
}
pub(crate) async fn reconnect(&self) -> Result<(), Error> {
match self {
Self::Persistent(t) => t.reconnect().await,
Self::OneShot(_) => Err(Error::ConnectionFailed(
"reconnect() is only supported on persistent connections".into(),
)),
}
}
pub(crate) async fn is_connected(&self) -> bool {
match self {
Self::Persistent(t) => t.is_connected().await,
Self::OneShot(_) => false,
}
}
pub(crate) async fn send_keepalive(&self) {
if let Self::Persistent(t) = self {
t.send_keepalive().await;
}
}
}
pub(crate) struct FrameTvImpl<T: FrameTvTransport> {
pub(crate) transport: T,
pub(crate) rest_client: RestClient,
}
impl<T: FrameTvTransport> FrameTvImpl<T> {
async fn art_request(
&self,
request_name: &'static str,
params: ArtRequestParams,
) -> Result<crate::protocol::art_response::ArtResponse, Error> {
let req = ArtRequest::new(request_name, params);
let json = req.to_json()?;
self.transport.send_art_request(&json, None).await
}
async fn art_request_wait(
&self,
request_name: &'static str,
params: ArtRequestParams,
wait_for: &str,
) -> Result<crate::protocol::art_response::ArtResponse, Error> {
let req = ArtRequest::new(request_name, params);
let json = req.to_json()?;
self.transport.send_art_request(&json, Some(wait_for)).await
}
}
impl<T: FrameTvTransport> FrameTvApi for FrameTvImpl<T> {
async fn get_uploaded_art_images(&self) -> Result<Vec<UploadedArtImage>, Error> {
let resp = self
.art_request(
"get_content_list",
ArtRequestParams::GetContentList {
category: Some("MY-C0002".to_string()),
},
)
.await?;
let content_list_str = resp.content_list.unwrap_or_else(|| "[]".to_string());
let items: Vec<ContentListItem> = serde_json::from_str(&content_list_str)?;
Ok(items
.into_iter()
.map(|item| UploadedArtImage {
content_id: item.content_id,
category_id: item.category_id.unwrap_or_default(),
width: item.width.unwrap_or(0),
height: item.height.unwrap_or(0),
matte_id: item.matte_id,
portrait_matte_id: item.portrait_matte_id,
})
.collect())
}
async fn get_art_image_info(&self, content_id: &str) -> Result<UploadedArtImage, Error> {
let images = self.get_uploaded_art_images().await?;
images
.into_iter()
.find(|img| img.content_id == content_id)
.ok_or_else(|| Error::TvError {
request: "get_art_image_info".to_string(),
code: "not_found".to_string(),
})
}
async fn get_selected_art_image(&self) -> Result<UploadedArtImage, Error> {
let resp = self
.art_request("get_current_artwork", ArtRequestParams::Empty {})
.await?;
let content_id =
resp.content_id
.or(resp.current_content_id)
.ok_or_else(|| Error::TvError {
request: "get_current_artwork".to_string(),
code: "no_content_id".to_string(),
})?;
self.get_art_image_info(&content_id).await
}
async fn select_art_image_by_id(&self, content_id: &str) -> Result<(), Error> {
self.art_request(
"select_image",
ArtRequestParams::SelectImage {
content_id: content_id.to_string(),
category_id: None,
show: true,
},
)
.await?;
Ok(())
}
async fn upload_art_image(&self, image: ArtImage) -> Result<UploadedArtImage, Error> {
let file_type = image.file_type.as_str();
let resp = self
.transport
.upload_image(&image.data, file_type, None)
.await?;
let content_id = resp.content_id.ok_or_else(|| Error::TvError {
request: "send_image".to_string(),
code: "no_content_id".to_string(),
})?;
Ok(UploadedArtImage {
content_id,
category_id: "MY-C0002".to_string(),
width: image.width,
height: image.height,
matte_id: None,
portrait_matte_id: None,
})
}
async fn upload_and_select_art_image(
&self,
image: ArtImage,
) -> Result<UploadedArtImage, Error> {
let uploaded = self.upload_art_image(image).await?;
self.select_art_image_by_id(uploaded.id()).await?;
Ok(uploaded)
}
async fn delete_uploaded_art_images(&self, content_ids: &[&str]) -> Result<(), Error> {
let list: Vec<ContentIdEntry> = content_ids
.iter()
.map(|id| ContentIdEntry {
content_id: id.to_string(),
})
.collect();
self.art_request(
"delete_image_list",
ArtRequestParams::DeleteImageList {
content_id_list: list,
},
)
.await?;
Ok(())
}
async fn get_art_image_thumbnail(&self, content_id: &str) -> Result<ArtImageThumbnail, Error> {
let data = self.transport.download_thumbnail(content_id).await?;
Ok(ArtImageThumbnail { data })
}
async fn set_art_image_favorite(&self, content_id: &str, favorite: bool) -> Result<(), Error> {
self.art_request_wait(
"change_favorite",
ArtRequestParams::ChangeFavorite {
content_id: content_id.to_string(),
status: if favorite { "on" } else { "off" }.to_string(),
},
"favorite_changed",
)
.await?;
Ok(())
}
async fn get_available_mattes(&self) -> Result<Vec<Matte>, Error> {
let resp = self
.art_request("get_matte_list", ArtRequestParams::Empty {})
.await?;
let matte_list_str = resp.matte_type_list.unwrap_or_else(|| "[]".to_string());
let items: Vec<MatteTypeItem> = serde_json::from_str(&matte_list_str)?;
Ok(items
.into_iter()
.map(|item| Matte {
matte_id: item.matte_id,
matte_type: item.matte_type.unwrap_or_default(),
})
.collect())
}
async fn set_art_image_matte(
&self,
content_id: &str,
matte_id: &str,
portrait_matte_id: Option<&str>,
) -> Result<(), Error> {
self.art_request(
"change_matte",
ArtRequestParams::ChangeMatte {
content_id: content_id.to_string(),
matte_id: matte_id.to_string(),
portrait_matte_id: portrait_matte_id.map(String::from),
},
)
.await?;
Ok(())
}
async fn get_available_photo_filters(&self) -> Result<Vec<PhotoFilter>, Error> {
let resp = self
.art_request("get_photo_filter_list", ArtRequestParams::Empty {})
.await?;
let filter_list_str = resp.filter_list.unwrap_or_else(|| "[]".to_string());
let items: Vec<FilterItem> = serde_json::from_str(&filter_list_str)?;
Ok(items
.into_iter()
.map(|item| PhotoFilter {
filter_id: item.filter_id,
filter_name: item.filter_name.unwrap_or_default(),
})
.collect())
}
async fn set_art_image_photo_filter(
&self,
content_id: &str,
filter_id: &str,
) -> Result<(), Error> {
self.art_request(
"set_photo_filter",
ArtRequestParams::SetPhotoFilter {
content_id: content_id.to_string(),
filter_id: filter_id.to_string(),
},
)
.await?;
Ok(())
}
async fn enable_artmode(&self) -> Result<(), Error> {
self.art_request(
"set_artmode_status",
ArtRequestParams::SetValue {
value: "on".to_string(),
},
)
.await?;
Ok(())
}
async fn disable_artmode(&self) -> Result<(), Error> {
self.art_request(
"set_artmode_status",
ArtRequestParams::SetValue {
value: "off".to_string(),
},
)
.await?;
Ok(())
}
async fn is_artmode_enabled(&self) -> Result<bool, Error> {
let resp = self
.art_request("get_artmode_status", ArtRequestParams::Empty {})
.await?;
Ok(resp.value.as_deref() == Some("on"))
}
async fn enable_slideshow(&self, config: Option<SlideshowConfig>) -> Result<(), Error> {
let config = config.unwrap_or_default();
let minutes = config.art_image_duration.as_secs() / 60;
let value = if minutes == 0 {
"5".to_string()
} else {
minutes.to_string()
};
let slideshow_type = if config.shuffle {
"shuffleslideshow"
} else {
"slideshow"
};
self.art_request(
"set_slideshow_status",
ArtRequestParams::SetSlideshowStatus {
value,
category_id: Some(config.category.as_str().to_string()),
slideshow_type: Some(slideshow_type.to_string()),
},
)
.await?;
Ok(())
}
async fn disable_slideshow(&self) -> Result<(), Error> {
self.art_request(
"set_slideshow_status",
ArtRequestParams::SetSlideshowStatus {
value: "off".to_string(),
category_id: None,
slideshow_type: None,
},
)
.await?;
Ok(())
}
async fn set_slideshow_config(&self, config: SlideshowConfig) -> Result<(), Error> {
self.enable_slideshow(Some(config)).await
}
async fn is_slideshow_enabled(&self) -> Result<bool, Error> {
let resp = self
.art_request("get_slideshow_status", ArtRequestParams::Empty {})
.await?;
Ok(resp.value.as_deref() != Some("off"))
}
async fn get_slideshow_config(&self) -> Result<SlideshowConfig, Error> {
let resp = self
.art_request("get_slideshow_status", ArtRequestParams::Empty {})
.await?;
let value = resp.value.unwrap_or_else(|| "off".to_string());
let minutes: u64 = value.parse().unwrap_or(0);
Ok(SlideshowConfig {
art_image_duration: std::time::Duration::from_secs(minutes * 60),
shuffle: false,
category: SlideshowCategory::MyPictures,
})
}
async fn set_display_brightness(&self, brightness: DisplayBrightness) -> Result<(), Error> {
self.art_request(
"set_brightness",
ArtRequestParams::SetValue {
value: brightness.as_value().to_string(),
},
)
.await?;
Ok(())
}
async fn set_display_color_temp(&self, color_temp: DisplayColorTemp) -> Result<(), Error> {
self.art_request(
"set_color_temperature",
ArtRequestParams::SetValue {
value: color_temp.as_value().to_string(),
},
)
.await?;
Ok(())
}
async fn get_display_brightness(&self) -> Result<DisplayBrightness, Error> {
let resp = self
.art_request("get_brightness", ArtRequestParams::Empty {})
.await?;
let val: u8 = resp.value.unwrap_or_default().parse().unwrap_or(0);
Ok(DisplayBrightness::from_value(val).unwrap_or(DisplayBrightness::L0))
}
async fn get_display_color_temp(&self) -> Result<DisplayColorTemp, Error> {
let resp = self
.art_request("get_color_temperature", ArtRequestParams::Empty {})
.await?;
let val: i8 = resp.value.unwrap_or_default().parse().unwrap_or(0);
Ok(DisplayColorTemp::from_value(val).unwrap_or(DisplayColorTemp::Zero))
}
async fn get_display_rotation(&self) -> Result<DisplayRotation, Error> {
let resp = self
.art_request("get_current_rotation", ArtRequestParams::Empty {})
.await?;
let rotation = resp
.current_rotation_status
.and_then(|v| v.as_u64())
.unwrap_or(0) as u16;
Ok(DisplayRotation::from_value(rotation))
}
async fn enable_auto_brightness(&self) -> Result<(), Error> {
self.art_request(
"set_brightness_sensor_setting",
ArtRequestParams::SetValue {
value: "on".to_string(),
},
)
.await?;
Ok(())
}
async fn disable_auto_brightness(&self) -> Result<(), Error> {
self.art_request(
"set_brightness_sensor_setting",
ArtRequestParams::SetValue {
value: "off".to_string(),
},
)
.await?;
Ok(())
}
async fn set_motion_timer(&self, timer: MotionTimer) -> Result<(), Error> {
let value = if timer == MotionTimer::Off {
"off".to_string()
} else {
timer.as_minutes().to_string()
};
self.art_request("set_motion_timer", ArtRequestParams::SetValue { value })
.await?;
Ok(())
}
async fn set_motion_sensitivity(&self, sensitivity: MotionSensitivity) -> Result<(), Error> {
self.art_request(
"set_motion_sensitivity",
ArtRequestParams::SetValue {
value: sensitivity.as_value().to_string(),
},
)
.await?;
Ok(())
}
async fn press_remote_control_button(
&self,
button: crate::types::RemoteControlButton,
) -> Result<(), Error> {
self.transport.send_remote_key(button.key_code()).await
}
async fn get_device_info(&self) -> Result<DeviceInfo, Error> {
self.rest_client.get_device_info().await
}
}