use crate::stats::enums::stats_event::StatsEvent;
use crate::tracker::enums::updates_action::UpdatesAction;
use crate::tracker::structs::info_hash::InfoHash;
use crate::tracker::structs::torrent_tracker::TorrentTracker;
use crate::tracker::structs::user_entry_item::UserEntryItem;
use crate::tracker::structs::user_id::UserId;
use log::{
error,
info
};
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
impl TorrentTracker {
pub async fn load_users(&self, tracker: Arc<TorrentTracker>)
{
if let Ok(users) = self.sqlx.load_users(tracker).await {
info!("Loaded {users} users");
}
}
pub async fn save_users(&self, tracker: Arc<TorrentTracker>, users: BTreeMap<UserId, (UserEntryItem, UpdatesAction)>) -> Result<(), ()>
{
let users_len = users.len();
if let Ok(()) = self.sqlx.save_users(tracker, users).await {
info!("[SYNC USERS] Synced {users_len} users");
Ok(())
} else {
error!("[SYNC USERS] Unable to sync {users_len} users");
Err(())
}
}
pub fn add_user(&self, user_id: UserId, user_entry_item: UserEntryItem) -> bool
{
let mut lock = self.users.write();
match lock.entry(user_id) {
Entry::Vacant(v) => {
self.update_stats(StatsEvent::Users, 1);
v.insert(user_entry_item);
true
}
Entry::Occupied(mut o) => {
o.insert(user_entry_item);
false
}
}
}
pub fn add_user_active_torrent(&self, user_id: UserId, info_hash: InfoHash) -> bool
{
let mut lock = self.users.write();
match lock.entry(user_id) {
Entry::Vacant(_) => {
false
}
Entry::Occupied(mut o) => {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
o.get_mut().torrents_active.insert(info_hash, timestamp);
true
}
}
}
pub fn get_user(&self, id: UserId) -> Option<UserEntryItem>
{
let lock = self.users.read_recursive();
lock.get(&id).cloned()
}
pub fn get_users(&self) -> BTreeMap<UserId, UserEntryItem>
{
let lock = self.users.read_recursive();
lock.clone()
}
pub fn remove_user(&self, user_id: UserId) -> Option<UserEntryItem>
{
let mut lock = self.users.write();
if let Some(data) = lock.remove(&user_id) {
self.update_stats(StatsEvent::Users, -1);
Some(data)
} else {
None
}
}
pub fn remove_user_active_torrent(&self, user_id: UserId, info_hash: InfoHash) -> bool
{
let mut lock = self.users.write();
match lock.entry(user_id) {
Entry::Vacant(_) => {
false
}
Entry::Occupied(mut o) => {
o.get_mut().torrents_active.remove(&info_hash).is_some()
}
}
}
pub fn check_user_key(&self, key: UserId) -> Option<UserId>
{
let lock = self.users.read_recursive();
for (user_id, user_entry_item) in lock.iter() {
if user_entry_item.key == key {
return Some(*user_id);
}
}
None
}
pub fn clean_user_active_torrents(&self, peer_timeout: Duration)
{
let current_time = SystemTime::now();
let timeout_threshold = current_time.duration_since(UNIX_EPOCH).unwrap().as_secs() - peer_timeout.as_secs();
let remove_active_torrents = {
let lock = self.users.read_recursive();
info!("[USERS] Scanning {} users with dead active torrents", lock.len());
let mut to_remove = Vec::new();
for (user_id, user_entry_item) in lock.iter() {
for (info_hash, &updated) in &user_entry_item.torrents_active {
if updated < timeout_threshold {
to_remove.push((*user_id, *info_hash));
}
}
}
to_remove
};
let torrents_cleaned = remove_active_torrents.len() as u64;
if !remove_active_torrents.is_empty() {
let mut lock = self.users.write();
for (user_id, info_hash) in remove_active_torrents {
if let Entry::Occupied(mut o) = lock.entry(user_id) {
o.get_mut().torrents_active.remove(&info_hash);
}
}
}
info!("[USERS] Removed {torrents_cleaned} active torrents in users");
}
}