use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::PathBuf;
use anyhow::Result;
use chrono::prelude::*;
use csv::{Reader, Writer};
use num_format::ToFormattedString;
use serde::{Deserialize, Serialize};
use crate::models::recent_tracks::Track;
use crate::stats::Stats;
use crate::utils;
pub struct SavedScrobbles {
saved_scrobbles: Vec<SavedScrobble>,
}
impl Default for SavedScrobbles {
fn default() -> Self {
Self {
saved_scrobbles: Vec::default(),
}
}
}
impl SavedScrobbles {
pub fn new(saved_scrobbles: Vec<SavedScrobble>) -> Self {
let mut saved_scrobbles = Self { saved_scrobbles };
saved_scrobbles.sort();
saved_scrobbles
}
pub fn from_scrobbles(scrobbles: &[Track]) -> Self {
let mut saved_scrobbles = Self {
saved_scrobbles: SavedScrobbles::convert_scrobbles(scrobbles),
};
saved_scrobbles.sort();
saved_scrobbles
}
pub fn from_csv_reader(rdr: &mut Reader<File>) -> Self {
let saved_scrobbles = rdr
.deserialize::<SavedScrobble>()
.map(|scrobble| scrobble.expect("Error deserializing scrobble"))
.collect::<Vec<SavedScrobble>>();
let mut saved_scrobbles = SavedScrobbles::new(saved_scrobbles);
saved_scrobbles.sort();
saved_scrobbles
}
pub fn to_csv_writer(&self, wtr: &mut Writer<File>) {
for scrobble in &self.saved_scrobbles {
wtr.serialize(scrobble).expect("Error serializing scrobble")
}
}
pub fn save_as_json(&self, file: &PathBuf) -> Result<()> {
let f = fs::File::create(file)?;
let bw = BufWriter::new(f);
serde_json::to_writer_pretty(bw, &self.saved_scrobbles)?;
Ok(())
}
pub fn load_from_json(file: &PathBuf) -> Result<Self> {
let f = fs::File::open(file)?;
let br = BufReader::new(f);
let saved_scrobbles: Vec<SavedScrobble> = serde_json::from_reader(br)?;
Ok(SavedScrobbles::new(saved_scrobbles))
}
pub fn append_new_scrobbles(&mut self, new_scrobbles: &[Track]) {
let mut new_saved_scrobbles = SavedScrobbles::convert_scrobbles(new_scrobbles);
self.saved_scrobbles.append(&mut new_saved_scrobbles);
self.sort()
}
pub fn generate_stats(&self) -> Stats {
Stats::new(&self.saved_scrobbles)
}
pub fn most_recent_scrobble(&self) -> Option<&SavedScrobble> {
self.saved_scrobbles.first()
}
pub fn total_saved_scrobbles(&self) -> i32 {
self.saved_scrobbles.len() as i32
}
pub fn total_saved_scrobbles_formatted(&self) -> String {
self.total_saved_scrobbles()
.to_formatted_string(&utils::get_locale())
}
pub fn is_empty(&self) -> bool {
self.saved_scrobbles.is_empty()
}
fn sort(&mut self) {
self.saved_scrobbles
.sort_unstable_by_key(|s| s.timestamp_utc);
self.saved_scrobbles.dedup_by_key(|s| s.calculate_hash());
self.saved_scrobbles.reverse();
}
fn convert_scrobbles(scrobbles: &[Track]) -> Vec<SavedScrobble> {
scrobbles
.iter()
.map(|scrobble| SavedScrobble::from_scrobble(scrobble))
.collect::<Vec<SavedScrobble>>()
}
}
#[derive(Serialize, Deserialize, Clone, Hash)]
pub struct SavedScrobble {
pub title: String,
pub artist: String,
pub album: String,
pub loved: bool,
pub datetime_local: DateTime<Local>,
pub timestamp_utc: i64,
}
impl SavedScrobble {
pub fn from_scrobble(scrobble: &Track) -> Self {
Self {
title: scrobble.name.to_string(),
artist: scrobble.artist.name.to_string(),
album: scrobble.album.text.to_string(),
loved: scrobble.loved(),
datetime_local: scrobble.date().datetime_local(),
timestamp_utc: scrobble.date().time_stamp(),
}
}
pub fn from_scrobbles(scrobbles: &[Track]) -> Vec<SavedScrobble> {
scrobbles
.iter()
.map(|scrobble| SavedScrobble::from_scrobble(scrobble))
.collect::<Vec<SavedScrobble>>()
}
pub fn date(&self) -> NaiveDate {
self.datetime_local.naive_local().date()
}
pub fn time(&self) -> NaiveTime {
self.datetime_local.naive_local().time()
}
pub fn month_year(&self) -> String {
self.date().format("%B-%Y").to_string()
}
pub fn song_artist(&self) -> String {
format!("{} - {}", self.title, self.artist)
}
pub fn artist_album(&self) -> String {
format!("{} - {}", self.artist, self.album)
}
pub fn combined_title(&self) -> String {
format!("{} - {} - {}", self.title, self.artist, self.album)
}
pub fn calculate_hash(&self) -> u64 {
use std::hash::{Hash, Hasher};
let mut s = DefaultHasher::new();
self.hash(&mut s);
s.finish()
}
}