From 3a6f6d89316720002aa5444cdf48f837e374d9f6 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 17 Jan 2018 08:27:39 +0200 Subject: [PATCH] hammond_data: Split models::insertables into multiple modules. --- hammond-data/src/feed.rs | 3 +- hammond-data/src/models/mod.rs | 25 ++- .../models/{insertables.rs => new_episode.rs} | 199 ++---------------- hammond-data/src/models/new_podcast.rs | 131 ++++++++++++ hammond-data/src/models/new_source.rs | 58 +++++ hammond-data/src/models/queryables.rs | 2 +- hammond-data/src/parser.rs | 4 +- hammond-data/src/utils.rs | 2 +- 8 files changed, 231 insertions(+), 193 deletions(-) rename hammond-data/src/models/{insertables.rs => new_episode.rs} (54%) create mode 100644 hammond-data/src/models/new_podcast.rs create mode 100644 hammond-data/src/models/new_source.rs diff --git a/hammond-data/src/feed.rs b/hammond-data/src/feed.rs index a961914..f4d9390 100644 --- a/hammond-data/src/feed.rs +++ b/hammond-data/src/feed.rs @@ -11,7 +11,8 @@ use rss; use dbqueries; use models::queryables::{Podcast, Source}; -use models::insertables::{NewEpisode, NewPodcast}; +use models::NewPodcast; +use models::NewEpisode; use database::connection; use errors::*; diff --git a/hammond-data/src/models/mod.rs b/hammond-data/src/models/mod.rs index 9f4dc1b..2f23b13 100644 --- a/hammond-data/src/models/mod.rs +++ b/hammond-data/src/models/mod.rs @@ -1,2 +1,25 @@ -pub(crate) mod insertables; pub(crate) mod queryables; +pub(crate) mod new_episode; +pub(crate) mod new_podcast; +pub(crate) mod new_source; + +use diesel::prelude::*; + +pub(crate) use self::new_episode::{NewEpisode, NewEpisodeMinimal}; +pub(crate) use self::new_podcast::NewPodcast; +pub(crate) use self::new_source::NewSource; + +#[allow(dead_code)] +enum IndexState { + Index(T), + Update(T), + NotChanged, +} + +pub trait Insert { + fn insert(&self, &SqliteConnection) -> QueryResult; +} + +pub trait Update { + fn update(&self, &SqliteConnection, i32) -> QueryResult; +} diff --git a/hammond-data/src/models/insertables.rs b/hammond-data/src/models/new_episode.rs similarity index 54% rename from hammond-data/src/models/insertables.rs rename to hammond-data/src/models/new_episode.rs index 4ac1c1a..db4a69a 100644 --- a/hammond-data/src/models/insertables.rs +++ b/hammond-data/src/models/new_episode.rs @@ -1,196 +1,18 @@ -#![allow(unused_mut)] - use diesel::prelude::*; use diesel; +use schema::episode; use rss; use ammonia; use rfc822_sanitizer::parse_from_rfc2822_with_fallback as parse_rfc822; -use utils::{replace_extra_spaces, url_cleaner}; -use schema::{episode, podcast, source}; -use models::queryables::{Episode, Podcast, Source}; use dbqueries; -use database::connection; -use parser; - use errors::*; +use models::{Insert, Update}; +use models::queryables::Episode; -#[allow(dead_code)] -enum IndexState { - Index(T), - Update(T), - NotChanged, -} - -trait Insert { - fn insert(&self, &SqliteConnection) -> QueryResult; -} - -trait Update { - fn update(&self, &SqliteConnection, i32) -> QueryResult; -} - -#[derive(Insertable)] -#[table_name = "source"] -#[derive(Debug, Clone, Default, Builder, PartialEq)] -#[builder(default)] -#[builder(derive(Debug))] -#[builder(setter(into))] -pub(crate) struct NewSource { - uri: String, - last_modified: Option, - http_etag: Option, -} - -impl Insert for NewSource { - fn insert(&self, con: &SqliteConnection) -> QueryResult { - use schema::source::dsl::*; - diesel::insert_into(source).values(self).execute(&*con) - } -} - -impl NewSource { - pub(crate) fn new(uri: &str) -> NewSource { - NewSource { - uri: uri.trim().to_string(), - last_modified: None, - http_etag: None, - } - } - - fn index(&self) -> Result<()> { - let db = connection(); - let con = db.get()?; - - // Throw away the result like `insert or ignore` - // Diesel deos not support `insert or ignore` yet. - let _ = self.insert(&con); - Ok(()) - } - - // Look out for when tryinto lands into stable. - pub(crate) fn into_source(self) -> Result { - self.index()?; - dbqueries::get_source_from_uri(&self.uri) - } -} - -#[derive(Insertable, AsChangeset)] -#[table_name = "podcast"] -#[derive(Debug, Clone, Default, Builder, PartialEq)] -#[builder(default)] -#[builder(derive(Debug))] -#[builder(setter(into))] -pub(crate) struct NewPodcast { - title: String, - link: String, - description: String, - image_uri: Option, - source_id: i32, -} - -impl Insert for NewPodcast { - fn insert(&self, con: &SqliteConnection) -> QueryResult { - use schema::podcast::dsl::*; - diesel::insert_into(podcast).values(self).execute(&*con) - } -} - -impl Update for NewPodcast { - fn update(&self, con: &SqliteConnection, podcast_id: i32) -> QueryResult { - use schema::podcast::dsl::*; - - info!("Updating {}", self.title); - diesel::update(podcast.filter(id.eq(podcast_id))) - .set(self) - .execute(&*con) - } -} - -impl NewPodcast { - /// Parses a `rss::Channel` into a `NewPodcast` Struct. - pub(crate) fn new(chan: &rss::Channel, source_id: i32) -> NewPodcast { - let title = chan.title().trim(); - - // Prefer itunes summary over rss.description since many feeds put html into - // rss.description. - let summary = chan.itunes_ext().map(|s| s.summary()).and_then(|s| s); - let description = if let Some(sum) = summary { - replace_extra_spaces(&ammonia::clean(sum)) - } else { - replace_extra_spaces(&ammonia::clean(chan.description())) - }; - - let link = url_cleaner(chan.link()); - let x = chan.itunes_ext().map(|s| s.image()); - let image_uri = if let Some(img) = x { - img.map(|s| s.to_owned()) - } else { - chan.image().map(|foo| foo.url().to_owned()) - }; - - NewPodcastBuilder::default() - .title(title) - .description(description) - .link(link) - .image_uri(image_uri) - .source_id(source_id) - .build() - .unwrap() - } - - // Look out for when tryinto lands into stable. - pub(crate) fn into_podcast(self) -> Result { - self.index()?; - Ok(dbqueries::get_podcast_from_source_id(self.source_id)?) - } - - pub(crate) fn index(&self) -> Result<()> { - let pd = dbqueries::get_podcast_from_source_id(self.source_id); - - let db = connection(); - let con = db.get()?; - match pd { - Ok(foo) => { - if (foo.link() != self.link) || (foo.title() != self.title) - || (foo.image_uri() != self.image_uri.as_ref().map(|x| x.as_str())) - { - info!("NewEpisode: {:?}\n OldEpisode: {:?}", self, foo); - self.update(&con, foo.id())?; - } - } - Err(_) => { - self.insert(&con)?; - } - } - Ok(()) - } -} - -#[allow(dead_code)] -// Ignore the following geters. They are used in unit tests mainly. -impl NewPodcast { - pub(crate) fn source_id(&self) -> i32 { - self.source_id - } - - pub(crate) fn title(&self) -> &str { - &self.title - } - - pub(crate) fn link(&self) -> &str { - &self.link - } - - pub(crate) fn description(&self) -> &str { - &self.description - } - - pub(crate) fn image_uri(&self) -> Option<&str> { - self.image_uri.as_ref().map(|s| s.as_str()) - } -} +use utils::{replace_extra_spaces, url_cleaner}; +use parser; #[derive(Insertable, AsChangeset)] #[table_name = "episode"] @@ -217,6 +39,7 @@ impl From for NewEpisode { .duration(e.duration) .epoch(e.epoch) .podcast_id(e.podcast_id) + .guid(e.guid) .build() .unwrap() } @@ -324,17 +147,19 @@ pub(crate) struct NewEpisodeMinimal { uri: Option, duration: Option, epoch: i32, + guid: Option, podcast_id: i32, } impl NewEpisodeMinimal { #[allow(dead_code)] - fn new(item: &rss::Item, parent_id: i32) -> Result { + pub(crate) fn new(item: &rss::Item, parent_id: i32) -> Result { if item.title().is_none() { bail!("No title specified for the item.") } let title = item.title().unwrap().trim().to_owned(); + let guid = item.guid().map(|s| s.value().trim().to_owned()); let uri = if let Some(url) = item.enclosure().map(|s| url_cleaner(s.url())) { Some(url) @@ -359,14 +184,14 @@ impl NewEpisodeMinimal { .uri(uri) .duration(duration) .epoch(epoch) + .guid(guid) .podcast_id(parent_id) .build() .unwrap()) } #[allow(dead_code)] - fn into_new_episode(self, item: &rss::Item) -> NewEpisode { - let guid = item.guid().map(|s| s.value().trim().to_owned()); + pub(crate) fn into_new_episode(self, item: &rss::Item) -> NewEpisode { let length = || -> Option { item.enclosure().map(|x| x.length().parse().ok())? }(); // Prefer itunes summary over rss.description since many feeds put html into @@ -385,7 +210,7 @@ impl NewEpisodeMinimal { .duration(self.duration) .epoch(self.epoch) .podcast_id(self.podcast_id) - .guid(guid) + .guid(self.guid) .length(length) .description(description) .build() diff --git a/hammond-data/src/models/new_podcast.rs b/hammond-data/src/models/new_podcast.rs new file mode 100644 index 0000000..ccf3906 --- /dev/null +++ b/hammond-data/src/models/new_podcast.rs @@ -0,0 +1,131 @@ +use diesel::prelude::*; +use diesel; + +use rss; +use ammonia; + +use schema::podcast; +use models::queryables::Podcast; +use models::{Insert, Update}; + +use dbqueries; +use database::connection; +use utils::{replace_extra_spaces, url_cleaner}; + +use errors::*; + +#[derive(Insertable, AsChangeset)] +#[table_name = "podcast"] +#[derive(Debug, Clone, Default, Builder, PartialEq)] +#[builder(default)] +#[builder(derive(Debug))] +#[builder(setter(into))] +pub(crate) struct NewPodcast { + title: String, + link: String, + description: String, + image_uri: Option, + source_id: i32, +} + +impl Insert for NewPodcast { + fn insert(&self, con: &SqliteConnection) -> QueryResult { + use schema::podcast::dsl::*; + diesel::insert_into(podcast).values(self).execute(&*con) + } +} + +impl Update for NewPodcast { + fn update(&self, con: &SqliteConnection, podcast_id: i32) -> QueryResult { + use schema::podcast::dsl::*; + + info!("Updating {}", self.title); + diesel::update(podcast.filter(id.eq(podcast_id))) + .set(self) + .execute(&*con) + } +} + +impl NewPodcast { + /// Parses a `rss::Channel` into a `NewPodcast` Struct. + pub(crate) fn new(chan: &rss::Channel, source_id: i32) -> NewPodcast { + let title = chan.title().trim(); + + // Prefer itunes summary over rss.description since many feeds put html into + // rss.description. + let summary = chan.itunes_ext().map(|s| s.summary()).and_then(|s| s); + let description = if let Some(sum) = summary { + replace_extra_spaces(&ammonia::clean(sum)) + } else { + replace_extra_spaces(&ammonia::clean(chan.description())) + }; + + let link = url_cleaner(chan.link()); + let x = chan.itunes_ext().map(|s| s.image()); + let image_uri = if let Some(img) = x { + img.map(|s| s.to_owned()) + } else { + chan.image().map(|foo| foo.url().to_owned()) + }; + + NewPodcastBuilder::default() + .title(title) + .description(description) + .link(link) + .image_uri(image_uri) + .source_id(source_id) + .build() + .unwrap() + } + + // Look out for when tryinto lands into stable. + pub(crate) fn into_podcast(self) -> Result { + self.index()?; + Ok(dbqueries::get_podcast_from_source_id(self.source_id)?) + } + + pub(crate) fn index(&self) -> Result<()> { + let pd = dbqueries::get_podcast_from_source_id(self.source_id); + + let db = connection(); + let con = db.get()?; + match pd { + Ok(foo) => { + if (foo.link() != self.link) || (foo.title() != self.title) + || (foo.image_uri() != self.image_uri.as_ref().map(|x| x.as_str())) + { + info!("NewEpisode: {:?}\n OldEpisode: {:?}", self, foo); + self.update(&con, foo.id())?; + } + } + Err(_) => { + self.insert(&con)?; + } + } + Ok(()) + } +} + +#[allow(dead_code)] +// Ignore the following geters. They are used in unit tests mainly. +impl NewPodcast { + pub(crate) fn source_id(&self) -> i32 { + self.source_id + } + + pub(crate) fn title(&self) -> &str { + &self.title + } + + pub(crate) fn link(&self) -> &str { + &self.link + } + + pub(crate) fn description(&self) -> &str { + &self.description + } + + pub(crate) fn image_uri(&self) -> Option<&str> { + self.image_uri.as_ref().map(|s| s.as_str()) + } +} diff --git a/hammond-data/src/models/new_source.rs b/hammond-data/src/models/new_source.rs new file mode 100644 index 0000000..50b55e9 --- /dev/null +++ b/hammond-data/src/models/new_source.rs @@ -0,0 +1,58 @@ +#![allow(unused_mut)] + +use diesel::prelude::*; +use diesel; + +use schema::source; +use models::queryables::Source; +use models::{Insert, Update}; + +use dbqueries; +use database::connection; + +use errors::*; + +#[derive(Insertable)] +#[table_name = "source"] +#[derive(Debug, Clone, Default, Builder, PartialEq)] +#[builder(default)] +#[builder(derive(Debug))] +#[builder(setter(into))] +pub(crate) struct NewSource { + uri: String, + last_modified: Option, + http_etag: Option, +} + +impl Insert for NewSource { + fn insert(&self, con: &SqliteConnection) -> QueryResult { + use schema::source::dsl::*; + diesel::insert_into(source).values(self).execute(&*con) + } +} + +impl NewSource { + pub(crate) fn new(uri: &str) -> NewSource { + NewSource { + uri: uri.trim().to_string(), + last_modified: None, + http_etag: None, + } + } + + fn index(&self) -> Result<()> { + let db = connection(); + let con = db.get()?; + + // Throw away the result like `insert or ignore` + // Diesel deos not support `insert or ignore` yet. + let _ = self.insert(&con); + Ok(()) + } + + // Look out for when tryinto lands into stable. + pub(crate) fn into_source(self) -> Result { + self.index()?; + dbqueries::get_source_from_uri(&self.uri) + } +} diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index d6f8390..834df52 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -19,7 +19,7 @@ use schema::{episode, podcast, source}; use feed::Feed; use errors::*; -use models::insertables::NewSource; +use models::NewSource; use database::connection; use std::io::Read; diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index 68d304d..a17c932 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -36,8 +36,8 @@ mod tests { use std::io::BufReader; use rss::{Channel, ItemBuilder}; use rss::extension::itunes::ITunesItemExtensionBuilder; - use models::insertables::{NewEpisode, NewEpisodeBuilder}; - use models::insertables::{NewPodcast, NewPodcastBuilder}; + use models::new_episode::{NewEpisode, NewEpisodeBuilder}; + use models::new_podcast::{NewPodcast, NewPodcastBuilder}; use super::*; diff --git a/hammond-data/src/utils.rs b/hammond-data/src/utils.rs index 108a6ac..77e126f 100644 --- a/hammond-data/src/utils.rs +++ b/hammond-data/src/utils.rs @@ -153,7 +153,7 @@ mod tests { use super::*; use database::{connection, truncate_db}; - use models::insertables::NewEpisodeBuilder; + use models::new_episode::NewEpisodeBuilder; use self::tempdir::TempDir; use std::fs::File; use std::io::Write;