Update all libraries to the new database format

This commit is contained in:
2026-01-21 22:05:50 -07:00
parent 0e2a8e425b
commit 66168c120f
33 changed files with 1025 additions and 794 deletions
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-fs"
version = "0.0.16"
version = "0.0.17"
edition.workspace = true
rust-version.workspace = true
description = "Filesystem scanner for flix media"
@@ -14,6 +14,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
async-stream = { workspace = true }
either = { workspace = true }
flix-model = { workspace = true }
regex = { workspace = true, features = ["perf", "std"] }
thiserror = { workspace = true }
+27 -151
View File
@@ -4,8 +4,7 @@ use core::pin::Pin;
use std::ffi::OsStr;
use std::path::Path;
use flix_model::id::{CollectionId, MovieId, ShowId};
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
use flix_model::id::CollectionId;
use async_stream::stream;
use tokio::fs;
@@ -14,7 +13,9 @@ use tokio_stream::wrappers::ReadDirStream;
use crate::Error;
use crate::macros::is_image_extension;
use crate::scanner::{generic, movie, show};
use crate::scanner::{
CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, generic, movie, show,
};
/// A collection item
pub type Item = crate::Item<Scanner>;
@@ -22,74 +23,21 @@ pub type Item = crate::Item<Scanner>;
/// The scanner for collections
pub enum Scanner {
/// A scanned collection
Collection {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the collection
id: CollectionId,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Collection(CollectionScan),
/// A scanned movie
Movie {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the movie
id: MovieId,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Movie(MovieScan),
/// A scanned show
Show {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the show
id: ShowId,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Show(ShowScan),
/// A scanned episode
Season {
/// The ID of the show this season belongs to
show: ShowId,
/// The number of this season
season: SeasonNumber,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Season(SeasonScan),
/// A scanned episode
Episode {
/// The ID of the show this episode belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The number(s) of this episode
episode: EpisodeNumbers,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Episode(EpisodeScan),
}
impl From<movie::Scanner> for Scanner {
fn from(value: movie::Scanner) -> Self {
match value {
movie::Scanner::Movie {
parent,
id,
media_file_name,
poster_file_name,
} => Self::Movie {
parent,
id,
media_file_name,
poster_file_name,
},
movie::Scanner::Movie(m) => Self::Movie(m),
}
}
}
@@ -97,37 +45,9 @@ impl From<movie::Scanner> for Scanner {
impl From<show::Scanner> for Scanner {
fn from(value: show::Scanner) -> Self {
match value {
show::Scanner::Show {
parent,
id,
poster_file_name,
} => Self::Show {
parent,
id,
poster_file_name,
},
show::Scanner::Season {
show,
season,
poster_file_name,
} => Self::Season {
show,
season,
poster_file_name,
},
show::Scanner::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
} => Self::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
},
show::Scanner::Show(s) => Self::Show(s),
show::Scanner::Season(s) => Self::Season(s),
show::Scanner::Episode(e) => Self::Episode(e),
}
}
}
@@ -135,57 +55,11 @@ impl From<show::Scanner> for Scanner {
impl From<generic::Scanner> for Scanner {
fn from(value: generic::Scanner) -> Self {
match value {
generic::Scanner::Collection {
parent,
id,
poster_file_name,
} => Self::Collection {
parent,
id,
poster_file_name,
},
generic::Scanner::Movie {
parent,
id,
media_file_name,
poster_file_name,
} => Self::Movie {
parent,
id,
media_file_name,
poster_file_name,
},
generic::Scanner::Show {
parent,
id,
poster_file_name,
} => Self::Show {
parent,
id,
poster_file_name,
},
generic::Scanner::Season {
show,
season,
poster_file_name,
} => Self::Season {
show,
season,
poster_file_name,
},
generic::Scanner::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
} => Self::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
},
generic::Scanner::Collection(c) => Self::Collection(c),
generic::Scanner::Movie(m) => Self::Movie(m),
generic::Scanner::Show(s) => Self::Show(s),
generic::Scanner::Season(s) => Self::Season(s),
generic::Scanner::Episode(e) => Self::Episode(e),
}
}
}
@@ -194,8 +68,8 @@ impl Scanner {
/// Scan a folder for a collection
pub fn scan_collection(
path: &Path,
parent: Option<CollectionId>,
id: CollectionId,
parent_ref: Option<MediaRef<CollectionId>>,
id_ref: MediaRef<CollectionId>,
) -> Pin<Box<impl Stream<Item = Item>>> {
Box::pin(stream!({
let dirs = match fs::read_dir(path).await {
@@ -266,15 +140,17 @@ impl Scanner {
yield Item {
path: path.to_owned(),
event: Ok(Self::Collection {
parent,
id,
event: Ok(Self::Collection(CollectionScan {
parent_ref,
id_ref: id_ref.clone(),
poster_file_name,
}),
})),
};
for subdir in subdirs_to_scan {
for await event in generic::Scanner::scan_detect_folder(&subdir, Some(id)) {
for await event in
generic::Scanner::scan_detect_folder(&subdir, Some(id_ref.clone()))
{
yield event.map(|e| e.into());
}
}
+6 -16
View File
@@ -13,6 +13,7 @@ use tokio_stream::wrappers::ReadDirStream;
use crate::Error;
use crate::macros::{is_image_extension, is_media_extension};
use crate::scanner::{EpisodeScan, MediaRef};
/// An episode item
pub type Item = crate::Item<Scanner>;
@@ -20,25 +21,14 @@ pub type Item = crate::Item<Scanner>;
/// The scanner for epispdes
pub enum Scanner {
/// A scanned episode
Episode {
/// The ID of the show this episode belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The number(s) of this episode
episode: EpisodeNumbers,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Episode(EpisodeScan),
}
impl Scanner {
/// Scan a folder for an episode
pub fn scan_episode(
path: &Path,
show: ShowId,
show_ref: MediaRef<ShowId>,
season: SeasonNumber,
episode: EpisodeNumbers,
) -> impl Stream<Item = Item> {
@@ -135,13 +125,13 @@ impl Scanner {
yield Item {
path: path.to_owned(),
event: Ok(Self::Episode {
show,
event: Ok(Self::Episode(EpisodeScan {
show_ref,
season,
episode,
media_file_name,
poster_file_name,
}),
})),
};
})
}
+61 -162
View File
@@ -6,16 +6,18 @@ use std::path::Path;
use std::sync::OnceLock;
use flix_model::id::{CollectionId, MovieId, RawId, ShowId};
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
use async_stream::stream;
use either::Either;
use regex::Regex;
use tokio::fs;
use tokio_stream::Stream;
use tokio_stream::wrappers::ReadDirStream;
use crate::Error;
use crate::scanner::{collection, movie, show};
use crate::scanner::{
CollectionScan, EpisodeScan, MediaRef, MovieScan, SeasonScan, ShowScan, collection, movie, show,
};
static MEDIA_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
static SEASON_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
@@ -24,116 +26,28 @@ static SEASON_FOLDER_REGEX: OnceLock<Regex> = OnceLock::new();
pub type Item = crate::Item<Scanner>;
/// The scanner for collections
#[derive(Debug)]
pub enum Scanner {
/// A scanned collection
Collection {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the collection
id: CollectionId,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Collection(CollectionScan),
/// A scanned movie
Movie {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the movie
id: MovieId,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Movie(MovieScan),
/// A scanned show
Show {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the show
id: ShowId,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Show(ShowScan),
/// A scanned episode
Season {
/// The ID of the show this season belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Season(SeasonScan),
/// A scanned episode
Episode {
/// The ID of the show this episode belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The number(s) of this episode
episode: EpisodeNumbers,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Episode(EpisodeScan),
}
impl From<collection::Scanner> for Scanner {
fn from(value: collection::Scanner) -> Self {
match value {
collection::Scanner::Collection {
parent,
id,
poster_file_name,
} => Self::Collection {
parent,
id,
poster_file_name,
},
collection::Scanner::Movie {
parent,
id,
media_file_name,
poster_file_name,
} => Self::Movie {
parent,
id,
media_file_name,
poster_file_name,
},
collection::Scanner::Show {
parent,
id,
poster_file_name,
} => Self::Show {
parent,
id,
poster_file_name,
},
collection::Scanner::Season {
show,
season,
poster_file_name,
} => Self::Season {
show,
season,
poster_file_name,
},
collection::Scanner::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
} => Self::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
},
collection::Scanner::Collection(c) => Self::Collection(c),
collection::Scanner::Movie(m) => Self::Movie(m),
collection::Scanner::Show(s) => Self::Show(s),
collection::Scanner::Season(s) => Self::Season(s),
collection::Scanner::Episode(e) => Self::Episode(e),
}
}
}
@@ -141,17 +55,7 @@ impl From<collection::Scanner> for Scanner {
impl From<movie::Scanner> for Scanner {
fn from(value: movie::Scanner) -> Self {
match value {
movie::Scanner::Movie {
parent,
id,
media_file_name,
poster_file_name,
} => Self::Movie {
parent,
id,
media_file_name,
poster_file_name,
},
movie::Scanner::Movie(m) => Self::Movie(m),
}
}
}
@@ -159,42 +63,22 @@ impl From<movie::Scanner> for Scanner {
impl From<show::Scanner> for Scanner {
fn from(value: show::Scanner) -> Self {
match value {
show::Scanner::Show {
parent,
id,
poster_file_name,
} => Self::Show {
parent,
id,
poster_file_name,
},
show::Scanner::Season {
show,
season,
poster_file_name,
} => Self::Season {
show,
season,
poster_file_name,
},
show::Scanner::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
} => Self::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
},
show::Scanner::Show(s) => Self::Show(s),
show::Scanner::Season(s) => Self::Season(s),
show::Scanner::Episode(e) => Self::Episode(e),
}
}
}
impl Scanner {
/// Helper function for stripping allowed numerical prefixes for sorting ("01 - ")
fn strip_numeric_prefix(mut s: &str) -> &str {
while let Some('0'..='9') = s.chars().next() {
s = &s[1..]
}
s.strip_prefix(" - ").unwrap_or(s)
}
/// Detect the type of a folder and call the correct scanner. Use
/// this only for detecting possibly ambiguous media:
/// - Collections
@@ -202,7 +86,7 @@ impl Scanner {
/// - Shows
pub fn scan_detect_folder(
path: &Path,
parent: Option<CollectionId>,
parent: Option<MediaRef<CollectionId>>,
) -> impl Stream<Item = Item> {
enum MediaType {
Collection,
@@ -211,7 +95,7 @@ impl Scanner {
}
let media_folder_re = MEDIA_FOLDER_REGEX.get_or_init(|| {
Regex::new(r"^[[[:alnum:]]' -]+ \([[:digit:]]+\) \[[[:digit:]]+\]$")
Regex::new(r"^[[[:alnum:]]' -]+ \([[:digit:]]+\)( \[[[:digit:]]+\])?$")
.unwrap_or_else(|err| panic!("regex is invalid: {err}"))
});
let season_folder_re = SEASON_FOLDER_REGEX.get_or_init(|| {
@@ -227,16 +111,23 @@ impl Scanner {
return;
};
let Some(Ok(id)) = dir_name
let dir_name = Self::strip_numeric_prefix(dir_name);
// Use the explicit ID ("[X]") if it exists, otherwise parse the folder name
let media_id = if let Some((id_str, _)) = dir_name
.split_once('[')
.and_then(|(_, s)| s.split_once(']'))
.map(|(s, _)| s.parse::<RawId>())
else {
yield Item {
path: path.to_owned(),
event: Err(Error::UnexpectedFolder),
{
let Ok(id) = id_str.parse::<RawId>() else {
yield Item {
path: path.to_owned(),
event: Err(Error::UnexpectedFolder),
};
return;
};
return;
Either::Left(id)
} else {
Either::Right(flix_model::text::normalize_fs_name(dir_name))
};
let media_type: MediaType;
@@ -306,24 +197,32 @@ impl Scanner {
match media_type {
MediaType::Collection => {
for await event in collection::Scanner::scan_collection(
path,
parent,
CollectionId::from_raw(id),
) {
let id = match media_id {
Either::Left(raw) => MediaRef::Id(CollectionId::from_raw(raw)),
Either::Right(slug) => MediaRef::Slug(slug),
};
for await event in collection::Scanner::scan_collection(path, parent, id) {
yield event.map(|e| e.into());
}
}
MediaType::Movie => {
for await event in
movie::Scanner::scan_movie(path, parent, MovieId::from_raw(id))
{
let id = match media_id {
Either::Left(raw) => MediaRef::Id(MovieId::from_raw(raw)),
Either::Right(slug) => MediaRef::Slug(slug),
};
for await event in movie::Scanner::scan_movie(path, parent, id) {
yield event.map(|e| e.into());
}
}
MediaType::Show => {
for await event in show::Scanner::scan_show(path, parent, ShowId::from_raw(id))
{
let id = match media_id {
Either::Left(raw) => MediaRef::Id(ShowId::from_raw(raw)),
Either::Right(slug) => MediaRef::Slug(slug),
};
for await event in show::Scanner::scan_show(path, parent, id) {
yield event.map(|e| e.into());
}
}
+73
View File
@@ -3,6 +3,9 @@
//! The most common scanner to use is [generic::Scanner] which will
//! automatically detect and use the appropriate scanner.
use flix_model::id::{CollectionId, MovieId, ShowId};
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
pub mod library;
pub mod generic;
@@ -14,3 +17,73 @@ pub mod movie;
pub mod episode;
pub mod season;
pub mod show;
/// A reference to a piece of media
#[derive(Debug, Clone)]
pub enum MediaRef<ID> {
/// An explicit ID
Id(ID),
/// A filesystem slug
Slug(String),
}
/// A scanned collection
#[derive(Debug)]
pub struct CollectionScan {
/// The ID of the parent collection (if any)
pub parent_ref: Option<MediaRef<CollectionId>>,
/// The ID of the collection
pub id_ref: MediaRef<CollectionId>,
/// The file name of the poster file
pub poster_file_name: Option<String>,
}
/// A scanned movie
#[derive(Debug)]
pub struct MovieScan {
/// The ID of the parent collection (if any)
pub parent_ref: Option<MediaRef<CollectionId>>,
/// The ID of the movie
pub id_ref: MediaRef<MovieId>,
/// The file name of the media file
pub media_file_name: String,
/// The file name of the poster file
pub poster_file_name: Option<String>,
}
/// A scanned show
#[derive(Debug)]
pub struct ShowScan {
/// The ID of the parent collection (if any)
pub parent_ref: Option<MediaRef<CollectionId>>,
/// The ID of the show
pub id_ref: MediaRef<ShowId>,
/// The file name of the poster file
pub poster_file_name: Option<String>,
}
/// A scanned season
#[derive(Debug)]
pub struct SeasonScan {
/// The ID of the show this season belongs to
pub show_ref: MediaRef<ShowId>,
/// The season this episode belongs to
pub season: SeasonNumber,
/// The file name of the poster file
pub poster_file_name: Option<String>,
}
/// A scanned episode
#[derive(Debug)]
pub struct EpisodeScan {
/// The ID of the show this episode belongs to
pub show_ref: MediaRef<ShowId>,
/// The season this episode belongs to
pub season: SeasonNumber,
/// The number(s) of this episode
pub episode: EpisodeNumbers,
/// The file name of the media file
pub media_file_name: String,
/// The file name of the poster file
pub poster_file_name: Option<String>,
}
+8 -16
View File
@@ -12,6 +12,7 @@ use tokio_stream::wrappers::ReadDirStream;
use crate::Error;
use crate::macros::{is_image_extension, is_media_extension};
use crate::scanner::{MediaRef, MovieScan};
/// An movie item
pub type Item = crate::Item<Scanner>;
@@ -19,24 +20,15 @@ pub type Item = crate::Item<Scanner>;
/// The scanner for movies
pub enum Scanner {
/// A scanned movie
Movie {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the movie
id: MovieId,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Movie(MovieScan),
}
impl Scanner {
/// Scan a folder for a movie
pub fn scan_movie(
path: &Path,
parent: Option<CollectionId>,
id: MovieId,
parent_ref: Option<MediaRef<CollectionId>>,
id_ref: MediaRef<MovieId>,
) -> impl Stream<Item = Item> {
stream!({
let dirs = match fs::read_dir(path).await {
@@ -131,12 +123,12 @@ impl Scanner {
yield Item {
path: path.to_owned(),
event: Ok(Self::Movie {
parent,
id,
event: Ok(Self::Movie(MovieScan {
parent_ref,
id_ref,
media_file_name,
poster_file_name,
}),
})),
};
})
}
+10 -40
View File
@@ -13,53 +13,23 @@ use tokio_stream::wrappers::ReadDirStream;
use crate::Error;
use crate::macros::is_image_extension;
use crate::scanner::episode;
use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, episode};
/// A season item
pub type Item = crate::Item<Scanner>;
/// The scanner for seasons
pub enum Scanner {
/// A scanned season
Season(SeasonScan),
/// A scanned episode
Season {
/// The ID of the show this season belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The file name of the poster file
poster_file_name: Option<String>,
},
/// A scanned episode
Episode {
/// The ID of the show this episode belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The number(s) of this episode
episode: EpisodeNumbers,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Episode(EpisodeScan),
}
impl From<episode::Scanner> for Scanner {
fn from(value: episode::Scanner) -> Self {
match value {
episode::Scanner::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
} => Self::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
},
episode::Scanner::Episode(e) => Self::Episode(e),
}
}
}
@@ -68,7 +38,7 @@ impl Scanner {
/// Scan a folder for a season and its episodes
pub fn scan_season(
path: &Path,
show: ShowId,
show_ref: MediaRef<ShowId>,
season: SeasonNumber,
) -> impl Stream<Item = Item> {
stream!({
@@ -140,11 +110,11 @@ impl Scanner {
yield Item {
path: path.to_owned(),
event: Ok(Self::Season {
show,
event: Ok(Self::Season(SeasonScan {
show_ref: show_ref.clone(),
season,
poster_file_name,
}),
})),
};
for episode_dir in episode_dirs_to_scan {
@@ -207,7 +177,7 @@ impl Scanner {
for await event in episode::Scanner::scan_episode(
&episode_dir,
show,
show_ref.clone(),
season_number,
episode_numbers,
) {
+17 -60
View File
@@ -4,7 +4,7 @@ use std::ffi::OsStr;
use std::path::Path;
use flix_model::id::{CollectionId, ShowId};
use flix_model::numbers::{EpisodeNumbers, SeasonNumber};
use flix_model::numbers::SeasonNumber;
use async_stream::stream;
use tokio::fs;
@@ -13,7 +13,7 @@ use tokio_stream::wrappers::ReadDirStream;
use crate::Error;
use crate::macros::is_image_extension;
use crate::scanner::season;
use crate::scanner::{EpisodeScan, MediaRef, SeasonScan, ShowScan, season};
/// A show item
pub type Item = crate::Item<Scanner>;
@@ -21,63 +21,18 @@ pub type Item = crate::Item<Scanner>;
/// The scanner for shows
pub enum Scanner {
/// A scanned show
Show {
/// The ID of the parent collection (if any)
parent: Option<CollectionId>,
/// The ID of the show
id: ShowId,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Show(ShowScan),
/// A scanned season
Season(SeasonScan),
/// A scanned episode
Season {
/// The ID of the show this season belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The file name of the poster file
poster_file_name: Option<String>,
},
/// A scanned episode
Episode {
/// The ID of the show this episode belongs to
show: ShowId,
/// The season this episode belongs to
season: SeasonNumber,
/// The number(s) of this episode
episode: EpisodeNumbers,
/// The file name of the media file
media_file_name: String,
/// The file name of the poster file
poster_file_name: Option<String>,
},
Episode(EpisodeScan),
}
impl From<season::Scanner> for Scanner {
fn from(value: season::Scanner) -> Self {
match value {
season::Scanner::Season {
show,
season,
poster_file_name,
} => Self::Season {
show,
season,
poster_file_name,
},
season::Scanner::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
} => Self::Episode {
show,
season,
episode,
media_file_name,
poster_file_name,
},
season::Scanner::Season(s) => Self::Season(s),
season::Scanner::Episode(e) => Self::Episode(e),
}
}
}
@@ -86,8 +41,8 @@ impl Scanner {
/// Scan a folder for a show and its seasons/episodes
pub fn scan_show(
path: &Path,
parent: Option<CollectionId>,
id: ShowId,
parent_ref: Option<MediaRef<CollectionId>>,
id_ref: MediaRef<ShowId>,
) -> impl Stream<Item = Item> {
stream!({
let dirs = match fs::read_dir(path).await {
@@ -158,11 +113,11 @@ impl Scanner {
yield Item {
path: path.to_owned(),
event: Ok(Self::Show {
parent,
id,
event: Ok(Self::Show(ShowScan {
parent_ref,
id_ref: id_ref.clone(),
poster_file_name,
}),
})),
};
for season_dir in season_dirs_to_scan {
@@ -185,7 +140,9 @@ impl Scanner {
continue;
};
for await event in season::Scanner::scan_season(&season_dir, id, season_number) {
for await event in
season::Scanner::scan_season(&season_dir, id_ref.clone(), season_number)
{
yield event.map(|e| e.into());
}
}