[Add] Structure

This commit is contained in:
Alex Emmet 2026-07-20 22:22:12 +02:00
commit c363ea48d0
27 changed files with 1730 additions and 1400 deletions

View file

@ -1,13 +1,14 @@
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, TypeMap};
use serde_json::Map;
use std::{collections::BTreeMap, env, sync::Arc, time::Duration};
use std::{collections::BTreeMap, sync::Arc, time::Duration};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::{
calls::{call_util, caller::Caller},
omega::omega_connection::get_omega_connection,
calls::{call_util::LiveKitService, caller::Caller, error::CallError},
omega::omega_connection::OmegaConnection,
util::data_type_id,
};
pub struct CallGroup {
@ -17,6 +18,7 @@ pub struct CallGroup {
pub anonymous_joining: RwLock<bool>,
pub short_link: RwLock<Option<String>>,
pub secrets: RwLock<BTreeMap<u64, CallSecretEnvelope>>,
livekit: Arc<LiveKitService>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@ -32,27 +34,27 @@ impl CallSecretEnvelope {
pub fn from_data_value(value: &DataValue) -> Option<Self> {
let tm = TypeMap::latest();
let secret_id = value
.get_field(DataType::SecretId.to_id(&tm))?
.get_field(data_type_id(DataType::SecretId, &tm))?
.as_str()?
.to_string();
let version_number = value
.get_field(DataType::VersionNumber.to_id(&tm))?
.get_field(data_type_id(DataType::VersionNumber, &tm))?
.as_signed_number()
.and_then(|n| i64::try_from(n).ok())
.or_else(|| {
value
.get_field(DataType::VersionNumber.to_id(&tm))?
.get_field(data_type_id(DataType::VersionNumber, &tm))?
.as_number()
.and_then(|n| i64::try_from(n).ok())
})?;
let encrypted_secret = value
.get_field(DataType::EncryptedSecret.to_id(&tm))?
.get_field(data_type_id(DataType::EncryptedSecret, &tm))?
.as_bytes()?;
let kem_ciphertext = value
.get_field(DataType::KemCiphertext.to_id(&tm))?
.get_field(data_type_id(DataType::KemCiphertext, &tm))?
.as_bytes()?;
let wrapping_scheme = value
.get_field(DataType::WrappingScheme.to_id(&tm))?
.get_field(data_type_id(DataType::WrappingScheme, &tm))?
.as_str()?
.to_string();
@ -69,23 +71,23 @@ impl CallSecretEnvelope {
let tm = TypeMap::latest();
let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
map.insert(
DataType::SecretId.to_id(&tm),
data_type_id(DataType::SecretId, &tm),
DataValue::Str(self.secret_id.clone()),
);
map.insert(
DataType::VersionNumber.to_id(&tm),
data_type_id(DataType::VersionNumber, &tm),
DataValue::SignedNumber(self.version_number.into()),
);
map.insert(
DataType::EncryptedSecret.to_id(&tm),
data_type_id(DataType::EncryptedSecret, &tm),
DataValue::Bytes(self.encrypted_secret.clone()),
);
map.insert(
DataType::KemCiphertext.to_id(&tm),
data_type_id(DataType::KemCiphertext, &tm),
DataValue::Bytes(self.kem_ciphertext.clone()),
);
map.insert(
DataType::WrappingScheme.to_id(&tm),
data_type_id(DataType::WrappingScheme, &tm),
DataValue::Str(self.wrapping_scheme.clone()),
);
DataValue::container_from_map(&map)
@ -98,6 +100,14 @@ pub fn call_invite_secret_from_cv(cv: &CommunicationValue) -> Option<CallSecretE
impl CallGroup {
pub fn new(call_id: Uuid, user: Arc<Caller>) -> Self {
Self::new_with_service(call_id, user, Arc::new(LiveKitService::new(None)))
}
pub fn new_with_service(
call_id: Uuid,
user: Arc<Caller>,
livekit: Arc<LiveKitService>,
) -> Self {
CallGroup {
call_id,
members: RwLock::new(vec![user]),
@ -105,6 +115,7 @@ impl CallGroup {
anonymous_joining: RwLock::new(false),
short_link: RwLock::new(None),
secrets: RwLock::new(BTreeMap::new()),
livekit,
}
}
@ -126,7 +137,7 @@ impl CallGroup {
}
pub async fn update_admins(&self) {
let call_metadata = match call_util::get_room_metadata(self.call_id).await {
let call_metadata = match self.livekit.get_room_metadata(self.call_id).await {
Ok(metadata) => metadata,
Err(_) => "{}".to_string(),
};
@ -150,17 +161,31 @@ impl CallGroup {
call_metadata.insert("admins".to_string(), serde_json::json!(admin_ids));
let _ = call_util::set_room_metadata(
self.call_id,
serde_json::Value::Object(call_metadata).to_string(),
)
.await;
if let Err(error) = self
.livekit
.set_room_metadata(
self.call_id,
serde_json::Value::Object(call_metadata).to_string(),
)
.await
{
log::warn!(
"Unable to update administrators for call {}: {}",
self.call_id,
error
);
}
}
pub async fn set_anonymous_joining(&self, enable: bool) {
pub async fn set_anonymous_joining(
&self,
enable: bool,
omega: &OmegaConnection,
omikron_id: u64,
) {
*self.anonymous_joining.write().await = enable;
let call_metadata = match call_util::get_room_metadata(self.call_id).await {
let call_metadata = match self.livekit.get_room_metadata(self.call_id).await {
Ok(metadata) => metadata,
Err(_) => "{}".to_string(),
};
@ -175,22 +200,27 @@ impl CallGroup {
call_metadata.insert("anonymous_joining".to_string(), serde_json::json!(enable));
let _ = call_util::set_room_metadata(
self.call_id,
serde_json::Value::Object(call_metadata).to_string(),
)
.await;
if let Err(error) = self
.livekit
.set_room_metadata(
self.call_id,
serde_json::Value::Object(call_metadata).to_string(),
)
.await
{
log::warn!(
"Unable to update anonymous access for call {}: {}",
self.call_id,
error
);
}
if self.short_link.read().await.is_none() {
let long_link = format!(
"https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}",
self.call_id,
env::var("ID")
.unwrap_or("0".to_string())
.parse::<i64>()
.unwrap_or(0),
self.call_id, omikron_id,
);
let response_cv = get_omega_connection()
let response_cv = omega
.await_response(
&CommunicationValue::new(CommunicationType::ShortenLink)
.add_typed_default(DataType::Link, DataValue::Str(long_link)),
@ -198,33 +228,33 @@ impl CallGroup {
)
.await;
if let Ok(response) = response_cv {
*self.short_link.write().await = Some(
response
.get_data(DataType::Link)
.as_str()
.unwrap()
.to_string(),
);
log::info!(
"Shortened link for call {} is {}",
self.call_id,
self.short_link.read().await.as_ref().unwrap()
);
if let Some(link) = response.get_data(DataType::Link).as_str() {
*self.short_link.write().await = Some(link.to_string());
log::info!("Shortened link for call {}", self.call_id);
}
}
}
}
pub async fn create_anonymous_token(&self, user_id: u64) -> Option<String> {
pub async fn create_anonymous_token(&self, user_id: u64) -> Result<Option<String>, CallError> {
if self.is_anonymous().await {
if let Ok(token) = call_util::create_token(user_id, self.call_id, false) {
return Some(token);
}
return self
.livekit
.create_token(user_id, self.call_id, false)
.map(Some);
}
None
Ok(None)
}
pub async fn remove_caller(&self, user_id: u64) {
let _ = call_util::remove_participant(self.call_id, user_id).await;
if let Err(error) = self.livekit.remove_participant(self.call_id, user_id).await {
log::warn!(
"Unable to remove user {} from call {}: {}",
user_id,
self.call_id,
error
);
}
self.members
.write()
.await
@ -263,19 +293,19 @@ mod tests {
let tm = TypeMap::latest();
let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
map.insert(
DataType::SecretId.to_id(&tm),
data_type_id(DataType::SecretId, &tm),
DataValue::Str("call:test:main".to_string()),
);
map.insert(
DataType::VersionNumber.to_id(&tm),
data_type_id(DataType::VersionNumber, &tm),
DataValue::SignedNumber(1),
);
map.insert(
DataType::EncryptedSecret.to_id(&tm),
data_type_id(DataType::EncryptedSecret, &tm),
DataValue::Bytes(vec![1, 2, 3]),
);
map.insert(
DataType::WrappingScheme.to_id(&tm),
data_type_id(DataType::WrappingScheme, &tm),
DataValue::Str("mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1".to_string()),
);

View file

@ -1,106 +1,151 @@
use dashmap::DashMap;
use once_cell::sync::Lazy;
use std::sync::Arc;
use uuid::Uuid;
use crate::calls::{
call_group::{CallGroup, CallSecretEnvelope},
call_util,
call_util::LiveKitService,
caller::Caller,
error::CallError,
};
pub static CALL_GROUPS: Lazy<DashMap<Uuid, Arc<CallGroup>>> = Lazy::new(|| DashMap::new());
#[allow(dead_code)]
pub async fn get_call_invites(user_id: u64) -> Vec<Arc<Caller>> {
let mut callers = Vec::new();
for (_, cg) in CALL_GROUPS.clone().into_iter() {
let members = cg.members.read().await;
for member in members.iter() {
if member.user_id == user_id {
callers.push(member.clone());
}
pub struct CallManager {
pub(crate) groups: DashMap<Uuid, Arc<CallGroup>>,
pub livekit: Arc<LiveKitService>,
}
impl Default for CallManager {
fn default() -> Self {
Self {
groups: DashMap::new(),
livekit: Arc::new(LiveKitService::new(None)),
}
}
callers
}
pub async fn get_call(call_id: Uuid) -> Option<Arc<CallGroup>> {
if let Some(b) = CALL_GROUPS.get(&call_id) {
Some(b.clone())
} else {
None
impl CallManager {
pub fn new(livekit: Arc<LiveKitService>) -> Self {
Self {
groups: DashMap::new(),
livekit,
}
}
}
pub async fn get_call_groups(user_id: u64) -> Vec<Arc<CallGroup>> {
let mut call_groups = Vec::new();
for (_, cg) in CALL_GROUPS.clone().into_iter() {
let is_member = {
#[allow(dead_code)]
pub async fn get_call_invites(&self, user_id: u64) -> Vec<Arc<Caller>> {
let mut callers = Vec::new();
let call_groups = self
.groups
.iter()
.map(|entry| entry.value().clone())
.collect::<Vec<_>>();
for cg in call_groups {
let members = cg.members.read().await;
members.iter().any(|m| m.user_id == user_id)
};
if is_member {
call_groups.push(cg.clone());
for member in members.iter() {
if member.user_id == user_id {
callers.push(member.clone());
}
}
}
}
call_groups
}
pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option<String> {
if let Some(cg) = CALL_GROUPS.get(&call_id) {
let mut members = cg.members.write().await;
if let Some(member) = members.iter().find(|m| m.user_id == user_id) {
return Some(member.create_token());
}
let new_caller = Arc::new(Caller::new(user_id, call_id, false));
let token = new_caller.create_token();
members.push(new_caller);
return Some(token);
callers
}
let caller = Arc::new(Caller::new(user_id, call_id, true));
let call_group = Arc::new(CallGroup::new(call_id, caller.clone()));
pub async fn get_call(&self, call_id: Uuid) -> Option<Arc<CallGroup>> {
if let Some(b) = self.groups.get(&call_id) {
Some(b.clone())
} else {
None
}
}
CALL_GROUPS.insert(call_id, call_group.clone());
pub async fn get_call_groups(&self, user_id: u64) -> Vec<Arc<CallGroup>> {
let mut call_groups = Vec::new();
let tracked_groups = self
.groups
.iter()
.map(|entry| entry.value().clone())
.collect::<Vec<_>>();
for cg in tracked_groups {
let is_member = {
let members = cg.members.read().await;
members.iter().any(|m| m.user_id == user_id)
};
let _ = call_util::create_room(call_id).await;
call_group.update_admins().await;
if is_member {
call_groups.push(cg.clone());
}
}
call_groups
}
Some(caller.create_token())
}
pub async fn get_call_token(&self, user_id: u64, call_id: Uuid) -> Result<String, CallError> {
if let Some(cg) = self.groups.get(&call_id) {
let mut members = cg.members.write().await;
pub async fn add_invite(
call_id: Uuid,
inviter_id: u64,
invitee_id: u64,
secret: CallSecretEnvelope,
) -> bool {
if let Some(cg) = CALL_GROUPS.get(&call_id) {
let mut members = cg.members.write().await;
let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id);
if is_inviter_member {
if !members.iter().any(|m| m.user_id == invitee_id) {
members.push(Arc::new(Caller::new(invitee_id, call_id, false)));
if let Some(member) = members.iter().find(|m| m.user_id == user_id) {
return self.livekit.create_token(
member.user_id,
member.call_id,
member.has_admin(),
);
}
let mut secrets = cg.secrets.write().await;
secrets.insert(invitee_id, secret);
let new_caller = Arc::new(Caller::new(user_id, call_id, false));
let token = self.livekit.create_token(
new_caller.user_id,
new_caller.call_id,
new_caller.has_admin(),
)?;
return true;
members.push(new_caller);
return Ok(token);
}
}
false
}
pub fn should_forward_invite(inviter_id: u64, invitee_id: u64) -> bool {
inviter_id != invitee_id
let caller = Arc::new(Caller::new(user_id, call_id, true));
let call_group = Arc::new(CallGroup::new_with_service(
call_id,
caller.clone(),
self.livekit.clone(),
));
self.livekit.create_room(call_id).await?;
self.groups.insert(call_id, call_group.clone());
call_group.update_admins().await;
self.livekit
.create_token(caller.user_id, caller.call_id, caller.has_admin())
}
pub async fn add_invite(
&self,
call_id: Uuid,
inviter_id: u64,
invitee_id: u64,
secret: CallSecretEnvelope,
) -> bool {
if let Some(cg) = self.groups.get(&call_id) {
let mut members = cg.members.write().await;
let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id);
if is_inviter_member {
if !members.iter().any(|m| m.user_id == invitee_id) {
members.push(Arc::new(Caller::new(invitee_id, call_id, false)));
}
let mut secrets = cg.secrets.write().await;
secrets.insert(invitee_id, secret);
return true;
}
}
false
}
pub fn should_forward_invite(&self, inviter_id: u64, invitee_id: u64) -> bool {
inviter_id != invitee_id
}
}
#[cfg(test)]
@ -126,19 +171,22 @@ mod tests {
call_id,
Arc::new(Caller::new(sender_id, call_id, true)),
));
CALL_GROUPS.insert(call_id, group.clone());
let manager = CallManager::default();
manager.groups.insert(call_id, group.clone());
let receiver_secret = envelope("receiver");
assert!(add_invite(call_id, sender_id, receiver_id, receiver_secret.clone(),).await);
assert!(
manager
.add_invite(call_id, sender_id, receiver_id, receiver_secret.clone(),)
.await
);
assert_eq!(
group.get_secret_for_user(receiver_id).await,
Some(receiver_secret.clone())
);
assert_eq!(group.get_secret_for_user(sender_id).await, None);
CALL_GROUPS.remove(&call_id);
}
#[tokio::test]
@ -149,11 +197,16 @@ mod tests {
call_id,
Arc::new(Caller::new(sender_id, call_id, true)),
));
CALL_GROUPS.insert(call_id, group.clone());
let manager = CallManager::default();
manager.groups.insert(call_id, group.clone());
let secret = envelope("self");
assert!(add_invite(call_id, sender_id, sender_id, secret.clone()).await);
assert!(
manager
.add_invite(call_id, sender_id, sender_id, secret.clone())
.await
);
assert_eq!(group.get_secret_for_user(sender_id).await, Some(secret));
assert_eq!(
@ -166,13 +219,12 @@ mod tests {
.count(),
1
);
CALL_GROUPS.remove(&call_id);
}
#[test]
fn self_invites_are_not_forwarded() {
assert!(!should_forward_invite(44, 44));
assert!(should_forward_invite(44, 55));
let manager = CallManager::default();
assert!(!manager.should_forward_invite(44, 44));
assert!(manager.should_forward_invite(44, 55));
}
}

View file

@ -1,139 +1,175 @@
use livekit_api::services::room::CreateRoomOptions;
use std::{str::FromStr, sync::Arc, time::Duration};
use livekit_api::{
access_token::{self},
services::room::RoomClient,
services::room::{CreateRoomOptions, RoomClient},
};
use livekit_protocol::Room;
use std::env;
use std::str::FromStr;
use std::time::Duration;
use uuid::Uuid;
use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType};
use crate::{
calls::{call_manager::CallManager, error::CallError},
config::LiveKitConfig,
log, log_err,
util::logger::PrintType,
};
pub fn get_livekit() -> Result<(String, String, String), ()> {
let hostname = match env::var("LIVEKI_HOSTNAME") {
Ok(secret) => secret,
Err(_) => {
log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!");
return Err(());
}
};
let api_key = match env::var("LIVEKIT_API_KEY") {
Ok(key) => key,
Err(_) => {
log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!");
return Err(());
}
};
let api_secret = match env::var("LIVEKIT_API_SECRET") {
Ok(secret) => secret,
Err(_) => {
log_err!(0, PrintType::General, "LIVEKIT_API_SECRET not set!");
return Err(());
}
};
Ok((hostname, api_key, api_secret))
const LIVEKIT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
pub struct LiveKitService {
config: Option<LiveKitConfig>,
}
pub async fn create_room(call_id: Uuid) -> Result<(), ()> {
let (hostname, api_key, api_secret) = get_livekit()?;
let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
impl LiveKitService {
pub fn new(config: Option<LiveKitConfig>) -> Self {
Self { config }
}
let options = CreateRoomOptions::default();
fn livekit_config(&self) -> Result<&LiveKitConfig, CallError> {
self.config.as_ref().ok_or(CallError::NotConfigured)
}
room_service
.create_room(&call_id.to_string(), options)
fn room_client(&self) -> Result<RoomClient, CallError> {
let config = self.livekit_config()?;
Ok(RoomClient::with_api_key(
&config.hostname,
&config.api_key,
&config.api_secret,
))
}
pub async fn create_room(&self, call_id: Uuid) -> Result<(), CallError> {
tokio::time::timeout(
LIVEKIT_REQUEST_TIMEOUT,
self.room_client()?
.create_room(&call_id.to_string(), CreateRoomOptions::default()),
)
.await
.map_err(|_| ())?;
Ok(())
}
pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result<String, ()> {
let (_, api_key, api_secret) = get_livekit()?;
let token = access_token::AccessToken::with_api_key(&api_key, &api_secret)
.with_identity(&user_id.to_string())
.with_grants(access_token::VideoGrants {
room_join: true,
can_update_own_metadata: true,
room_admin: has_admin,
room: call_id.to_string(),
..Default::default()
})
.to_jwt();
if let Ok(token) = token {
Ok(token)
} else {
Err(())
.map_err(|_| CallError::RequestTimedOut { call_id })?
.map_err(|error| CallError::RoomCreationFailed {
call_id,
detail: error.to_string(),
})?;
Ok(())
}
}
#[allow(dead_code)]
pub async fn get_room(call_id: Uuid) -> Result<(RoomClient, Room), ()> {
if let Ok((hostname, api_key, api_secret)) = get_livekit() {
let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
let rooms = room_service.list_rooms(Vec::new()).await;
if let Ok(rooms) = rooms {
for room in rooms {
if room.name == call_id.to_string() {
return Ok((room_service, room));
pub fn create_token(
&self,
user_id: u64,
call_id: Uuid,
has_admin: bool,
) -> Result<String, CallError> {
let config = self.livekit_config()?;
access_token::AccessToken::with_api_key(&config.api_key, &config.api_secret)
.with_identity(&user_id.to_string())
.with_grants(access_token::VideoGrants {
room_join: true,
can_update_own_metadata: true,
room_admin: has_admin,
room: call_id.to_string(),
..Default::default()
})
.to_jwt()
.map_err(|error| CallError::TokenCreationFailed {
detail: error.to_string(),
})
}
#[allow(dead_code)]
pub async fn get_room(&self, call_id: Uuid) -> Result<(RoomClient, Room), CallError> {
let room_service = self.room_client()?;
let rooms =
tokio::time::timeout(LIVEKIT_REQUEST_TIMEOUT, room_service.list_rooms(Vec::new()))
.await
.map_err(|_| CallError::RequestTimedOut { call_id })?
.map_err(|error| CallError::RoomLookupFailed {
detail: error.to_string(),
})?;
rooms
.into_iter()
.find(|room| room.name == call_id.to_string())
.map(|room| (room_service, room))
.ok_or(CallError::RoomNotFound { call_id })
}
pub async fn remove_participant(&self, call_id: Uuid, user_id: u64) -> Result<(), CallError> {
tokio::time::timeout(
LIVEKIT_REQUEST_TIMEOUT,
self.room_client()?
.remove_participant(&call_id.to_string(), &user_id.to_string()),
)
.await
.map_err(|_| CallError::RequestTimedOut { call_id })?
.map_err(|error| CallError::ParticipantRemovalFailed {
call_id,
user_id,
detail: error.to_string(),
})?;
Ok(())
}
#[allow(dead_code)]
pub async fn get_room_metadata(&self, call_id: Uuid) -> Result<String, CallError> {
let (_, room) = self.get_room(call_id).await?;
Ok(room.metadata)
}
pub async fn set_room_metadata(
&self,
call_id: Uuid,
metadata: String,
) -> Result<(), CallError> {
tokio::time::timeout(
LIVEKIT_REQUEST_TIMEOUT,
self.room_client()?
.update_room_metadata(&call_id.to_string(), &metadata),
)
.await
.map_err(|_| CallError::RequestTimedOut { call_id })?
.map_err(|error| CallError::MetadataUpdateFailed {
call_id,
detail: error.to_string(),
})?;
Ok(())
}
pub fn garbage_collect_calls(self: Arc<Self>, manager: Arc<CallManager>) {
tokio::spawn(async move {
loop {
match self.room_client() {
Ok(room_service) => clean_calls(&manager, room_service).await,
Err(CallError::NotConfigured) => return,
Err(error) => log_err!(0, PrintType::Call, "Call cleanup skipped: {error}"),
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
}
});
}
return Err(());
}
pub async fn remove_participant(call_id: Uuid, user_id: u64) -> Result<(), ()> {
if let Ok((hostname, api_key, api_secret)) = get_livekit() {
let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
if let Ok(_) = room_service
.remove_participant(&call_id.to_string(), &user_id.to_string())
pub async fn clean_calls(manager: &CallManager, room_service: RoomClient) {
let rooms =
match tokio::time::timeout(LIVEKIT_REQUEST_TIMEOUT, room_service.list_rooms(Vec::new()))
.await
{
return Ok(());
}
}
return Err(());
}
#[allow(dead_code)]
pub async fn get_room_metadata(call_id: Uuid) -> Result<String, ()> {
if let Ok((_, room)) = get_room(call_id).await {
Ok(room.metadata)
} else {
Err(())
}
}
pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> {
let (hostname, api_key, api_secret) = get_livekit()?;
let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
room_service
.update_room_metadata(&call_id.to_string(), &metadata)
.await
.map_err(|_| ())?;
Ok(())
}
pub fn garbage_collect_calls() {
tokio::spawn(async move {
loop {
if let Ok((hostname, api_key, api_secret)) = get_livekit() {
let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
clean_calls(room_service).await;
Ok(Ok(rooms)) => rooms,
Ok(Err(error)) => {
log_err!(
0,
PrintType::Call,
"Unable to list LiveKit rooms during cleanup: {error}"
);
return;
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
});
}
pub async fn clean_calls(room_service: RoomClient) {
let rooms = room_service.list_rooms(Vec::new()).await.unwrap();
let mut call_ids: Vec<Uuid> = Vec::new();
let mut no_users: Vec<Uuid> = Vec::new();
Err(_) => {
log_err!(0, PrintType::Call, "LiveKit room cleanup timed out");
return;
}
};
let mut call_ids = Vec::new();
let mut no_users = Vec::new();
for room in rooms {
if let Ok(id) = Uuid::from_str(&room.name) {
if room.num_participants == 0 {
@ -142,23 +178,35 @@ pub async fn clean_calls(room_service: RoomClient) {
call_ids.push(id);
}
}
let size_pre = CALL_GROUPS.len();
for (id, _) in CALL_GROUPS.clone().into_iter() {
let size_pre = manager.groups.len();
let tracked_calls = manager
.groups
.iter()
.map(|entry| *entry.key())
.collect::<Vec<_>>();
for id in tracked_calls {
if !call_ids.contains(&id) {
CALL_GROUPS.remove(&id);
manager.groups.remove(&id);
}
}
for (_, cg) in CALL_GROUPS.clone().into_iter() {
*cg.show.write().await = !no_users.contains(&cg.call_id);
let call_groups = manager
.groups
.iter()
.map(|entry| entry.value().clone())
.collect::<Vec<_>>();
for call_group in call_groups {
*call_group.show.write().await = !no_users.contains(&call_group.call_id);
}
let size_post = CALL_GROUPS.len();
if size_pre - size_post != 0 {
let size_post = manager.groups.len();
if size_pre != size_post {
log!(
0,
PrintType::Call,
"Cleaned {} calls, {} remaining",
size_pre - size_post,
size_pre.saturating_sub(size_post),
size_post
);
}

View file

@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::calls::call_util;
use crate::calls::{call_util::LiveKitService, error::CallError};
pub struct Caller {
pub user_id: u64,
@ -39,11 +39,7 @@ impl Caller {
pub async fn set_timeout(&self, timeout: i64) {
*self.timeout.write().await = timeout;
}
pub fn create_token(&self) -> String {
if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) {
token
} else {
String::new()
}
pub fn create_token(&self, livekit: &LiveKitService) -> Result<String, CallError> {
livekit.create_token(self.user_id, self.call_id, self.has_admin())
}
}

25
src/calls/error.rs Normal file
View file

@ -0,0 +1,25 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CallError {
#[error("LiveKit is not configured")]
NotConfigured,
#[error("LiveKit request for room {call_id} timed out")]
RequestTimedOut { call_id: uuid::Uuid },
#[error("failed to create room {call_id}: {detail}")]
RoomCreationFailed { call_id: uuid::Uuid, detail: String },
#[error("room {call_id} was not found")]
RoomNotFound { call_id: uuid::Uuid },
#[error("failed to list rooms: {detail}")]
RoomLookupFailed { detail: String },
#[error("failed to remove participant {user_id} from room {call_id}: {detail}")]
ParticipantRemovalFailed {
call_id: uuid::Uuid,
user_id: u64,
detail: String,
},
#[error("failed to update room {call_id} metadata: {detail}")]
MetadataUpdateFailed { call_id: uuid::Uuid, detail: String },
#[error("failed to create access token: {detail}")]
TokenCreationFailed { detail: String },
}

View file

@ -2,3 +2,4 @@ pub mod call_group;
pub mod call_manager;
pub mod call_util;
pub mod caller;
pub mod error;