use ::bson::{doc, Document};
use ::mongodb::{options::IndexOptions, IndexModel};
use derive_more::Display;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
#[display("{}", self.as_str())]
pub enum CollectionName {
Media,
MediaFiles,
Chapters,
WatchedLocations,
Speakers,
Persons,
UserTags,
SceneAnnotations,
#[cfg(feature = "audio")]
AudioFacets,
#[cfg(feature = "audio")]
AudioTracks,
#[cfg(feature = "audio")]
AudioSegments,
#[cfg(feature = "video")]
VideoFacets,
#[cfg(feature = "video")]
VideoTracks,
#[cfg(feature = "video")]
Scenes,
#[cfg(feature = "video")]
Keyframes,
#[cfg(feature = "subtitle")]
SubtitleFacets,
#[cfg(feature = "subtitle")]
SubtitleTracks,
#[cfg(feature = "subtitle")]
SubtitleCues,
#[cfg(feature = "subtitle")]
SubtitleTrackVttRegions,
#[cfg(feature = "subtitle")]
SubtitleTrackVttStyles,
#[cfg(feature = "subtitle")]
SubtitleTrackAssStyles,
#[cfg(feature = "subtitle")]
SubtitleTrackLrcMetadata,
#[cfg(feature = "subtitle")]
SubtitleCueLrcWords,
#[cfg(feature = "subtitle")]
SubtitleTrackTtmlRegions,
#[cfg(feature = "subtitle")]
SubtitleTrackTtmlStyles,
#[cfg(feature = "subtitle")]
SubtitleTrackSamiStyles,
#[cfg(feature = "subtitle")]
SubtitleTrackVobSubPalettes,
}
impl CollectionName {
pub const fn as_str(self) -> &'static str {
match self {
Self::Media => "media",
Self::MediaFiles => "media_files",
Self::Chapters => "chapters",
Self::WatchedLocations => "watched_locations",
Self::Speakers => "speakers",
Self::Persons => "persons",
Self::UserTags => "user_tags",
Self::SceneAnnotations => "scene_annotations",
#[cfg(feature = "audio")]
Self::AudioFacets => "audio_facets",
#[cfg(feature = "audio")]
Self::AudioTracks => "audio_tracks",
#[cfg(feature = "audio")]
Self::AudioSegments => "audio_segments",
#[cfg(feature = "video")]
Self::VideoFacets => "video_facets",
#[cfg(feature = "video")]
Self::VideoTracks => "video_tracks",
#[cfg(feature = "video")]
Self::Scenes => "scenes",
#[cfg(feature = "video")]
Self::Keyframes => "keyframes",
#[cfg(feature = "subtitle")]
Self::SubtitleFacets => "subtitle_facets",
#[cfg(feature = "subtitle")]
Self::SubtitleTracks => "subtitle_tracks",
#[cfg(feature = "subtitle")]
Self::SubtitleCues => "subtitle_cues",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackVttRegions => "subtitle_track_vtt_regions",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackVttStyles => "subtitle_track_vtt_styles",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackAssStyles => "subtitle_track_ass_styles",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackLrcMetadata => "subtitle_track_lrc_metadata",
#[cfg(feature = "subtitle")]
Self::SubtitleCueLrcWords => "subtitle_cue_lrc_words",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackTtmlRegions => "subtitle_track_ttml_regions",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackTtmlStyles => "subtitle_track_ttml_styles",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackSamiStyles => "subtitle_track_sami_styles",
#[cfg(feature = "subtitle")]
Self::SubtitleTrackVobSubPalettes => "subtitle_track_vob_sub_palettes",
}
}
}
fn unique_on(keys: Document, name: &str) -> IndexModel {
IndexModel::builder()
.keys(keys)
.options(
IndexOptions::builder()
.unique(true)
.name(name.to_owned())
.build(),
)
.build()
}
fn index_on(keys: Document, name: &str) -> IndexModel {
IndexModel::builder()
.keys(keys)
.options(IndexOptions::builder().name(name.to_owned()).build())
.build()
}
pub fn media_indexes() -> Vec<IndexModel> {
vec![
unique_on(doc! { "checksum": 1 }, "media_checksum_unique"),
index_on(doc! { "kind": 1 }, "media_kind"),
index_on(doc! { "error_flags": 1 }, "media_error_flags"),
index_on(doc! { "capture_date": 1 }, "media_capture_date"),
]
}
pub fn media_file_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "media_id": 1 }, "media_file_media_id"),
index_on(
doc! { "watched_location_id": 1 },
"media_file_watched_location_id",
),
]
}
pub fn chapter_indexes() -> Vec<IndexModel> {
use ::bson::doc as bdoc;
use ::mongodb::options::Collation;
vec![
index_on(doc! { "media_id": 1 }, "chapter_media_id"),
IndexModel::builder()
.keys(doc! { "media_id": 1, "index": 1 })
.options(
IndexOptions::builder()
.name("chapter_media_id_index_unique".to_owned())
.unique(true)
.build(),
)
.build(),
IndexModel::builder()
.keys(bdoc! { "title": 1 })
.options(
IndexOptions::builder()
.name("chapter_title_ci".to_owned())
.collation(
Collation::builder()
.locale("en".to_owned())
.strength(::mongodb::options::CollationStrength::Secondary)
.build(),
)
.build(),
)
.build(),
]
}
pub fn watched_location_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "volume": 1 }, "watched_volume"),
index_on(doc! { "enabled": 1 }, "watched_enabled"),
]
}
pub fn speaker_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "audio_track_id": 1 }, "speakers_audio_track_id"),
index_on(doc! { "person_id": 1 }, "speakers_person_id"),
]
}
pub fn person_indexes() -> Vec<IndexModel> {
vec![index_on(
doc! {
"voiceprint.provenance.model_name": 1,
"voiceprint.provenance.model_version": 1,
},
"persons_voiceprint_model",
)]
}
pub fn user_tag_indexes() -> Vec<IndexModel> {
vec![index_on(doc! { "name": 1 }, "user_tags_name")]
}
pub fn scene_annotation_indexes() -> Vec<IndexModel> {
vec![
unique_on(doc! { "scene_id": 1 }, "scene_annotations_scene_id_unique"),
index_on(doc! { "favorite": 1 }, "scene_annotations_favorite"),
index_on(doc! { "rating": 1 }, "scene_annotations_rating"),
]
}
#[cfg(feature = "audio")]
pub fn audio_facet_indexes() -> Vec<IndexModel> {
vec![unique_on(
doc! { "media_id": 1 },
"audio_facets_media_id_unique",
)]
}
#[cfg(feature = "audio")]
pub fn audio_track_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "audio_id": 1 }, "audio_tracks_audio_id"),
index_on(doc! { "is_primary": 1 }, "audio_tracks_primary"),
index_on(doc! { "content": 1 }, "audio_tracks_content"),
index_on(doc! { "language": 1 }, "audio_tracks_language"),
]
}
#[cfg(feature = "audio")]
pub fn audio_segment_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "audio_track_id": 1 },
"audio_segments_audio_track_id",
),
unique_on(
doc! { "audio_track_id": 1, "index": 1 },
"audio_segments_audio_track_id_index_unique",
),
index_on(doc! { "speaker_id": 1 }, "audio_segments_speaker_id"),
]
}
#[cfg(feature = "video")]
pub fn video_facet_indexes() -> Vec<IndexModel> {
vec![unique_on(
doc! { "media_id": 1 },
"video_facets_media_id_unique",
)]
}
#[cfg(feature = "video")]
pub fn video_track_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "video_id": 1 }, "video_tracks_video_id"),
index_on(doc! { "is_primary": 1 }, "video_tracks_primary"),
]
}
#[cfg(feature = "video")]
pub fn scene_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "video_track_id": 1 }, "scenes_video_track_id"),
unique_on(
doc! { "video_track_id": 1, "index": 1 },
"scenes_video_track_id_index_unique",
),
]
}
#[cfg(feature = "video")]
pub fn keyframe_indexes() -> Vec<IndexModel> {
vec![index_on(doc! { "scene_id": 1 }, "keyframes_scene_id")]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_facet_indexes() -> Vec<IndexModel> {
vec![unique_on(
doc! { "media_id": 1 },
"subtitle_facets_media_id_unique",
)]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_indexes() -> Vec<IndexModel> {
vec![
index_on(doc! { "subtitle_id": 1 }, "subtitle_tracks_subtitle_id"),
index_on(doc! { "is_primary": 1 }, "subtitle_tracks_primary"),
index_on(doc! { "language": 1 }, "subtitle_tracks_language"),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_cue_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_cues_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "ordinal": 1 },
"subtitle_cues_subtitle_track_id_ordinal_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_vtt_region_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_vtt_regions_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "name": 1 },
"subtitle_track_vtt_regions_subtitle_track_id_name_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_vtt_style_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_vtt_styles_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "ordinal": 1 },
"subtitle_track_vtt_styles_subtitle_track_id_ordinal_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_ass_style_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_ass_styles_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "name": 1 },
"subtitle_track_ass_styles_subtitle_track_id_name_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_lrc_metadata_indexes() -> Vec<IndexModel> {
vec![]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_cue_lrc_word_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_cue_id": 1 },
"subtitle_cue_lrc_words_subtitle_cue_id",
),
unique_on(
doc! { "subtitle_cue_id": 1, "ordinal": 1 },
"subtitle_cue_lrc_words_subtitle_cue_id_ordinal_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_ttml_region_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_ttml_regions_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "xml_id": 1 },
"subtitle_track_ttml_regions_subtitle_track_id_xml_id_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_ttml_style_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_ttml_styles_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "xml_id": 1 },
"subtitle_track_ttml_styles_subtitle_track_id_xml_id_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_sami_style_indexes() -> Vec<IndexModel> {
vec![
index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_sami_styles_subtitle_track_id",
),
unique_on(
doc! { "subtitle_track_id": 1, "class_name": 1 },
"subtitle_track_sami_styles_subtitle_track_id_class_name_unique",
),
]
}
#[cfg(feature = "subtitle")]
pub fn subtitle_track_vob_sub_palette_indexes() -> Vec<IndexModel> {
vec![index_on(
doc! { "subtitle_track_id": 1 },
"subtitle_track_vob_sub_palettes_subtitle_track_id",
)]
}
pub fn all_indexes() -> Vec<(CollectionName, Vec<IndexModel>)> {
#[allow(unused_mut)]
let mut v: Vec<(CollectionName, Vec<IndexModel>)> = vec![
(CollectionName::Media, media_indexes()),
(CollectionName::MediaFiles, media_file_indexes()),
(CollectionName::Chapters, chapter_indexes()),
(CollectionName::WatchedLocations, watched_location_indexes()),
(CollectionName::Speakers, speaker_indexes()),
(CollectionName::Persons, person_indexes()),
(CollectionName::UserTags, user_tag_indexes()),
(CollectionName::SceneAnnotations, scene_annotation_indexes()),
];
#[cfg(feature = "audio")]
v.extend([
(CollectionName::AudioFacets, audio_facet_indexes()),
(CollectionName::AudioTracks, audio_track_indexes()),
(CollectionName::AudioSegments, audio_segment_indexes()),
]);
#[cfg(feature = "video")]
v.extend([
(CollectionName::VideoFacets, video_facet_indexes()),
(CollectionName::VideoTracks, video_track_indexes()),
(CollectionName::Scenes, scene_indexes()),
(CollectionName::Keyframes, keyframe_indexes()),
]);
#[cfg(feature = "subtitle")]
v.extend([
(CollectionName::SubtitleFacets, subtitle_facet_indexes()),
(CollectionName::SubtitleTracks, subtitle_track_indexes()),
(CollectionName::SubtitleCues, subtitle_cue_indexes()),
(
CollectionName::SubtitleTrackVttRegions,
subtitle_track_vtt_region_indexes(),
),
(
CollectionName::SubtitleTrackVttStyles,
subtitle_track_vtt_style_indexes(),
),
(
CollectionName::SubtitleTrackAssStyles,
subtitle_track_ass_style_indexes(),
),
(
CollectionName::SubtitleTrackLrcMetadata,
subtitle_track_lrc_metadata_indexes(),
),
(
CollectionName::SubtitleCueLrcWords,
subtitle_cue_lrc_word_indexes(),
),
(
CollectionName::SubtitleTrackTtmlRegions,
subtitle_track_ttml_region_indexes(),
),
(
CollectionName::SubtitleTrackTtmlStyles,
subtitle_track_ttml_style_indexes(),
),
(
CollectionName::SubtitleTrackSamiStyles,
subtitle_track_sami_style_indexes(),
),
(
CollectionName::SubtitleTrackVobSubPalettes,
subtitle_track_vob_sub_palette_indexes(),
),
]);
v
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_indexes_covers_every_collection() {
let v = all_indexes();
let mut expected = 8usize;
if cfg!(feature = "audio") {
expected += 3;
}
if cfg!(feature = "video") {
expected += 4;
}
if cfg!(feature = "subtitle") {
expected += 12;
}
assert_eq!(v.len(), expected);
let mut names: Vec<_> = v.iter().map(|(c, _)| c.as_str()).collect();
names.sort();
let mut dedup = names.clone();
dedup.dedup();
assert_eq!(names, dedup);
}
#[test]
fn media_unique_checksum_present() {
let idx = media_indexes();
let names: Vec<_> = idx
.iter()
.map(|m| {
m.options
.as_ref()
.and_then(|o| o.name.clone())
.unwrap_or_default()
})
.collect();
assert!(names.iter().any(|n| n == "media_checksum_unique"));
}
}