From 158910a61fdb2712879eef2eaa43f15b8b4bb901 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 16 Mar 2025 21:42:11 +0100 Subject: [PATCH] feat: implement sync group merge, unsync and devices by sync group --- src/db/repository/device.rs | 106 ++++++++++++++++++++++++++++++++++-- src/gpodder/mod.rs | 4 +- src/gpodder/repository.rs | 2 +- 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/db/repository/device.rs b/src/db/repository/device.rs index 2d02c1c..38d5d4c 100644 --- a/src/db/repository/device.rs +++ b/src/db/repository/device.rs @@ -3,7 +3,7 @@ use diesel::prelude::*; use super::SqliteRepository; use crate::{ - db::{self, schema::*}, + db::{self, schema::*, SyncGroup}, gpodder, }; @@ -102,7 +102,58 @@ impl gpodder::DeviceRepository for SqliteRepository { user: &gpodder::User, device_ids: Vec<&str>, ) -> Result { - todo!() + 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( @@ -110,7 +161,23 @@ impl gpodder::DeviceRepository for SqliteRepository { user: &gpodder::User, device_ids: Vec<&str>, ) -> Result<(), gpodder::AuthErr> { - todo!() + 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( @@ -124,7 +191,36 @@ impl gpodder::DeviceRepository for SqliteRepository { fn devices_by_sync_group( &self, user: &gpodder::User, - ) -> Result<(Vec, Vec>), gpodder::AuthErr> { - todo!() + ) -> 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)) } } diff --git a/src/gpodder/mod.rs b/src/gpodder/mod.rs index 7d26b84..c9abd8e 100644 --- a/src/gpodder/mod.rs +++ b/src/gpodder/mod.rs @@ -92,7 +92,7 @@ pub trait DeviceRepository { /// Remove the devices from their respective sync groups fn remove_from_sync_group(&self, user: &User, device_ids: Vec<&str>) -> Result<(), AuthErr>; - /// Return all devices for the user, grouped per sync group + /// Return all device IDs for the user, grouped per sync group /// /// # Returns /// @@ -100,7 +100,7 @@ pub trait DeviceRepository { fn devices_by_sync_group( &self, user: &User, - ) -> Result<(Vec, Vec>), AuthErr>; + ) -> Result<(Vec, Vec>), AuthErr>; } pub trait SubscriptionRepository { diff --git a/src/gpodder/repository.rs b/src/gpodder/repository.rs index 4cc17be..62f860e 100644 --- a/src/gpodder/repository.rs +++ b/src/gpodder/repository.rs @@ -106,7 +106,7 @@ impl GpodderRepository { pub fn devices_by_sync_group( &self, user: &models::User, - ) -> Result<(Vec, Vec>), AuthErr> { + ) -> Result<(Vec, Vec>), AuthErr> { self.store.devices_by_sync_group(user) }