Compare commits

...

2 Commits

9 changed files with 156 additions and 8 deletions

View File

@ -24,6 +24,9 @@ create table devices (
user_id bigint not null
references users (id)
on delete cascade,
sync_group_id bigint
references sync_group (id)
on delete set null,
caption text not null,
type text not null,
@ -31,6 +34,10 @@ create table devices (
unique (user_id, device_id)
);
create table sync_groups (
id integer primary key not null
);
create table device_subscriptions (
id integer primary key not null,

View File

@ -5,10 +5,12 @@ mod schema;
use diesel::connection::InstrumentationEvent;
use diesel::r2d2::CustomizeConnection;
use diesel::Connection;
pub use models::device::{Device, DeviceType, NewDevice};
pub use models::device_subscription::{DeviceSubscription, NewDeviceSubscription};
pub use models::episode_action::{ActionType, EpisodeAction, NewEpisodeAction};
pub use models::session::Session;
pub use models::sync_group::SyncGroup;
pub use models::user::{NewUser, User};
pub use repository::SqliteRepository;

View File

@ -21,6 +21,7 @@ pub struct Device {
pub user_id: i64,
pub caption: String,
pub type_: DeviceType,
pub sync_group_id: Option<i64>,
}
#[derive(Deserialize, Insertable)]

View File

@ -2,4 +2,5 @@ pub mod device;
pub mod device_subscription;
pub mod episode_action;
pub mod session;
pub mod sync_group;
pub mod user;

View File

@ -0,0 +1,33 @@
use diesel::{
dsl::{exists, not},
prelude::*,
};
use crate::db::schema::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = sync_groups)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct SyncGroup {
pub id: i64,
}
impl SyncGroup {
pub fn new(conn: &mut SqliteConnection) -> QueryResult<Self> {
diesel::insert_into(sync_groups::table)
.default_values()
.returning(SyncGroup::as_returning())
.get_result(conn)
}
pub fn remove_unused(conn: &mut SqliteConnection) -> QueryResult<usize> {
diesel::delete(
sync_groups::table.filter(not(exists(
devices::table
.select(1.into_sql::<diesel::sql_types::Integer>())
.filter(devices::sync_group_id.eq(sync_groups::id.nullable())),
))),
)
.execute(conn)
}
}

View File

@ -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<i64, gpodder::AuthErr> {
todo!()
let conn = &mut self.pool.get()?;
Ok(conn.transaction(|conn| {
let devices: Vec<(i64, Option<i64>)> = 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<i64> = 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::<i64>))
.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<gpodder::Device>, Vec<Vec<gpodder::Device>>), gpodder::AuthErr> {
todo!()
) -> Result<(Vec<String>, Vec<Vec<String>>), 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<i64>), _>(conn)?;
let mut cur_group = &mut not_synchronized;
let mut cur_group_id: Option<i64> = 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))
}
}

View File

@ -15,6 +15,7 @@ diesel::table! {
id -> BigInt,
device_id -> Text,
user_id -> BigInt,
sync_group_id -> Nullable<BigInt>,
caption -> Text,
#[sql_name = "type"]
type_ -> Text,
@ -45,6 +46,12 @@ diesel::table! {
}
}
diesel::table! {
sync_groups (id) {
id -> BigInt,
}
}
diesel::table! {
users (id) {
id -> BigInt,
@ -64,5 +71,6 @@ diesel::allow_tables_to_appear_in_same_query!(
devices,
episode_actions,
sessions,
sync_groups,
users,
);

View File

@ -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<Device>, Vec<Vec<Device>>), AuthErr>;
) -> Result<(Vec<String>, Vec<Vec<String>>), AuthErr>;
}
pub trait SubscriptionRepository {

View File

@ -106,7 +106,7 @@ impl GpodderRepository {
pub fn devices_by_sync_group(
&self,
user: &models::User,
) -> Result<(Vec<models::Device>, Vec<Vec<models::Device>>), AuthErr> {
) -> Result<(Vec<String>, Vec<Vec<String>>), AuthErr> {
self.store.devices_by_sync_group(user)
}