parent
6e9249e265
commit
ba85505c9a
@ -1,10 +1,12 @@
|
||||
use paste::paste;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use paste::paste;
|
||||
use structopt::{clap::AppSettings, StructOpt};
|
||||
|
||||
use musichoard::{
|
||||
collection::artist::ArtistId,
|
||||
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
|
||||
ArtistId, MusicHoard, MusicHoardBuilder, NoLibrary,
|
||||
MusicHoard, MusicHoardBuilder, NoLibrary,
|
||||
};
|
||||
|
||||
type MH = MusicHoard<NoLibrary, JsonDatabase<JsonDatabaseFileBackend>>;
|
||||
|
84
src/core/collection/album.rs
Normal file
84
src/core/collection/album.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use std::mem;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::core::collection::{
|
||||
merge::{Merge, MergeSorted},
|
||||
track::Track,
|
||||
};
|
||||
|
||||
/// An album is a collection of tracks that were released together.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Album {
|
||||
pub id: AlbumId,
|
||||
pub tracks: Vec<Track>,
|
||||
}
|
||||
|
||||
/// The album identifier.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||
pub struct AlbumId {
|
||||
pub year: u32,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl PartialOrd for Album {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Album {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Album {
|
||||
fn merge_in_place(&mut self, other: Self) {
|
||||
assert_eq!(self.id, other.id);
|
||||
let tracks = mem::take(&mut self.tracks);
|
||||
self.tracks = MergeSorted::new(tracks.into_iter(), other.tracks.into_iter()).collect();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::testmod::FULL_COLLECTION;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn merge_album_no_overlap() {
|
||||
let left = FULL_COLLECTION[0].albums[0].to_owned();
|
||||
let mut right = FULL_COLLECTION[0].albums[1].to_owned();
|
||||
right.id = left.id.clone();
|
||||
|
||||
let mut expected = left.clone();
|
||||
expected.tracks.append(&mut right.tracks.clone());
|
||||
expected.tracks.sort_unstable();
|
||||
|
||||
let merged = left.clone().merge(right.clone());
|
||||
assert_eq!(expected, merged);
|
||||
|
||||
// Non-overlapping merge should be commutative.
|
||||
let merged = right.clone().merge(left.clone());
|
||||
assert_eq!(expected, merged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_album_overlap() {
|
||||
let mut left = FULL_COLLECTION[0].albums[0].to_owned();
|
||||
let mut right = FULL_COLLECTION[0].albums[1].to_owned();
|
||||
right.id = left.id.clone();
|
||||
left.tracks.push(right.tracks[0].clone());
|
||||
left.tracks.sort_unstable();
|
||||
|
||||
let mut expected = left.clone();
|
||||
expected.tracks.append(&mut right.tracks.clone());
|
||||
expected.tracks.sort_unstable();
|
||||
expected.tracks.dedup();
|
||||
|
||||
let merged = left.clone().merge(right);
|
||||
assert_eq!(expected, merged);
|
||||
}
|
||||
}
|
1014
src/core/collection/artist.rs
Normal file
1014
src/core/collection/artist.rs
Normal file
File diff suppressed because it is too large
Load Diff
67
src/core/collection/merge.rs
Normal file
67
src/core/collection/merge.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::{cmp::Ordering, iter::Peekable};
|
||||
|
||||
/// A trait for merging two objects. The merge is asymmetric with the left argument considered to be
|
||||
/// the primary whose properties are to be kept in case of collisions.
|
||||
pub trait Merge {
|
||||
fn merge_in_place(&mut self, other: Self);
|
||||
|
||||
fn merge(mut self, other: Self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.merge_in_place(other);
|
||||
self
|
||||
}
|
||||
|
||||
fn merge_vecs<T: Ord + Eq>(this: &mut Vec<T>, mut other: Vec<T>) {
|
||||
this.append(&mut other);
|
||||
this.sort_unstable();
|
||||
this.dedup();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MergeSorted<L, R>
|
||||
where
|
||||
L: Iterator<Item = R::Item>,
|
||||
R: Iterator,
|
||||
{
|
||||
left: Peekable<L>,
|
||||
right: Peekable<R>,
|
||||
}
|
||||
|
||||
impl<L, R> MergeSorted<L, R>
|
||||
where
|
||||
L: Iterator<Item = R::Item>,
|
||||
R: Iterator,
|
||||
{
|
||||
pub fn new(left: L, right: R) -> MergeSorted<L, R> {
|
||||
MergeSorted {
|
||||
left: left.peekable(),
|
||||
right: right.peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> Iterator for MergeSorted<L, R>
|
||||
where
|
||||
L: Iterator<Item = R::Item>,
|
||||
R: Iterator,
|
||||
L::Item: Ord + Merge,
|
||||
{
|
||||
type Item = L::Item;
|
||||
|
||||
fn next(&mut self) -> Option<L::Item> {
|
||||
let which = match (self.left.peek(), self.right.peek()) {
|
||||
(Some(l), Some(r)) => l.cmp(r),
|
||||
(Some(_), None) => Ordering::Less,
|
||||
(None, Some(_)) => Ordering::Greater,
|
||||
(None, None) => return None,
|
||||
};
|
||||
|
||||
match which {
|
||||
Ordering::Less => self.left.next(),
|
||||
Ordering::Equal => Some(self.left.next().unwrap().merge(self.right.next().unwrap())),
|
||||
Ordering::Greater => self.right.next(),
|
||||
}
|
||||
}
|
||||
}
|
40
src/core/collection/mod.rs
Normal file
40
src/core/collection/mod.rs
Normal file
@ -0,0 +1,40 @@
|
||||
//! The collection module defines the core data types and their relations.
|
||||
|
||||
pub mod album;
|
||||
pub mod artist;
|
||||
pub mod track;
|
||||
|
||||
mod merge;
|
||||
pub use merge::Merge;
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
/// The [`Collection`] alias type for convenience.
|
||||
pub type Collection = Vec<artist::Artist>;
|
||||
|
||||
/// Error type for the [`collection`] module.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// An error occurred when processing a URL.
|
||||
UrlError(String),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Self::UrlError(ref s) => write!(f, "an error occurred when processing a URL: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for Error {
|
||||
fn from(err: url::ParseError) -> Error {
|
||||
Error::UrlError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<uuid::Error> for Error {
|
||||
fn from(err: uuid::Error) -> Error {
|
||||
Error::UrlError(err.to_string())
|
||||
}
|
||||
}
|
81
src/core/collection/track.rs
Normal file
81
src/core/collection/track.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::core::collection::merge::Merge;
|
||||
|
||||
/// A single track on an album.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Track {
|
||||
pub id: TrackId,
|
||||
pub artist: Vec<String>,
|
||||
pub quality: Quality,
|
||||
}
|
||||
|
||||
/// The track identifier.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TrackId {
|
||||
pub number: u32,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
/// The track quality. Combines format and bitrate information.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Quality {
|
||||
pub format: Format,
|
||||
pub bitrate: u32,
|
||||
}
|
||||
|
||||
/// The track file format.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
pub enum Format {
|
||||
Flac,
|
||||
Mp3,
|
||||
}
|
||||
|
||||
impl PartialOrd for Track {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Track {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Track {
|
||||
fn merge_in_place(&mut self, other: Self) {
|
||||
assert_eq!(self.id, other.id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn merge_track() {
|
||||
let left = Track {
|
||||
id: TrackId {
|
||||
number: 4,
|
||||
title: String::from("a title"),
|
||||
},
|
||||
artist: vec![String::from("left artist")],
|
||||
quality: Quality {
|
||||
format: Format::Flac,
|
||||
bitrate: 1411,
|
||||
},
|
||||
};
|
||||
let right = Track {
|
||||
id: left.id.clone(),
|
||||
artist: vec![String::from("right artist")],
|
||||
quality: Quality {
|
||||
format: Format::Mp3,
|
||||
bitrate: 320,
|
||||
},
|
||||
};
|
||||
|
||||
let merged = left.clone().merge(right);
|
||||
assert_eq!(left, merged);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::IJsonDatabaseBackend;
|
||||
use crate::core::database::json::IJsonDatabaseBackend;
|
||||
|
||||
/// JSON database backend that uses a local file for persistent storage.
|
||||
pub struct JsonDatabaseFileBackend {
|
@ -1,13 +1,14 @@
|
||||
//! Module for storing MusicHoard data in a JSON file database.
|
||||
|
||||
pub mod backend;
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use crate::Collection;
|
||||
|
||||
use super::{IDatabase, LoadError, SaveError};
|
||||
|
||||
pub mod backend;
|
||||
use crate::core::{
|
||||
collection::Collection,
|
||||
database::{IDatabase, LoadError, SaveError},
|
||||
};
|
||||
|
||||
impl From<serde_json::Error> for LoadError {
|
||||
fn from(err: serde_json::Error) -> LoadError {
|
||||
@ -66,7 +67,13 @@ mod tests {
|
||||
|
||||
use mockall::predicate;
|
||||
|
||||
use crate::{testlib::FULL_COLLECTION, Artist, ArtistId, Collection};
|
||||
use crate::core::{
|
||||
collection::{
|
||||
artist::{Artist, ArtistId},
|
||||
Collection,
|
||||
},
|
||||
testmod::FULL_COLLECTION,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use testmod::DATABASE_JSON;
|
@ -1,14 +1,14 @@
|
||||
//! Module for storing MusicHoard data in a database.
|
||||
|
||||
#[cfg(feature = "database-json")]
|
||||
pub mod json;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use crate::Collection;
|
||||
|
||||
#[cfg(feature = "database-json")]
|
||||
pub mod json;
|
||||
use crate::core::collection::Collection;
|
||||
|
||||
/// Trait for interacting with the database.
|
||||
#[cfg_attr(test, automock)]
|
@ -8,7 +8,7 @@ use std::{
|
||||
str,
|
||||
};
|
||||
|
||||
use super::{Error, IBeetsLibraryExecutor};
|
||||
use crate::core::library::{beets::IBeetsLibraryExecutor, Error};
|
||||
|
||||
const BEET_DEFAULT: &str = "beet";
|
||||
|
@ -1,14 +1,15 @@
|
||||
//! Module for interacting with the music library via
|
||||
//! [beets](https://beets.readthedocs.io/en/stable/).
|
||||
|
||||
pub mod executor;
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use crate::Format;
|
||||
|
||||
use super::{Error, Field, ILibrary, Item, Query};
|
||||
|
||||
pub mod executor;
|
||||
use crate::core::{
|
||||
collection::track::Format,
|
||||
library::{Error, Field, ILibrary, Item, Query},
|
||||
};
|
||||
|
||||
macro_rules! list_format_separator {
|
||||
() => {
|
||||
@ -176,7 +177,7 @@ mod testmod;
|
||||
mod tests {
|
||||
use mockall::predicate;
|
||||
|
||||
use crate::library::testmod::LIBRARY_ITEMS;
|
||||
use crate::core::library::testmod::LIBRARY_ITEMS;
|
||||
|
||||
use super::*;
|
||||
use testmod::LIBRARY_BEETS;
|
@ -1,14 +1,14 @@
|
||||
//! Module for interacting with the music library.
|
||||
|
||||
#[cfg(feature = "library-beets")]
|
||||
pub mod beets;
|
||||
|
||||
use std::{collections::HashSet, fmt, num::ParseIntError, str::Utf8Error};
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use crate::Format;
|
||||
|
||||
#[cfg(feature = "library-beets")]
|
||||
pub mod beets;
|
||||
use crate::core::collection::track::Format;
|
||||
|
||||
/// Trait for interacting with the music library.
|
||||
#[cfg_attr(test, automock)]
|
@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{library::Item, Format};
|
||||
use crate::core::{collection::track::Format, library::Item};
|
||||
|
||||
pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
|
||||
vec![
|
7
src/core/mod.rs
Normal file
7
src/core/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod collection;
|
||||
pub mod database;
|
||||
pub mod library;
|
||||
pub mod musichoard;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod testmod;
|
56
src/core/musichoard/mod.rs
Normal file
56
src/core/musichoard/mod.rs
Normal file
@ -0,0 +1,56 @@
|
||||
//! The core MusicHoard module. Serves as the main entry-point into the library.
|
||||
|
||||
#![allow(clippy::module_inception)]
|
||||
pub mod musichoard;
|
||||
pub mod musichoard_builder;
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::core::{collection, database, library};
|
||||
|
||||
/// Error type for `musichoard`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// The [`MusicHoard`] is not able to read/write its in-memory collection.
|
||||
CollectionError(String),
|
||||
/// The [`MusicHoard`] failed to read/write from/to the library.
|
||||
LibraryError(String),
|
||||
/// The [`MusicHoard`] failed to read/write from/to the database.
|
||||
DatabaseError(String),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Self::CollectionError(ref s) => write!(f, "failed to read/write the collection: {s}"),
|
||||
Self::LibraryError(ref s) => write!(f, "failed to read/write from/to the library: {s}"),
|
||||
Self::DatabaseError(ref s) => {
|
||||
write!(f, "failed to read/write from/to the database: {s}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<collection::Error> for Error {
|
||||
fn from(err: collection::Error) -> Self {
|
||||
Error::CollectionError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<library::Error> for Error {
|
||||
fn from(err: library::Error) -> Error {
|
||||
Error::LibraryError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<database::LoadError> for Error {
|
||||
fn from(err: database::LoadError) -> Error {
|
||||
Error::DatabaseError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<database::SaveError> for Error {
|
||||
fn from(err: database::SaveError) -> Error {
|
||||
Error::DatabaseError(err.to_string())
|
||||
}
|
||||
}
|
1053
src/core/musichoard/musichoard.rs
Normal file
1053
src/core/musichoard/musichoard.rs
Normal file
File diff suppressed because it is too large
Load Diff
92
src/core/musichoard/musichoard_builder.rs
Normal file
92
src/core/musichoard/musichoard_builder.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use crate::core::{
|
||||
database::IDatabase,
|
||||
library::ILibrary,
|
||||
musichoard::musichoard::{MusicHoard, NoDatabase, NoLibrary},
|
||||
};
|
||||
|
||||
/// Builder for [`MusicHoard`]. Its purpose is to make it easier to set various combinations of
|
||||
/// library/database or their absence.
|
||||
pub struct MusicHoardBuilder<LIB, DB> {
|
||||
library: LIB,
|
||||
database: DB,
|
||||
}
|
||||
|
||||
impl Default for MusicHoardBuilder<NoLibrary, NoDatabase> {
|
||||
/// Create a [`MusicHoardBuilder`].
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MusicHoardBuilder<NoLibrary, NoDatabase> {
|
||||
/// Create a [`MusicHoardBuilder`].
|
||||
pub fn new() -> Self {
|
||||
MusicHoardBuilder {
|
||||
library: NoLibrary,
|
||||
database: NoDatabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<LIB, DB> MusicHoardBuilder<LIB, DB> {
|
||||
/// Set a library for [`MusicHoard`].
|
||||
pub fn set_library<NEWLIB: ILibrary>(self, library: NEWLIB) -> MusicHoardBuilder<NEWLIB, DB> {
|
||||
MusicHoardBuilder {
|
||||
library,
|
||||
database: self.database,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a database for [`MusicHoard`].
|
||||
pub fn set_database<NEWDB: IDatabase>(self, database: NEWDB) -> MusicHoardBuilder<LIB, NEWDB> {
|
||||
MusicHoardBuilder {
|
||||
library: self.library,
|
||||
database,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build [`MusicHoard`] with the currently set library and database.
|
||||
pub fn build(self) -> MusicHoard<LIB, DB> {
|
||||
MusicHoard::new(self.library, self.database)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::{database::NullDatabase, library::NullLibrary};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_library_no_database() {
|
||||
MusicHoardBuilder::default();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_library_no_database() {
|
||||
let mut mh = MusicHoardBuilder::default()
|
||||
.set_library(NullLibrary)
|
||||
.build();
|
||||
assert!(mh.rescan_library().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_library_with_database() {
|
||||
let mut mh = MusicHoardBuilder::default()
|
||||
.set_database(NullDatabase)
|
||||
.build();
|
||||
assert!(mh.load_from_database().is_ok());
|
||||
assert!(mh.save_to_database().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_library_with_database() {
|
||||
let mut mh = MusicHoardBuilder::default()
|
||||
.set_library(NullLibrary)
|
||||
.set_database(NullDatabase)
|
||||
.build();
|
||||
assert!(mh.rescan_library().is_ok());
|
||||
assert!(mh.load_from_database().is_ok());
|
||||
assert!(mh.save_to_database().is_ok());
|
||||
}
|
||||
}
|
11
src/core/testmod.rs
Normal file
11
src/core/testmod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::core::collection::{
|
||||
album::{Album, AlbumId},
|
||||
artist::{Artist, ArtistId, ArtistProperties, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
||||
track::{Format, Quality, Track, TrackId},
|
||||
};
|
||||
use crate::tests::*;
|
||||
|
||||
pub static LIBRARY_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| library_collection!());
|
||||
pub static FULL_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full_collection!());
|
2296
src/lib.rs
2296
src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
||||
mod tui;
|
||||
|
||||
use std::{ffi::OsString, fs::OpenOptions, io, path::PathBuf};
|
||||
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
@ -18,7 +20,6 @@ use musichoard::{
|
||||
MusicHoardBuilder, NoDatabase, NoLibrary,
|
||||
};
|
||||
|
||||
mod tui;
|
||||
use tui::{event::EventChannel, handler::EventHandler, listener::EventListener, ui::Ui, Tui};
|
||||
|
||||
#[derive(StructOpt)]
|
||||
@ -128,7 +129,4 @@ fn main() {
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod testmacros;
|
||||
|
||||
#[cfg(test)]
|
||||
mod testbin;
|
||||
mod tests;
|
||||
|
@ -1,7 +0,0 @@
|
||||
use musichoard::{
|
||||
Album, AlbumId, Artist, ArtistId, ArtistProperties, Bandcamp, Format, MusicBrainz, MusicButler,
|
||||
Qobuz, Quality, Track, TrackId,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full_collection!());
|
@ -1,8 +0,0 @@
|
||||
use crate::{
|
||||
Album, AlbumId, Artist, ArtistId, ArtistProperties, Bandcamp, Format, MusicBrainz, MusicButler,
|
||||
Qobuz, Quality, Track, TrackId,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static LIBRARY_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| library_collection!());
|
||||
pub static FULL_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full_collection!());
|
@ -321,3 +321,6 @@ macro_rules! full_collection {
|
||||
collection
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use full_collection;
|
||||
pub(crate) use library_collection;
|
@ -2,7 +2,7 @@ use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use std::fmt;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use super::ui::UiError;
|
||||
use crate::tui::ui::UiError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventError {
|
||||
@ -104,7 +104,7 @@ mod tests {
|
||||
|
||||
use crate::tui::ui::UiError;
|
||||
|
||||
use super::{Event, EventChannel, EventError};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn event_sender() {
|
||||
|
@ -3,7 +3,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use super::{
|
||||
use crate::tui::{
|
||||
event::{Event, EventError, EventReceiver},
|
||||
ui::IUi,
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use musichoard::{database::IDatabase, library::ILibrary, Collection, MusicHoard};
|
||||
use musichoard::{collection::Collection, database::IDatabase, library::ILibrary, MusicHoard};
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
@ -157,24 +157,24 @@ impl<B: Backend, UI: IUi> Tui<B, UI> {
|
||||
// GRCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod testmod;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{io, thread};
|
||||
|
||||
use musichoard::Collection;
|
||||
use ratatui::{backend::TestBackend, Terminal};
|
||||
|
||||
use crate::testbin::COLLECTION;
|
||||
use musichoard::collection::Collection;
|
||||
|
||||
use super::{
|
||||
event::EventError,
|
||||
handler::MockIEventHandler,
|
||||
lib::MockIMusicHoard,
|
||||
listener::MockIEventListener,
|
||||
ui::{IUi, Ui},
|
||||
Error, Tui,
|
||||
use crate::tui::{
|
||||
handler::MockIEventHandler, lib::MockIMusicHoard, listener::MockIEventListener, ui::Ui,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use testmod::COLLECTION;
|
||||
|
||||
pub fn terminal() -> Terminal<TestBackend> {
|
||||
let backend = TestBackend::new(150, 30);
|
||||
Terminal::new(backend).unwrap()
|
||||
|
10
src/tui/testmod.rs
Normal file
10
src/tui/testmod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use musichoard::collection::{
|
||||
album::{Album, AlbumId},
|
||||
artist::{Artist, ArtistId, ArtistProperties, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
||||
track::{Format, Quality, Track, TrackId},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::tests::*;
|
||||
|
||||
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full_collection!());
|
@ -1,6 +1,11 @@
|
||||
use std::fmt;
|
||||
|
||||
use musichoard::{Album, Artist, Collection, Format, Track};
|
||||
use musichoard::collection::{
|
||||
album::Album,
|
||||
artist::Artist,
|
||||
track::{Format, Track},
|
||||
Collection,
|
||||
};
|
||||
use ratatui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Rect},
|
||||
@ -9,7 +14,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::{lib::IMusicHoard, Error};
|
||||
use crate::tui::{lib::IMusicHoard, Error};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UiError {
|
||||
@ -733,8 +738,8 @@ impl<MH: IMusicHoard> IUi for Ui<MH> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::testbin::COLLECTION;
|
||||
use crate::tui::lib::MockIMusicHoard;
|
||||
use crate::tui::testmod::COLLECTION;
|
||||
use crate::tui::tests::{terminal, ui};
|
||||
|
||||
use super::*;
|
||||
|
@ -1,14 +1,15 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use musichoard::{
|
||||
collection::artist::Artist,
|
||||
database::{
|
||||
json::{backend::JsonDatabaseFileBackend, JsonDatabase},
|
||||
IDatabase,
|
||||
},
|
||||
Artist,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use crate::testlib::COLLECTION;
|
||||
|
||||
|
@ -5,14 +5,14 @@ mod testlib;
|
||||
|
||||
use musichoard::MusicHoard;
|
||||
|
||||
use crate::testlib::COLLECTION;
|
||||
|
||||
#[cfg(feature = "database-json")]
|
||||
use musichoard::database::json::{backend::JsonDatabaseFileBackend, JsonDatabase};
|
||||
|
||||
#[cfg(feature = "library-beets")]
|
||||
use musichoard::library::beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary};
|
||||
|
||||
use crate::testlib::COLLECTION;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "database-json")]
|
||||
#[cfg(feature = "library-beets")]
|
||||
|
@ -12,7 +12,7 @@ use musichoard::library::{
|
||||
Field, ILibrary, Item, Query,
|
||||
};
|
||||
|
||||
use super::testmod::LIBRARY_ITEMS;
|
||||
use crate::library::testmod::LIBRARY_ITEMS;
|
||||
|
||||
pub static BEETS_TEST_CONFIG_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| fs::canonicalize("./tests/files/library/config.yml").unwrap());
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod testmod;
|
||||
|
||||
#[cfg(feature = "library-beets")]
|
||||
pub mod beets;
|
||||
|
||||
mod testmod;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use musichoard::{library::Item, Format};
|
||||
use musichoard::{collection::track::Format, library::Item};
|
||||
|
||||
pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
|
||||
vec![
|
||||
|
@ -1,9 +1,12 @@
|
||||
use musichoard::{
|
||||
Album, AlbumId, Artist, ArtistId, ArtistProperties, Bandcamp, Collection, Format, MusicBrainz,
|
||||
MusicButler, Qobuz, Quality, Track, TrackId,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use musichoard::collection::{
|
||||
album::{Album, AlbumId},
|
||||
artist::{Artist, ArtistId, ArtistProperties, Bandcamp, MusicBrainz, MusicButler, Qobuz},
|
||||
track::{Format, Quality, Track, TrackId},
|
||||
Collection,
|
||||
};
|
||||
|
||||
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
|
||||
vec![
|
||||
Artist {
|
||||
|
Loading…
Reference in New Issue
Block a user