framesmith 0.1.0

A Rust library for controlling Samsung Frame TVs over the local network
Documentation
use serde::Serialize;

#[derive(Debug, Serialize)]
pub(crate) struct ArtRequest {
    pub request: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request_id: Option<String>,
    #[serde(flatten)]
    pub params: ArtRequestParams,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub(crate) enum ArtRequestParams {
    Empty {},

    GetContentList {
        #[serde(skip_serializing_if = "Option::is_none")]
        category: Option<String>,
    },

    SelectImage {
        content_id: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        category_id: Option<String>,
        show: bool,
    },

    ChangeFavorite {
        content_id: String,
        status: String,
    },

    ChangeMatte {
        content_id: String,
        matte_id: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        portrait_matte_id: Option<String>,
    },

    SetPhotoFilter {
        content_id: String,
        filter_id: String,
    },

    DeleteImageList {
        content_id_list: Vec<ContentIdEntry>,
    },

    SendImage {
        file_type: String,
        image_date: String,
        matte_id: String,
        portrait_matte_id: String,
        file_size: u64,
        conn_info: ConnInfo,
    },

    GetThumbnail {
        content_id: String,
        conn_info: ConnInfo,
    },

    SetValue {
        value: String,
    },

    SetSlideshowStatus {
        value: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        category_id: Option<String>,
        #[serde(rename = "type")]
        #[serde(skip_serializing_if = "Option::is_none")]
        slideshow_type: Option<String>,
    },
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct ContentIdEntry {
    pub content_id: String,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct ConnInfo {
    pub d2d_mode: String,
    pub connection_id: u32,
    pub id: String,
}

impl ArtRequest {
    pub fn new(request: &'static str, params: ArtRequestParams) -> Self {
        let art_id = uuid::Uuid::new_v4().to_string();
        Self {
            request,
            id: Some(art_id),
            request_id: Some(uuid::Uuid::new_v4().to_string()),
            params,
        }
    }

    /// Create a request with a specific art session id (shared with ConnInfo).
    /// All ID fields (id, request_id, conn_info.id) use the same UUID,
    /// matching the Python reference implementation.
    pub fn new_with_id(art_id: String, request: &'static str, params: ArtRequestParams) -> Self {
        Self {
            request,
            id: Some(art_id.clone()),
            request_id: Some(art_id),
            params,
        }
    }

    pub fn to_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string(self)
    }
}

impl ConnInfo {
    pub fn with_id(id: String) -> Self {
        Self {
            d2d_mode: "socket".to_string(),
            connection_id: rand_connection_id(),
            id,
        }
    }
}

fn rand_connection_id() -> u32 {
    use std::collections::hash_map::RandomState;
    use std::hash::{BuildHasher, Hasher};
    let s = RandomState::new();
    let mut h = s.build_hasher();
    h.write_u8(0);
    (h.finish() % (4 * 1024 * 1024 * 1024)) as u32
}