use chrono::Utc;
use diesel::prelude::*;
#[cfg(feature = "napi")]
use napi_derive::napi;
#[cfg(feature = "pyo3")]
use pyo3::prelude::*;
#[cfg(feature = "pyo3")]
use rbox_derives::PyMutableMapping;
use super::album::Album;
use super::artist::{Artist, Composer, Lyricist, OriginalArtist, Remixer};
use super::color::Color;
use super::cue::Cue;
use super::genre::Genre;
use super::history::HistoryContent;
use super::image::Image;
use super::key::Key;
use super::label::Label;
use super::my_tag::MyTagContent;
use super::playlist::PlaylistContent;
use super::property::Property;
use super::recommend_like::RecommendedLike;
use super::schema::{album, artist, color, content, genre, image, key, label};
use super::{Date, DateString};
use crate::model_traits::{
load_related, Model, ModelDelete, ModelInsert, ModelRelations, ModelUpdate,
};
#[cfg(feature = "pyo3")]
use crate::util::{PyItemsIter, PyObjectIter, PyStrIter};
#[derive(Debug, Clone, PartialEq, HasQuery, Identifiable, AsChangeset, Associations)]
#[diesel(table_name = content)]
#[diesel(primary_key(id))]
#[diesel(belongs_to(Artist, foreign_key = artist_id))]
#[diesel(belongs_to(Remixer, foreign_key = remixer_id))]
#[diesel(belongs_to(OriginalArtist, foreign_key = original_artist_id))]
#[diesel(belongs_to(Composer, foreign_key = composer_id))]
#[diesel(belongs_to(Lyricist, foreign_key = lyricist_id))]
#[diesel(belongs_to(Album, foreign_key = album_id))]
#[diesel(belongs_to(Genre, foreign_key = genre_id))]
#[diesel(belongs_to(Label, foreign_key = label_id))]
#[diesel(belongs_to(Key, foreign_key = key_id))]
#[diesel(belongs_to(Color, foreign_key = color_id))]
#[diesel(belongs_to(Image, foreign_key = image_id))]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all, mapping))]
#[cfg_attr(feature = "pyo3", derive(PyMutableMapping))]
#[cfg_attr(feature = "napi", napi(object))]
pub struct Content {
pub id: i32,
pub title: Option<String>,
pub title_for_search: Option<String>,
pub subtitle: Option<String>,
pub bpmx100: Option<i32>,
pub length: Option<i32>,
pub track_no: Option<i32>,
pub disc_no: Option<i32>,
pub artist_id: Option<i32>,
pub remixer_id: Option<i32>,
pub original_artist_id: Option<i32>,
pub composer_id: Option<i32>,
pub lyricist_id: Option<i32>,
pub album_id: Option<i32>,
pub genre_id: Option<i32>,
pub label_id: Option<i32>,
pub key_id: Option<i32>,
pub color_id: Option<i32>,
pub image_id: Option<i32>,
pub dj_comment: Option<String>,
pub rating: Option<i32>,
pub release_year: Option<i32>,
pub release_date: Option<String>,
#[diesel(serialize_as = DateString)]
#[diesel(deserialize_as = DateString)]
pub date_created: Date,
#[diesel(serialize_as = DateString)]
#[diesel(deserialize_as = DateString)]
pub date_added: Date,
pub path: String,
pub file_name: Option<String>,
pub file_size: Option<i32>,
pub file_type: i32,
pub bitrate: Option<i32>,
pub bit_depth: Option<i32>,
pub sampling_rate: Option<i32>,
pub isrc: Option<String>,
pub is_hot_cue_auto_load_on: Option<i32>,
pub is_kuvo_deliver_status_on: Option<i32>,
pub kuvo_delivery_comment: Option<String>,
pub master_db_id: Option<i32>,
pub master_content_id: Option<i32>,
pub analysis_data_file_path: Option<String>,
pub analysed_bits: Option<i32>,
pub content_link: Option<i32>,
pub has_modified: Option<i32>,
pub cue_update_count: Option<i32>,
pub analysis_data_update_count: Option<i32>,
pub information_update_count: Option<i32>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(get_all))]
#[cfg_attr(feature = "napi", napi(object))]
pub struct ContentView {
pub content: Content,
pub artist: Option<Artist>,
pub remixer: Option<Artist>,
pub original_artist: Option<Artist>,
pub composer: Option<Artist>,
pub lyricist: Option<Artist>,
pub album: Option<Album>,
pub genre: Option<Genre>,
pub label: Option<Label>,
pub key: Option<Key>,
pub color: Option<Color>,
pub image: Option<Image>,
}
impl ModelRelations for Content {
type Resolved = ContentView;
fn resolve_many(
conn: &mut SqliteConnection,
models: Vec<Self>,
) -> QueryResult<Vec<Self::Resolved>> {
if models.is_empty() {
return Ok(Vec::new());
}
conn.transaction(|conn| {
let artists = load_related(
conn,
models.iter().flat_map(|row| {
[
row.artist_id,
row.remixer_id,
row.original_artist_id,
row.composer_id,
row.lyricist_id,
]
.into_iter()
.flatten()
}),
|conn, ids| Artist::query().filter(artist::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
let albums = load_related(
conn,
models.iter().filter_map(|row| row.album_id),
|conn, ids| Album::query().filter(album::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
let genres = load_related(
conn,
models.iter().filter_map(|row| row.genre_id),
|conn, ids| Genre::query().filter(genre::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
let labels = load_related(
conn,
models.iter().filter_map(|row| row.label_id),
|conn, ids| Label::query().filter(label::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
let keys = load_related(
conn,
models.iter().filter_map(|row| row.key_id),
|conn, ids| Key::query().filter(key::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
let colors = load_related(
conn,
models.iter().filter_map(|row| row.color_id),
|conn, ids| Color::query().filter(color::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
let images = load_related(
conn,
models.iter().filter_map(|row| row.image_id),
|conn, ids| Image::query().filter(image::id.eq_any(ids)).load(conn),
|row| row.id,
)?;
Ok(models
.into_iter()
.map(|content| ContentView {
artist: content
.artist_id
.as_ref()
.and_then(|id| artists.get(id))
.cloned(),
remixer: content
.remixer_id
.as_ref()
.and_then(|id| artists.get(id))
.cloned(),
original_artist: content
.original_artist_id
.as_ref()
.and_then(|id| artists.get(id))
.cloned(),
composer: content
.composer_id
.as_ref()
.and_then(|id| artists.get(id))
.cloned(),
lyricist: content
.lyricist_id
.as_ref()
.and_then(|id| artists.get(id))
.cloned(),
album: content
.album_id
.as_ref()
.and_then(|id| albums.get(id))
.cloned(),
genre: content
.genre_id
.as_ref()
.and_then(|id| genres.get(id))
.cloned(),
label: content
.label_id
.as_ref()
.and_then(|id| labels.get(id))
.cloned(),
key: content.key_id.as_ref().and_then(|id| keys.get(id)).cloned(),
color: content
.color_id
.as_ref()
.and_then(|id| colors.get(id))
.cloned(),
image: content
.image_id
.as_ref()
.and_then(|id| images.get(id))
.cloned(),
content,
})
.collect())
})
}
}
impl Model for Content {
type Id = i32;
fn all(conn: &mut SqliteConnection) -> QueryResult<Vec<Self>> {
Self::query().load(conn)
}
fn find(conn: &mut SqliteConnection, id: &Self::Id) -> QueryResult<Option<Self>> {
Self::query().find(id).first(conn).optional()
}
fn id_exists(conn: &mut SqliteConnection, id: &Self::Id) -> QueryResult<bool> {
diesel::dsl::select(diesel::dsl::exists(Self::query().find(id))).get_result(conn)
}
}
impl ModelUpdate for Content {
fn update(self, conn: &mut SqliteConnection) -> QueryResult<Self> {
diesel::update(content::table.find(self.id))
.set(self)
.get_result(conn)
}
}
impl ModelDelete for Content {
fn delete(conn: &mut SqliteConnection, id: &Self::Id) -> QueryResult<usize> {
conn.transaction(|conn| {
let mut result = diesel::delete(content::table.find(id)).execute(conn)?;
result += Self::cascade_delete(conn, *id)?;
Self::update_property(conn)?;
Ok(result)
})
}
fn delete_all(conn: &mut SqliteConnection, ids: Vec<&Self::Id>) -> QueryResult<usize> {
conn.transaction(|conn| {
let mut result =
diesel::delete(content::table.filter(content::id.eq_any(&ids))).execute(conn)?;
let ids: Vec<i32> = ids.iter().map(|id| *(*id)).collect();
result += Self::cascade_deletes(conn, &ids)?;
Self::update_property(conn)?;
Ok(result)
})
}
}
impl Content {
pub fn by_ids(conn: &mut SqliteConnection, ids: &[i32]) -> QueryResult<Vec<Self>> {
Self::query().filter(content::id.eq_any(ids)).load(conn)
}
pub fn find_by_path(conn: &mut SqliteConnection, path: &str) -> QueryResult<Option<Self>> {
Self::query()
.filter(content::path.eq(path))
.first(conn)
.optional()
}
pub fn path_exists(conn: &mut SqliteConnection, path: &str) -> QueryResult<bool> {
let query = Self::query().filter(content::path.eq(path));
diesel::dsl::select(diesel::dsl::exists(query)).get_result(conn)
}
pub fn set_artist_id(
conn: &mut SqliteConnection,
id: i32,
artist_id: i32,
) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::artist_id.eq(artist_id))
.execute(conn)
}
pub fn set_remixer_id(
conn: &mut SqliteConnection,
id: i32,
artist_id: i32,
) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::remixer_id.eq(artist_id))
.execute(conn)
}
pub fn set_original_artist_id(
conn: &mut SqliteConnection,
id: i32,
artist_id: i32,
) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::original_artist_id.eq(artist_id))
.execute(conn)
}
pub fn set_composer_id(
conn: &mut SqliteConnection,
id: i32,
artist_id: i32,
) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::composer_id.eq(artist_id))
.execute(conn)
}
pub fn set_lyricist_id(
conn: &mut SqliteConnection,
id: i32,
artist_id: i32,
) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::lyricist_id.eq(artist_id))
.execute(conn)
}
pub fn set_album_id(conn: &mut SqliteConnection, id: i32, album_id: i32) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::album_id.eq(album_id))
.execute(conn)
}
pub fn set_genre_id(conn: &mut SqliteConnection, id: i32, genre_id: i32) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::genre_id.eq(genre_id))
.execute(conn)
}
pub fn set_label_id(conn: &mut SqliteConnection, id: i32, label_id: i32) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::label_id.eq(label_id))
.execute(conn)
}
pub fn set_key_id(conn: &mut SqliteConnection, id: i32, key_id: i32) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::key_id.eq(key_id))
.execute(conn)
}
pub fn set_color_id(conn: &mut SqliteConnection, id: i32, color_id: i32) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::color_id.eq(color_id))
.execute(conn)
}
pub fn set_image_id(conn: &mut SqliteConnection, id: i32, image_id: i32) -> QueryResult<usize> {
diesel::update(content::table.find(id))
.set(content::image_id.eq(image_id))
.execute(conn)
}
fn cascade_delete(conn: &mut SqliteConnection, id: i32) -> QueryResult<usize> {
let mut result = 0;
result += Cue::delete_by_content_id(conn, id)?;
result += HistoryContent::delete_by_content_id(conn, id)?;
result += PlaylistContent::delete_by_content_id(conn, id)?;
result += MyTagContent::delete_by_content_id(conn, id)?;
result += RecommendedLike::delete_by_content_id(conn, id)?;
Ok(result)
}
fn cascade_deletes(conn: &mut SqliteConnection, ids: &[i32]) -> QueryResult<usize> {
let mut result = 0;
result += Cue::delete_by_content_ids(conn, ids)?;
result += HistoryContent::delete_by_content_ids(conn, ids)?;
result += PlaylistContent::delete_by_content_ids(conn, ids)?;
result += MyTagContent::delete_by_content_ids(conn, ids)?;
result += RecommendedLike::delete_by_content_ids(conn, ids)?;
Ok(result)
}
fn update_property(conn: &mut SqliteConnection) -> QueryResult<usize> {
let count: i64 = content::table.count().get_result(conn)?;
Property::set_number_of_contents_all(conn, count as i32)
}
}
#[derive(Debug, Clone, PartialEq, Default, Insertable)]
#[diesel(table_name = content)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all, mapping))]
#[cfg_attr(feature = "pyo3", derive(PyMutableMapping))]
#[cfg_attr(feature = "napi", napi(object))]
pub struct NewContent {
#[diesel(serialize_as = DateString)]
#[diesel(deserialize_as = DateString)]
pub date_created: Date,
#[diesel(serialize_as = DateString)]
#[diesel(deserialize_as = DateString)]
pub date_added: Date,
pub path: String,
pub title: Option<String>,
pub title_for_search: Option<String>,
pub subtitle: Option<String>,
pub bpmx100: Option<i32>,
pub length: Option<i32>,
pub track_no: Option<i32>,
pub disc_no: Option<i32>,
pub artist_id: Option<i32>,
pub remixer_id: Option<i32>,
pub original_artist_id: Option<i32>,
pub composer_id: Option<i32>,
pub lyricist_id: Option<i32>,
pub album_id: Option<i32>,
pub genre_id: Option<i32>,
pub label_id: Option<i32>,
pub key_id: Option<i32>,
pub color_id: Option<i32>,
pub image_id: Option<i32>,
pub dj_comment: Option<String>,
pub rating: Option<i32>,
pub release_year: Option<i32>,
pub release_date: Option<String>,
pub file_name: Option<String>,
pub file_size: Option<i32>,
pub file_type: Option<i32>,
pub bitrate: Option<i32>,
pub bit_depth: Option<i32>,
pub sampling_rate: Option<i32>,
pub isrc: Option<String>,
pub is_hot_cue_auto_load_on: Option<i32>,
pub is_kuvo_deliver_status_on: Option<i32>,
pub kuvo_delivery_comment: Option<String>,
pub master_db_id: Option<i32>,
pub master_content_id: Option<i32>,
pub analysis_data_file_path: Option<String>,
pub analysed_bits: Option<i32>,
pub content_link: Option<i32>,
pub has_modified: Option<i32>,
pub cue_update_count: Option<i32>,
pub analysis_data_update_count: Option<i32>,
pub information_update_count: Option<i32>,
}
impl ModelInsert for NewContent {
type Model = Content;
fn insert(self, conn: &mut SqliteConnection) -> QueryResult<Self::Model> {
let result = diesel::insert_into(content::table)
.values(self)
.get_result(conn)?;
Content::update_property(conn)?;
Ok(result)
}
fn insert_all(conn: &mut SqliteConnection, items: Vec<Self>) -> QueryResult<Vec<Self::Model>> {
let results = diesel::insert_into(content::table)
.values(items)
.get_results(conn)?;
Content::update_property(conn)?;
Ok(results)
}
}
impl NewContent {
pub fn new<S: Into<String>>(path: S) -> Self {
let now = Utc::now();
Self {
date_created: now.clone(),
date_added: now.clone(),
path: path.into(),
..Default::default()
}
}
}