Extended the NewModel builder and added a bunch of unit tests.
This commit is contained in:
parent
ee4f08c5cf
commit
cb65782829
@ -10,7 +10,7 @@ use std::time::Duration;
|
|||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use xdg_;
|
use xdg_dirs;
|
||||||
|
|
||||||
type Pool = Arc<r2d2::Pool<ConnectionManager<SqliteConnection>>>;
|
type Pool = Arc<r2d2::Pool<ConnectionManager<SqliteConnection>>>;
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ lazy_static!{
|
|||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref DB_PATH: PathBuf = xdg_::HAMMOND_XDG.place_data_file("hammond.db").unwrap();
|
static ref DB_PATH: PathBuf = xdg_dirs::HAMMOND_XDG.place_data_file("hammond.db").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -169,6 +169,25 @@ mod tests {
|
|||||||
index_all().unwrap();
|
index_all().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Insert feeds and update/index them.
|
||||||
|
fn test_fetch_loop() {
|
||||||
|
truncate_db().unwrap();
|
||||||
|
let inpt = vec![
|
||||||
|
"https://request-for-explanation.github.io/podcast/rss.xml",
|
||||||
|
"https://feeds.feedburner.com/InterceptedWithJeremyScahill",
|
||||||
|
"http://feeds.propublica.org/propublica/podcast",
|
||||||
|
"http://feeds.feedburner.com/linuxunplugged",
|
||||||
|
];
|
||||||
|
|
||||||
|
inpt.iter().for_each(|url| {
|
||||||
|
// Index the urls into the source table.
|
||||||
|
Source::from_url(url).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch_all().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_complete_index() {
|
fn test_complete_index() {
|
||||||
// vec of (path, url) tuples.
|
// vec of (path, url) tuples.
|
||||||
|
|||||||
@ -36,7 +36,7 @@ mod schema;
|
|||||||
|
|
||||||
pub use models::queryables::{Episode, Podcast, Source};
|
pub use models::queryables::{Episode, Podcast, Source};
|
||||||
|
|
||||||
pub mod xdg_ {
|
pub mod xdg_dirs {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use xdg;
|
use xdg;
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,9 @@ pub(crate) struct NewPodcast {
|
|||||||
link: String,
|
link: String,
|
||||||
description: String,
|
description: String,
|
||||||
image_uri: Option<String>,
|
image_uri: Option<String>,
|
||||||
|
favorite: bool,
|
||||||
|
archive: bool,
|
||||||
|
always_dl: bool,
|
||||||
source_id: i32,
|
source_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,9 +130,13 @@ pub(crate) struct NewPodcastBuilder {
|
|||||||
link: String,
|
link: String,
|
||||||
description: String,
|
description: String,
|
||||||
image_uri: Option<String>,
|
image_uri: Option<String>,
|
||||||
|
favorite: bool,
|
||||||
|
archive: bool,
|
||||||
|
always_dl: bool,
|
||||||
source_id: i32,
|
source_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl NewPodcastBuilder {
|
impl NewPodcastBuilder {
|
||||||
pub(crate) fn new() -> NewPodcastBuilder {
|
pub(crate) fn new() -> NewPodcastBuilder {
|
||||||
NewPodcastBuilder::default()
|
NewPodcastBuilder::default()
|
||||||
@ -160,12 +167,30 @@ impl NewPodcastBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn favorite(mut self, s: bool) -> NewPodcastBuilder {
|
||||||
|
self.favorite = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn archive(mut self, s: bool) -> NewPodcastBuilder {
|
||||||
|
self.archive = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn always_dl(mut self, s: bool) -> NewPodcastBuilder {
|
||||||
|
self.always_dl = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build(self) -> NewPodcast {
|
pub(crate) fn build(self) -> NewPodcast {
|
||||||
NewPodcast {
|
NewPodcast {
|
||||||
title: self.title,
|
title: self.title,
|
||||||
link: self.link,
|
link: self.link,
|
||||||
description: self.description,
|
description: self.description,
|
||||||
image_uri: self.image_uri,
|
image_uri: self.image_uri,
|
||||||
|
favorite: self.favorite,
|
||||||
|
archive: self.archive,
|
||||||
|
always_dl: self.always_dl,
|
||||||
source_id: self.source_id,
|
source_id: self.source_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,11 +226,15 @@ impl NewPodcast {
|
|||||||
pub(crate) struct NewEpisode {
|
pub(crate) struct NewEpisode {
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
uri: String,
|
uri: String,
|
||||||
|
local_uri: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
published_date: Option<String>,
|
published_date: Option<String>,
|
||||||
length: Option<i32>,
|
length: Option<i32>,
|
||||||
guid: Option<String>,
|
guid: Option<String>,
|
||||||
epoch: i32,
|
epoch: i32,
|
||||||
|
played: Option<i32>,
|
||||||
|
favorite: bool,
|
||||||
|
archive: bool,
|
||||||
podcast_id: i32,
|
podcast_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,14 +291,19 @@ impl NewEpisode {
|
|||||||
pub(crate) struct NewEpisodeBuilder {
|
pub(crate) struct NewEpisodeBuilder {
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
uri: String,
|
uri: String,
|
||||||
|
local_uri: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
published_date: Option<String>,
|
published_date: Option<String>,
|
||||||
length: Option<i32>,
|
length: Option<i32>,
|
||||||
guid: Option<String>,
|
guid: Option<String>,
|
||||||
epoch: i32,
|
epoch: i32,
|
||||||
|
played: Option<i32>,
|
||||||
|
favorite: bool,
|
||||||
|
archive: bool,
|
||||||
podcast_id: i32,
|
podcast_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl NewEpisodeBuilder {
|
impl NewEpisodeBuilder {
|
||||||
pub(crate) fn new() -> NewEpisodeBuilder {
|
pub(crate) fn new() -> NewEpisodeBuilder {
|
||||||
NewEpisodeBuilder::default()
|
NewEpisodeBuilder::default()
|
||||||
@ -285,6 +319,11 @@ impl NewEpisodeBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn local_uri(mut self, s: Option<String>) -> NewEpisodeBuilder {
|
||||||
|
self.local_uri = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn description(mut self, s: Option<String>) -> NewEpisodeBuilder {
|
pub(crate) fn description(mut self, s: Option<String>) -> NewEpisodeBuilder {
|
||||||
self.description = s;
|
self.description = s;
|
||||||
self
|
self
|
||||||
@ -300,6 +339,11 @@ impl NewEpisodeBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn played(mut self, s: Option<i32>) -> NewEpisodeBuilder {
|
||||||
|
self.played = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn guid(mut self, s: Option<String>) -> NewEpisodeBuilder {
|
pub(crate) fn guid(mut self, s: Option<String>) -> NewEpisodeBuilder {
|
||||||
self.guid = s;
|
self.guid = s;
|
||||||
self
|
self
|
||||||
@ -315,15 +359,29 @@ impl NewEpisodeBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn favorite(mut self, s: bool) -> NewEpisodeBuilder {
|
||||||
|
self.favorite = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn archive(mut self, s: bool) -> NewEpisodeBuilder {
|
||||||
|
self.archive = s;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build(self) -> NewEpisode {
|
pub(crate) fn build(self) -> NewEpisode {
|
||||||
NewEpisode {
|
NewEpisode {
|
||||||
title: self.title,
|
title: self.title,
|
||||||
uri: self.uri,
|
uri: self.uri,
|
||||||
|
local_uri: self.local_uri,
|
||||||
description: self.description,
|
description: self.description,
|
||||||
published_date: self.published_date,
|
published_date: self.published_date,
|
||||||
length: self.length,
|
length: self.length,
|
||||||
guid: self.guid,
|
guid: self.guid,
|
||||||
epoch: self.epoch,
|
epoch: self.epoch,
|
||||||
|
played: self.played,
|
||||||
|
favorite: self.favorite,
|
||||||
|
archive: self.archive,
|
||||||
podcast_id: self.podcast_id,
|
podcast_id: self.podcast_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,24 +10,27 @@ use models::queryables::Episode;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
// TODO: Write unit test.
|
|
||||||
fn download_checker() -> Result<()> {
|
fn download_checker() -> Result<()> {
|
||||||
let episodes = dbqueries::get_downloaded_episodes()?;
|
let episodes = dbqueries::get_downloaded_episodes()?;
|
||||||
|
|
||||||
episodes.into_par_iter().for_each(|mut ep| {
|
episodes
|
||||||
if !Path::new(ep.local_uri().unwrap()).exists() {
|
.into_par_iter()
|
||||||
ep.set_local_uri(None);
|
.for_each(|mut ep| checker_helper(&mut ep));
|
||||||
let res = ep.save();
|
|
||||||
if let Err(err) = res {
|
|
||||||
error!("Error while trying to update episode: {:#?}", ep);
|
|
||||||
error!("Error: {}", err);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn checker_helper(ep: &mut Episode) {
|
||||||
|
if !Path::new(ep.local_uri().unwrap()).exists() {
|
||||||
|
ep.set_local_uri(None);
|
||||||
|
let res = ep.save();
|
||||||
|
if let Err(err) = res {
|
||||||
|
error!("Error while trying to update episode: {:#?}", ep);
|
||||||
|
error!("Error: {}", err);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Write unit test.
|
// TODO: Write unit test.
|
||||||
fn played_cleaner() -> Result<()> {
|
fn played_cleaner() -> Result<()> {
|
||||||
let episodes = dbqueries::get_played_episodes()?;
|
let episodes = dbqueries::get_played_episodes()?;
|
||||||
@ -37,6 +40,7 @@ fn played_cleaner() -> Result<()> {
|
|||||||
if ep.local_uri().is_some() && ep.played().is_some() {
|
if ep.local_uri().is_some() && ep.played().is_some() {
|
||||||
let played = ep.played().unwrap();
|
let played = ep.played().unwrap();
|
||||||
// TODO: expose a config and a user set option.
|
// TODO: expose a config and a user set option.
|
||||||
|
// Chnage the test too when exposed
|
||||||
let limit = played + 172_800; // add 2days in seconds
|
let limit = played + 172_800; // add 2days in seconds
|
||||||
if now_utc > limit {
|
if now_utc > limit {
|
||||||
let e = delete_local_content(&mut ep);
|
let e = delete_local_content(&mut ep);
|
||||||
@ -52,7 +56,6 @@ fn played_cleaner() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write unit test.
|
|
||||||
pub fn delete_local_content(ep: &mut Episode) -> Result<()> {
|
pub fn delete_local_content(ep: &mut Episode) -> Result<()> {
|
||||||
if ep.local_uri().is_some() {
|
if ep.local_uri().is_some() {
|
||||||
let uri = ep.local_uri().unwrap().to_owned();
|
let uri = ep.local_uri().unwrap().to_owned();
|
||||||
@ -90,3 +93,132 @@ pub fn url_cleaner(s: &str) -> String {
|
|||||||
_ => s.trim().to_owned(),
|
_ => s.trim().to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use database::{connection, truncate_db};
|
||||||
|
use models::insertables::NewEpisodeBuilder;
|
||||||
|
use self::tempdir::TempDir;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
fn helper_db() -> TempDir {
|
||||||
|
// Clean the db
|
||||||
|
truncate_db().unwrap();
|
||||||
|
// Setup tmp file stuff
|
||||||
|
let tmp_dir = TempDir::new("hammond_test").unwrap();
|
||||||
|
let valid_path = tmp_dir.path().join("virtual_dl.mp3");
|
||||||
|
let bad_path = tmp_dir.path().join("invalid_thing.mp3");
|
||||||
|
let mut tmp_file = File::create(&valid_path).unwrap();
|
||||||
|
writeln!(tmp_file, "Foooo").unwrap();
|
||||||
|
|
||||||
|
// Setup episodes
|
||||||
|
let db = connection();
|
||||||
|
let con = db.get().unwrap();
|
||||||
|
NewEpisodeBuilder::new()
|
||||||
|
.uri("foo_bar".to_string())
|
||||||
|
.local_uri(Some(valid_path.to_str().unwrap().to_owned()))
|
||||||
|
.build()
|
||||||
|
.into_episode(&con)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
NewEpisodeBuilder::new()
|
||||||
|
.uri("bar_baz".to_string())
|
||||||
|
.local_uri(Some(bad_path.to_str().unwrap().to_owned()))
|
||||||
|
.build()
|
||||||
|
.into_episode(&con)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tmp_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_checker() {
|
||||||
|
let _tmp_dir = helper_db();
|
||||||
|
download_checker().unwrap();
|
||||||
|
let episodes = dbqueries::get_downloaded_episodes().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(episodes.len(), 1);
|
||||||
|
assert_eq!("foo_bar", episodes.first().unwrap().uri());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_checker_helper() {
|
||||||
|
let _tmp_dir = helper_db();
|
||||||
|
let mut episode = {
|
||||||
|
let db = connection();
|
||||||
|
let con = db.get().unwrap();
|
||||||
|
dbqueries::get_episode_from_uri(&con, "bar_baz").unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
checker_helper(&mut episode);
|
||||||
|
assert!(episode.local_uri().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_cleaner() {
|
||||||
|
let _tmp_dir = helper_db();
|
||||||
|
let mut episode = {
|
||||||
|
let db = connection();
|
||||||
|
let con = db.get().unwrap();
|
||||||
|
dbqueries::get_episode_from_uri(&con, "foo_bar").unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let valid_path = episode.local_uri().unwrap().to_owned();
|
||||||
|
delete_local_content(&mut episode).unwrap();
|
||||||
|
assert_eq!(Path::new(&valid_path).exists(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_played_cleaner_expired() {
|
||||||
|
let _tmp_dir = helper_db();
|
||||||
|
let mut episode = {
|
||||||
|
let db = connection();
|
||||||
|
let con = db.get().unwrap();
|
||||||
|
dbqueries::get_episode_from_uri(&con, "foo_bar").unwrap()
|
||||||
|
};
|
||||||
|
let now_utc = Utc::now().timestamp() as i32;
|
||||||
|
// let limit = now_utc - 172_800;
|
||||||
|
let epoch = now_utc - 200_000;
|
||||||
|
episode.set_played(Some(epoch));
|
||||||
|
episode.save().unwrap();
|
||||||
|
let valid_path = episode.local_uri().unwrap().to_owned();
|
||||||
|
|
||||||
|
// This should delete the file
|
||||||
|
played_cleaner().unwrap();
|
||||||
|
assert_eq!(Path::new(&valid_path).exists(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_played_cleaner_none() {
|
||||||
|
let _tmp_dir = helper_db();
|
||||||
|
let mut episode = {
|
||||||
|
let db = connection();
|
||||||
|
let con = db.get().unwrap();
|
||||||
|
dbqueries::get_episode_from_uri(&con, "foo_bar").unwrap()
|
||||||
|
};
|
||||||
|
let now_utc = Utc::now().timestamp() as i32;
|
||||||
|
// limit = 172_800;
|
||||||
|
let epoch = now_utc - 20_000;
|
||||||
|
episode.set_played(Some(epoch));
|
||||||
|
episode.save().unwrap();
|
||||||
|
let valid_path = episode.local_uri().unwrap().to_owned();
|
||||||
|
|
||||||
|
// This should not delete the file
|
||||||
|
played_cleaner().unwrap();
|
||||||
|
assert_eq!(Path::new(&valid_path).exists(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_url_cleaner() {
|
||||||
|
let good_url = "http://traffic.megaphone.fm/FL8608731318.mp3";
|
||||||
|
let bad_url = "http://traffic.megaphone.fm/FL8608731318.mp3?updated=1484685184";
|
||||||
|
|
||||||
|
assert_eq!(url_cleaner(bad_url), good_url);
|
||||||
|
assert_eq!(url_cleaner(good_url), good_url);
|
||||||
|
assert_eq!(url_cleaner(&format!(" {}\t\n", bad_url)), good_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use hammond_data::{Episode, Podcast};
|
use hammond_data::{Episode, Podcast};
|
||||||
use hammond_data::xdg_::{DL_DIR, HAMMOND_CACHE};
|
use hammond_data::xdg_dirs::{DL_DIR, HAMMOND_CACHE};
|
||||||
|
|
||||||
// TODO: Replace path that are of type &str with std::path.
|
// TODO: Replace path that are of type &str with std::path.
|
||||||
// TODO: Have a convention/document absolute/relative paths, if they should end with / or not.
|
// TODO: Have a convention/document absolute/relative paths, if they should end with / or not.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user