First commit
This commit is contained in:
13
crates/mirakurun-types/Cargo.toml
Normal file
13
crates/mirakurun-types/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mirakurun-types"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
808
crates/mirakurun-types/src/lib.rs
Normal file
808
crates/mirakurun-types/src/lib.rs
Normal file
@@ -0,0 +1,808 @@
|
||||
// Copyright 2026 Mirakurun contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
pub type ProgramId = u64;
|
||||
pub type EventId = u16;
|
||||
pub type ServiceId = u16;
|
||||
pub type NetworkId = u16;
|
||||
pub type ServiceItemId = u64;
|
||||
pub type UnixTimeMs = u64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ChannelType {
|
||||
#[serde(rename = "GR")]
|
||||
Gr,
|
||||
#[serde(rename = "BS")]
|
||||
Bs,
|
||||
#[serde(rename = "CS")]
|
||||
Cs,
|
||||
#[serde(rename = "SKY")]
|
||||
Sky,
|
||||
}
|
||||
|
||||
impl ChannelType {
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Gr => "GR",
|
||||
Self::Bs => "BS",
|
||||
Self::Cs => "CS",
|
||||
Self::Sky => "SKY",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ChannelType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value {
|
||||
"GR" => Ok(Self::Gr),
|
||||
"BS" => Ok(Self::Bs),
|
||||
"CS" => Ok(Self::Cs),
|
||||
"SKY" => Ok(Self::Sky),
|
||||
_ => Err(format!("unsupported channel type: {value}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Channel {
|
||||
#[serde(rename = "type")]
|
||||
pub channel_type: ChannelType,
|
||||
pub channel: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub services: Option<Vec<Service>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Service {
|
||||
pub id: ServiceItemId,
|
||||
pub service_id: ServiceId,
|
||||
pub network_id: NetworkId,
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub service_type: u8,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub logo_id: Option<u16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub has_logo_data: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remote_control_key_id: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epg_ready: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epg_updated_at: Option<UnixTimeMs>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<Box<Channel>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Program {
|
||||
pub id: ProgramId,
|
||||
pub event_id: EventId,
|
||||
pub service_id: ServiceId,
|
||||
pub network_id: NetworkId,
|
||||
pub start_at: UnixTimeMs,
|
||||
pub duration: u64,
|
||||
pub is_free: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub genres: Option<Vec<ProgramGenre>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub video: Option<ProgramVideo>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audios: Option<Vec<ProgramAudio>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub series: Option<ProgramSeries>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extended: Option<BTreeMap<String, String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub related_items: Option<Vec<ProgramRelatedItem>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramGenre {
|
||||
pub lv1: u8,
|
||||
pub lv2: u8,
|
||||
pub un1: u8,
|
||||
pub un2: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramVideo {
|
||||
#[serde(rename = "type")]
|
||||
pub video_type: ProgramVideoType,
|
||||
pub resolution: ProgramVideoResolution,
|
||||
pub stream_content: u8,
|
||||
pub component_type: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ProgramVideoType {
|
||||
#[serde(rename = "mpeg2")]
|
||||
Mpeg2,
|
||||
#[serde(rename = "h.264")]
|
||||
H264,
|
||||
#[serde(rename = "h.265")]
|
||||
H265,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ProgramVideoResolution {
|
||||
#[serde(rename = "240p")]
|
||||
P240,
|
||||
#[serde(rename = "480i")]
|
||||
I480,
|
||||
#[serde(rename = "480p")]
|
||||
P480,
|
||||
#[serde(rename = "720p")]
|
||||
P720,
|
||||
#[serde(rename = "1080i")]
|
||||
I1080,
|
||||
#[serde(rename = "1080p")]
|
||||
P1080,
|
||||
#[serde(rename = "2160p")]
|
||||
P2160,
|
||||
#[serde(rename = "4320p")]
|
||||
P4320,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramAudio {
|
||||
pub component_type: u8,
|
||||
pub component_tag: u8,
|
||||
pub is_main: bool,
|
||||
pub sampling_rate: u32,
|
||||
pub langs: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramSeries {
|
||||
pub id: u16,
|
||||
pub repeat: u8,
|
||||
pub pattern: u8,
|
||||
pub expires_at: i64,
|
||||
pub episode: u16,
|
||||
pub last_episode: u16,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProgramRelatedItemType {
|
||||
Shared,
|
||||
Relay,
|
||||
Movement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgramRelatedItem {
|
||||
#[serde(rename = "type")]
|
||||
pub item_type: ProgramRelatedItemType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub network_id: Option<NetworkId>,
|
||||
pub service_id: ServiceId,
|
||||
pub event_id: EventId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct TunerDevice {
|
||||
pub index: usize,
|
||||
pub name: String,
|
||||
pub types: Vec<ChannelType>,
|
||||
pub command: Option<String>,
|
||||
pub pid: Option<u32>,
|
||||
pub users: Vec<TunerUser>,
|
||||
pub is_available: bool,
|
||||
pub is_remote: bool,
|
||||
pub is_free: bool,
|
||||
pub is_using: bool,
|
||||
pub is_fault: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TunerUser {
|
||||
pub id: String,
|
||||
pub priority: i32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub disable_decoder: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream_setting: Option<StreamSetting>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stream_info: Option<BTreeMap<String, StreamPidInfo>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StreamSetting {
|
||||
pub channel: ConfigChannel,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub network_id: Option<NetworkId>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub service_id: Option<ServiceId>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub event_id: Option<EventId>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_provide: Option<bool>,
|
||||
#[serde(rename = "parseNIT", skip_serializing_if = "Option::is_none")]
|
||||
pub parse_nit: Option<bool>,
|
||||
#[serde(rename = "parseSDT", skip_serializing_if = "Option::is_none")]
|
||||
pub parse_sdt: Option<bool>,
|
||||
#[serde(rename = "parseEIT", skip_serializing_if = "Option::is_none")]
|
||||
pub parse_eit: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct StreamPidInfo {
|
||||
pub packet: u64,
|
||||
pub drop: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TunerProcess {
|
||||
pub pid: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JobScheduleItem {
|
||||
pub key: String,
|
||||
pub schedule: String,
|
||||
pub job: JobReference,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct JobReference {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JobItem {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
pub status: JobStatus,
|
||||
pub retry_count: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_rerunnable: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub retry_on_abort: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub retry_on_fail: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub retry_max: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub retry_delay: Option<u64>,
|
||||
pub is_aborting: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub has_aborted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub has_skipped: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub has_failed: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
pub created_at: UnixTimeMs,
|
||||
pub updated_at: UnixTimeMs,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub started_at: Option<UnixTimeMs>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub finished_at: Option<UnixTimeMs>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum JobStatus {
|
||||
Queued,
|
||||
Standby,
|
||||
Running,
|
||||
Finished,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
pub resource: EventResource,
|
||||
#[serde(rename = "type")]
|
||||
pub event_type: EventType,
|
||||
pub data: Value,
|
||||
pub time: UnixTimeMs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EventResource {
|
||||
Program,
|
||||
Service,
|
||||
Tuner,
|
||||
Job,
|
||||
JobSchedule,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum EventType {
|
||||
Create,
|
||||
Update,
|
||||
Remove,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConfigServer {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub path: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub port: Option<u16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hostname: Option<String>,
|
||||
#[serde(rename = "disableIPv6", skip_serializing_if = "Option::is_none")]
|
||||
pub disable_ipv6: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_level: Option<i8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_log_history: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub job_max_running: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub job_max_standby: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_buffer_bytes_before_ready: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub event_end_timeout: Option<u64>,
|
||||
#[serde(
|
||||
rename = "programGCJobSchedule",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub program_gc_job_schedule: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epg_gathering_job_schedule: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epg_retrieval_time: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub logo_data_interval: Option<u64>,
|
||||
#[serde(rename = "disableEITParsing", skip_serializing_if = "Option::is_none")]
|
||||
pub disable_eit_parsing: Option<bool>,
|
||||
#[serde(rename = "disableWebUI", skip_serializing_if = "Option::is_none")]
|
||||
pub disable_web_ui: Option<bool>,
|
||||
#[serde(rename = "allowIPv4CidrRanges", default = "default_ipv4_ranges")]
|
||||
pub allow_ipv4_cidr_ranges: Vec<String>,
|
||||
#[serde(rename = "allowIPv6CidrRanges", default = "default_ipv6_ranges")]
|
||||
pub allow_ipv6_cidr_ranges: Vec<String>,
|
||||
#[serde(default = "default_origins")]
|
||||
pub allow_origins: Vec<String>,
|
||||
#[serde(rename = "allowPNA", default = "default_allow_pna")]
|
||||
pub allow_pna: bool,
|
||||
#[serde(default = "default_tsplay_endpoint")]
|
||||
pub tsplay_endpoint: String,
|
||||
}
|
||||
|
||||
impl Default for ConfigServer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: Some("/var/run/mirakurun.sock".into()),
|
||||
port: Some(40_772),
|
||||
hostname: None,
|
||||
disable_ipv6: None,
|
||||
log_level: Some(2),
|
||||
max_log_history: None,
|
||||
job_max_running: None,
|
||||
job_max_standby: None,
|
||||
max_buffer_bytes_before_ready: None,
|
||||
event_end_timeout: None,
|
||||
program_gc_job_schedule: None,
|
||||
epg_gathering_job_schedule: None,
|
||||
epg_retrieval_time: None,
|
||||
logo_data_interval: None,
|
||||
disable_eit_parsing: None,
|
||||
disable_web_ui: None,
|
||||
allow_ipv4_cidr_ranges: default_ipv4_ranges(),
|
||||
allow_ipv6_cidr_ranges: default_ipv6_ranges(),
|
||||
allow_origins: default_origins(),
|
||||
allow_pna: true,
|
||||
tsplay_endpoint: default_tsplay_endpoint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConfigTuner {
|
||||
pub name: String,
|
||||
pub types: Vec<ChannelType>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dvb_device_path: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remote_mirakurun_host: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remote_mirakurun_port: Option<u16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remote_mirakurun_decoder: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub decoder: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_disabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConfigChannel {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub channel_type: ChannelType,
|
||||
pub channel: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub service_id: Option<ServiceId>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tsmf_rel_ts: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command_vars: Option<BTreeMap<String, CommandVariable>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_disabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub satelite: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub satellite: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub space: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub freq: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub polarity: Option<Polarity>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CommandVariable {
|
||||
String(String),
|
||||
Number(i64),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CommandVariable {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::String(value) => formatter.write_str(value),
|
||||
Self::Number(value) => value.fmt(formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Polarity {
|
||||
H,
|
||||
V,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChannelScanStatus {
|
||||
pub is_scanning: bool,
|
||||
pub status: ChannelScanPhase,
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
pub channel_type: Option<ChannelType>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dry_run: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub progress: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub current_channel: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scan_log: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub new_count: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub takeover_count: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result: Option<Vec<ConfigChannel>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub start_time: Option<UnixTimeMs>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub update_time: Option<UnixTimeMs>,
|
||||
}
|
||||
|
||||
impl Default for ChannelScanStatus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_scanning: false,
|
||||
status: ChannelScanPhase::NotStarted,
|
||||
channel_type: None,
|
||||
dry_run: None,
|
||||
progress: None,
|
||||
current_channel: None,
|
||||
scan_log: None,
|
||||
new_count: None,
|
||||
takeover_count: None,
|
||||
result: None,
|
||||
start_time: None,
|
||||
update_time: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChannelScanPhase {
|
||||
NotStarted,
|
||||
Scanning,
|
||||
Completed,
|
||||
Cancelled,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Version {
|
||||
pub current: String,
|
||||
pub latest: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Status {
|
||||
pub time: UnixTimeMs,
|
||||
pub version: String,
|
||||
pub process: ProcessStatus,
|
||||
pub epg: EpgStatus,
|
||||
pub rpc_count: u64,
|
||||
pub stream_count: StreamCount,
|
||||
pub error_count: ErrorCount,
|
||||
pub timer_accuracy: TimerAccuracy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProcessStatus {
|
||||
pub arch: String,
|
||||
pub platform: String,
|
||||
pub versions: BTreeMap<String, String>,
|
||||
pub env: BTreeMap<String, String>,
|
||||
pub pid: u32,
|
||||
pub memory_usage: MemoryUsage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MemoryUsage {
|
||||
pub rss: u64,
|
||||
pub heap_total: u64,
|
||||
pub heap_used: u64,
|
||||
pub external: u64,
|
||||
pub array_buffers: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpgStatus {
|
||||
pub gathering_networks: Vec<NetworkId>,
|
||||
pub stored_events: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StreamCount {
|
||||
pub tuner_device: u64,
|
||||
pub ts_filter: u64,
|
||||
pub decoder: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorCount {
|
||||
pub uncaught_exception: u64,
|
||||
pub unhandled_rejection: u64,
|
||||
pub buffer_overflow: u64,
|
||||
pub tuner_device_respawn: u64,
|
||||
pub decoder_respawn: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TimerAccuracy {
|
||||
pub last: f64,
|
||||
pub m1: TimerSample,
|
||||
pub m5: TimerSample,
|
||||
pub m15: TimerSample,
|
||||
}
|
||||
|
||||
impl Default for TimerAccuracy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last: 0.0,
|
||||
m1: TimerSample::default(),
|
||||
m5: TimerSample::default(),
|
||||
m15: TimerSample::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct TimerSample {
|
||||
pub avg: f64,
|
||||
pub min: f64,
|
||||
pub max: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ApiError {
|
||||
pub code: u16,
|
||||
pub reason: Option<String>,
|
||||
pub errors: Vec<OpenApiError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpenApiError {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_code: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub location: Option<String>,
|
||||
}
|
||||
|
||||
const fn default_allow_pna() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_tsplay_endpoint() -> String {
|
||||
"https://mirakurun-secure-contexts-api.pages.dev/tsplay/".into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn default_ipv4_ranges() -> Vec<String> {
|
||||
[
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn default_ipv6_ranges() -> Vec<String> {
|
||||
vec!["fc00::/7".into()]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn default_origins() -> Vec<String> {
|
||||
vec!["https://mirakurun-secure-contexts-api.pages.dev".into()]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn service_item_id(network_id: NetworkId, service_id: ServiceId) -> ServiceItemId {
|
||||
(network_id as u64) * 100_000 + service_id as u64
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn program_id(
|
||||
network_id: NetworkId,
|
||||
service_id: ServiceId,
|
||||
event_id: EventId,
|
||||
) -> ProgramId {
|
||||
service_item_id(network_id, service_id) * 100_000 + event_id as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::{
|
||||
ChannelType, ConfigChannel, ConfigServer, StreamSetting, program_id, service_item_id,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn compound_ids_match_mirakurun_formula() {
|
||||
assert_eq!(service_item_id(32_738, 1024), 3_273_801_024);
|
||||
assert_eq!(program_id(32_738, 1024, 65_535), 327_380_102_465_535);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn channel_type_has_wire_value() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ChannelType::Gr).expect("serialize enum"),
|
||||
"\"GR\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_server_applies_wire_defaults() {
|
||||
let config: ConfigServer = serde_json::from_str("{}").expect("deserialize config");
|
||||
assert!(config.allow_pna);
|
||||
assert!(!config.allow_origins.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acronym_config_fields_match_node_wire_names() {
|
||||
let config: ConfigServer = serde_json::from_value(json!({
|
||||
"disableIPv6": true,
|
||||
"programGCJobSchedule": "45 * * * *",
|
||||
"disableEITParsing": true,
|
||||
"disableWebUI": true,
|
||||
"allowIPv4CidrRanges": ["127.0.0.0/8"],
|
||||
"allowIPv6CidrRanges": ["::1/128"],
|
||||
"allowPNA": false
|
||||
}))
|
||||
.expect("deserialize config");
|
||||
assert_eq!(config.disable_ipv6, Some(true));
|
||||
assert_eq!(
|
||||
config.program_gc_job_schedule.as_deref(),
|
||||
Some("45 * * * *")
|
||||
);
|
||||
assert_eq!(config.disable_eit_parsing, Some(true));
|
||||
assert_eq!(config.disable_web_ui, Some(true));
|
||||
assert!(!config.allow_pna);
|
||||
|
||||
let encoded = serde_json::to_value(config).expect("serialize config");
|
||||
assert_eq!(encoded["disableIPv6"], true);
|
||||
assert_eq!(encoded["programGCJobSchedule"], "45 * * * *");
|
||||
assert_eq!(encoded["disableEITParsing"], true);
|
||||
assert_eq!(encoded["disableWebUI"], true);
|
||||
assert_eq!(encoded["allowPNA"], false);
|
||||
assert!(encoded.get("disableIpv6").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_parser_flags_keep_uppercase_acronyms() {
|
||||
let setting = StreamSetting {
|
||||
channel: ConfigChannel {
|
||||
name: "test".into(),
|
||||
channel_type: ChannelType::Gr,
|
||||
channel: "27".into(),
|
||||
service_id: None,
|
||||
tsmf_rel_ts: None,
|
||||
command_vars: None,
|
||||
is_disabled: None,
|
||||
satelite: None,
|
||||
satellite: None,
|
||||
space: None,
|
||||
freq: None,
|
||||
polarity: None,
|
||||
},
|
||||
network_id: None,
|
||||
service_id: None,
|
||||
event_id: None,
|
||||
no_provide: None,
|
||||
parse_nit: Some(true),
|
||||
parse_sdt: Some(true),
|
||||
parse_eit: Some(true),
|
||||
};
|
||||
let encoded = serde_json::to_value(setting).expect("serialize stream setting");
|
||||
assert_eq!(encoded["parseNIT"], true);
|
||||
assert_eq!(encoded["parseSDT"], true);
|
||||
assert_eq!(encoded["parseEIT"], true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user