From 32cd24fc7b7d69a691552e58a593f63ed93764d2 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Dec 2017 18:20:40 +0200 Subject: [PATCH 01/41] EpisodesView: Added glade files and initial yak shaving. --- hammond-gtk/resources/gtk/episode_widget.ui | 6 +- hammond-gtk/resources/gtk/episodes_view.ui | 82 +++++++++++++++++++ .../resources/gtk/episodes_view_widget.ui | 25 ++++++ hammond-gtk/resources/gtk/show_widget.ui | 46 ++++++----- hammond-gtk/resources/resources.xml | 2 + hammond-gtk/src/content.rs | 7 +- hammond-gtk/src/views/episodes.rs | 46 +++++++++++ hammond-gtk/src/widgets/episode.rs | 6 +- hammond-gtk/src/widgets/show.rs | 4 - 9 files changed, 190 insertions(+), 34 deletions(-) create mode 100644 hammond-gtk/resources/gtk/episodes_view.ui create mode 100644 hammond-gtk/resources/gtk/episodes_view_widget.ui diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index dbf6a43..e7cf0a5 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -15,6 +15,7 @@ True False + center vertical 5 @@ -261,8 +262,8 @@ - False - True + True + False 5 0 @@ -275,6 +276,7 @@ False True 5 + end 1 diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui new file mode 100644 index 0000000..8e8f3ab --- /dev/null +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -0,0 +1,82 @@ + + + + + + True + False + vertical + + + True + True + in + + + True + False + + + True + False + center + + + True + False + vertical + + + + + + True + False + 0 + + + + + 600 + True + False + center + vertical + + + + + + False + True + 1 + + + + + True + False + vertical + + + + + + True + False + 2 + + + + + + + + + True + True + 0 + + + + diff --git a/hammond-gtk/resources/gtk/episodes_view_widget.ui b/hammond-gtk/resources/gtk/episodes_view_widget.ui new file mode 100644 index 0000000..a8ec5f9 --- /dev/null +++ b/hammond-gtk/resources/gtk/episodes_view_widget.ui @@ -0,0 +1,25 @@ + + + + + + True + False + + + True + False + 102 + image-x-generic-symbolic + + + False + True + 0 + + + + + + + diff --git a/hammond-gtk/resources/gtk/show_widget.ui b/hammond-gtk/resources/gtk/show_widget.ui index cc3c9b4..ead850a 100644 --- a/hammond-gtk/resources/gtk/show_widget.ui +++ b/hammond-gtk/resources/gtk/show_widget.ui @@ -78,6 +78,26 @@ False vertical 10 + + + True + False + start + end + Show description + True + 57 + + + + + + False + False + end + 1 + + True @@ -128,6 +148,9 @@ True center center + False @@ -145,27 +168,6 @@ 0 - - - True - False - start - center - Show description - True - word-char - 55 - - - - - - True - True - end - 1 - - True @@ -176,7 +178,7 @@ False - True + False 0 diff --git a/hammond-gtk/resources/resources.xml b/hammond-gtk/resources/resources.xml index cb87ca4..0c91cd0 100644 --- a/hammond-gtk/resources/resources.xml +++ b/hammond-gtk/resources/resources.xml @@ -4,6 +4,8 @@ gtk/episode_widget.ui gtk/show_widget.ui gtk/empty_view.ui + gtk/episodes_view.ui + gtk/episodes_view_widget.ui gtk/shows_view.ui gtk/shows_child.ui gtk/headerbar.ui diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index adc4cb6..3bdf019 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -6,6 +6,7 @@ use hammond_data::dbqueries; use views::shows::ShowsPopulated; use views::empty::EmptyView; +use views::episodes::EpisodesView; use widgets::show::ShowWidget; use headerbar::Header; @@ -155,14 +156,14 @@ struct EpisodeStack { impl EpisodeStack { fn new() -> Rc { - let _pop = RecentEpisodes {}; + let episodes = EpisodesView::default(); let empty = EmptyView::new(); let stack = gtk::Stack::new(); - // stack.add_named(&pop.container, "populated"); + stack.add_named(&episodes.container, "episodes"); stack.add_named(&empty.container, "empty"); // FIXME: - stack.set_visible_child_name("empty"); + stack.set_visible_child_name("episodes"); Rc::new(EpisodeStack { // empty, diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 8b13789..e8eb3da 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -1 +1,47 @@ +use gtk; +use gtk::prelude::*; +use widgets::episode::EpisodeWidget; + +#[derive(Debug, Clone)] +pub struct EpisodesView { + pub container: gtk::Box, + frame_parent: gtk::Box, +} + +impl Default for EpisodesView { + fn default() -> Self { + let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view.ui"); + let container: gtk::Box = builder.get_object("container").unwrap(); + let frame_parent: gtk::Box = builder.get_object("frame_parent").unwrap(); + + EpisodesView { + container, + frame_parent, + } + } +} + +#[derive(Debug, Clone)] +struct EpisodesViewWidget { + container: gtk::Box, + image: gtk::Image, + episode: gtk::Box, +} + +impl Default for EpisodesViewWidget { + fn default() -> Self { + let builder = + gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view_widget.ui"); + let container: gtk::Box = builder.get_object("container").unwrap(); + let image: gtk::Image = builder.get_object("cover").unwrap(); + let ep = EpisodeWidget::default(); + container.add(&ep.container); + + EpisodesViewWidget { + container, + image, + episode: ep.container, + } + } +} diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index fd9888d..8beb2b0 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -32,9 +32,9 @@ type Foo = RefCell< thread_local!(static GLOBAL: Foo = RefCell::new(None)); -#[derive(Debug)] -struct EpisodeWidget { - container: gtk::Box, +#[derive(Debug, Clone)] +pub struct EpisodeWidget { + pub container: gtk::Box, play: gtk::Button, delete: gtk::Button, download: gtk::Button, diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs index c232661..7a30a63 100644 --- a/hammond-gtk/src/widgets/show.rs +++ b/hammond-gtk/src/widgets/show.rs @@ -40,10 +40,6 @@ impl Default for ShowWidget { let link: gtk::Button = builder.get_object("link_button").unwrap(); let settings: gtk::MenuButton = builder.get_object("settings_button").unwrap(); - unsub - .get_style_context() - .map(|c| c.add_class("destructive-action")); - ShowWidget { container, cover, From 61bd7893c7b19642eebb72c9c4924888e7e2fdc9 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Dec 2017 19:10:50 +0200 Subject: [PATCH 02/41] EpisodeWidget: Remove Podcast depndancy from new() method. --- hammond-data/src/dbqueries.rs | 22 +++++++++++++ hammond-gtk/resources/gtk/episode_widget.ui | 5 +++ hammond-gtk/src/content.rs | 2 +- hammond-gtk/src/views/episodes.rs | 36 +++++++++++++++++++++ hammond-gtk/src/widgets/episode.rs | 14 ++++---- 5 files changed, 70 insertions(+), 9 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 7adb91c..5e26fcd 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -84,6 +84,28 @@ pub fn get_episodes_with_limit(limit: u32) -> Result> { .load::(&*con)?) } +pub fn get_episodeswidgets_with_limit(limit: u32) -> Result> { + use schema::episode::dsl::*; + + let db = connection(); + let con = db.get()?; + + Ok(episode + .select(( + rowid, + title, + uri, + local_uri, + epoch, + length, + played, + podcast_id, + )) + .order(epoch.desc()) + .limit(i64::from(limit)) + .load::(&*con)?) +} + pub fn get_podcast_from_id(pid: i32) -> Result { use schema::podcast::dsl::*; diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index e7cf0a5..e0aa126 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -177,6 +177,7 @@ Cancel True True + True center @@ -191,6 +192,7 @@ delete_button True True + True end center @@ -213,6 +215,7 @@ True True True + True end center True @@ -235,6 +238,7 @@ True True + True center @@ -271,6 +275,7 @@ False + True False diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index 3bdf019..0ea2bfb 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -156,7 +156,7 @@ struct EpisodeStack { impl EpisodeStack { fn new() -> Rc { - let episodes = EpisodesView::default(); + let episodes = EpisodesView::new(); let empty = EmptyView::new(); let stack = gtk::Stack::new(); diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index e8eb3da..dc8c923 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -1,8 +1,12 @@ use gtk; use gtk::prelude::*; +use hammond_data::dbqueries; + use widgets::episode::EpisodeWidget; +use std::rc::Rc; + #[derive(Debug, Clone)] pub struct EpisodesView { pub container: gtk::Box, @@ -22,6 +26,38 @@ impl Default for EpisodesView { } } +impl EpisodesView { + pub fn new() -> Rc { + let view = EpisodesView::default(); + + let episodes = dbqueries::get_episodeswidgets_with_limit(100).unwrap(); + let frame = gtk::Frame::new("Recent Episodes"); + let list = gtk::ListBox::new(); + + view.frame_parent.add(&frame); + frame.add(&list); + + list.set_vexpand(false); + list.set_hexpand(false); + list.set_visible(true); + list.set_selection_mode(gtk::SelectionMode::None); + + episodes.into_iter().for_each(|mut ep| { + let widget = EpisodeWidget::new(&mut ep); + list.add(&widget.container); + + let sep = gtk::Separator::new(gtk::Orientation::Vertical); + sep.set_sensitive(false); + sep.set_can_focus(false); + + list.add(&sep); + sep.show() + }); + + Rc::new(view) + } +} + #[derive(Debug, Clone)] struct EpisodesViewWidget { container: gtk::Box, diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 8beb2b0..d872fe5 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -82,16 +82,16 @@ impl Default for EpisodeWidget { } impl EpisodeWidget { - pub fn new(episode: &mut EpisodeWidgetQuery, pd: &Podcast) -> EpisodeWidget { + pub fn new(episode: &mut EpisodeWidgetQuery) -> EpisodeWidget { let widget = EpisodeWidget::default(); - widget.init(episode, pd); + widget.init(episode); widget } // TODO: calculate lenght. // TODO: wire the progress_bar to the downloader. // TODO: wire the cancel button. - fn init(&self, episode: &mut EpisodeWidgetQuery, pd: &Podcast) { + fn init(&self, episode: &mut EpisodeWidgetQuery) { self.title.set_xalign(0.0); self.title.set_text(episode.title()); @@ -147,7 +147,6 @@ impl EpisodeWidget { download.show(); })); - let pd_title = pd.title().to_owned(); let play = &self.play; let delete = &self.delete; let cancel = &self.cancel; @@ -155,7 +154,6 @@ impl EpisodeWidget { self.download.connect_clicked( clone!(play, delete, episode, cancel, progress => move |dl| { on_download_clicked( - &pd_title, &mut episode.clone(), dl, &play, @@ -170,7 +168,6 @@ impl EpisodeWidget { // TODO: show notification when dl is finished. fn on_download_clicked( - pd_title: &str, ep: &mut EpisodeWidgetQuery, download_bttn: >k::Button, play_bttn: >k::Button, @@ -194,7 +191,8 @@ fn on_download_clicked( }), ); - let pd_title = pd_title.to_owned(); + let pd = dbqueries::get_podcast_from_id(ep.podcast_id()).unwrap(); + let pd_title = pd.title().to_owned(); let mut ep = ep.clone(); cancel_bttn.show(); progress_bar.show(); @@ -270,7 +268,7 @@ pub fn episodes_listbox(pd: &Podcast) -> Result { let list = gtk::ListBox::new(); episodes.into_iter().for_each(|mut ep| { - let widget = EpisodeWidget::new(&mut ep, pd); + let widget = EpisodeWidget::new(&mut ep); list.add(&widget.container); let sep = gtk::Separator::new(gtk::Orientation::Vertical); From 5220eaceee3b426c359f5c833bd017ec80cb121e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Dec 2017 21:35:51 +0200 Subject: [PATCH 03/41] EpisodesView: Add an empty cover to the widget. --- .../resources/gtk/episodes_view_widget.ui | 2 +- hammond-gtk/src/views/episodes.rs | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view_widget.ui b/hammond-gtk/resources/gtk/episodes_view_widget.ui index a8ec5f9..3aa6d88 100644 --- a/hammond-gtk/resources/gtk/episodes_view_widget.ui +++ b/hammond-gtk/resources/gtk/episodes_view_widget.ui @@ -9,7 +9,7 @@ True False - 102 + 64 image-x-generic-symbolic diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index dc8c923..e418a44 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -2,6 +2,7 @@ use gtk; use gtk::prelude::*; use hammond_data::dbqueries; +use hammond_data::EpisodeWidgetQuery; use widgets::episode::EpisodeWidget; @@ -43,8 +44,8 @@ impl EpisodesView { list.set_selection_mode(gtk::SelectionMode::None); episodes.into_iter().for_each(|mut ep| { - let widget = EpisodeWidget::new(&mut ep); - list.add(&widget.container); + let viewep = EpisodesViewWidget::new(&mut ep); + list.add(&viewep.container); let sep = gtk::Separator::new(gtk::Orientation::Vertical); sep.set_sensitive(false); @@ -72,7 +73,24 @@ impl Default for EpisodesViewWidget { let container: gtk::Box = builder.get_object("container").unwrap(); let image: gtk::Image = builder.get_object("cover").unwrap(); let ep = EpisodeWidget::default(); - container.add(&ep.container); + container.pack_start(&ep.container, true, true, 5); + + EpisodesViewWidget { + container, + image, + episode: ep.container, + } + } +} + +impl EpisodesViewWidget { + fn new(episode: &mut EpisodeWidgetQuery) -> EpisodesViewWidget { + let builder = + gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view_widget.ui"); + let container: gtk::Box = builder.get_object("container").unwrap(); + let image: gtk::Image = builder.get_object("cover").unwrap(); + let ep = EpisodeWidget::new(episode); + container.pack_start(&ep.container, true, true, 5); EpisodesViewWidget { container, From f602e8c36d5c7297e31e0d3660815d094a23d7df Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 19 Dec 2017 11:04:44 +0200 Subject: [PATCH 04/41] EpisodesView: Create a DieselModel for EpisodeViewWidget. --- hammond-data/src/dbqueries.rs | 33 +++++++++++++---------- hammond-data/src/models/queryables.rs | 39 +++++++++++++++++++++++++++ hammond-data/src/utils.rs | 4 +++ hammond-gtk/src/views/episodes.rs | 7 ++--- 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 5e26fcd..47b59ef 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -2,7 +2,7 @@ use diesel::prelude::*; use diesel; -use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; +use models::queryables::{Episode, EpisodeViewWidgetQuery, EpisodeWidgetQuery, Podcast, Source}; use chrono::prelude::*; use errors::*; @@ -84,26 +84,31 @@ pub fn get_episodes_with_limit(limit: u32) -> Result> { .load::(&*con)?) } -pub fn get_episodeswidgets_with_limit(limit: u32) -> Result> { - use schema::episode::dsl::*; +pub fn get_episodes_view_widgets_with_limit(limit: u32) -> Result> { + use schema::{episode, podcast}; + + joinable!(episode -> podcast (rowid)); + allow_tables_to_appear_in_same_query!(episode, podcast); let db = connection(); let con = db.get()?; - Ok(episode + Ok(episode::table + .left_join(podcast::table) .select(( - rowid, - title, - uri, - local_uri, - epoch, - length, - played, - podcast_id, + episode::rowid, + episode::title, + episode::uri, + episode::local_uri, + episode::epoch, + episode::length, + episode::played, + episode::podcast_id, + (podcast::image_uri).nullable(), )) - .order(epoch.desc()) + .order(episode::epoch.desc()) .limit(i64::from(limit)) - .load::(&*con)?) + .load::(&*con)?) } pub fn get_podcast_from_id(pid: i32) -> Result { diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index 4290979..e87d8b6 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -320,6 +320,45 @@ impl EpisodeWidgetQuery { } } +impl From for EpisodeWidgetQuery { + fn from(view: EpisodeViewWidgetQuery) -> EpisodeWidgetQuery { + EpisodeWidgetQuery { + rowid: view.rowid, + title: view.title, + uri: view.uri, + local_uri: view.local_uri, + epoch: view.epoch, + length: view.length, + played: view.played, + // favorite: view.favorite, + // archive: view.archive, + podcast_id: view.podcast_id, + } + } +} + +#[derive(Queryable, Debug, Clone)] +/// Diesel Model to be used for constructing `EpisodeWidgets`. +pub struct EpisodeViewWidgetQuery { + rowid: i32, + title: String, + uri: Option, + local_uri: Option, + epoch: i32, + length: Option, + played: Option, + // favorite: bool, + // archive: bool, + podcast_id: i32, + image_uri: Option, +} + +impl EpisodeViewWidgetQuery { + pub fn image_uri(&self) -> Option<&str> { + self.image_uri.as_ref().map(|s| s.as_str()) + } +} + #[derive(Queryable, Identifiable, AsChangeset, Associations, PartialEq)] #[belongs_to(Source, foreign_key = "source_id")] #[changeset_options(treat_none_as_null = "true")] diff --git a/hammond-data/src/utils.rs b/hammond-data/src/utils.rs index fb9fe1e..f15c757 100644 --- a/hammond-data/src/utils.rs +++ b/hammond-data/src/utils.rs @@ -14,6 +14,8 @@ use std::path::Path; use std::fs; fn download_checker() -> Result<()> { + // TODO: give it it's own diesel model, + // so it does not pull useless and expensive stuff like description. let episodes = dbqueries::get_downloaded_episodes()?; episodes @@ -35,6 +37,8 @@ fn checker_helper(ep: &mut Episode) { } fn played_cleaner() -> Result<()> { + // TODO: give it it's own diesel model, + // so it does not pull useless and expensive stuff like description. let episodes = dbqueries::get_played_episodes()?; let now_utc = Utc::now().timestamp() as i32; diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index e418a44..37b18c3 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -31,7 +31,7 @@ impl EpisodesView { pub fn new() -> Rc { let view = EpisodesView::default(); - let episodes = dbqueries::get_episodeswidgets_with_limit(100).unwrap(); + let episodes = dbqueries::get_episodes_view_widgets_with_limit(100).unwrap(); let frame = gtk::Frame::new("Recent Episodes"); let list = gtk::ListBox::new(); @@ -43,8 +43,9 @@ impl EpisodesView { list.set_visible(true); list.set_selection_mode(gtk::SelectionMode::None); - episodes.into_iter().for_each(|mut ep| { - let viewep = EpisodesViewWidget::new(&mut ep); + episodes.into_iter().for_each(|ep| { + info!("{:?}", &ep.image_uri()); + let viewep = EpisodesViewWidget::new(&mut ep.into()); list.add(&viewep.container); let sep = gtk::Separator::new(gtk::Orientation::Vertical); From ad9a932143699265be1fdcb952dd7a193ccf4069 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 19 Dec 2017 12:56:16 +0200 Subject: [PATCH 05/41] Fix diesel query. --- hammond-data/src/dbqueries.rs | 2 +- hammond-gtk/src/views/episodes.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 47b59ef..b51f884 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -87,7 +87,7 @@ pub fn get_episodes_with_limit(limit: u32) -> Result> { pub fn get_episodes_view_widgets_with_limit(limit: u32) -> Result> { use schema::{episode, podcast}; - joinable!(episode -> podcast (rowid)); + joinable!(episode -> podcast (podcast_id)); allow_tables_to_appear_in_same_query!(episode, podcast); let db = connection(); diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 37b18c3..120bc7f 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -44,7 +44,6 @@ impl EpisodesView { list.set_selection_mode(gtk::SelectionMode::None); episodes.into_iter().for_each(|ep| { - info!("{:?}", &ep.image_uri()); let viewep = EpisodesViewWidget::new(&mut ep.into()); list.add(&viewep.container); From 895591f628eeab91635161ac7b60841662d02d41 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 19 Dec 2017 13:19:38 +0200 Subject: [PATCH 06/41] EpisodesView: Use show cover image. --- hammond-data/src/lib.rs | 2 +- hammond-data/src/models/queryables.rs | 8 ++++++++ hammond-gtk/src/utils.rs | 7 +++++++ hammond-gtk/src/views/episodes.rs | 22 ++++++++++++++++------ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/hammond-data/src/lib.rs b/hammond-data/src/lib.rs index 97aec76..711eb0c 100644 --- a/hammond-data/src/lib.rs +++ b/hammond-data/src/lib.rs @@ -61,7 +61,7 @@ pub(crate) mod models; mod parser; mod schema; -pub use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; +pub use models::queryables::{Episode, EpisodeViewWidgetQuery, EpisodeWidgetQuery, Podcast, Source}; /// [XDG Base Direcotory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths. #[allow(missing_debug_implementations)] diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index e87d8b6..5120f83 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -354,9 +354,17 @@ pub struct EpisodeViewWidgetQuery { } impl EpisodeViewWidgetQuery { + /// Get the `image_uri`. + /// + /// Represents the uri(url usually) that the Feed cover image is located at. pub fn image_uri(&self) -> Option<&str> { self.image_uri.as_ref().map(|s| s.as_str()) } + + /// `Podcast` table foreign key. + pub fn podcast_id(&self) -> i32 { + self.podcast_id + } } #[derive(Queryable, Identifiable, AsChangeset, Associations, PartialEq)] diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs index c2f484d..66f5b54 100644 --- a/hammond-gtk/src/utils.rs +++ b/hammond-gtk/src/utils.rs @@ -59,6 +59,8 @@ fn refresh_podcasts_view() -> glib::Continue { glib::Continue(false) } +// FIXME: use something that would just scale? + pub fn get_pixbuf_from_path(pd: &Podcast) -> Option { let img_path = downloader::cache_image(pd)?; Pixbuf::new_from_file_at_scale(&img_path, 256, 256, true).ok() @@ -69,6 +71,11 @@ pub fn get_pixbuf_from_path_128(pd: &Podcast) -> Option { Pixbuf::new_from_file_at_scale(&img_path, 128, 128, true).ok() } +pub fn get_pixbuf_from_path_64(pd: &Podcast) -> Option { + let img_path = downloader::cache_image(pd)?; + Pixbuf::new_from_file_at_scale(&img_path, 64, 64, true).ok() +} + #[cfg(test)] mod tests { use hammond_data::Source; diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 120bc7f..00a883b 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -2,9 +2,10 @@ use gtk; use gtk::prelude::*; use hammond_data::dbqueries; -use hammond_data::EpisodeWidgetQuery; +use hammond_data::EpisodeViewWidgetQuery; use widgets::episode::EpisodeWidget; +use utils::get_pixbuf_from_path_64; use std::rc::Rc; @@ -32,11 +33,12 @@ impl EpisodesView { let view = EpisodesView::default(); let episodes = dbqueries::get_episodes_view_widgets_with_limit(100).unwrap(); - let frame = gtk::Frame::new("Recent Episodes"); + let frame = gtk::Frame::new(None); let list = gtk::ListBox::new(); - view.frame_parent.add(&frame); + view.frame_parent.pack_start(&frame, true, false, 10); frame.add(&list); + frame.set_shadow_type(gtk::ShadowType::In); list.set_vexpand(false); list.set_hexpand(false); @@ -44,7 +46,7 @@ impl EpisodesView { list.set_selection_mode(gtk::SelectionMode::None); episodes.into_iter().for_each(|ep| { - let viewep = EpisodesViewWidget::new(&mut ep.into()); + let viewep = EpisodesViewWidget::new(ep); list.add(&viewep.container); let sep = gtk::Separator::new(gtk::Orientation::Vertical); @@ -84,12 +86,20 @@ impl Default for EpisodesViewWidget { } impl EpisodesViewWidget { - fn new(episode: &mut EpisodeWidgetQuery) -> EpisodesViewWidget { + fn new(episode: EpisodeViewWidgetQuery) -> EpisodesViewWidget { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view_widget.ui"); let container: gtk::Box = builder.get_object("container").unwrap(); let image: gtk::Image = builder.get_object("cover").unwrap(); - let ep = EpisodeWidget::new(episode); + + // FIXME: + let pd = dbqueries::get_podcast_from_id(episode.podcast_id()).unwrap(); + let img = get_pixbuf_from_path_64(&pd); + if let Some(i) = img { + image.set_from_pixbuf(&i); + } + + let ep = EpisodeWidget::new(&mut episode.into()); container.pack_start(&ep.container, true, true, 5); EpisodesViewWidget { From 914cad72f597e855de31e0c5f721b844ca5a7082 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 19 Dec 2017 20:45:40 +0200 Subject: [PATCH 07/41] EpisodesView: Custom Diesel model not really necessary. --- hammond-data/src/dbqueries.rs | 13 +++----- hammond-data/src/lib.rs | 2 +- hammond-data/src/models/queryables.rs | 47 --------------------------- hammond-gtk/src/utils.rs | 1 + hammond-gtk/src/views/episodes.rs | 21 ++++++------ 5 files changed, 17 insertions(+), 67 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index b51f884..caaa76a 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -2,7 +2,7 @@ use diesel::prelude::*; use diesel; -use models::queryables::{Episode, EpisodeViewWidgetQuery, EpisodeWidgetQuery, Podcast, Source}; +use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; use chrono::prelude::*; use errors::*; @@ -84,17 +84,13 @@ pub fn get_episodes_with_limit(limit: u32) -> Result> { .load::(&*con)?) } -pub fn get_episodes_view_widgets_with_limit(limit: u32) -> Result> { - use schema::{episode, podcast}; - - joinable!(episode -> podcast (podcast_id)); - allow_tables_to_appear_in_same_query!(episode, podcast); +pub fn get_episodes_widgets_with_limit(limit: u32) -> Result> { + use schema::episode; let db = connection(); let con = db.get()?; Ok(episode::table - .left_join(podcast::table) .select(( episode::rowid, episode::title, @@ -104,11 +100,10 @@ pub fn get_episodes_view_widgets_with_limit(limit: u32) -> Result(&*con)?) + .load::(&*con)?) } pub fn get_podcast_from_id(pid: i32) -> Result { diff --git a/hammond-data/src/lib.rs b/hammond-data/src/lib.rs index 711eb0c..97aec76 100644 --- a/hammond-data/src/lib.rs +++ b/hammond-data/src/lib.rs @@ -61,7 +61,7 @@ pub(crate) mod models; mod parser; mod schema; -pub use models::queryables::{Episode, EpisodeViewWidgetQuery, EpisodeWidgetQuery, Podcast, Source}; +pub use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; /// [XDG Base Direcotory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths. #[allow(missing_debug_implementations)] diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index 5120f83..4290979 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -320,53 +320,6 @@ impl EpisodeWidgetQuery { } } -impl From for EpisodeWidgetQuery { - fn from(view: EpisodeViewWidgetQuery) -> EpisodeWidgetQuery { - EpisodeWidgetQuery { - rowid: view.rowid, - title: view.title, - uri: view.uri, - local_uri: view.local_uri, - epoch: view.epoch, - length: view.length, - played: view.played, - // favorite: view.favorite, - // archive: view.archive, - podcast_id: view.podcast_id, - } - } -} - -#[derive(Queryable, Debug, Clone)] -/// Diesel Model to be used for constructing `EpisodeWidgets`. -pub struct EpisodeViewWidgetQuery { - rowid: i32, - title: String, - uri: Option, - local_uri: Option, - epoch: i32, - length: Option, - played: Option, - // favorite: bool, - // archive: bool, - podcast_id: i32, - image_uri: Option, -} - -impl EpisodeViewWidgetQuery { - /// Get the `image_uri`. - /// - /// Represents the uri(url usually) that the Feed cover image is located at. - pub fn image_uri(&self) -> Option<&str> { - self.image_uri.as_ref().map(|s| s.as_str()) - } - - /// `Podcast` table foreign key. - pub fn podcast_id(&self) -> i32 { - self.podcast_id - } -} - #[derive(Queryable, Identifiable, AsChangeset, Associations, PartialEq)] #[belongs_to(Source, foreign_key = "source_id")] #[changeset_options(treat_none_as_null = "true")] diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs index 66f5b54..3e22f8e 100644 --- a/hammond-gtk/src/utils.rs +++ b/hammond-gtk/src/utils.rs @@ -60,6 +60,7 @@ fn refresh_podcasts_view() -> glib::Continue { } // FIXME: use something that would just scale? +// TODO: make a diesel model with only title, local_uri pub fn get_pixbuf_from_path(pd: &Podcast) -> Option { let img_path = downloader::cache_image(pd)?; diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 00a883b..91c3137 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -2,7 +2,7 @@ use gtk; use gtk::prelude::*; use hammond_data::dbqueries; -use hammond_data::EpisodeViewWidgetQuery; +use hammond_data::EpisodeWidgetQuery; use widgets::episode::EpisodeWidget; use utils::get_pixbuf_from_path_64; @@ -32,7 +32,7 @@ impl EpisodesView { pub fn new() -> Rc { let view = EpisodesView::default(); - let episodes = dbqueries::get_episodes_view_widgets_with_limit(100).unwrap(); + let episodes = dbqueries::get_episodes_widgets_with_limit(100).unwrap(); let frame = gtk::Frame::new(None); let list = gtk::ListBox::new(); @@ -45,8 +45,8 @@ impl EpisodesView { list.set_visible(true); list.set_selection_mode(gtk::SelectionMode::None); - episodes.into_iter().for_each(|ep| { - let viewep = EpisodesViewWidget::new(ep); + episodes.into_iter().for_each(|mut ep| { + let viewep = EpisodesViewWidget::new(&mut ep); list.add(&viewep.container); let sep = gtk::Separator::new(gtk::Orientation::Vertical); @@ -86,20 +86,21 @@ impl Default for EpisodesViewWidget { } impl EpisodesViewWidget { - fn new(episode: EpisodeViewWidgetQuery) -> EpisodesViewWidget { + fn new(episode: &mut EpisodeWidgetQuery) -> EpisodesViewWidget { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view_widget.ui"); let container: gtk::Box = builder.get_object("container").unwrap(); let image: gtk::Image = builder.get_object("cover").unwrap(); // FIXME: - let pd = dbqueries::get_podcast_from_id(episode.podcast_id()).unwrap(); - let img = get_pixbuf_from_path_64(&pd); - if let Some(i) = img { - image.set_from_pixbuf(&i); + if let Ok(pd) = dbqueries::get_podcast_from_id(episode.podcast_id()) { + let img = get_pixbuf_from_path_64(&pd); + if let Some(i) = img { + image.set_from_pixbuf(&i); + } } - let ep = EpisodeWidget::new(&mut episode.into()); + let ep = EpisodeWidget::new(episode); container.pack_start(&ep.container, true, true, 5); EpisodesViewWidget { From 632f011db5aeb4d15e0f0803ba54b53586760048 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 13:13:32 +0200 Subject: [PATCH 08/41] EpisodeView: Implement initial view update. --- hammond-gtk/src/content.rs | 12 +++++++++++- hammond-gtk/src/views/episodes.rs | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index 0ea2bfb..c869741 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -173,6 +173,16 @@ impl EpisodeStack { } fn update(&self) { - // unimplemented!() + // FIXME: figure out if it should switch to empty view + let vis = self.stack.get_visible_child_name().unwrap(); + let old = self.stack.get_child_by_name("episodes").unwrap(); + + let eps = EpisodesView::new(); + + self.stack.remove(&old); + self.stack.add_named(&eps.container, "episodes"); + self.stack.set_visible_child_name(&vis); + + old.destroy(); } } diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 91c3137..35f51a2 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -57,6 +57,7 @@ impl EpisodesView { sep.show() }); + view.container.show_all(); Rc::new(view) } } From bda09c032a44940e9978a1ec046ebf3b2b90d6f1 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 13:18:29 +0200 Subject: [PATCH 09/41] EpisodeWidget: Update the progrress bar only when download clicked. --- hammond-gtk/src/widgets/episode.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index d872fe5..7bd11e3 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -101,12 +101,6 @@ impl EpisodeWidget { .map(|c| c.add_class("dim-label")); } - let progress = self.progress.clone(); - timeout_add(200, move || { - progress.pulse(); - glib::Continue(true) - }); - if let Some(size) = episode.length() { let megabytes = size / 1024 / 1024; // episode.length represents bytes self.size.set_text(&format!("{} MB", megabytes)) @@ -150,7 +144,7 @@ impl EpisodeWidget { let play = &self.play; let delete = &self.delete; let cancel = &self.cancel; - let progress = &self.progress; + let progress = self.progress.clone(); self.download.connect_clicked( clone!(play, delete, episode, cancel, progress => move |dl| { on_download_clicked( @@ -159,7 +153,7 @@ impl EpisodeWidget { &play, &delete, &cancel, - &progress + progress.clone() ); }), ); @@ -173,20 +167,28 @@ fn on_download_clicked( play_bttn: >k::Button, del_bttn: >k::Button, cancel_bttn: >k::Button, - progress_bar: >k::ProgressBar, + progress_bar: gtk::ProgressBar, ) { + let progress = progress_bar.clone(); + + // Start the proggress_bar pulse. + timeout_add(200, move || { + progress_bar.pulse(); + glib::Continue(true) + }); + // Create a async channel. let (sender, receiver) = channel(); // Pass the desired arguments into the Local Thread Storage. GLOBAL.with( - clone!(download_bttn, play_bttn, del_bttn, cancel_bttn, progress_bar => move |global| { + clone!(download_bttn, play_bttn, del_bttn, cancel_bttn, progress => move |global| { *global.borrow_mut() = Some(( download_bttn, play_bttn, del_bttn, cancel_bttn, - progress_bar, + progress, receiver)); }), ); @@ -195,7 +197,7 @@ fn on_download_clicked( let pd_title = pd.title().to_owned(); let mut ep = ep.clone(); cancel_bttn.show(); - progress_bar.show(); + progress.show(); download_bttn.hide(); thread::spawn(move || { let download_fold = downloader::get_download_folder(&pd_title).unwrap(); From ccfea527ac0f24627fe3ef0c4c3c927a799b8822 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 13:25:31 +0200 Subject: [PATCH 10/41] gitlab-ci: use --force for clippy and rustfmt isntallation. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8726f9e..e64a2ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -44,7 +44,7 @@ rustfmt: CFG_RELEASE_CHANNEL: "nightly" script: - rustc --version && cargo --version - - cargo install rustfmt-nightly + - cargo install rustfmt-nightly --force - cargo fmt --all -- --write-mode=diff # Configure and run clippy on nightly @@ -54,5 +54,5 @@ clippy: stage: lint script: - rustc --version && cargo --version - - cargo install clippy + - cargo install clippy --force - cargo clippy --all From ace62f7ed6b1d213ee78f503f9f8f55566e4405f Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 14:49:28 +0200 Subject: [PATCH 11/41] EpisodesView: Initial draft of episodewidget's splitting into the frames. --- hammond-gtk/resources/gtk/episodes_view.ui | 162 ++++++++++++++++++++- hammond-gtk/src/views/episodes.rs | 83 +++++++++-- 2 files changed, 229 insertions(+), 16 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index 8e8f3ab..4776fd9 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -42,8 +42,168 @@ False center vertical + 7 - + + True + False + True + start + Today + + + + + + False + True + 0 + + + + + True + False + True + 0 + in + + + True + False + True + none + + + + + + + + False + True + 1 + + + + + True + False + True + start + Yesterday + + + + + + False + True + 2 + + + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 3 + + + + + True + False + True + start + This Week + + + + + + False + True + 4 + + + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 5 + + + + + True + False + True + start + This Month + + + + + + False + True + 6 + + + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 7 + diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 35f51a2..c711fb4 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -1,5 +1,6 @@ use gtk; use gtk::prelude::*; +use chrono::prelude::*; use hammond_data::dbqueries; use hammond_data::EpisodeWidgetQuery; @@ -9,10 +10,26 @@ use utils::get_pixbuf_from_path_64; use std::rc::Rc; +#[derive(Debug, Clone)] +enum ListSplit { + Today, + Yday, + Week, + Month, +} + #[derive(Debug, Clone)] pub struct EpisodesView { pub container: gtk::Box, frame_parent: gtk::Box, + today_box: gtk::ListBox, + yday_box: gtk::ListBox, + week_box: gtk::ListBox, + month_box: gtk::ListBox, + today_label: gtk::Label, + yday_label: gtk::Label, + week_label: gtk::Label, + month_label: gtk::Label, } impl Default for EpisodesView { @@ -20,10 +37,26 @@ impl Default for EpisodesView { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view.ui"); let container: gtk::Box = builder.get_object("container").unwrap(); let frame_parent: gtk::Box = builder.get_object("frame_parent").unwrap(); + let today_box: gtk::ListBox = builder.get_object("today_box").unwrap(); + let yday_box: gtk::ListBox = builder.get_object("yday_box").unwrap(); + let week_box: gtk::ListBox = builder.get_object("week_box").unwrap(); + let month_box: gtk::ListBox = builder.get_object("month_box").unwrap(); + let today_label: gtk::Label = builder.get_object("today_label").unwrap(); + let yday_label: gtk::Label = builder.get_object("yday_label").unwrap(); + let week_label: gtk::Label = builder.get_object("week_label").unwrap(); + let month_label: gtk::Label = builder.get_object("month_label").unwrap(); EpisodesView { container, frame_parent, + today_box, + yday_box, + week_box, + month_box, + today_label, + yday_label, + week_label, + month_label, } } } @@ -31,29 +64,35 @@ impl Default for EpisodesView { impl EpisodesView { pub fn new() -> Rc { let view = EpisodesView::default(); - let episodes = dbqueries::get_episodes_widgets_with_limit(100).unwrap(); - let frame = gtk::Frame::new(None); - let list = gtk::ListBox::new(); - - view.frame_parent.pack_start(&frame, true, false, 10); - frame.add(&list); - frame.set_shadow_type(gtk::ShadowType::In); - - list.set_vexpand(false); - list.set_hexpand(false); - list.set_visible(true); - list.set_selection_mode(gtk::SelectionMode::None); + let now_utc = Utc::now().timestamp() as i32; episodes.into_iter().for_each(|mut ep| { let viewep = EpisodesViewWidget::new(&mut ep); - list.add(&viewep.container); - let sep = gtk::Separator::new(gtk::Orientation::Vertical); sep.set_sensitive(false); sep.set_can_focus(false); - list.add(&sep); + let t = split(now_utc, ep.epoch()); + match t { + ListSplit::Today => { + view.today_box.add(&viewep.container); + view.today_box.add(&sep) + } + ListSplit::Yday => { + view.yday_box.add(&viewep.container); + view.yday_box.add(&sep) + } + ListSplit::Week => { + view.week_box.add(&viewep.container); + view.week_box.add(&sep) + } + _ => { + view.month_box.add(&viewep.container); + view.month_box.add(&sep) + } + } + sep.show() }); @@ -62,6 +101,20 @@ impl EpisodesView { } } +fn split(now_utc: i32, epoch: i32) -> ListSplit { + let t = now_utc - epoch; + + if t < 86_400 { + ListSplit::Today + } else if t < 172_800 { + ListSplit::Yday + } else if t < 604_800 { + ListSplit::Week + } else { + ListSplit::Month + } +} + #[derive(Debug, Clone)] struct EpisodesViewWidget { container: gtk::Box, From c070fc3032cb3346740305dccbb5849bd2b02f03 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 15:12:55 +0200 Subject: [PATCH 12/41] EpisodesView: Ugly and Hacky Prototype of episode splitting. --- hammond-gtk/resources/gtk/episodes_view.ui | 311 +++++++++++++++------ hammond-gtk/src/views/episodes.rs | 105 +++++-- 2 files changed, 318 insertions(+), 98 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index 4776fd9..b3a4456 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -44,15 +44,50 @@ vertical 7 - + True False True - start - Today - - - + vertical + + + True + False + start + Today + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 1 + + False @@ -61,22 +96,49 @@ - + True False True - 0 - in + vertical - + True False - True - none + start + Yesterday + + + + + False + True + 0 + - - + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 1 + @@ -86,15 +148,50 @@ - + True False True - start - Yesterday - - - + vertical + + + True + False + start + This Week + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 1 + + False @@ -103,20 +200,49 @@ - + True False - 0 - in + True + vertical - + True False - none + start + This Month + + + + + False + True + 0 + - - + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 1 + @@ -126,15 +252,50 @@ - + True False True - start - This Week - - - + vertical + + + True + False + start + This Year + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + True + False + none + + + + + + + + False + True + 1 + + False @@ -143,20 +304,50 @@ - + True False - 0 - in + True + vertical - + True False - none + start + Older than a Year + + + + + False + True + 0 + - - + + + True + False + 0 + in + + + True + False + True + none + + + + + + + + False + True + 1 + @@ -165,46 +356,6 @@ 5 - - - True - False - True - start - This Month - - - - - - False - True - 6 - - - - - True - False - 0 - in - - - True - False - none - - - - - - - - False - True - 7 - - False diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index c711fb4..57191f1 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -16,20 +16,32 @@ enum ListSplit { Yday, Week, Month, + Year, + Rest, } #[derive(Debug, Clone)] pub struct EpisodesView { pub container: gtk::Box, frame_parent: gtk::Box, - today_box: gtk::ListBox, - yday_box: gtk::ListBox, - week_box: gtk::ListBox, - month_box: gtk::ListBox, + today_box: gtk::Box, + yday_box: gtk::Box, + week_box: gtk::Box, + month_box: gtk::Box, + year_box: gtk::Box, + rest_box: gtk::Box, + today_list: gtk::ListBox, + yday_list: gtk::ListBox, + week_list: gtk::ListBox, + month_list: gtk::ListBox, + year_list: gtk::ListBox, + rest_list: gtk::ListBox, today_label: gtk::Label, yday_label: gtk::Label, week_label: gtk::Label, month_label: gtk::Label, + year_label: gtk::Label, + rest_label: gtk::Label, } impl Default for EpisodesView { @@ -37,14 +49,24 @@ impl Default for EpisodesView { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view.ui"); let container: gtk::Box = builder.get_object("container").unwrap(); let frame_parent: gtk::Box = builder.get_object("frame_parent").unwrap(); - let today_box: gtk::ListBox = builder.get_object("today_box").unwrap(); - let yday_box: gtk::ListBox = builder.get_object("yday_box").unwrap(); - let week_box: gtk::ListBox = builder.get_object("week_box").unwrap(); - let month_box: gtk::ListBox = builder.get_object("month_box").unwrap(); + let today_box: gtk::Box = builder.get_object("today_box").unwrap(); + let yday_box: gtk::Box = builder.get_object("yday_box").unwrap(); + let week_box: gtk::Box = builder.get_object("week_box").unwrap(); + let month_box: gtk::Box = builder.get_object("month_box").unwrap(); + let year_box: gtk::Box = builder.get_object("year_box").unwrap(); + let rest_box: gtk::Box = builder.get_object("rest_box").unwrap(); + let today_list: gtk::ListBox = builder.get_object("today_list").unwrap(); + let yday_list: gtk::ListBox = builder.get_object("yday_list").unwrap(); + let week_list: gtk::ListBox = builder.get_object("week_list").unwrap(); + let month_list: gtk::ListBox = builder.get_object("month_list").unwrap(); + let year_list: gtk::ListBox = builder.get_object("year_list").unwrap(); + let rest_list: gtk::ListBox = builder.get_object("rest_list").unwrap(); let today_label: gtk::Label = builder.get_object("today_label").unwrap(); let yday_label: gtk::Label = builder.get_object("yday_label").unwrap(); let week_label: gtk::Label = builder.get_object("week_label").unwrap(); let month_label: gtk::Label = builder.get_object("month_label").unwrap(); + let year_label: gtk::Label = builder.get_object("year_label").unwrap(); + let rest_label: gtk::Label = builder.get_object("rest_label").unwrap(); EpisodesView { container, @@ -53,10 +75,20 @@ impl Default for EpisodesView { yday_box, week_box, month_box, + year_box, + rest_box, + today_list, + yday_list, + week_list, + month_list, + year_list, + rest_list, today_label, yday_label, week_label, month_label, + year_label, + rest_label, } } } @@ -76,31 +108,64 @@ impl EpisodesView { let t = split(now_utc, ep.epoch()); match t { ListSplit::Today => { - view.today_box.add(&viewep.container); - view.today_box.add(&sep) + view.today_list.add(&viewep.container); + view.today_list.add(&sep) } ListSplit::Yday => { - view.yday_box.add(&viewep.container); - view.yday_box.add(&sep) + view.yday_list.add(&viewep.container); + view.yday_list.add(&sep) } ListSplit::Week => { - view.week_box.add(&viewep.container); - view.week_box.add(&sep) + view.week_list.add(&viewep.container); + view.week_list.add(&sep) } - _ => { - view.month_box.add(&viewep.container); - view.month_box.add(&sep) + ListSplit::Month => { + view.month_list.add(&viewep.container); + view.month_list.add(&sep) + } + ListSplit::Year => { + view.year_list.add(&viewep.container); + view.year_list.add(&sep) + } + ListSplit::Rest => { + view.rest_list.add(&viewep.container); + view.rest_list.add(&sep) } } sep.show() }); + if view.today_list.get_children().is_empty() { + view.today_box.hide(); + } + + if view.yday_list.get_children().is_empty() { + view.yday_box.hide(); + } + + if view.week_list.get_children().is_empty() { + view.week_box.hide(); + } + + if view.month_list.get_children().is_empty() { + view.month_box.hide(); + } + + if view.year_list.get_children().is_empty() { + view.year_box.hide(); + } + + if view.rest_list.get_children().is_empty() { + view.rest_box.hide(); + } + view.container.show_all(); Rc::new(view) } } +// TODO: Avoid epoch calculations, use chrono instead. fn split(now_utc: i32, epoch: i32) -> ListSplit { let t = now_utc - epoch; @@ -110,8 +175,12 @@ fn split(now_utc: i32, epoch: i32) -> ListSplit { ListSplit::Yday } else if t < 604_800 { ListSplit::Week - } else { + } else if t < 2_419_200 { ListSplit::Month + } else if t < 31_536_000 { + ListSplit::Year + } else { + ListSplit::Rest } } From 2e06205eda3a3dd23a3fb9bed80dfb4328484b8e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 18:19:31 +0200 Subject: [PATCH 13/41] Define a new Diesel Model for PodcastCover querries. Define new Diesel Model and impl From trait, Change the signature of downloader::cache_image function, Change and merge hammond-gtk::utils::get_pixbuf_from_path functions. --- hammond-data/src/dbqueries.rs | 13 ++++++++++- hammond-data/src/lib.rs | 2 +- hammond-data/src/models/queryables.rs | 33 +++++++++++++++++++++++++++ hammond-downloader/src/downloader.rs | 6 ++--- hammond-gtk/src/utils.rs | 20 ++++------------ hammond-gtk/src/views/episodes.rs | 6 ++--- hammond-gtk/src/views/shows.rs | 2 +- hammond-gtk/src/widgets/show.rs | 4 ++-- 8 files changed, 59 insertions(+), 27 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index caaa76a..261bd8b 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -2,7 +2,7 @@ use diesel::prelude::*; use diesel; -use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; +use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, PodcastCoverQuery, Source}; use chrono::prelude::*; use errors::*; @@ -114,6 +114,17 @@ pub fn get_podcast_from_id(pid: i32) -> Result { Ok(podcast.filter(id.eq(pid)).get_result::(&*con)?) } +pub fn get_podcast_cover_from_id(pid: i32) -> Result { + use schema::podcast::dsl::*; + + let db = connection(); + let con = db.get()?; + Ok(podcast + .select((id, title, image_uri)) + .filter(id.eq(pid)) + .get_result::(&*con)?) +} + pub fn get_pd_episodes(parent: &Podcast) -> Result> { use schema::episode::dsl::*; diff --git a/hammond-data/src/lib.rs b/hammond-data/src/lib.rs index 97aec76..21b70ac 100644 --- a/hammond-data/src/lib.rs +++ b/hammond-data/src/lib.rs @@ -61,7 +61,7 @@ pub(crate) mod models; mod parser; mod schema; -pub use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; +pub use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, PodcastCoverQuery, Source}; /// [XDG Base Direcotory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths. #[allow(missing_debug_implementations)] diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index 4290979..bd15320 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -427,6 +427,39 @@ impl Podcast { } } +#[derive(Queryable, Debug, Clone)] +/// Diesel Model of the podcast cover query. +/// Used for fetching information about a Podcast's cover. +pub struct PodcastCoverQuery { + id: i32, + title: String, + image_uri: Option, +} + +impl From for PodcastCoverQuery { + fn from(p: Podcast) -> PodcastCoverQuery { + PodcastCoverQuery { + id: *p.id(), + title: p.title, + image_uri: p.image_uri, + } + } +} + +impl PodcastCoverQuery { + /// Get the Feed `title`. + pub fn title(&self) -> &str { + &self.title + } + + /// Get the `image_uri`. + /// + /// Represents the uri(url usually) that the Feed cover image is located at. + pub fn image_uri(&self) -> Option<&str> { + self.image_uri.as_ref().map(|s| s.as_str()) + } +} + #[derive(Queryable, Identifiable, AsChangeset, PartialEq)] #[table_name = "source"] #[changeset_options(treat_none_as_null = "true")] diff --git a/hammond-downloader/src/downloader.rs b/hammond-downloader/src/downloader.rs index e3a4a0c..eb0fd2f 100644 --- a/hammond-downloader/src/downloader.rs +++ b/hammond-downloader/src/downloader.rs @@ -8,7 +8,7 @@ use std::io::{BufWriter, Read, Write}; use std::path::Path; use errors::*; -use hammond_data::{EpisodeWidgetQuery, Podcast}; +use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; use hammond_data::xdg_dirs::{DL_DIR, HAMMOND_CACHE}; // TODO: Replace path that are of type &str with std::path. @@ -131,7 +131,7 @@ pub fn get_episode(ep: &mut EpisodeWidgetQuery, download_folder: &str) -> Result } } -pub fn cache_image(pd: &Podcast) -> Option { +pub fn cache_image(pd: &PodcastCoverQuery) -> Option { let url = pd.image_uri()?.to_owned(); if url == "" { return None; @@ -207,7 +207,7 @@ mod tests { index(vec![feed]); // Get the Podcast - let pd = dbqueries::get_podcast_from_source_id(sid).unwrap(); + let pd = dbqueries::get_podcast_from_source_id(sid).unwrap().into(); let img_path = cache_image(&pd); let foo_ = format!( diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs index 3e22f8e..9c5eca9 100644 --- a/hammond-gtk/src/utils.rs +++ b/hammond-gtk/src/utils.rs @@ -2,7 +2,7 @@ use glib; use gdk_pixbuf::Pixbuf; use hammond_data::feed; -use hammond_data::{Podcast, Source}; +use hammond_data::{PodcastCoverQuery, Source}; use hammond_downloader::downloader; use std::thread; @@ -60,21 +60,9 @@ fn refresh_podcasts_view() -> glib::Continue { } // FIXME: use something that would just scale? -// TODO: make a diesel model with only title, local_uri - -pub fn get_pixbuf_from_path(pd: &Podcast) -> Option { +pub fn get_pixbuf_from_path(pd: &PodcastCoverQuery, size: u32) -> Option { let img_path = downloader::cache_image(pd)?; - Pixbuf::new_from_file_at_scale(&img_path, 256, 256, true).ok() -} - -pub fn get_pixbuf_from_path_128(pd: &Podcast) -> Option { - let img_path = downloader::cache_image(pd)?; - Pixbuf::new_from_file_at_scale(&img_path, 128, 128, true).ok() -} - -pub fn get_pixbuf_from_path_64(pd: &Podcast) -> Option { - let img_path = downloader::cache_image(pd)?; - Pixbuf::new_from_file_at_scale(&img_path, 64, 64, true).ok() + Pixbuf::new_from_file_at_scale(&img_path, size as i32, size as i32, true).ok() } #[cfg(test)] @@ -100,7 +88,7 @@ mod tests { // Get the Podcast let pd = dbqueries::get_podcast_from_source_id(sid).unwrap(); - let pxbuf = get_pixbuf_from_path(&pd); + let pxbuf = get_pixbuf_from_path(&pd.into(), 256); assert!(pxbuf.is_some()); } } diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 57191f1..51ce199 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -6,7 +6,7 @@ use hammond_data::dbqueries; use hammond_data::EpisodeWidgetQuery; use widgets::episode::EpisodeWidget; -use utils::get_pixbuf_from_path_64; +use utils::get_pixbuf_from_path; use std::rc::Rc; @@ -216,8 +216,8 @@ impl EpisodesViewWidget { let image: gtk::Image = builder.get_object("cover").unwrap(); // FIXME: - if let Ok(pd) = dbqueries::get_podcast_from_id(episode.podcast_id()) { - let img = get_pixbuf_from_path_64(&pd); + if let Ok(pd) = dbqueries::get_podcast_cover_from_id(episode.podcast_id()) { + let img = get_pixbuf_from_path(&pd, 64); if let Some(i) = img { image.set_from_pixbuf(&i); } diff --git a/hammond-gtk/src/views/shows.rs b/hammond-gtk/src/views/shows.rs index d12dc58..acd1022 100644 --- a/hammond-gtk/src/views/shows.rs +++ b/hammond-gtk/src/views/shows.rs @@ -111,7 +111,7 @@ impl ShowsChild { fn init(&self, pd: &Podcast) { self.container.set_tooltip_text(pd.title()); - let cover = get_pixbuf_from_path(pd); + let cover = get_pixbuf_from_path(&pd.clone().into(), 256); if let Some(img) = cover { self.cover.set_from_pixbuf(&img); }; diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs index 7a30a63..54b9840 100644 --- a/hammond-gtk/src/widgets/show.rs +++ b/hammond-gtk/src/widgets/show.rs @@ -10,7 +10,7 @@ use hammond_data::utils::replace_extra_spaces; use hammond_downloader::downloader; use widgets::episode::episodes_listbox; -use utils::get_pixbuf_from_path_128; +use utils::get_pixbuf_from_path; use content::ShowStack; use headerbar::Header; @@ -77,7 +77,7 @@ impl ShowWidget { let desc = dissolve::strip_html_tags(pd.description()).join(" "); self.description.set_text(&replace_extra_spaces(&desc)); - let img = get_pixbuf_from_path_128(pd); + let img = get_pixbuf_from_path(&pd.clone().into(), 128); if let Some(i) = img { self.cover.set_from_pixbuf(&i); } From 3c24b9f9d9ceca665d503bb75c06bce90d26c746 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 19:00:14 +0200 Subject: [PATCH 14/41] hammond-data::utils: Added new Diesel model for the download checker. --- hammond-data/src/dbqueries.rs | 8 +++-- hammond-data/src/models/queryables.rs | 52 +++++++++++++++++++++++++++ hammond-data/src/utils.rs | 16 ++++++--- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 261bd8b..2ce9061 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -2,7 +2,8 @@ use diesel::prelude::*; use diesel; -use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, PodcastCoverQuery, Source}; +use models::queryables::{Episode, EpisodeDownloadCleanerQuery, EpisodeWidgetQuery, Podcast, + PodcastCoverQuery, Source}; use chrono::prelude::*; use errors::*; @@ -32,14 +33,15 @@ pub fn get_episodes() -> Result> { Ok(episode.order(epoch.desc()).load::(&*con)?) } -pub fn get_downloaded_episodes() -> Result> { +pub(crate) fn get_downloaded_episodes() -> Result> { use schema::episode::dsl::*; let db = connection(); let con = db.get()?; Ok(episode + .select((rowid, local_uri)) .filter(local_uri.is_not_null()) - .load::(&*con)?) + .load::(&*con)?) } pub fn get_played_episodes() -> Result> { diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index bd15320..cf6dded 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -320,6 +320,58 @@ impl EpisodeWidgetQuery { } } +#[derive(Queryable, AsChangeset, PartialEq)] +#[table_name = "episode"] +#[changeset_options(treat_none_as_null = "true")] +#[primary_key(title, podcast_id)] +#[derive(Debug, Clone)] +/// Diesel Model to be used for constructing `EpisodeWidgets`. +pub(crate) struct EpisodeDownloadCleanerQuery { + rowid: i32, + local_uri: Option, +} + +impl From for EpisodeDownloadCleanerQuery { + fn from(e: Episode) -> EpisodeDownloadCleanerQuery { + EpisodeDownloadCleanerQuery { + rowid: e.rowid(), + local_uri: e.local_uri, + } + } +} + +impl EpisodeDownloadCleanerQuery { + /// Get the value of the sqlite's `ROW_ID` + pub(crate) fn rowid(&self) -> i32 { + self.rowid + } + + /// Get the value of the `local_uri`. + /// + /// Represents the local uri,usually filesystem path, + /// that the media file will be located at. + pub(crate) fn local_uri(&self) -> Option<&str> { + self.local_uri.as_ref().map(|s| s.as_str()) + } + + /// Set the `local_uri`. + pub(crate) fn set_local_uri(&mut self, value: Option<&str>) { + self.local_uri = value.map(|x| x.to_string()); + } + + /// Helper method to easily save/"sync" current state of self to the Database. + pub(crate) fn save(&self) -> Result { + use schema::episode::dsl::*; + + let db = connection(); + let tempdb = db.get()?; + + Ok(diesel::update(episode.filter(rowid.eq(self.rowid()))) + .set(self) + .execute(&*tempdb)?) + } +} + #[derive(Queryable, Identifiable, AsChangeset, Associations, PartialEq)] #[belongs_to(Source, foreign_key = "source_id")] #[changeset_options(treat_none_as_null = "true")] diff --git a/hammond-data/src/utils.rs b/hammond-data/src/utils.rs index f15c757..39ea52b 100644 --- a/hammond-data/src/utils.rs +++ b/hammond-data/src/utils.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use errors::*; use dbqueries; -use models::queryables::Episode; +use models::queryables::{Episode, EpisodeDownloadCleanerQuery}; use std::path::Path; use std::fs; @@ -25,7 +25,7 @@ fn download_checker() -> Result<()> { Ok(()) } -fn checker_helper(ep: &mut Episode) { +fn checker_helper(ep: &mut EpisodeDownloadCleanerQuery) { if !Path::new(ep.local_uri().unwrap()).exists() { ep.set_local_uri(None); let res = ep.save(); @@ -180,12 +180,16 @@ mod tests { #[test] fn test_download_checker() { - let _tmp_dir = helper_db(); + let tmp_dir = helper_db(); download_checker().unwrap(); let episodes = dbqueries::get_downloaded_episodes().unwrap(); + let valid_path = tmp_dir.path().join("virtual_dl.mp3"); assert_eq!(episodes.len(), 1); - assert_eq!("foo_bar", episodes.first().unwrap().title()); + assert_eq!( + Some(valid_path.to_str().unwrap()), + episodes.first().unwrap().local_uri() + ); } #[test] @@ -194,7 +198,9 @@ mod tests { let mut episode = { let db = connection(); let con = db.get().unwrap(); - dbqueries::get_episode_from_pk(&con, "bar_baz", 1).unwrap() + dbqueries::get_episode_from_pk(&con, "bar_baz", 1) + .unwrap() + .into() }; checker_helper(&mut episode); From db59bed69d7e1f1d21842d155ded25399881b7ce Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 19:18:20 +0200 Subject: [PATCH 15/41] hammond-data::utils: Modified EpisodeCleaner Diesel model to also be used with played_cleaner. --- hammond-data/src/dbqueries.rs | 19 +++++++++++---- hammond-data/src/models/queryables.rs | 34 +++++++++++++++++++-------- hammond-data/src/utils.rs | 22 ++++++++--------- hammond-gtk/src/widgets/episode.rs | 4 +++- 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 2ce9061..4f0c5d2 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -2,7 +2,7 @@ use diesel::prelude::*; use diesel; -use models::queryables::{Episode, EpisodeDownloadCleanerQuery, EpisodeWidgetQuery, Podcast, +use models::queryables::{Episode, EpisodeCleanerQuery, EpisodeWidgetQuery, Podcast, PodcastCoverQuery, Source}; use chrono::prelude::*; use errors::*; @@ -33,15 +33,15 @@ pub fn get_episodes() -> Result> { Ok(episode.order(epoch.desc()).load::(&*con)?) } -pub(crate) fn get_downloaded_episodes() -> Result> { +pub(crate) fn get_downloaded_episodes() -> Result> { use schema::episode::dsl::*; let db = connection(); let con = db.get()?; Ok(episode - .select((rowid, local_uri)) + .select((rowid, local_uri, played)) .filter(local_uri.is_not_null()) - .load::(&*con)?) + .load::(&*con)?) } pub fn get_played_episodes() -> Result> { @@ -52,6 +52,17 @@ pub fn get_played_episodes() -> Result> { Ok(episode.filter(played.is_not_null()).load::(&*con)?) } +pub fn get_played_cleaner_episodes() -> Result> { + use schema::episode::dsl::*; + + let db = connection(); + let con = db.get()?; + Ok(episode + .select((rowid, local_uri, played)) + .filter(played.is_not_null()) + .load::(&*con)?) +} + pub fn get_episode_from_rowid(ep_id: i32) -> Result { use schema::episode::dsl::*; diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index cf6dded..9ba23bb 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -325,24 +325,26 @@ impl EpisodeWidgetQuery { #[changeset_options(treat_none_as_null = "true")] #[primary_key(title, podcast_id)] #[derive(Debug, Clone)] -/// Diesel Model to be used for constructing `EpisodeWidgets`. -pub(crate) struct EpisodeDownloadCleanerQuery { +/// Diesel Model to be used internal with the `utils::checkup` function. +pub struct EpisodeCleanerQuery { rowid: i32, local_uri: Option, + played: Option, } -impl From for EpisodeDownloadCleanerQuery { - fn from(e: Episode) -> EpisodeDownloadCleanerQuery { - EpisodeDownloadCleanerQuery { +impl From for EpisodeCleanerQuery { + fn from(e: Episode) -> EpisodeCleanerQuery { + EpisodeCleanerQuery { rowid: e.rowid(), local_uri: e.local_uri, + played: e.played, } } } -impl EpisodeDownloadCleanerQuery { +impl EpisodeCleanerQuery { /// Get the value of the sqlite's `ROW_ID` - pub(crate) fn rowid(&self) -> i32 { + pub fn rowid(&self) -> i32 { self.rowid } @@ -350,17 +352,29 @@ impl EpisodeDownloadCleanerQuery { /// /// Represents the local uri,usually filesystem path, /// that the media file will be located at. - pub(crate) fn local_uri(&self) -> Option<&str> { + pub fn local_uri(&self) -> Option<&str> { self.local_uri.as_ref().map(|s| s.as_str()) } /// Set the `local_uri`. - pub(crate) fn set_local_uri(&mut self, value: Option<&str>) { + pub fn set_local_uri(&mut self, value: Option<&str>) { self.local_uri = value.map(|x| x.to_string()); } + /// Epoch representation of the last time the episode was played. + /// + /// None/Null for unplayed. + pub fn played(&self) -> Option { + self.played + } + + /// Set the `played` value. + pub fn set_played(&mut self, value: Option) { + self.played = value; + } + /// Helper method to easily save/"sync" current state of self to the Database. - pub(crate) fn save(&self) -> Result { + pub fn save(&self) -> Result { use schema::episode::dsl::*; let db = connection(); diff --git a/hammond-data/src/utils.rs b/hammond-data/src/utils.rs index 39ea52b..07dc34d 100644 --- a/hammond-data/src/utils.rs +++ b/hammond-data/src/utils.rs @@ -8,14 +8,12 @@ use itertools::Itertools; use errors::*; use dbqueries; -use models::queryables::{Episode, EpisodeDownloadCleanerQuery}; +use models::queryables::EpisodeCleanerQuery; use std::path::Path; use std::fs; fn download_checker() -> Result<()> { - // TODO: give it it's own diesel model, - // so it does not pull useless and expensive stuff like description. let episodes = dbqueries::get_downloaded_episodes()?; episodes @@ -25,7 +23,7 @@ fn download_checker() -> Result<()> { Ok(()) } -fn checker_helper(ep: &mut EpisodeDownloadCleanerQuery) { +fn checker_helper(ep: &mut EpisodeCleanerQuery) { if !Path::new(ep.local_uri().unwrap()).exists() { ep.set_local_uri(None); let res = ep.save(); @@ -37,9 +35,7 @@ fn checker_helper(ep: &mut EpisodeDownloadCleanerQuery) { } fn played_cleaner() -> Result<()> { - // TODO: give it it's own diesel model, - // so it does not pull useless and expensive stuff like description. - let episodes = dbqueries::get_played_episodes()?; + let episodes = dbqueries::get_played_cleaner_episodes()?; let now_utc = Utc::now().timestamp() as i32; episodes.into_par_iter().for_each(|mut ep| { @@ -54,7 +50,7 @@ fn played_cleaner() -> Result<()> { error!("Error while trying to delete file: {:?}", ep.local_uri()); error!("Error: {}", err); } else { - info!("Episode {:?} was deleted succesfully.", ep.title()); + info!("Episode {:?} was deleted succesfully.", ep.local_uri()); }; } } @@ -63,7 +59,7 @@ fn played_cleaner() -> Result<()> { } /// Check `ep.local_uri` field and delete the file it points to. -pub fn delete_local_content(ep: &mut Episode) -> Result<()> { +pub fn delete_local_content(ep: &mut EpisodeCleanerQuery) -> Result<()> { if ep.local_uri().is_some() { let uri = ep.local_uri().unwrap().to_owned(); if Path::new(&uri).exists() { @@ -110,7 +106,7 @@ pub fn url_cleaner(s: &str) -> String { } } -/// Helper functions that strips extra spaces and newlines and all the tabs. +/// Helper functions that strips extra spaces and newlines and ignores the tabs. #[allow(match_same_arms)] pub fn replace_extra_spaces(s: &str) -> String { s.trim() @@ -210,10 +206,12 @@ mod tests { #[test] fn test_download_cleaner() { let _tmp_dir = helper_db(); - let mut episode = { + let mut episode: EpisodeCleanerQuery = { let db = connection(); let con = db.get().unwrap(); - dbqueries::get_episode_from_pk(&con, "foo_bar", 0).unwrap() + dbqueries::get_episode_from_pk(&con, "foo_bar", 0) + .unwrap() + .into() }; let valid_path = episode.local_uri().unwrap().to_owned(); diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 7bd11e3..93ce6dd 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -232,7 +232,9 @@ fn on_play_bttn_clicked(episode_id: i32) { } fn on_delete_bttn_clicked(episode_id: i32) { - let mut ep = dbqueries::get_episode_from_rowid(episode_id).unwrap(); + let mut ep = dbqueries::get_episode_from_rowid(episode_id) + .unwrap() + .into(); let e = delete_local_content(&mut ep); if let Err(err) = e { From 336846f6dd6c04422579a6f67b4a2978b6964501 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 21:25:00 +0200 Subject: [PATCH 16/41] EpisodesStack: Implemnt the transition between empty and populated. --- hammond-gtk/src/content.rs | 10 ++++++---- hammond-gtk/src/views/episodes.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index c869741..49ed951 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -173,15 +173,17 @@ impl EpisodeStack { } fn update(&self) { - // FIXME: figure out if it should switch to empty view - let vis = self.stack.get_visible_child_name().unwrap(); let old = self.stack.get_child_by_name("episodes").unwrap(); - let eps = EpisodesView::new(); self.stack.remove(&old); self.stack.add_named(&eps.container, "episodes"); - self.stack.set_visible_child_name(&vis); + + if eps.is_empty() { + self.stack.set_visible_child_name("empty"); + } else { + self.stack.set_visible_child_name("episodes"); + } old.destroy(); } diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index 51ce199..bb7a44d 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -163,6 +163,34 @@ impl EpisodesView { view.container.show_all(); Rc::new(view) } + + pub fn is_empty(&self) -> bool { + if !self.today_list.get_children().is_empty() { + return false; + } + + if !self.yday_list.get_children().is_empty() { + return false; + } + + if !self.week_list.get_children().is_empty() { + return false; + } + + if !self.month_list.get_children().is_empty() { + return false; + } + + if !self.year_list.get_children().is_empty() { + return false; + } + + if !self.rest_list.get_children().is_empty() { + return false; + } + + true + } } // TODO: Avoid epoch calculations, use chrono instead. From 994ea5af2247f695301541d9ee6aed2982124231 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Dec 2017 22:08:07 +0200 Subject: [PATCH 17/41] EpisodesView: Remove unused label. --- hammond-gtk/resources/gtk/episodes_view.ui | 12 ++++++------ hammond-gtk/src/views/episodes.rs | 18 ------------------ 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index b3a4456..b633909 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -50,7 +50,7 @@ True vertical - + True False start @@ -102,7 +102,7 @@ True vertical - + True False start @@ -154,7 +154,7 @@ True vertical - + True False start @@ -206,7 +206,7 @@ True vertical - + True False start @@ -258,7 +258,7 @@ True vertical - + True False start @@ -310,7 +310,7 @@ True vertical - + True False start diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index bb7a44d..e34a7bc 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -36,12 +36,6 @@ pub struct EpisodesView { month_list: gtk::ListBox, year_list: gtk::ListBox, rest_list: gtk::ListBox, - today_label: gtk::Label, - yday_label: gtk::Label, - week_label: gtk::Label, - month_label: gtk::Label, - year_label: gtk::Label, - rest_label: gtk::Label, } impl Default for EpisodesView { @@ -61,12 +55,6 @@ impl Default for EpisodesView { let month_list: gtk::ListBox = builder.get_object("month_list").unwrap(); let year_list: gtk::ListBox = builder.get_object("year_list").unwrap(); let rest_list: gtk::ListBox = builder.get_object("rest_list").unwrap(); - let today_label: gtk::Label = builder.get_object("today_label").unwrap(); - let yday_label: gtk::Label = builder.get_object("yday_label").unwrap(); - let week_label: gtk::Label = builder.get_object("week_label").unwrap(); - let month_label: gtk::Label = builder.get_object("month_label").unwrap(); - let year_label: gtk::Label = builder.get_object("year_label").unwrap(); - let rest_label: gtk::Label = builder.get_object("rest_label").unwrap(); EpisodesView { container, @@ -83,12 +71,6 @@ impl Default for EpisodesView { month_list, year_list, rest_list, - today_label, - yday_label, - week_label, - month_label, - year_label, - rest_label, } } } From 74a6e5814abeb082314e73fcbed09171e7012bfd Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 15:14:29 +0200 Subject: [PATCH 18/41] ShowWidget: Update EpisodesView when unsub button is activated. --- hammond-gtk/src/content.rs | 20 ++++++++++++-------- hammond-gtk/src/widgets/show.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index 49ed951..5181a91 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -23,8 +23,8 @@ pub struct Content { impl Content { pub fn new(header: Rc
) -> Rc { let stack = gtk::Stack::new(); - let shows = ShowStack::new(header); let episodes = EpisodeStack::new(); + let shows = ShowStack::new(header, episodes.clone()); stack.add_titled(&episodes.stack, "episodes", "Episodes"); stack.add_titled(&shows.stack, "shows", "Shows"); @@ -50,15 +50,17 @@ impl Content { pub struct ShowStack { pub stack: gtk::Stack, header: Rc
, + epstack: Rc, } impl ShowStack { - fn new(header: Rc
) -> Rc { + fn new(header: Rc
, epstack: Rc) -> Rc { let stack = gtk::Stack::new(); let show = Rc::new(ShowStack { stack, header: header.clone(), + epstack, }); let pop = ShowsPopulated::new(show.clone(), header); @@ -110,7 +112,12 @@ impl ShowStack { pub fn replace_widget(&self, pd: &Podcast) { let old = self.stack.get_child_by_name("widget").unwrap(); - let new = ShowWidget::new(Rc::new(self.clone()), self.header.clone(), pd); + let new = ShowWidget::new( + Rc::new(self.clone()), + self.epstack.clone(), + self.header.clone(), + pd, + ); self.stack.remove(&old); self.stack.add_named(&new.container, "widget"); @@ -145,10 +152,7 @@ impl ShowStack { } #[derive(Debug, Clone)] -struct RecentEpisodes; - -#[derive(Debug, Clone)] -struct EpisodeStack { +pub struct EpisodeStack { // populated: RecentEpisodes, // empty: EmptyView, stack: gtk::Stack, @@ -172,7 +176,7 @@ impl EpisodeStack { }) } - fn update(&self) { + pub fn update(&self) { let old = self.stack.get_child_by_name("episodes").unwrap(); let eps = EpisodesView::new(); diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs index 54b9840..66da950 100644 --- a/hammond-gtk/src/widgets/show.rs +++ b/hammond-gtk/src/widgets/show.rs @@ -11,7 +11,7 @@ use hammond_downloader::downloader; use widgets::episode::episodes_listbox; use utils::get_pixbuf_from_path; -use content::ShowStack; +use content::{EpisodeStack, ShowStack}; use headerbar::Header; use std::rc::Rc; @@ -53,18 +53,30 @@ impl Default for ShowWidget { } impl ShowWidget { - pub fn new(shows: Rc, header: Rc
, pd: &Podcast) -> ShowWidget { + pub fn new( + shows: Rc, + epstack: Rc, + header: Rc
, + pd: &Podcast, + ) -> ShowWidget { let pdw = ShowWidget::default(); - pdw.init(shows, header, pd); + pdw.init(shows, epstack, header, pd); pdw } - pub fn init(&self, shows: Rc, header: Rc
, pd: &Podcast) { + pub fn init( + &self, + shows: Rc, + epstack: Rc, + header: Rc
, + pd: &Podcast, + ) { WidgetExt::set_name(&self.container, &pd.id().to_string()); // TODO: should spawn a thread to avoid locking the UI probably. - self.unsub.connect_clicked(clone!(shows, pd => move |bttn| { - on_unsub_button_clicked(shows.clone(), &pd, bttn); + self.unsub + .connect_clicked(clone!(shows, epstack, pd => move |bttn| { + on_unsub_button_clicked(shows.clone(), epstack.clone(), &pd, bttn); header.switch_to_normal(); })); @@ -94,7 +106,12 @@ impl ShowWidget { } } -fn on_unsub_button_clicked(shows: Rc, pd: &Podcast, unsub_button: >k::Button) { +fn on_unsub_button_clicked( + shows: Rc, + epstack: Rc, + pd: &Podcast, + unsub_button: >k::Button, +) { let res = dbqueries::remove_feed(pd); if res.is_ok() { info!("{} was removed succesfully.", pd.title()); @@ -112,6 +129,7 @@ fn on_unsub_button_clicked(shows: Rc, pd: &Podcast, unsub_button: > } shows.switch_podcasts_animated(); shows.update_podcasts(); + epstack.update(); } #[allow(dead_code)] From e416bca963872bc6581755484273ee429290ee0c Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 17:36:07 +0200 Subject: [PATCH 19/41] Implemented a pixbuf cache mechanism. Since gdk_pixbuf::Pixbuf is refference counted and every episode, use the cover of the Podcast Feed/Show, We can only create a Pixbuf cover per show and pass around the Rc pointer. GObjects do not implement Send trait, so SendCell is a way around that. Also lazy_static requires Sync trait, so that's what the mutexes are. --- Cargo.lock | 8 ++++++++ hammond-data/src/models/queryables.rs | 5 +++++ hammond-gtk/Cargo.toml | 2 ++ hammond-gtk/src/main.rs | 3 +++ hammond-gtk/src/utils.rs | 25 ++++++++++++++++++++++++- 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 500afb9..6fb2a52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,11 +611,13 @@ dependencies = [ "gtk 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "hammond-data 0.1.0", "hammond-downloader 0.1.0", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "loggerv 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "send-cell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1280,6 +1282,11 @@ dependencies = [ "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "send-cell" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.24" @@ -1746,6 +1753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" "checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" "checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" +"checksum send-cell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c620dd7e056b468b9d374a9f51cfa6bb4bf17a8ca4ee62e5efa0d99aaff2c41" "checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1" "checksum serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7cf5b0b5b4bd22eeecb7e01ac2e1225c7ef5e4272b79ee28a8392a8c8489c839" "checksum serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce0fd303af908732989354c6f02e05e2e6d597152870f2c6990efb0577137480" diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index 9ba23bb..aff1c4b 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -513,6 +513,11 @@ impl From for PodcastCoverQuery { } impl PodcastCoverQuery { + /// Get the Feed `id`. + pub fn id(&self) -> i32 { + self.id + } + /// Get the Feed `title`. pub fn title(&self) -> &str { &self.title diff --git a/hammond-gtk/Cargo.toml b/hammond-gtk/Cargo.toml index b1cf01e..5f250f3 100644 --- a/hammond-gtk/Cargo.toml +++ b/hammond-gtk/Cargo.toml @@ -12,11 +12,13 @@ gdk = "0.7.0" gdk-pixbuf = "0.3.0" gio = "0.3.0" glib = "0.4.0" +lazy_static = "1.0.0" log = "0.3.8" loggerv = "0.6.0" open = "1.2.1" rayon = "0.9.0" regex = "0.2.3" +send-cell = "0.1.2" [dependencies.diesel] features = ["sqlite"] diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index d565154..dd3b96a 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -12,10 +12,13 @@ extern crate dissolve; extern crate hammond_data; extern crate hammond_downloader; #[macro_use] +extern crate lazy_static; +#[macro_use] extern crate log; extern crate loggerv; extern crate open; extern crate regex; +extern crate send_cell; // extern crate rayon; // use rayon::prelude::*; diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs index 9c5eca9..1f73b1b 100644 --- a/hammond-gtk/src/utils.rs +++ b/hammond-gtk/src/utils.rs @@ -1,3 +1,4 @@ +use send_cell::SendCell; use glib; use gdk_pixbuf::Pixbuf; @@ -8,7 +9,9 @@ use hammond_downloader::downloader; use std::thread; use std::cell::RefCell; use std::sync::mpsc::{channel, Receiver}; +use std::sync::Mutex; use std::rc::Rc; +use std::collections::HashMap; use content::Content; @@ -59,10 +62,30 @@ fn refresh_podcasts_view() -> glib::Continue { glib::Continue(false) } +lazy_static! { + static ref CACHED_PIXBUFS: Mutex>>> = { + Mutex::new(HashMap::new()) + }; +} + // FIXME: use something that would just scale? pub fn get_pixbuf_from_path(pd: &PodcastCoverQuery, size: u32) -> Option { + let mut hashmap = CACHED_PIXBUFS.lock().unwrap(); + { + let res = hashmap.get(&(pd.id(), size)); + if let Some(px) = res { + let m = px.lock().unwrap(); + return Some(m.clone().into_inner()); + } + } + let img_path = downloader::cache_image(pd)?; - Pixbuf::new_from_file_at_scale(&img_path, size as i32, size as i32, true).ok() + let px = Pixbuf::new_from_file_at_scale(&img_path, size as i32, size as i32, true).ok(); + if let Some(px) = px { + hashmap.insert((pd.id(), size), Mutex::new(SendCell::new(px.clone()))); + return Some(px); + } + None } #[cfg(test)] From 13b2043b76d965ecb230c3ab913c6717b9dccb3e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 19:10:37 +0200 Subject: [PATCH 20/41] EpisodesStack: Fix view selection upon creation. --- hammond-gtk/src/content.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index 5181a91..7722ae0 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -166,8 +166,12 @@ impl EpisodeStack { stack.add_named(&episodes.container, "episodes"); stack.add_named(&empty.container, "empty"); - // FIXME: - stack.set_visible_child_name("episodes"); + + if episodes.is_empty() { + stack.set_visible_child_name("empty"); + } else { + stack.set_visible_child_name("episodes"); + } Rc::new(EpisodeStack { // empty, From c8310b1eb93fb2563430cfe0d9b70a9b4d408b4d Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 20:01:24 +0200 Subject: [PATCH 21/41] EpisodesView: Bump the labels size a bit. --- hammond-gtk/resources/gtk/episodes_view.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index b633909..28c480a 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -57,6 +57,7 @@ Today +
@@ -109,6 +110,7 @@ Yesterday +
@@ -161,6 +163,7 @@ This Week +
@@ -213,6 +216,7 @@ This Month +
@@ -265,6 +269,7 @@ This Year +
@@ -317,6 +322,7 @@ Older than a Year +
From 378b8609aa25196fccaef52302fe4af547c96aea Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 21:31:25 +0200 Subject: [PATCH 22/41] EpisodeWidget: Change the byte unit based on the size. Closes #18. --- Cargo.lock | 7 +++++++ hammond-gtk/Cargo.toml | 1 + hammond-gtk/resources/gtk/episode_widget.ui | 1 + hammond-gtk/src/main.rs | 1 + hammond-gtk/src/widgets/episode.rs | 10 ++++++++-- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fb2a52..cdba1e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,6 +611,7 @@ dependencies = [ "gtk 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "hammond-data 0.1.0", "hammond-downloader 0.1.0", + "humansize 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "loggerv 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -637,6 +638,11 @@ name = "httparse" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "humansize" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hyper" version = "0.11.9" @@ -1680,6 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum gtk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "905fcfbaaad1b44ec0b4bba9e4d527d728284c62bc2ba41fccedace2b096766f" "checksum html5ever 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba3a1fd1857a714d410c191364c5d7bf8a6487c0ab5575146d37dd7eb17ef523" "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" +"checksum humansize 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d99804bdb0790b0c312a5a1115f83804b821f1a96d80759fbb57ce796d1f3778" "checksum hyper 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e0594792d2109069d0caffd176f674d770a84adf024c5bb48e686b1ee5ac7659" "checksum hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c81fa95203e2a6087242c38691a0210f23e9f3f8f944350bd676522132e2985" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" diff --git a/hammond-gtk/Cargo.toml b/hammond-gtk/Cargo.toml index 5f250f3..ceb387d 100644 --- a/hammond-gtk/Cargo.toml +++ b/hammond-gtk/Cargo.toml @@ -12,6 +12,7 @@ gdk = "0.7.0" gdk-pixbuf = "0.3.0" gio = "0.3.0" glib = "0.4.0" +humansize = "1.0.2" lazy_static = "1.0.0" log = "0.3.8" loggerv = "0.6.0" diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index e0aa126..b0332fc 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -122,6 +122,7 @@ True False + True 42 MB True False diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index dd3b96a..08bfeee 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -11,6 +11,7 @@ extern crate diesel; extern crate dissolve; extern crate hammond_data; extern crate hammond_downloader; +extern crate humansize; #[macro_use] extern crate lazy_static; #[macro_use] diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 93ce6dd..ddc0e3c 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -5,6 +5,7 @@ use gtk::prelude::*; use chrono::prelude::*; use open; +use humansize::{file_size_opts as size_opts, FileSize}; use hammond_data::dbqueries; use hammond_data::{EpisodeWidgetQuery, Podcast}; @@ -101,9 +102,14 @@ impl EpisodeWidget { .map(|c| c.add_class("dim-label")); } + // TODO: configure it so it will not show decimal places. if let Some(size) = episode.length() { - let megabytes = size / 1024 / 1024; // episode.length represents bytes - self.size.set_text(&format!("{} MB", megabytes)) + let s = size.file_size(size_opts::CONVENTIONAL); + if let Ok(s) = s { + self.size.set_text(&s); + } else { + self.size.hide(); + } }; let date = Utc.timestamp(i64::from(episode.epoch()), 0) From 4a033e6d89a9115e4292da0d3698409095668e99 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 21:37:51 +0200 Subject: [PATCH 23/41] EpisodeWidget: Remove the decimal places in the size label. --- hammond-gtk/src/widgets/episode.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index ddc0e3c..00e0f8e 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -102,9 +102,21 @@ impl EpisodeWidget { .map(|c| c.add_class("dim-label")); } - // TODO: configure it so it will not show decimal places. + // Declare a custom humansize option struct + // See: https://docs.rs/humansize/1.0.2/humansize/file_size_opts/struct.FileSizeOpts.html + let custom_options = size_opts::FileSizeOpts { + divider: size_opts::Kilo::Binary, + units: size_opts::Kilo::Decimal, + decimal_places: 0, + decimal_zeroes: 0, + fixed_at: size_opts::FixedAt::No, + long_units: false, + space: true, + suffix: "", + }; + if let Some(size) = episode.length() { - let s = size.file_size(size_opts::CONVENTIONAL); + let s = size.file_size(custom_options); if let Ok(s) = s { self.size.set_text(&s); } else { From b7e160f735be468bf1023c4816af705a61297dbb Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 21:50:50 +0200 Subject: [PATCH 24/41] EpisodeWidget: Change date representation based on it's year. Closes #19. --- hammond-gtk/src/widgets/episode.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 00e0f8e..da74711 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -124,10 +124,13 @@ impl EpisodeWidget { } }; - let date = Utc.timestamp(i64::from(episode.epoch()), 0) - .format("%b %e") - .to_string(); - self.date.set_text(&date); + let now = Utc::now(); + let date = Utc.timestamp(i64::from(episode.epoch()), 0); + if now.year() == date.year() { + self.date.set_text(&date.format("%e %b").to_string()); + } else { + self.date.set_text(&date.format("%e %b %Y").to_string()); + }; // Show or hide the play/delete/download buttons upon widget initialization. let local_uri = episode.local_uri(); From 2ad0539a82c34793d3acac603ec5f23699f0ddc6 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Dec 2017 22:15:17 +0200 Subject: [PATCH 25/41] Downloader: Overwrite episode.lenght column upon download finish with the actual file size. Closes #22. --- hammond-downloader/src/downloader.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hammond-downloader/src/downloader.rs b/hammond-downloader/src/downloader.rs index eb0fd2f..d0505fd 100644 --- a/hammond-downloader/src/downloader.rs +++ b/hammond-downloader/src/downloader.rs @@ -6,6 +6,7 @@ use mime_guess; use std::fs::{rename, DirBuilder, File}; use std::io::{BufWriter, Read, Write}; use std::path::Path; +use std::fs; use errors::*; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; @@ -123,6 +124,11 @@ pub fn get_episode(ep: &mut EpisodeWidgetQuery, download_folder: &str) -> Result if let Ok(path) = res { // If download succedes set episode local_uri to dlpath. ep.set_local_uri(Some(&path)); + + let size = fs::metadata(path); + if let Ok(s) = size { + ep.set_length(Some(s.len() as i32)) + }; ep.save()?; Ok(()) } else { From 8513ba3644be77dc03bd823884fc91c6b3e46b1e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 14:29:41 +0200 Subject: [PATCH 26/41] EpisodesView: Use chrono to categorize widgets intead of epoch calculations. --- hammond-gtk/resources/gtk/episodes_view.ui | 2 +- hammond-gtk/src/views/episodes.rs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index 28c480a..9df8483 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -42,7 +42,7 @@ False center vertical - 7 + 25 True diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index e34a7bc..f1930c5 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -79,7 +79,7 @@ impl EpisodesView { pub fn new() -> Rc { let view = EpisodesView::default(); let episodes = dbqueries::get_episodes_widgets_with_limit(100).unwrap(); - let now_utc = Utc::now().timestamp() as i32; + let now_utc = Utc::now(); episodes.into_iter().for_each(|mut ep| { let viewep = EpisodesViewWidget::new(&mut ep); @@ -87,7 +87,7 @@ impl EpisodesView { sep.set_sensitive(false); sep.set_can_focus(false); - let t = split(now_utc, ep.epoch()); + let t = split(&now_utc, i64::from(ep.epoch())); match t { ListSplit::Today => { view.today_list.add(&viewep.container); @@ -175,19 +175,18 @@ impl EpisodesView { } } -// TODO: Avoid epoch calculations, use chrono instead. -fn split(now_utc: i32, epoch: i32) -> ListSplit { - let t = now_utc - epoch; +fn split(now: &DateTime, epoch: i64) -> ListSplit { + let ep = Utc.timestamp(epoch, 0); - if t < 86_400 { + if now.ordinal() == ep.ordinal() && now.year() == ep.year() { ListSplit::Today - } else if t < 172_800 { + } else if now.ordinal() == ep.ordinal() + 1 && now.year() == ep.year() { ListSplit::Yday - } else if t < 604_800 { + } else if now.iso_week().week() == ep.iso_week().week() && now.year() == ep.year() { ListSplit::Week - } else if t < 2_419_200 { + } else if now.month() == ep.month() && now.year() == ep.year() { ListSplit::Month - } else if t < 31_536_000 { + } else if now.year() == ep.year() { ListSplit::Year } else { ListSplit::Rest From b820ee4db70b8b8002c9781b9f622fd378dc4fd4 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 14:51:57 +0200 Subject: [PATCH 27/41] hammond-data::parser : Do not clean image urls cause feeds put redirects for uri for some reason. --- hammond-data/src/models/insertables.rs | 4 ---- hammond-data/src/parser.rs | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/hammond-data/src/models/insertables.rs b/hammond-data/src/models/insertables.rs index c847a2e..983e11b 100644 --- a/hammond-data/src/models/insertables.rs +++ b/hammond-data/src/models/insertables.rs @@ -112,10 +112,6 @@ impl NewPodcast { let con = db.get()?; match pd { Ok(foo) => { - if foo.source_id() != self.source_id { - error!("NSPD sid: {}, SPD sid: {}", self.source_id, foo.source_id()); - }; - if (foo.link() != self.link) || (foo.title() != self.title) || (foo.image_uri() != self.image_uri.as_ref().map(|x| x.as_str())) { diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index 566b06f..b6b244d 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -17,9 +17,9 @@ pub(crate) fn new_podcast(chan: &Channel, source_id: i32) -> NewPodcast { 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| url_cleaner(s)) + img.map(|s| s.to_owned()) } else { - chan.image().map(|foo| url_cleaner(foo.url())) + chan.image().map(|foo| foo.url().to_owned()) }; NewPodcastBuilder::default() @@ -43,10 +43,6 @@ pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { .map(|s| replace_extra_spaces(&ammonia::clean(s))); let guid = item.guid().map(|s| s.value().trim().to_owned()); - // Its kinda weird this being an Option type. - // Rss 2.0 specified that it's optional. - // Though the db scema has a requirment of episode uri being Unique && Not Null. - // TODO: Restructure let x = item.enclosure().map(|s| url_cleaner(s.url())); // FIXME: refactor let uri = if x.is_some() { From 5541b18a6aee372bedc4ce2e08ff70a6680c9be4 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 16:55:45 +0200 Subject: [PATCH 28/41] hammond-data::parser: Add itunesext_duration parser. --- hammond-data/src/parser.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index b6b244d..ea7f7db 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -64,6 +64,7 @@ pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { let epoch = date.map(|x| x.timestamp() as i32).unwrap_or(0); let length = item.enclosure().map(|x| x.length().parse().unwrap_or(0)); + let _duration = parse_itunes_duration(item); Ok(NewEpisodeBuilder::default() .title(title) @@ -78,6 +79,25 @@ pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { .unwrap()) } +/// Parses an Item Itunes extension and returns it's duration value in seconds. +// FIXME: Rafactor +fn parse_itunes_duration(item: &Item) -> Option { + let duration = item.itunes_ext().map(|s| s.duration())??; + + let mut seconds = 0; + let fk_apple = duration.split(':').collect::>(); + if fk_apple.len() == 3 { + seconds += fk_apple[0].parse::().unwrap_or(0) * 3600; + seconds += fk_apple[1].parse::().unwrap_or(0) * 60; + seconds += fk_apple[2].parse::().unwrap_or(0); + } else if fk_apple.len() == 2 { + seconds += fk_apple[0].parse::().unwrap_or(0) * 60; + seconds += fk_apple[1].parse::().unwrap_or(0); + } + + Some(seconds) +} + #[cfg(test)] mod tests { use std::fs::File; From 4512790f2d42e6ead1b3172a23529f4122346908 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 17:30:07 +0200 Subject: [PATCH 29/41] hammond-data: Add duration column to the episode table. --- .../down.sql | 22 ++++++++++++++ .../up.sql | 23 ++++++++++++++ hammond-data/src/dbqueries.rs | 3 +- hammond-data/src/models/insertables.rs | 2 ++ hammond-data/src/models/queryables.rs | 30 +++++++++++++++++++ hammond-data/src/parser.rs | 3 +- hammond-data/src/schema.rs | 1 + 7 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 hammond-data/migrations/2017-12-22-145740_add_duration_column/down.sql create mode 100644 hammond-data/migrations/2017-12-22-145740_add_duration_column/up.sql diff --git a/hammond-data/migrations/2017-12-22-145740_add_duration_column/down.sql b/hammond-data/migrations/2017-12-22-145740_add_duration_column/down.sql new file mode 100644 index 0000000..7f1bbcb --- /dev/null +++ b/hammond-data/migrations/2017-12-22-145740_add_duration_column/down.sql @@ -0,0 +1,22 @@ +ALTER TABLE episode RENAME TO old_table; + +CREATE TABLE episode ( + title TEXT NOT NULL, + uri TEXT, + local_uri TEXT, + description TEXT, + published_date TEXT, + epoch INTEGER NOT NULL DEFAULT 0, + length INTEGER, + guid TEXT, + played INTEGER, + podcast_id INTEGER NOT NULL, + favorite INTEGER DEFAULT 0, + archive INTEGER DEFAULT 0, + PRIMARY KEY (title, podcast_id) +); + +INSERT INTO episode (title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id) +SELECT title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id +FROM old_table; +Drop table old_table; diff --git a/hammond-data/migrations/2017-12-22-145740_add_duration_column/up.sql b/hammond-data/migrations/2017-12-22-145740_add_duration_column/up.sql new file mode 100644 index 0000000..f2dcf10 --- /dev/null +++ b/hammond-data/migrations/2017-12-22-145740_add_duration_column/up.sql @@ -0,0 +1,23 @@ +ALTER TABLE episode RENAME TO old_table; + +CREATE TABLE episode ( + title TEXT NOT NULL, + uri TEXT, + local_uri TEXT, + description TEXT, + published_date TEXT, + epoch INTEGER NOT NULL DEFAULT 0, + length INTEGER, + duration INTEGER, + guid TEXT, + played INTEGER, + podcast_id INTEGER NOT NULL, + favorite INTEGER DEFAULT 0, + archive INTEGER DEFAULT 0, + PRIMARY KEY (title, podcast_id) +); + +INSERT INTO episode (title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id) +SELECT title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id +FROM old_table; +Drop table old_table; \ No newline at end of file diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 4f0c5d2..65236d6 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -111,6 +111,7 @@ pub fn get_episodes_widgets_with_limit(limit: u32) -> Result Result, published_date: Option, length: Option, + duration: Option, guid: Option, epoch: i32, podcast_id: i32, @@ -207,6 +208,7 @@ impl NewEpisode { if foo.title() != self.title.as_str() || foo.epoch() != self.epoch || foo.uri() != self.uri.as_ref().map(|s| s.as_str()) + || foo.duration() != self.duration { self.update(con, foo.rowid())?; } diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index aff1c4b..d4409cd 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -33,6 +33,7 @@ pub struct Episode { published_date: Option, epoch: i32, length: Option, + duration: Option, guid: Option, played: Option, favorite: bool, @@ -125,6 +126,8 @@ impl Episode { } /// Get the `length`. + /// + /// The number represents the size of the file in bytes. pub fn length(&self) -> Option { self.length } @@ -134,6 +137,18 @@ impl Episode { self.length = value; } + /// Get the `duration` value. + /// + /// The number represents the duration of the item/episode in seconds. + pub fn duration(&self) -> Option { + self.duration + } + + /// Set the `duration`. + pub fn set_duration(&mut self, value: Option) { + self.duration = value; + } + /// Epoch representation of the last time the episode was played. /// /// None/Null for unplayed. @@ -204,6 +219,7 @@ pub struct EpisodeWidgetQuery { local_uri: Option, epoch: i32, length: Option, + duration: Option, played: Option, // favorite: bool, // archive: bool, @@ -250,6 +266,8 @@ impl EpisodeWidgetQuery { } /// Get the `length`. + /// + /// The number represents the size of the file in bytes. pub fn length(&self) -> Option { self.length } @@ -259,6 +277,18 @@ impl EpisodeWidgetQuery { self.length = value; } + /// Get the `duration` value. + /// + /// The number represents the duration of the item/episode in seconds. + pub fn duration(&self) -> Option { + self.duration + } + + /// Set the `duration`. + pub fn set_duration(&mut self, value: Option) { + self.duration = value; + } + /// Epoch representation of the last time the episode was played. /// /// None/Null for unplayed. diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index ea7f7db..a08e025 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -64,13 +64,14 @@ pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { let epoch = date.map(|x| x.timestamp() as i32).unwrap_or(0); let length = item.enclosure().map(|x| x.length().parse().unwrap_or(0)); - let _duration = parse_itunes_duration(item); + let duration = parse_itunes_duration(item); Ok(NewEpisodeBuilder::default() .title(title) .uri(uri) .description(description) .length(length) + .duration(duration) .published_date(pub_date) .epoch(epoch) .guid(guid) diff --git a/hammond-data/src/schema.rs b/hammond-data/src/schema.rs index 7b43171..6389143 100644 --- a/hammond-data/src/schema.rs +++ b/hammond-data/src/schema.rs @@ -8,6 +8,7 @@ table! { published_date -> Nullable, epoch -> Integer, length -> Nullable, + duration -> Nullable, guid -> Nullable, played -> Nullable, favorite -> Bool, From 0129efb02ef1e8ea06f56617d1ed0478d3e031e9 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 17:49:15 +0200 Subject: [PATCH 30/41] EpisodeWidget: Display episode's duration. Closes #21. --- hammond-gtk/src/widgets/episode.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index da74711..efaf745 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -46,6 +46,7 @@ pub struct EpisodeWidget { size: gtk::Label, progress: gtk::ProgressBar, progress_label: gtk::Label, + separator1: gtk::Label, } impl Default for EpisodeWidget { @@ -66,6 +67,8 @@ impl Default for EpisodeWidget { let size: gtk::Label = builder.get_object("size_label").unwrap(); let progress_label: gtk::Label = builder.get_object("progress_label").unwrap(); + let separator1: gtk::Label = builder.get_object("separator1").unwrap(); + EpisodeWidget { container, progress, @@ -78,6 +81,7 @@ impl Default for EpisodeWidget { size, date, progress_label, + separator1, } } } @@ -124,6 +128,12 @@ impl EpisodeWidget { } }; + if let Some(secs) = episode.duration() { + self.duration.set_text(&format!("{} min", secs / 60)); + self.duration.show(); + self.separator1.show(); + }; + let now = Utc::now(); let date = Utc.timestamp(i64::from(episode.epoch()), 0); if now.year() == date.year() { From 31b19dd88ec2834bbdd5b8e9c26f47da64531d57 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 18:28:18 +0200 Subject: [PATCH 31/41] Just things apple force you to do. --- hammond-data/src/parser.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index a08e025..5ef3d21 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -82,9 +82,20 @@ pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { /// Parses an Item Itunes extension and returns it's duration value in seconds. // FIXME: Rafactor +// TODO: Write tests +#[allow(non_snake_case)] fn parse_itunes_duration(item: &Item) -> Option { let duration = item.itunes_ext().map(|s| s.duration())??; + // FOR SOME FUCKING REASON, IN THE APPLE EXTENSION SPEC + // THE DURATION CAN BE EITHER AN INT OF SECONDS OR + // A STRING OF THE FOLLOWING FORMATS: + // HH:MM:SS, H:MM:SS, MM:SS, M:SS + // LIKE WHO THE FUCK THOUGH THAT WOULD BE A GOOD IDEA. + if let Ok(NO_FUCKING_LOGIC) = duration.parse::() { + return Some(NO_FUCKING_LOGIC); + }; + let mut seconds = 0; let fk_apple = duration.split(':').collect::>(); if fk_apple.len() == 3 { From 7aebb4d50d334cc3c01bd05d2b59852f56ad7f98 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 18:49:24 +0200 Subject: [PATCH 32/41] gitlab-ci: Add caching to the ci config. --- .gitlab-ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e64a2ba..1ae8422 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,10 @@ before_script: - rustc --version && cargo --version - cargo build - cargo test --verbose -- --test-threads=1 + cache: + paths: + - target/ + - cargo/ variables: # RUSTFLAGS: "-C link-dead-code" @@ -46,6 +50,10 @@ rustfmt: - rustc --version && cargo --version - cargo install rustfmt-nightly --force - cargo fmt --all -- --write-mode=diff + cache: + paths: + - target/ + - cargo/ # Configure and run clippy on nightly # Only fails on errors atm. @@ -56,3 +64,7 @@ clippy: - rustc --version && cargo --version - cargo install clippy --force - cargo clippy --all + cache: + paths: + - target/ + - cargo/ From fac048a24d56b471352c6ff185c3d9fe813deb80 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 20:47:37 +0200 Subject: [PATCH 33/41] Small Visual tweaks all over the client. --- .gitlab-ci.yml | 1 + hammond-gtk/resources/gtk/episode_widget.ui | 16 ++++++++-------- hammond-gtk/resources/gtk/episodes_view.ui | 9 ++++++++- hammond-gtk/resources/gtk/show_widget.ui | 6 +++--- hammond-gtk/resources/gtk/shows_view.ui | 6 ++++-- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ae8422..63dd6f7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,6 +28,7 @@ before_script: variables: # RUSTFLAGS: "-C link-dead-code" RUST_BACKTRACE: "FULL" + CARGO_HOME: $CI_PROJECT_DIR/cargo stable:test: # https://hub.docker.com/_/rust/ diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index b0332fc..f6dd911 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -10,14 +10,14 @@ True False - 5 + 6 True False center vertical - 5 + 6 True @@ -51,7 +51,7 @@ True False - 5 + 6 True @@ -164,7 +164,7 @@ False True - 5 + 6 0 @@ -172,7 +172,7 @@ True False - 5 + 6 Cancel @@ -260,7 +260,7 @@ False True - 5 + 6 end 1 @@ -269,7 +269,7 @@ True False - 5 + 6 0 @@ -281,7 +281,7 @@ False True - 5 + 6 end 1 diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index 9df8483..1d5333f 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -41,14 +41,16 @@ True False center + 24 vertical - 25 + 24 True False True vertical + 6 True @@ -102,6 +104,7 @@ False True vertical + 6 True @@ -155,6 +158,7 @@ False True vertical + 6 True @@ -208,6 +212,7 @@ False True vertical + 6 True @@ -261,6 +266,7 @@ False True vertical + 6 True @@ -314,6 +320,7 @@ False True vertical + 6 True diff --git a/hammond-gtk/resources/gtk/show_widget.ui b/hammond-gtk/resources/gtk/show_widget.ui index ead850a..0f83b05 100644 --- a/hammond-gtk/resources/gtk/show_widget.ui +++ b/hammond-gtk/resources/gtk/show_widget.ui @@ -58,7 +58,7 @@ True False center - 10 + 12 True @@ -77,7 +77,7 @@ True False vertical - 10 + 12 True @@ -191,7 +191,7 @@ False True - 25 + 24 0 diff --git a/hammond-gtk/resources/gtk/shows_view.ui b/hammond-gtk/resources/gtk/shows_view.ui index 7981cb9..5050ca0 100644 --- a/hammond-gtk/resources/gtk/shows_view.ui +++ b/hammond-gtk/resources/gtk/shows_view.ui @@ -21,9 +21,11 @@ False center start + 24 + 24 True - 5 - 5 + 12 + 12 20 none From ae6a97d7256d3f9d45ff8b55cdfcdbf94384bf17 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 21:21:39 +0200 Subject: [PATCH 34/41] EpisodesViewWidget: Add margins from the cover. --- hammond-gtk/resources/gtk/episodes_view_widget.ui | 4 ++++ hammond-gtk/src/views/episodes.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view_widget.ui b/hammond-gtk/resources/gtk/episodes_view_widget.ui index 3aa6d88..bbe09b7 100644 --- a/hammond-gtk/resources/gtk/episodes_view_widget.ui +++ b/hammond-gtk/resources/gtk/episodes_view_widget.ui @@ -5,10 +5,14 @@ True False + 6 True False + 6 + 6 + 6 64 image-x-generic-symbolic diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index f1930c5..a388b3a 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -207,7 +207,7 @@ impl Default for EpisodesViewWidget { let container: gtk::Box = builder.get_object("container").unwrap(); let image: gtk::Image = builder.get_object("cover").unwrap(); let ep = EpisodeWidget::default(); - container.pack_start(&ep.container, true, true, 5); + container.pack_start(&ep.container, true, true, 6); EpisodesViewWidget { container, @@ -233,7 +233,7 @@ impl EpisodesViewWidget { } let ep = EpisodeWidget::new(episode); - container.pack_start(&ep.container, true, true, 5); + container.pack_start(&ep.container, true, true, 6); EpisodesViewWidget { container, From 832495beceb67972e3f7fa4e9db61ff06db3c00e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Dec 2017 22:14:08 +0200 Subject: [PATCH 35/41] EpisodesView: Remove Year category. --- hammond-gtk/resources/gtk/episodes_view.ui | 56 +--------------------- hammond-gtk/src/main.rs | 2 +- hammond-gtk/src/views/episodes.rs | 21 -------- 3 files changed, 2 insertions(+), 77 deletions(-) diff --git a/hammond-gtk/resources/gtk/episodes_view.ui b/hammond-gtk/resources/gtk/episodes_view.ui index 1d5333f..8a493c0 100644 --- a/hammond-gtk/resources/gtk/episodes_view.ui +++ b/hammond-gtk/resources/gtk/episodes_view.ui @@ -260,60 +260,6 @@ 3 - - - True - False - True - vertical - 6 - - - True - False - start - This Year - - - - - - - False - True - 0 - - - - - True - False - 0 - in - - - True - False - none - - - - - - - - False - True - 1 - - - - - False - True - 4 - - True @@ -326,7 +272,7 @@ True False start - Older than a Year + Older diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index 08bfeee..70f65f4 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -66,7 +66,7 @@ fn build_ui(app: >k::Application) { // Get the main window let window = gtk::ApplicationWindow::new(app); - window.set_default_size(1150, 650); + window.set_default_size(1024, 576); // Get the headerbar let header = Rc::new(headerbar::Header::default()); diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index a388b3a..915cac2 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -16,7 +16,6 @@ enum ListSplit { Yday, Week, Month, - Year, Rest, } @@ -28,13 +27,11 @@ pub struct EpisodesView { yday_box: gtk::Box, week_box: gtk::Box, month_box: gtk::Box, - year_box: gtk::Box, rest_box: gtk::Box, today_list: gtk::ListBox, yday_list: gtk::ListBox, week_list: gtk::ListBox, month_list: gtk::ListBox, - year_list: gtk::ListBox, rest_list: gtk::ListBox, } @@ -47,13 +44,11 @@ impl Default for EpisodesView { let yday_box: gtk::Box = builder.get_object("yday_box").unwrap(); let week_box: gtk::Box = builder.get_object("week_box").unwrap(); let month_box: gtk::Box = builder.get_object("month_box").unwrap(); - let year_box: gtk::Box = builder.get_object("year_box").unwrap(); let rest_box: gtk::Box = builder.get_object("rest_box").unwrap(); let today_list: gtk::ListBox = builder.get_object("today_list").unwrap(); let yday_list: gtk::ListBox = builder.get_object("yday_list").unwrap(); let week_list: gtk::ListBox = builder.get_object("week_list").unwrap(); let month_list: gtk::ListBox = builder.get_object("month_list").unwrap(); - let year_list: gtk::ListBox = builder.get_object("year_list").unwrap(); let rest_list: gtk::ListBox = builder.get_object("rest_list").unwrap(); EpisodesView { @@ -63,13 +58,11 @@ impl Default for EpisodesView { yday_box, week_box, month_box, - year_box, rest_box, today_list, yday_list, week_list, month_list, - year_list, rest_list, } } @@ -105,10 +98,6 @@ impl EpisodesView { view.month_list.add(&viewep.container); view.month_list.add(&sep) } - ListSplit::Year => { - view.year_list.add(&viewep.container); - view.year_list.add(&sep) - } ListSplit::Rest => { view.rest_list.add(&viewep.container); view.rest_list.add(&sep) @@ -134,10 +123,6 @@ impl EpisodesView { view.month_box.hide(); } - if view.year_list.get_children().is_empty() { - view.year_box.hide(); - } - if view.rest_list.get_children().is_empty() { view.rest_box.hide(); } @@ -163,10 +148,6 @@ impl EpisodesView { return false; } - if !self.year_list.get_children().is_empty() { - return false; - } - if !self.rest_list.get_children().is_empty() { return false; } @@ -186,8 +167,6 @@ fn split(now: &DateTime, epoch: i64) -> ListSplit { ListSplit::Week } else if now.month() == ep.month() && now.year() == ep.year() { ListSplit::Month - } else if now.year() == ep.year() { - ListSplit::Year } else { ListSplit::Rest } From 95c290df5038adfe53e47be35037a5da0c7866ac Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Fri, 22 Dec 2017 21:54:28 +0100 Subject: [PATCH 36/41] [ui] add custom style and [fix] draw List separators with css --- hammond-gtk/resources/gtk/style.css | 7 +++++++ hammond-gtk/resources/resources.xml | 1 + hammond-gtk/src/main.rs | 4 ++++ hammond-gtk/src/views/episodes.rs | 11 ----------- hammond-gtk/src/widgets/episode.rs | 7 ------- 5 files changed, 12 insertions(+), 18 deletions(-) create mode 100644 hammond-gtk/resources/gtk/style.css diff --git a/hammond-gtk/resources/gtk/style.css b/hammond-gtk/resources/gtk/style.css new file mode 100644 index 0000000..39bb770 --- /dev/null +++ b/hammond-gtk/resources/gtk/style.css @@ -0,0 +1,7 @@ +row { + border-bottom: solid 1px rgba(0,0,0, 0.1); +} + +row:last-child { + border-bottom: none; +} diff --git a/hammond-gtk/resources/resources.xml b/hammond-gtk/resources/resources.xml index 0c91cd0..366f71c 100644 --- a/hammond-gtk/resources/resources.xml +++ b/hammond-gtk/resources/resources.xml @@ -9,5 +9,6 @@ gtk/shows_view.ui gtk/shows_child.ui gtk/headerbar.ui + gtk/style.css diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index 08bfeee..c50f04a 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -64,6 +64,10 @@ fn build_ui(app: >k::Application) { menu.append("Update feeds", "app.update"); app.set_app_menu(&menu); + // Add custom style + let provider = gtk::CssProvider::new(); + gtk::CssProvider::load_from_resource(&provider, "/org/gnome/hammond/gtk/style.css"); + gtk::StyleContext::add_provider_for_screen(&gdk::Screen::get_default().unwrap(), &provider, 600); // Get the main window let window = gtk::ApplicationWindow::new(app); window.set_default_size(1150, 650); diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs index f1930c5..756b4a6 100644 --- a/hammond-gtk/src/views/episodes.rs +++ b/hammond-gtk/src/views/episodes.rs @@ -83,39 +83,28 @@ impl EpisodesView { episodes.into_iter().for_each(|mut ep| { let viewep = EpisodesViewWidget::new(&mut ep); - let sep = gtk::Separator::new(gtk::Orientation::Vertical); - sep.set_sensitive(false); - sep.set_can_focus(false); let t = split(&now_utc, i64::from(ep.epoch())); match t { ListSplit::Today => { view.today_list.add(&viewep.container); - view.today_list.add(&sep) } ListSplit::Yday => { view.yday_list.add(&viewep.container); - view.yday_list.add(&sep) } ListSplit::Week => { view.week_list.add(&viewep.container); - view.week_list.add(&sep) } ListSplit::Month => { view.month_list.add(&viewep.container); - view.month_list.add(&sep) } ListSplit::Year => { view.year_list.add(&viewep.container); - view.year_list.add(&sep) } ListSplit::Rest => { view.rest_list.add(&viewep.container); - view.rest_list.add(&sep) } } - - sep.show() }); if view.today_list.get_children().is_empty() { diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index efaf745..fb9c4ef 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -305,13 +305,6 @@ pub fn episodes_listbox(pd: &Podcast) -> Result { episodes.into_iter().for_each(|mut ep| { let widget = EpisodeWidget::new(&mut ep); list.add(&widget.container); - - let sep = gtk::Separator::new(gtk::Orientation::Vertical); - sep.set_sensitive(false); - sep.set_can_focus(false); - - list.add(&sep); - sep.show() }); list.set_vexpand(false); From f9d17afad31f9007fbbd3a0114ce1b96db807efb Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 23 Dec 2017 10:39:20 +0200 Subject: [PATCH 37/41] hammond-gtk: Remove app menu. --- hammond-gtk/src/main.rs | 46 ++++++++++------------------------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index c9a409b..424b528 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -27,7 +27,7 @@ use log::LogLevel; use hammond_data::utils::checkup; use gtk::prelude::*; -use gio::{ActionMapExt, ApplicationExt, MenuExt, SimpleActionExt}; +use gio::ApplicationExt; use std::rc::Rc; // http://gtk-rs.org/tuto/closures @@ -58,19 +58,9 @@ mod utils; mod static_resource; fn build_ui(app: >k::Application) { - let menu = gio::Menu::new(); - menu.append("Quit", "app.quit"); - menu.append("Checkup", "app.check"); - menu.append("Update feeds", "app.update"); - app.set_app_menu(&menu); - - // Add custom style - let provider = gtk::CssProvider::new(); - gtk::CssProvider::load_from_resource(&provider, "/org/gnome/hammond/gtk/style.css"); - gtk::StyleContext::add_provider_for_screen(&gdk::Screen::get_default().unwrap(), &provider, 600); // Get the main window let window = gtk::ApplicationWindow::new(app); - window.set_default_size(1024, 576); + window.set_default_size(860, 640); // Get the headerbar let header = Rc::new(headerbar::Header::default()); @@ -84,28 +74,6 @@ fn build_ui(app: >k::Application) { Inhibit(false) }); - // Setup quit in the app menu since default is overwritten. - let quit = gio::SimpleAction::new("quit", None); - let window2 = window.clone(); - quit.connect_activate(move |_, _| { - window2.destroy(); - }); - app.add_action(&quit); - - // Setup the checkup in the app menu. - let check = gio::SimpleAction::new("check", None); - check.connect_activate(move |_, _| { - let _ = checkup(); - }); - app.add_action(&check); - - let update = gio::SimpleAction::new("update", None); - let ct_clone = ct.clone(); - update.connect_activate(move |_, _| { - utils::refresh_feed(ct_clone.clone(), None); - }); - app.add_action(&update); - // Update on startup gtk::timeout_add_seconds( 30, @@ -114,7 +82,6 @@ fn build_ui(app: >k::Application) { glib::Continue(false) }), ); - // Auto-updater, runs every hour. // TODO: expose the interval in which it run to a user setting. // TODO: show notifications. @@ -146,6 +113,15 @@ fn main() { let application = gtk::Application::new("org.gnome.Hammond", gio::ApplicationFlags::empty()) .expect("Initialization failed..."); + // Add custom style + let provider = gtk::CssProvider::new(); + gtk::CssProvider::load_from_resource(&provider, "/org/gnome/hammond/gtk/style.css"); + gtk::StyleContext::add_provider_for_screen( + &gdk::Screen::get_default().unwrap(), + &provider, + 600, + ); + application.connect_startup(move |app| { build_ui(app); }); From 37dbfff7667d3516b58726bfa87bfd381b4da820 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 23 Dec 2017 14:22:02 +0200 Subject: [PATCH 38/41] gitlab-ci: Do not cache rustfmt and clippy stuff. --- .gitlab-ci.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 63dd6f7..f929e62 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,10 +51,6 @@ rustfmt: - rustc --version && cargo --version - cargo install rustfmt-nightly --force - cargo fmt --all -- --write-mode=diff - cache: - paths: - - target/ - - cargo/ # Configure and run clippy on nightly # Only fails on errors atm. @@ -65,7 +61,3 @@ clippy: - rustc --version && cargo --version - cargo install clippy --force - cargo clippy --all - cache: - paths: - - target/ - - cargo/ From 2d6f02c4075cc6e09c6b7e479d25a3a25d4bd564 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 23 Dec 2017 15:44:28 +0200 Subject: [PATCH 39/41] EpisodeWidget: Do not display size if it's 0 bytes. --- hammond-data/src/parser.rs | 2 +- hammond-gtk/resources/gtk/episode_widget.ui | 3 +-- hammond-gtk/src/widgets/episode.rs | 15 ++++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index 5ef3d21..352d273 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -63,7 +63,7 @@ pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { let pub_date = date.map(|x| x.to_rfc2822()).ok(); let epoch = date.map(|x| x.timestamp() as i32).unwrap_or(0); - let length = item.enclosure().map(|x| x.length().parse().unwrap_or(0)); + let length = || -> Option { item.enclosure().map(|x| x.length().parse().ok())? }(); let duration = parse_itunes_duration(item); Ok(NewEpisodeBuilder::default() diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index f6dd911..ff4a249 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -104,8 +104,8 @@ - True False + True ยท False