use std::time::Duration;
use as_variant::as_variant;
use ruma_common::{DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId};
use ruma_macros::StringEnum;
use serde::{Deserialize, Serialize};
use tracing::warn;
use super::focus::{ActiveFocus, ActiveLivekitFocus, Focus};
use crate::PrivOwnedStr;
#[cfg(feature = "unstable-msc4075")]
use crate::rtc::notification::CallIntent;
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub enum MembershipData<'a> {
Legacy(&'a LegacyMembershipData),
Session(&'a SessionMembershipData),
}
impl MembershipData<'_> {
pub fn application(&self) -> &Application {
match self {
MembershipData::Legacy(data) => &data.application,
MembershipData::Session(data) => &data.application,
}
}
pub fn device_id(&self) -> &DeviceId {
match self {
MembershipData::Legacy(data) => &data.device_id,
MembershipData::Session(data) => &data.device_id,
}
}
pub fn focus_active(&self) -> &ActiveFocus {
match self {
MembershipData::Legacy(_) => &ActiveFocus::Livekit(ActiveLivekitFocus {
focus_selection: super::focus::FocusSelection::OldestMembership,
}),
MembershipData::Session(data) => &data.focus_active,
}
}
pub fn foci_preferred(&self) -> &Vec<Focus> {
match self {
MembershipData::Legacy(data) => &data.foci_active,
MembershipData::Session(data) => &data.foci_preferred,
}
}
#[cfg(feature = "unstable-msc4075")]
pub fn call_intent(&self) -> Option<&CallIntent> {
as_variant!(self.application(), Application::Call)
.and_then(|call| call.call_intent.as_ref())
}
pub fn is_room_call(&self) -> bool {
as_variant!(self.application(), Application::Call)
.is_some_and(|call| call.scope == CallScope::Room)
}
pub fn is_call(&self) -> bool {
as_variant!(self.application(), Application::Call).is_some()
}
pub fn created_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
match self {
MembershipData::Legacy(data) => data.created_ts,
MembershipData::Session(data) => data.created_ts,
}
}
pub fn is_expired(&self, origin_server_ts: Option<MilliSecondsSinceUnixEpoch>) -> bool {
if let Some(expire_ts) = self.expires_ts(origin_server_ts) {
MilliSecondsSinceUnixEpoch::now() > expire_ts
} else {
warn!(
"Encountered a Call Member state event where the expire_ts could not be constructed."
);
false
}
}
pub fn expires_ts(
&self,
origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,
) -> Option<MilliSecondsSinceUnixEpoch> {
let expires = match &self {
MembershipData::Legacy(data) => data.expires,
MembershipData::Session(data) => data.expires,
};
let ev_created_ts = self.created_ts().or(origin_server_ts)?.to_system_time();
ev_created_ts.and_then(|t| MilliSecondsSinceUnixEpoch::from_system_time(t + expires))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct LegacyMembershipData {
#[serde(flatten)]
pub application: Application,
pub device_id: OwnedDeviceId,
#[serde(with = "ruma_common::serde::duration::ms")]
pub expires: Duration,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_ts: Option<MilliSecondsSinceUnixEpoch>,
pub foci_active: Vec<Focus>,
#[serde(rename = "membershipID")]
pub membership_id: String,
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct LegacyMembershipDataInit {
pub application: Application,
pub device_id: OwnedDeviceId,
pub expires: Duration,
pub foci_active: Vec<Focus>,
pub membership_id: String,
}
impl From<LegacyMembershipDataInit> for LegacyMembershipData {
fn from(init: LegacyMembershipDataInit) -> Self {
let LegacyMembershipDataInit {
application,
device_id,
expires,
foci_active,
membership_id,
} = init;
Self { application, device_id, expires, created_ts: None, foci_active, membership_id }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct SessionMembershipData {
#[serde(flatten)]
pub application: Application,
pub device_id: OwnedDeviceId,
pub foci_preferred: Vec<Focus>,
pub focus_active: ActiveFocus,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_ts: Option<MilliSecondsSinceUnixEpoch>,
#[serde(with = "ruma_common::serde::duration::ms")]
pub expires: Duration,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[serde(tag = "application")]
pub enum Application {
#[serde(rename = "m.call")]
Call(CallApplicationContent),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct CallApplicationContent {
pub call_id: String,
pub scope: CallScope,
#[serde(rename = "m.call.intent", default, skip_serializing_if = "Option::is_none")]
#[cfg(feature = "unstable-msc4075")]
pub call_intent: Option<CallIntent>,
}
impl CallApplicationContent {
pub fn new(call_id: String, scope: CallScope) -> Self {
Self {
call_id,
scope,
#[cfg(feature = "unstable-msc4075")]
call_intent: None,
}
}
#[cfg(feature = "unstable-msc4075")]
pub fn new_with_intent(call_id: String, scope: CallScope, call_intent: CallIntent) -> Self {
Self { call_id, scope, call_intent: Some(call_intent) }
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, StringEnum)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[ruma_enum(rename_all(prefix = "m.", rule = "snake_case"))]
pub enum CallScope {
Room,
User,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}