Skip to content

Commit 161970e

Browse files
authored
chore: count complete total episodes instead of relying on ordering and counting latest one (#2214)
1 parent fc13e00 commit 161970e

60 files changed

Lines changed: 1296 additions & 515 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/podfetch-domain/src/audiobookshelf/listening_session.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub trait ListeningSessionRepository: Send + Sync {
2424
type Error;
2525

2626
fn create(&self, session: ListeningSession) -> Result<ListeningSession, Self::Error>;
27-
fn list_for_user(&self, user_id: Uuid, limit: i64)
28-
-> Result<Vec<ListeningSession>, Self::Error>;
27+
fn list_for_user(
28+
&self,
29+
user_id: Uuid,
30+
limit: i64,
31+
) -> Result<Vec<ListeningSession>, Self::Error>;
2932
}

crates/podfetch-domain/src/podcast_episode.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ pub trait PodcastEpisodeRepository: Send + Sync {
103103
podcast_id: Uuid,
104104
) -> Result<usize, Self::Error>;
105105

106+
// Get total episode count for a podcast (for auto-padding episode numbers)
107+
fn get_total_episode_count(&self, podcast_id: Uuid) -> Result<usize, Self::Error>;
108+
106109
// Get last N episodes by date
107110
fn get_last_n_episodes(
108111
&self,

crates/podfetch-domain/src/podcast_episode_chapter.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub trait PodcastEpisodeChapterRepository: Send + Sync {
2929

3030
fn upsert(&self, chapter: UpsertPodcastEpisodeChapter) -> Result<(), Self::Error>;
3131

32-
fn get_by_episode_id(&self, episode_id: Uuid)
33-
-> Result<Vec<PodcastEpisodeChapter>, Self::Error>;
32+
fn get_by_episode_id(
33+
&self,
34+
episode_id: Uuid,
35+
) -> Result<Vec<PodcastEpisodeChapter>, Self::Error>;
3436
}

crates/podfetch-domain/src/podcast_episode_transcript.rs

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,32 @@ pub trait PodcastEpisodeTranscriptRepository: Send + Sync {
6464
type Error;
6565
/// Upsert keyed on (episode_id, original_url); returns the row's id.
6666
fn upsert(&self, transcript: UpsertTranscript) -> Result<Uuid, Self::Error>;
67-
fn get_by_episode_id(&self, episode_id: Uuid) -> Result<Vec<PodcastEpisodeTranscript>, Self::Error>;
67+
fn get_by_episode_id(
68+
&self,
69+
episode_id: Uuid,
70+
) -> Result<Vec<PodcastEpisodeTranscript>, Self::Error>;
6871
/// Every transcript row across all episodes. Used by `reparse_all`, which
6972
/// needs to walk the whole table rather than a single episode's rows.
7073
fn get_all(&self) -> Result<Vec<PodcastEpisodeTranscript>, Self::Error>;
7174
fn get_by_id(&self, id: Uuid) -> Result<Option<PodcastEpisodeTranscript>, Self::Error>;
7275
fn set_file_path(&self, id: Uuid, file_path: &str) -> Result<(), Self::Error>;
73-
fn set_status(&self, id: Uuid, status: TranscriptStatus, error: Option<&str>) -> Result<(), Self::Error>;
74-
fn set_preferred(&self, episode_id: Uuid, preferred_id: Option<Uuid>) -> Result<(), Self::Error>;
76+
fn set_status(
77+
&self,
78+
id: Uuid,
79+
status: TranscriptStatus,
80+
error: Option<&str>,
81+
) -> Result<(), Self::Error>;
82+
fn set_preferred(
83+
&self,
84+
episode_id: Uuid,
85+
preferred_id: Option<Uuid>,
86+
) -> Result<(), Self::Error>;
7587
/// Deletes the transcript's old segments and inserts the new ones in one transaction.
76-
fn replace_segments(&self, transcript_id: Uuid, segments: &[TranscriptSegment]) -> Result<(), Self::Error>;
88+
fn replace_segments(
89+
&self,
90+
transcript_id: Uuid,
91+
segments: &[TranscriptSegment],
92+
) -> Result<(), Self::Error>;
7793
fn get_segments(&self, transcript_id: Uuid) -> Result<Vec<TranscriptSegment>, Self::Error>;
7894
fn search(
7995
&self,
@@ -106,7 +122,12 @@ pub trait TranscriptionJobRepository: Send + Sync {
106122
/// Enqueues a job; returns Ok(None) if one already exists for the episode (UNIQUE).
107123
fn enqueue(&self, episode_id: Uuid) -> Result<Option<TranscriptionJob>, Self::Error>;
108124
fn next_pending(&self) -> Result<Option<TranscriptionJob>, Self::Error>;
109-
fn set_status(&self, id: Uuid, status: TranscriptionJobStatus, error: Option<&str>) -> Result<(), Self::Error>;
125+
fn set_status(
126+
&self,
127+
id: Uuid,
128+
status: TranscriptionJobStatus,
129+
error: Option<&str>,
130+
) -> Result<(), Self::Error>;
110131
fn increment_attempts(&self, id: Uuid) -> Result<i32, Self::Error>;
111132
fn reset_running_to_pending(&self) -> Result<usize, Self::Error>;
112133
fn get_by_episode_id(&self, episode_id: Uuid) -> Result<Option<TranscriptionJob>, Self::Error>;
@@ -188,8 +209,14 @@ mod tests {
188209

189210
#[test]
190211
fn transcript_source_from_str() {
191-
assert_eq!(TranscriptSource::from_str("feed"), Some(TranscriptSource::Feed));
192-
assert_eq!(TranscriptSource::from_str("generated"), Some(TranscriptSource::Generated));
212+
assert_eq!(
213+
TranscriptSource::from_str("feed"),
214+
Some(TranscriptSource::Feed)
215+
);
216+
assert_eq!(
217+
TranscriptSource::from_str("generated"),
218+
Some(TranscriptSource::Generated)
219+
);
193220
assert_eq!(TranscriptSource::from_str("unknown"), None);
194221
}
195222

@@ -211,10 +238,22 @@ mod tests {
211238

212239
#[test]
213240
fn transcript_status_from_str() {
214-
assert_eq!(TranscriptStatus::from_str("pending"), Some(TranscriptStatus::Pending));
215-
assert_eq!(TranscriptStatus::from_str("downloaded"), Some(TranscriptStatus::Downloaded));
216-
assert_eq!(TranscriptStatus::from_str("parsed"), Some(TranscriptStatus::Parsed));
217-
assert_eq!(TranscriptStatus::from_str("failed"), Some(TranscriptStatus::Failed));
241+
assert_eq!(
242+
TranscriptStatus::from_str("pending"),
243+
Some(TranscriptStatus::Pending)
244+
);
245+
assert_eq!(
246+
TranscriptStatus::from_str("downloaded"),
247+
Some(TranscriptStatus::Downloaded)
248+
);
249+
assert_eq!(
250+
TranscriptStatus::from_str("parsed"),
251+
Some(TranscriptStatus::Parsed)
252+
);
253+
assert_eq!(
254+
TranscriptStatus::from_str("failed"),
255+
Some(TranscriptStatus::Failed)
256+
);
218257
assert_eq!(TranscriptStatus::from_str("unknown"), None);
219258
}
220259

@@ -241,10 +280,22 @@ mod tests {
241280

242281
#[test]
243282
fn transcription_job_status_from_str() {
244-
assert_eq!(TranscriptionJobStatus::from_str("pending"), Some(TranscriptionJobStatus::Pending));
245-
assert_eq!(TranscriptionJobStatus::from_str("running"), Some(TranscriptionJobStatus::Running));
246-
assert_eq!(TranscriptionJobStatus::from_str("done"), Some(TranscriptionJobStatus::Done));
247-
assert_eq!(TranscriptionJobStatus::from_str("failed"), Some(TranscriptionJobStatus::Failed));
283+
assert_eq!(
284+
TranscriptionJobStatus::from_str("pending"),
285+
Some(TranscriptionJobStatus::Pending)
286+
);
287+
assert_eq!(
288+
TranscriptionJobStatus::from_str("running"),
289+
Some(TranscriptionJobStatus::Running)
290+
);
291+
assert_eq!(
292+
TranscriptionJobStatus::from_str("done"),
293+
Some(TranscriptionJobStatus::Done)
294+
);
295+
assert_eq!(
296+
TranscriptionJobStatus::from_str("failed"),
297+
Some(TranscriptionJobStatus::Failed)
298+
);
248299
assert_eq!(TranscriptionJobStatus::from_str("unknown"), None);
249300
}
250301

@@ -257,7 +308,10 @@ mod tests {
257308
TranscriptionJobStatus::Failed,
258309
];
259310
for variant in variants {
260-
assert_eq!(TranscriptionJobStatus::from_str(variant.as_str()), Some(variant));
311+
assert_eq!(
312+
TranscriptionJobStatus::from_str(variant.as_str()),
313+
Some(variant)
314+
);
261315
}
262316
}
263317
}

crates/podfetch-domain/src/tag.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ pub trait TagRepository: Send + Sync {
6060

6161
fn create(&self, tag: Tag) -> Result<Tag, Self::Error>;
6262
fn get_tags(&self, user_id: Uuid) -> Result<Vec<Tag>, Self::Error>;
63-
fn get_tags_of_podcast(&self, podcast_id: Uuid, user_id: Uuid) -> Result<Vec<Tag>, Self::Error>;
63+
fn get_tags_of_podcast(&self, podcast_id: Uuid, user_id: Uuid)
64+
-> Result<Vec<Tag>, Self::Error>;
6465
fn get_tag_by_id_and_user_id(
6566
&self,
6667
tag_id: &str,

crates/podfetch-persistence/src/adapters.rs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,11 @@ impl TagRepository for TagRepositoryImpl {
755755
self.inner.get_tags(user_id).map_err(Into::into)
756756
}
757757

758-
fn get_tags_of_podcast(&self, podcast_id: Uuid, user_id: Uuid) -> Result<Vec<Tag>, Self::Error> {
758+
fn get_tags_of_podcast(
759+
&self,
760+
podcast_id: Uuid,
761+
user_id: Uuid,
762+
) -> Result<Vec<Tag>, Self::Error> {
759763
self.inner
760764
.get_tags_of_podcast(podcast_id, user_id)
761765
.map_err(Into::into)
@@ -1229,7 +1233,10 @@ impl PodcastEpisodeTranscriptRepository for PodcastEpisodeTranscriptRepositoryIm
12291233
self.inner.upsert(transcript).map_err(Into::into)
12301234
}
12311235

1232-
fn get_by_episode_id(&self, episode_id: Uuid) -> Result<Vec<PodcastEpisodeTranscript>, Self::Error> {
1236+
fn get_by_episode_id(
1237+
&self,
1238+
episode_id: Uuid,
1239+
) -> Result<Vec<PodcastEpisodeTranscript>, Self::Error> {
12331240
self.inner.get_by_episode_id(episode_id).map_err(Into::into)
12341241
}
12351242

@@ -1245,16 +1252,33 @@ impl PodcastEpisodeTranscriptRepository for PodcastEpisodeTranscriptRepositoryIm
12451252
self.inner.set_file_path(id, file_path).map_err(Into::into)
12461253
}
12471254

1248-
fn set_status(&self, id: Uuid, status: TranscriptStatus, error: Option<&str>) -> Result<(), Self::Error> {
1255+
fn set_status(
1256+
&self,
1257+
id: Uuid,
1258+
status: TranscriptStatus,
1259+
error: Option<&str>,
1260+
) -> Result<(), Self::Error> {
12491261
self.inner.set_status(id, status, error).map_err(Into::into)
12501262
}
12511263

1252-
fn set_preferred(&self, episode_id: Uuid, preferred_id: Option<Uuid>) -> Result<(), Self::Error> {
1253-
self.inner.set_preferred(episode_id, preferred_id).map_err(Into::into)
1264+
fn set_preferred(
1265+
&self,
1266+
episode_id: Uuid,
1267+
preferred_id: Option<Uuid>,
1268+
) -> Result<(), Self::Error> {
1269+
self.inner
1270+
.set_preferred(episode_id, preferred_id)
1271+
.map_err(Into::into)
12541272
}
12551273

1256-
fn replace_segments(&self, transcript_id: Uuid, segments: &[TranscriptSegment]) -> Result<(), Self::Error> {
1257-
self.inner.replace_segments(transcript_id, segments).map_err(Into::into)
1274+
fn replace_segments(
1275+
&self,
1276+
transcript_id: Uuid,
1277+
segments: &[TranscriptSegment],
1278+
) -> Result<(), Self::Error> {
1279+
self.inner
1280+
.replace_segments(transcript_id, segments)
1281+
.map_err(Into::into)
12581282
}
12591283

12601284
fn get_segments(&self, transcript_id: Uuid) -> Result<Vec<TranscriptSegment>, Self::Error> {
@@ -1304,7 +1328,12 @@ impl TranscriptionJobRepository for TranscriptionJobRepositoryImpl {
13041328
self.inner.next_pending().map_err(Into::into)
13051329
}
13061330

1307-
fn set_status(&self, id: Uuid, status: TranscriptionJobStatus, error: Option<&str>) -> Result<(), Self::Error> {
1331+
fn set_status(
1332+
&self,
1333+
id: Uuid,
1334+
status: TranscriptionJobStatus,
1335+
error: Option<&str>,
1336+
) -> Result<(), Self::Error> {
13081337
self.inner.set_status(id, status, error).map_err(Into::into)
13091338
}
13101339

crates/podfetch-persistence/src/device.rs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,12 @@ impl DeviceRepository for DieselDeviceRepository {
220220
.filter(
221221
kind.eq(device_kind::CHROMECAST_SHARED)
222222
.or(kind.eq(device_kind::MOPIDY_SHARED))
223-
.or(kind.eq(device_kind::CHROMECAST_PERSONAL).and(user_id.eq(&viewer)))
224-
.or(kind.eq(device_kind::MOPIDY_PERSONAL).and(user_id.eq(&viewer))),
223+
.or(kind
224+
.eq(device_kind::CHROMECAST_PERSONAL)
225+
.and(user_id.eq(&viewer)))
226+
.or(kind
227+
.eq(device_kind::MOPIDY_PERSONAL)
228+
.and(user_id.eq(&viewer))),
225229
)
226230
.load::<DeviceEntity>(&mut conn)
227231
.map(|items| items.into_iter().map(Into::into).collect())
@@ -231,7 +235,10 @@ impl DeviceRepository for DieselDeviceRepository {
231235
fn find_by_id(&self, id_to_find: Uuid) -> Result<Option<Device>, Self::Error> {
232236
use self::devices::dsl::*;
233237
let mut conn = self.database.connection()?;
234-
match devices.filter(id.eq(id_to_find.to_string())).first::<DeviceEntity>(&mut conn) {
238+
match devices
239+
.filter(id.eq(id_to_find.to_string()))
240+
.first::<DeviceEntity>(&mut conn)
241+
{
235242
Ok(entity) => Ok(Some(entity.into())),
236243
Err(diesel::result::Error::NotFound) => Ok(None),
237244
Err(e) => Err(e.into()),
@@ -241,7 +248,9 @@ impl DeviceRepository for DieselDeviceRepository {
241248
fn delete_by_id(&self, id_to_delete: Uuid) -> Result<usize, Self::Error> {
242249
use self::devices::dsl::*;
243250
let mut conn = self.database.connection()?;
244-
diesel::delete(devices.filter(id.eq(id_to_delete.to_string()))).execute(&mut conn).map_err(Into::into)
251+
diesel::delete(devices.filter(id.eq(id_to_delete.to_string())))
252+
.execute(&mut conn)
253+
.map_err(Into::into)
245254
}
246255
}
247256

@@ -267,7 +276,11 @@ mod mopidy_persistence_tests {
267276

268277
#[derive(diesel::Insertable)]
269278
#[diesel(table_name = seed_schema::users)]
270-
struct SeedUser { id: String, username: String, role: String }
279+
struct SeedUser {
280+
id: String,
281+
username: String,
282+
role: String,
283+
}
271284

272285
fn seed_user() -> Uuid {
273286
use seed_schema::users;
@@ -307,17 +320,28 @@ mod mopidy_persistence_tests {
307320
let viewer = seed_user();
308321

309322
let created = repo
310-
.create(mopidy_device(owner, device_kind::MOPIDY_SHARED, "http://m.local:6680"))
323+
.create(mopidy_device(
324+
owner,
325+
device_kind::MOPIDY_SHARED,
326+
"http://m.local:6680",
327+
))
311328
.expect("create mopidy device");
312329
assert_eq!(created.base_url.as_deref(), Some("http://m.local:6680"));
313330

314331
let castable = repo.list_castable_for_user(viewer).expect("list castable");
315332
assert!(castable.iter().any(|d| d.id == created.id));
316333

317-
let found = repo.find_by_id(created.id.unwrap()).expect("find").expect("present");
334+
let found = repo
335+
.find_by_id(created.id.unwrap())
336+
.expect("find")
337+
.expect("present");
318338
assert_eq!(found.base_url, created.base_url);
319339
assert_eq!(repo.delete_by_id(created.id.unwrap()).expect("delete"), 1);
320-
assert!(repo.find_by_id(created.id.unwrap()).expect("find again").is_none());
340+
assert!(
341+
repo.find_by_id(created.id.unwrap())
342+
.expect("find again")
343+
.is_none()
344+
);
321345
}
322346

323347
#[test]
@@ -328,18 +352,26 @@ mod mopidy_persistence_tests {
328352
let other = seed_user();
329353

330354
let created = repo
331-
.create(mopidy_device(owner, device_kind::MOPIDY_PERSONAL, "http://personal.local:6680"))
355+
.create(mopidy_device(
356+
owner,
357+
device_kind::MOPIDY_PERSONAL,
358+
"http://personal.local:6680",
359+
))
332360
.expect("create personal mopidy device");
333361

334362
// Owner A sees their own personal device.
335-
let castable_owner = repo.list_castable_for_user(owner).expect("list castable owner");
363+
let castable_owner = repo
364+
.list_castable_for_user(owner)
365+
.expect("list castable owner");
336366
assert!(
337367
castable_owner.iter().any(|d| d.id == created.id),
338368
"owner should see their own personal device"
339369
);
340370

341371
// A different user B must NOT see A's personal device.
342-
let castable_other = repo.list_castable_for_user(other).expect("list castable other");
372+
let castable_other = repo
373+
.list_castable_for_user(other)
374+
.expect("list castable other");
343375
assert!(
344376
!castable_other.iter().any(|d| d.id == created.id),
345377
"personal device must not be visible to a different user"

crates/podfetch-persistence/src/device_sync_group.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ impl DeviceSyncGroupRepository for DieselDeviceSyncGroupRepository {
8080
let mut connection = self.database.connection()?;
8181

8282
diesel::delete(
83-
dsg_dsl::device_sync_groups
84-
.filter(dsg_dsl::user_id.eq(user_id_to_replace.to_string())),
83+
dsg_dsl::device_sync_groups.filter(dsg_dsl::user_id.eq(user_id_to_replace.to_string())),
8584
)
8685
.execute(&mut connection)
8786
.map_err(PersistenceError::from)?;

0 commit comments

Comments
 (0)