From ebbebf77354bd69c042d0021440f9627aae9a07b Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Dec 2017 10:57:29 +0200 Subject: [PATCH 1/7] Initial implementation of the new EpisodeWidget. --- hammond-gtk/resources/gtk/episode_widget.ui | 301 ++++++++++---------- hammond-gtk/src/widgets/episode.rs | 138 ++++----- scripts/release.sh | 2 +- 3 files changed, 211 insertions(+), 230 deletions(-) diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index 71d846c..350caea 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -2,190 +2,171 @@ - - 100 - 25 + True False - 5 - 5 - 5 - - - True - True - end - center - 5 - 5 - - - True - False - media-playback-start-symbolic - - - - - False - False - 5 - end - 0 - - - - - True - True - True - end - center - 5 - 5 - True - - - True - False - document-save-symbolic - - - - - False - False - 5 - end - 0 - - - - - delete_button - True - True - end - center - - - True - False - user-trash-symbolic - - - - - False - False - end - 0 - - - - - True - True - Mark episode as Unplayed. - end - center - - - True - False - edit-undo-symbolic - - - - - False - False - end - 1 - - - - - True - True - True - Mark episode as played. - end - center - - - True - False - object-select-symbolic - - - - - False - False - end - 2 - - + vertical True False - vertical + 5 - + True False - start - center - True - True - end - 1 + vertical + 5 + + + True + False + start + Episode Title + True + True + end + 1 + + + True + True + 0 + + + + + True + False + 5 + + + True + False + 42 min + + + False + True + 0 + + + + + True + False + document-save-symbolic + + + False + True + 1 + + + + + True + True + 1 + + - False - False + True + True + 5 0 - + True - True - True + False - - True + + Cancel True - in - 100 - 600 - True - True + True + + + False + True + 5 + end + 0 + + + + + delete_button + True + True + end - + True - True - 5 - False - word-char - False + False + user-trash-symbolic + + False + False + 5 + end + 1 + - - + + True - False - Description: + True + True + end + True + + + True + False + document-save-symbolic + + + + False + False + 5 + end + 2 + + + + + True + True + end + + + True + False + media-playback-start-symbolic + + + + + False + False + 5 + end + 3 + False True + 5 1 @@ -193,7 +174,19 @@ True True - 3 + 5 + 0 + + + + + False + + + False + True + 5 + 1 diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index e69efd1..020341c 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -1,68 +1,75 @@ use glib; use gtk; use gtk::prelude::*; -use gtk::{ContainerExt, TextBufferExt}; use open; -use dissolve::strip_html_tags; use hammond_data::dbqueries; use hammond_data::{Episode, Podcast}; use hammond_downloader::downloader; use hammond_data::utils::*; use hammond_data::errors::*; -use hammond_data::utils::replace_extra_spaces; use std::thread; use std::cell::RefCell; use std::sync::mpsc::{channel, Receiver}; use std::path::Path; -type Foo = RefCell)>>; +type Foo = RefCell< + Option< + ( + gtk::Button, + gtk::Button, + gtk::Button, + gtk::Button, + gtk::ProgressBar, + Receiver, + ), + >, +>; thread_local!(static GLOBAL: Foo = RefCell::new(None)); #[derive(Debug)] struct EpisodeWidget { container: gtk::Box, - download: gtk::Button, play: gtk::Button, delete: gtk::Button, - played: gtk::Button, - unplayed: gtk::Button, + download: gtk::Button, + cancel: gtk::Button, title: gtk::Label, - description: gtk::TextView, - // description: gtk::Label, - expander: gtk::Expander, + duration: gtk::Label, + progress: gtk::ProgressBar, + an_indicator: gtk::Image, } impl EpisodeWidget { fn new() -> EpisodeWidget { - // This is just a prototype and will be reworked probably. let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episode_widget.ui"); - let container: gtk::Box = builder.get_object("episode_box").unwrap(); + let container: gtk::Box = builder.get_object("episode_container").unwrap(); + + let progress: gtk::ProgressBar = builder.get_object("progress_bar").unwrap(); + let an_indicator: gtk::Image = builder.get_object("an_indicator").unwrap(); + let download: gtk::Button = builder.get_object("download_button").unwrap(); let play: gtk::Button = builder.get_object("play_button").unwrap(); let delete: gtk::Button = builder.get_object("delete_button").unwrap(); - let played: gtk::Button = builder.get_object("mark_played_button").unwrap(); - let unplayed: gtk::Button = builder.get_object("mark_unplayed_button").unwrap(); + let cancel: gtk::Button = builder.get_object("cancel_button").unwrap(); let title: gtk::Label = builder.get_object("title_label").unwrap(); - let expander: gtk::Expander = builder.get_object("expand_desc").unwrap(); - let description: gtk::TextView = builder.get_object("desc_text_view").unwrap(); - // let description: gtk::Label = builder.get_object("desc_text").unwrap(); + let duration: gtk::Label = builder.get_object("duration_label").unwrap(); EpisodeWidget { container, + progress, + an_indicator, download, play, + cancel, delete, - played, - unplayed, title, - expander, - description, + duration, } } @@ -76,27 +83,6 @@ impl EpisodeWidget { self.title.set_xalign(0.0); self.title.set_text(episode.title()); - if episode.description().is_some() { - let text = episode.description().unwrap().to_owned(); - let description = &self.description; - self.expander - .connect_activate(clone!(description, text => move |_| { - // let mut text = text.clone(); - // html_to_markup(&mut text); - // description.set_markup(&text) - - let plain_text = strip_html_tags(&text).join(" "); - // TODO: handle unwrap - let buff = description.get_buffer().unwrap(); - buff.set_text(&replace_extra_spaces(&plain_text)); - })); - } - - if episode.played().is_some() { - self.unplayed.show(); - self.played.hide(); - } - // Show or hide the play/delete/download buttons upon widget initialization. let local_uri = episode.local_uri(); if local_uri.is_some() && Path::new(local_uri.unwrap()).exists() { @@ -105,15 +91,10 @@ impl EpisodeWidget { self.delete.show(); } - let played = &self.played; - let unplayed = &self.unplayed; - self.play - .connect_clicked(clone!(episode, played, unplayed => move |_| { + self.play.connect_clicked(clone!(episode => move |_| { let mut episode = episode.clone(); on_play_bttn_clicked(episode.rowid()); let _ = episode.set_played_now(); - played.hide(); - unplayed.show(); })); let play = &self.play; @@ -126,38 +107,24 @@ impl EpisodeWidget { download.show(); })); - let unplayed = &self.unplayed; - self.played - .connect_clicked(clone!(episode, unplayed => move |played| { - let mut episode = episode.clone(); - let _ = episode.set_played_now(); - played.hide(); - unplayed.show(); - })); - - let played = &self.played; - self.unplayed - .connect_clicked(clone!(episode, played => move |un| { - let mut episode = episode.clone(); - episode.set_played(None); - let _ = episode.save(); - un.hide(); - played.show(); - })); - let pd_title = pd.title().to_owned(); let play = &self.play; let delete = &self.delete; - self.download - .connect_clicked(clone!(play, delete, episode => move |dl| { + let cancel = &self.cancel; + let progress = &self.progress; + self.download.connect_clicked( + clone!(play, delete, episode, cancel, progress => move |dl| { on_download_clicked( &pd_title, &mut episode.clone(), dl, &play, &delete, + &cancel, + &progress ); - })); + }), + ); } } @@ -168,17 +135,30 @@ fn on_download_clicked( download_bttn: >k::Button, play_bttn: >k::Button, del_bttn: >k::Button, + cancel_bttn: >k::Button, + progress_bar: >k::ProgressBar, ) { // 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 => move |global| { - *global.borrow_mut() = Some((download_bttn, play_bttn, del_bttn, receiver)); - })); + GLOBAL.with( + clone!(download_bttn, play_bttn, del_bttn, cancel_bttn, progress_bar => move |global| { + *global.borrow_mut() = Some(( + download_bttn, + play_bttn, + del_bttn, + cancel_bttn, + progress_bar, + receiver)); + }), + ); let pd_title = pd_title.to_owned(); let mut ep = ep.clone(); + cancel_bttn.show(); + progress_bar.show(); + download_bttn.hide(); thread::spawn(move || { let download_fold = downloader::get_download_folder(&pd_title).unwrap(); let e = downloader::get_episode(&mut ep, download_fold.as_str()); @@ -223,13 +203,21 @@ fn on_delete_bttn_clicked(episode_id: i32) { fn receive() -> glib::Continue { GLOBAL.with(|global| { - if let Some((ref download_bttn, ref play_bttn, ref del_bttn, ref reciever)) = - *global.borrow() + if let Some(( + ref download_bttn, + ref play_bttn, + ref del_bttn, + ref cancel_bttn, + ref progress_bar, + ref reciever, + )) = *global.borrow() { if reciever.try_recv().is_ok() { download_bttn.hide(); play_bttn.show(); del_bttn.show(); + cancel_bttn.hide(); + progress_bar.hide(); } } }); diff --git a/scripts/release.sh b/scripts/release.sh index cced5a7..c393955 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -35,5 +35,5 @@ cp -rf vendor $DIST/ # packaging cd $DEST/dist -tar -czvf $VERSION.tar.gz $VERSION +tar -cJvf $VERSION.tar.xz $VERSION From 8fe6b526a54b3ac363412cc81659967f642972c0 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Dec 2017 12:01:35 +0200 Subject: [PATCH 2/7] Add a new Diesel Model for the EpisodeWidget. --- hammond-data/src/dbqueries.rs | 17 +++- hammond-data/src/lib.rs | 2 +- hammond-data/src/models/queryables.rs | 129 ++++++++++++++++++++++++++ hammond-downloader/src/downloader.rs | 4 +- hammond-gtk/src/widgets/episode.rs | 15 +-- 5 files changed, 157 insertions(+), 10 deletions(-) diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 391ec6e..f1bb8dd 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, Podcast, Source}; +use models::queryables::{Episode, EpisodeWidgetQuery, Podcast, Source}; use chrono::prelude::*; use errors::*; @@ -103,6 +103,21 @@ pub fn get_pd_episodes(parent: &Podcast) -> Result> { .load::(&*con)?) } +pub fn get_pd_episodeswidgets(parent: &Podcast) -> 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)) + .filter(podcast_id.eq(parent.id())) + // .group_by(epoch) + .order(epoch.desc()) + .load::(&*con)?, + ) +} + pub fn get_pd_unplayed_episodes(parent: &Podcast) -> Result> { use schema::episode::dsl::*; diff --git a/hammond-data/src/lib.rs b/hammond-data/src/lib.rs index c562fbd..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, 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 f0cab54..e8ae722 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -1,4 +1,6 @@ use chrono::prelude::*; +use diesel::prelude::*; +use diesel; use reqwest; use diesel::SaveChangesDsl; @@ -189,6 +191,133 @@ impl Episode { } } +#[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 struct EpisodeWidgetQuery { + rowid: i32, + title: String, + uri: Option, + local_uri: Option, + epoch: i32, + length: Option, + played: Option, + // favorite: bool, + // archive: bool, + podcast_id: i32, +} + +impl EpisodeWidgetQuery { + /// Get the value of the sqlite's `ROW_ID` + pub fn rowid(&self) -> i32 { + self.rowid + } + + /// Get the value of the `title` field. + pub fn title(&self) -> &str { + &self.title + } + + /// Get the value of the `uri`. + /// + /// Represents the url(usually) that the media file will be located at. + pub fn uri(&self) -> Option<&str> { + self.uri.as_ref().map(|s| s.as_str()) + } + + /// Get the value of the `local_uri`. + /// + /// Represents the local uri,usually filesystem path, + /// that the media file will be located at. + pub fn local_uri(&self) -> Option<&str> { + self.local_uri.as_ref().map(|s| s.as_str()) + } + + /// Set the `local_uri`. + pub fn set_local_uri(&mut self, value: Option<&str>) { + self.local_uri = value.map(|x| x.to_string()); + } + + /// Get the `epoch` value. + /// + /// Retrieved from the rss Item publish date. + /// Value is set to Utc whenever possible. + pub fn epoch(&self) -> i32 { + self.epoch + } + + /// Get the `length`. + pub fn length(&self) -> Option { + self.length + } + + /// Set the `length`. + pub fn set_length(&mut self, value: Option) { + self.length = value; + } + + /// 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; + } + + // /// Represents the archiving policy for the episode. + // pub fn archive(&self) -> bool { + // self.archive + // } + + // /// Set the `archive` policy. + // /// + // /// If true, the download cleanr will ignore the episode + // /// and the corresponding media value will never be automaticly deleted. + // pub fn set_archive(&mut self, b: bool) { + // self.archive = b + // } + + // /// Get the `favorite` status of the `Episode`. + // pub fn favorite(&self) -> bool { + // self.favorite + // } + + // /// Set `favorite` status. + // pub fn set_favorite(&mut self, b: bool) { + // self.favorite = b + // } + + /// `Podcast` table foreign key. + pub fn podcast_id(&self) -> i32 { + self.podcast_id + } + + /// Sets the `played` value with the current `epoch` timestap and save it. + pub fn set_played_now(&mut self) -> Result<()> { + let epoch = Utc::now().timestamp() as i32; + self.set_played(Some(epoch)); + self.save()?; + Ok(()) + } + + /// Helper method to easily save/"sync" current state of self to the Database. + pub fn save(&self) -> Result { + use schema::episode::dsl::*; + + let db = connection(); + let tempdb = db.get()?; + + Ok(diesel::update(episode).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-downloader/src/downloader.rs b/hammond-downloader/src/downloader.rs index fafd726..4343e91 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::{Episode, Podcast}; +use hammond_data::{Episode, EpisodeWidgetQuery, Podcast}; use hammond_data::xdg_dirs::{DL_DIR, HAMMOND_CACHE}; // TODO: Replace path that are of type &str with std::path. @@ -106,7 +106,7 @@ pub fn get_download_folder(pd_title: &str) -> Result { } // TODO: Refactor -pub fn get_episode(ep: &mut Episode, download_folder: &str) -> Result<()> { +pub fn get_episode(ep: &mut EpisodeWidgetQuery, download_folder: &str) -> Result<()> { // Check if its alrdy downloaded if ep.local_uri().is_some() { if Path::new(ep.local_uri().unwrap()).exists() { diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 020341c..89dbf1e 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -5,10 +5,10 @@ use gtk::prelude::*; use open; use hammond_data::dbqueries; -use hammond_data::{Episode, Podcast}; -use hammond_downloader::downloader; +use hammond_data::{EpisodeWidgetQuery, Podcast}; use hammond_data::utils::*; use hammond_data::errors::*; +use hammond_downloader::downloader; use std::thread; use std::cell::RefCell; @@ -73,13 +73,16 @@ impl EpisodeWidget { } } - pub fn new_initialized(episode: &mut Episode, pd: &Podcast) -> EpisodeWidget { + pub fn new_initialized(episode: &mut EpisodeWidgetQuery, pd: &Podcast) -> EpisodeWidget { let widget = EpisodeWidget::new(); widget.init(episode, pd); widget } - fn init(&self, episode: &mut Episode, pd: &Podcast) { + // TODO: calculate lenght. + // TODO: wire the progress_bar to the downloader. + // TODO: wire the cancel button. + fn init(&self, episode: &mut EpisodeWidgetQuery, pd: &Podcast) { self.title.set_xalign(0.0); self.title.set_text(episode.title()); @@ -131,7 +134,7 @@ impl EpisodeWidget { // TODO: show notification when dl is finished. fn on_download_clicked( pd_title: &str, - ep: &mut Episode, + ep: &mut EpisodeWidgetQuery, download_bttn: >k::Button, play_bttn: >k::Button, del_bttn: >k::Button, @@ -225,7 +228,7 @@ fn receive() -> glib::Continue { } pub fn episodes_listbox(pd: &Podcast) -> Result { - let episodes = dbqueries::get_pd_episodes(pd)?; + let episodes = dbqueries::get_pd_episodeswidgets(pd)?; let list = gtk::ListBox::new(); episodes.into_iter().for_each(|mut ep| { From afdb79b71258cab8c97e08e3c7df3aff7702684f Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Dec 2017 13:38:18 +0200 Subject: [PATCH 3/7] Set the progress bar into activity mode. --- hammond-data/src/models/queryables.rs | 4 +++- hammond-gtk/resources/gtk/episode_widget.ui | 1 - hammond-gtk/src/widgets/episode.rs | 8 +++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/hammond-data/src/models/queryables.rs b/hammond-data/src/models/queryables.rs index e8ae722..4290979 100644 --- a/hammond-data/src/models/queryables.rs +++ b/hammond-data/src/models/queryables.rs @@ -314,7 +314,9 @@ impl EpisodeWidgetQuery { let db = connection(); let tempdb = db.get()?; - Ok(diesel::update(episode).set(self).execute(&*tempdb)?) + Ok(diesel::update(episode.filter(rowid.eq(self.rowid))) + .set(self) + .execute(&*tempdb)?) } } diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index 350caea..a6b5c6b 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -53,7 +53,6 @@ - True False document-save-symbolic diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 89dbf1e..d6d8a1e 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -85,6 +85,13 @@ impl EpisodeWidget { fn init(&self, episode: &mut EpisodeWidgetQuery, pd: &Podcast) { self.title.set_xalign(0.0); self.title.set_text(episode.title()); + self.progress.set_pulse_step(0.1); + + let progress = self.progress.clone(); + timeout_add(200, move || { + progress.pulse(); + glib::Continue(true) + }); // Show or hide the play/delete/download buttons upon widget initialization. let local_uri = episode.local_uri(); @@ -232,7 +239,6 @@ pub fn episodes_listbox(pd: &Podcast) -> Result { let list = gtk::ListBox::new(); episodes.into_iter().for_each(|mut ep| { - // let w = epidose_widget(&mut ep, pd.title()); let widget = EpisodeWidget::new_initialized(&mut ep, pd); list.add(&widget.container) }); From a7208b0c61129fcbfb93a617f7903b85f4cdf5a4 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Dec 2017 14:46:41 +0200 Subject: [PATCH 4/7] Set EpisodeWidget button valignment to center instead of fill. --- hammond-gtk/resources/gtk/episode_widget.ui | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index a6b5c6b..7cc0807 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -81,16 +81,17 @@ True False + 5 Cancel True True + center False True - 5 end 0 @@ -101,6 +102,7 @@ True True end + center True @@ -112,7 +114,6 @@ False False - 5 end 1 @@ -123,6 +124,7 @@ True True end + center True @@ -135,7 +137,6 @@ False False - 5 end 2 @@ -144,7 +145,7 @@ True True - end + center True @@ -156,7 +157,6 @@ False False - 5 end 3 From e3b540170a28630658e7818a48c87f761c8c7670 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Dec 2017 15:32:21 +0200 Subject: [PATCH 5/7] Add file size indication based on rss item length. --- hammond-gtk/resources/gtk/episode_widget.ui | 14 +++++++++++++- hammond-gtk/src/widgets/episode.rs | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index 7cc0807..7e6106d 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -51,6 +51,18 @@ 0 + + + True + False + 42 mb + + + False + True + 1 + + False @@ -59,7 +71,7 @@ False True - 1 + 2 diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index d6d8a1e..746ea53 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -39,6 +39,7 @@ struct EpisodeWidget { cancel: gtk::Button, title: gtk::Label, duration: gtk::Label, + size: gtk::Label, progress: gtk::ProgressBar, an_indicator: gtk::Image, } @@ -59,6 +60,7 @@ impl EpisodeWidget { let title: gtk::Label = builder.get_object("title_label").unwrap(); let duration: gtk::Label = builder.get_object("duration_label").unwrap(); + let size: gtk::Label = builder.get_object("size_label").unwrap(); EpisodeWidget { container, @@ -70,6 +72,7 @@ impl EpisodeWidget { delete, title, duration, + size, } } @@ -83,6 +86,7 @@ impl EpisodeWidget { // TODO: wire the progress_bar to the downloader. // TODO: wire the cancel button. fn init(&self, episode: &mut EpisodeWidgetQuery, pd: &Podcast) { + self.duration.hide(); self.title.set_xalign(0.0); self.title.set_text(episode.title()); self.progress.set_pulse_step(0.1); @@ -93,6 +97,11 @@ impl EpisodeWidget { glib::Continue(true) }); + if let Some(size) = episode.length() { + let megabytes: f32 = size as f32 / 1024.0 / 1024.0; // episode.length represents bytes + self.size.set_text(&format!("{:.1} mb", megabytes)) + }; + // Show or hide the play/delete/download buttons upon widget initialization. let local_uri = episode.local_uri(); if local_uri.is_some() && Path::new(local_uri.unwrap()).exists() { From 0ac78fcff1cc81a5a4cfe81c7edb977bb5637ef5 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Dec 2017 18:03:37 +0200 Subject: [PATCH 6/7] Added date label into EpisodeWidget. --- Cargo.lock | 1 + hammond-data/src/parser.rs | 1 + hammond-gtk/Cargo.toml | 1 + hammond-gtk/resources/gtk/episode_widget.ui | 82 +++++++++++++++++---- hammond-gtk/src/main.rs | 1 + hammond-gtk/src/widgets/episode.rs | 22 ++++-- 6 files changed, 86 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95d51b2..500afb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,6 +601,7 @@ dependencies = [ name = "hammond-gtk" version = "0.1.0" dependencies = [ + "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "diesel 0.99.0 (registry+https://github.com/rust-lang/crates.io-index)", "dissolve 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "gdk 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/hammond-data/src/parser.rs b/hammond-data/src/parser.rs index 393ac54..566b06f 100644 --- a/hammond-data/src/parser.rs +++ b/hammond-data/src/parser.rs @@ -33,6 +33,7 @@ pub(crate) fn new_podcast(chan: &Channel, source_id: i32) -> NewPodcast { } /// Parses an `rss::Item` into a `NewEpisode` Struct. +// TODO: parse itunes duration extension. pub(crate) fn new_episode(item: &Item, parent_id: i32) -> Result { if item.title().is_none() { bail!("No title specified for the item.") diff --git a/hammond-gtk/Cargo.toml b/hammond-gtk/Cargo.toml index 810cbbb..b1cf01e 100644 --- a/hammond-gtk/Cargo.toml +++ b/hammond-gtk/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" workspace = "../" [dependencies] +chrono = "0.4.0" dissolve = "0.2.2" gdk = "0.7.0" gdk-pixbuf = "0.3.0" diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index 7e6106d..2e7558e 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -18,15 +18,27 @@ vertical 5 - + True False - start - Episode Title - True - True - end - 1 + + + True + False + start + Episode Title + True + True + end + False + 1 + + + False + True + 0 + + True @@ -40,10 +52,11 @@ False 5 - + True False - 42 min + 1970/01/01 + False False @@ -52,10 +65,10 @@ - - True + False - 42 mb + True + · False @@ -64,9 +77,11 @@ - + False - document-save-symbolic + True + 42 min + False False @@ -74,6 +89,44 @@ 2 + + + True + False + · + + + False + True + 3 + + + + + True + False + 42 MB + False + + + False + True + 4 + + + + + False + True + 12 MB / 42 MB + False + + + False + True + 5 + + True @@ -185,7 +238,6 @@ True True - 5 0 diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index 0c594b9..5b1bac6 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -6,6 +6,7 @@ extern crate gio; extern crate glib; extern crate gtk; +extern crate chrono; extern crate diesel; extern crate dissolve; extern crate hammond_data; diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 746ea53..54d2620 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -1,6 +1,8 @@ use glib; use gtk; + use gtk::prelude::*; +use chrono::prelude::*; use open; @@ -38,10 +40,11 @@ struct EpisodeWidget { download: gtk::Button, cancel: gtk::Button, title: gtk::Label, + date: gtk::Label, duration: gtk::Label, size: gtk::Label, progress: gtk::ProgressBar, - an_indicator: gtk::Image, + progress_label: gtk::Label, } impl EpisodeWidget { @@ -49,9 +52,7 @@ impl EpisodeWidget { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episode_widget.ui"); let container: gtk::Box = builder.get_object("episode_container").unwrap(); - let progress: gtk::ProgressBar = builder.get_object("progress_bar").unwrap(); - let an_indicator: gtk::Image = builder.get_object("an_indicator").unwrap(); let download: gtk::Button = builder.get_object("download_button").unwrap(); let play: gtk::Button = builder.get_object("play_button").unwrap(); @@ -59,13 +60,14 @@ impl EpisodeWidget { let cancel: gtk::Button = builder.get_object("cancel_button").unwrap(); let title: gtk::Label = builder.get_object("title_label").unwrap(); + let date: gtk::Label = builder.get_object("date_label").unwrap(); let duration: gtk::Label = builder.get_object("duration_label").unwrap(); let size: gtk::Label = builder.get_object("size_label").unwrap(); + let progress_label: gtk::Label = builder.get_object("progress_label").unwrap(); EpisodeWidget { container, progress, - an_indicator, download, play, cancel, @@ -73,6 +75,8 @@ impl EpisodeWidget { title, duration, size, + date, + progress_label, } } @@ -86,7 +90,6 @@ impl EpisodeWidget { // TODO: wire the progress_bar to the downloader. // TODO: wire the cancel button. fn init(&self, episode: &mut EpisodeWidgetQuery, pd: &Podcast) { - self.duration.hide(); self.title.set_xalign(0.0); self.title.set_text(episode.title()); self.progress.set_pulse_step(0.1); @@ -98,10 +101,15 @@ impl EpisodeWidget { }); if let Some(size) = episode.length() { - let megabytes: f32 = size as f32 / 1024.0 / 1024.0; // episode.length represents bytes - self.size.set_text(&format!("{:.1} mb", megabytes)) + let megabytes = size / 1024 / 1024; // episode.length represents bytes + self.size.set_text(&format!("{} MB", megabytes)) }; + let date = Utc.timestamp(i64::from(episode.epoch()), 0) + .format("%b %e") + .to_string(); + self.date.set_text(&date); + // Show or hide the play/delete/download buttons upon widget initialization. let local_uri = episode.local_uri(); if local_uri.is_some() && Path::new(local_uri.unwrap()).exists() { From 0c1e759a45aa0ebe38c6420e1283c842ddf7f5cd Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Dec 2017 12:27:30 +0200 Subject: [PATCH 7/7] Dim out secondary label of EpisodeWidget. --- hammond-gtk/resources/gtk/episode_widget.ui | 12 +++++++++--- hammond-gtk/src/widgets/episode.rs | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index 2e7558e..4669fd1 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -6,6 +6,7 @@ True False vertical + 5 True @@ -27,7 +28,6 @@ False start Episode Title - True True end False @@ -56,6 +56,7 @@ True False 1970/01/01 + True False @@ -65,10 +66,11 @@ - + False True · + False False @@ -81,6 +83,7 @@ False True 42 min + True False @@ -90,10 +93,11 @@ - + True False · + False False @@ -106,6 +110,7 @@ True False 42 MB + True False @@ -119,6 +124,7 @@ False True 12 MB / 42 MB + True False diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 54d2620..6047602 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -65,6 +65,22 @@ impl EpisodeWidget { let size: gtk::Label = builder.get_object("size_label").unwrap(); let progress_label: gtk::Label = builder.get_object("progress_label").unwrap(); + let sep1: gtk::Label = builder.get_object("seperator1").unwrap(); + let sep2: gtk::Label = builder.get_object("seperator2").unwrap(); + + // Dim(grey out) the labels. + // If it's possible through glade, feel free to open a PR. + duration + .get_style_context() + .map(|c| c.add_class("dim-label")); + progress_label + .get_style_context() + .map(|c| c.add_class("dim-label")); + date.get_style_context().map(|c| c.add_class("dim-label")); + size.get_style_context().map(|c| c.add_class("dim-label")); + sep1.get_style_context().map(|c| c.add_class("dim-label")); + sep2.get_style_context().map(|c| c.add_class("dim-label")); + EpisodeWidget { container, progress, @@ -92,7 +108,6 @@ impl EpisodeWidget { fn init(&self, episode: &mut EpisodeWidgetQuery, pd: &Podcast) { self.title.set_xalign(0.0); self.title.set_text(episode.title()); - self.progress.set_pulse_step(0.1); let progress = self.progress.clone(); timeout_add(200, move || {