use chrono::{DateTime, Utc}; use diesel::prelude::*; use super::SqliteRepository; use crate::{ db::{self, schema::*, SyncGroup}, gpodder, }; impl From for gpodder::DeviceType { fn from(value: db::DeviceType) -> Self { match value { db::DeviceType::Desktop => Self::Desktop, db::DeviceType::Laptop => Self::Laptop, db::DeviceType::Mobile => Self::Mobile, db::DeviceType::Server => Self::Server, db::DeviceType::Other => Self::Other, } } } impl From for db::DeviceType { fn from(value: gpodder::DeviceType) -> Self { match value { gpodder::DeviceType::Desktop => Self::Desktop, gpodder::DeviceType::Laptop => Self::Laptop, gpodder::DeviceType::Mobile => Self::Mobile, gpodder::DeviceType::Server => Self::Server, gpodder::DeviceType::Other => Self::Other, } } } impl gpodder::DeviceRepository for SqliteRepository { fn devices_for_user( &self, user: &gpodder::User, ) -> Result, gpodder::AuthErr> { Ok(devices::table .select(db::Device::as_select()) .filter(devices::user_id.eq(user.id)) .get_results(&mut self.pool.get()?)? .into_iter() .map(|d| gpodder::Device { id: d.device_id, caption: d.caption, r#type: d.type_.into(), // TODO implement subscription count subscriptions: 0, }) .collect()) } fn update_device_info( &self, user: &gpodder::User, device_id: &str, patch: gpodder::DevicePatch, ) -> Result<(), gpodder::AuthErr> { if let Some(mut device) = devices::table .select(db::Device::as_select()) .filter( devices::user_id .eq(user.id) .and(devices::device_id.eq(device_id)), ) .get_result(&mut self.pool.get()?) .optional()? { if let Some(caption) = patch.caption { device.caption = caption; } if let Some(type_) = patch.r#type { device.type_ = type_.into(); } diesel::update(devices::table.filter(devices::id.eq(device.id))) .set(( devices::caption.eq(&device.caption), devices::type_.eq(&device.type_), )) .execute(&mut self.pool.get()?)?; } else { let device = db::NewDevice { device_id: device_id.to_string(), user_id: user.id, caption: patch.caption.unwrap_or(String::new()), type_: patch.r#type.unwrap_or(gpodder::DeviceType::Other).into(), }; diesel::insert_into(devices::table) .values(device) .execute(&mut self.pool.get()?)?; } Ok(()) } fn merge_sync_groups( &self, user: &gpodder::User, device_ids: Vec<&str>, ) -> Result { let conn = &mut self.pool.get()?; Ok(conn.transaction(|conn| { let devices: Vec<(i64, Option)> = devices::table .select((devices::id, devices::sync_group_id)) .filter( devices::user_id .eq(user.id) .and(devices::device_id.eq_any(device_ids)), ) .get_results(conn)?; let mut sync_group_ids: Vec = devices .iter() .filter_map(|(_, group_id)| *group_id) .collect(); // Remove any duplicates, giving us each sync group ID once sync_group_ids.sort(); sync_group_ids.dedup(); // If any of the devices are already in a sync group, we reuse the first one we find. // Otherwise, we generate a new one. let sync_group_id = if let Some(id) = sync_group_ids.pop() { id } else { db::SyncGroup::new(conn)?.id }; // Move all devices in the other sync groups into the new sync group diesel::update( devices::table.filter(devices::sync_group_id.eq_any(sync_group_ids.iter())), ) .set(devices::sync_group_id.eq(sync_group_id)) .execute(conn)?; // Add the non-synchronized devices into the new sync group let unsynced_device_ids = devices .iter() .filter_map(|(id, group_id)| if group_id.is_none() { Some(id) } else { None }); diesel::update(devices::table.filter(devices::id.eq_any(unsynced_device_ids))) .set(devices::sync_group_id.eq(sync_group_id)) .execute(conn)?; // Remove the other now unused sync groups diesel::delete(sync_groups::table.filter(sync_groups::id.eq_any(sync_group_ids))) .execute(conn)?; Ok::<_, diesel::result::Error>(sync_group_id) })?) } fn remove_from_sync_group( &self, user: &gpodder::User, device_ids: Vec<&str>, ) -> Result<(), gpodder::AuthErr> { let conn = &mut self.pool.get()?; diesel::update( devices::table.filter( devices::user_id .eq(user.id) .and(devices::device_id.eq_any(device_ids)), ), ) .set(devices::sync_group_id.eq(None::)) .execute(conn)?; // This is in a different transaction on purpose, as the success of this removal shouldn't // fail the entire query SyncGroup::remove_unused(conn)?; Ok(()) } fn synchronize_sync_group( &self, group_id: i64, time_changed: DateTime, ) -> Result<(), gpodder::AuthErr> { todo!() } fn devices_by_sync_group( &self, user: &gpodder::User, ) -> Result<(Vec, Vec>), gpodder::AuthErr> { let mut not_synchronized = Vec::new(); let mut synchronized = Vec::new(); let conn = &mut self.pool.get()?; let mut devices = devices::table .select((devices::device_id, devices::sync_group_id)) .filter(devices::user_id.eq(user.id)) .order(devices::sync_group_id) .load_iter::<(String, Option), _>(conn)?; let mut cur_group = &mut not_synchronized; let mut cur_group_id: Option = None; while let Some((device_id, group_id)) = devices.next().transpose()? { if group_id != cur_group_id { if group_id.is_none() { cur_group = &mut not_synchronized; } else { synchronized.push(Vec::new()); let index = synchronized.len() - 1; cur_group = &mut synchronized[index]; } cur_group_id = group_id; } cur_group.push(device_id); } Ok((not_synchronized, synchronized)) } }