From cfe79a73d6241b4be7a734a3189087b5813fcd16 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 1 Jun 2018 16:19:33 +0300 Subject: [PATCH 01/12] EpisodeWidget: Initial refactor. State machines were a fun experiement but a nightmare to deal with after the fact. This is the first steps for a refactor in a tradition style with the goal to ultimatly making it easy to and port to relm. --- hammond-gtk/resources/gtk/episode_widget.ui | 49 +-- hammond-gtk/src/widgets/episode.rs | 378 ++++++++++++----- hammond-gtk/src/widgets/episode_states.rs | 440 +------------------- hammond-gtk/src/widgets/home_view.rs | 3 +- hammond-gtk/src/widgets/show.rs | 2 +- 5 files changed, 304 insertions(+), 568 deletions(-) diff --git a/hammond-gtk/resources/gtk/episode_widget.ui b/hammond-gtk/resources/gtk/episode_widget.ui index 4b55fc3..0323169 100644 --- a/hammond-gtk/resources/gtk/episode_widget.ui +++ b/hammond-gtk/resources/gtk/episode_widget.ui @@ -41,37 +41,26 @@ Tobias Bernard True False - + True False vertical 6 - + True False - - - True - False - Episode Title - end - 55 - True - False - 1 - 0 - - - False - False - 0 - - + Episode Title + end + 55 + True + False + 1 + 0 False - False + True 0 @@ -213,16 +202,16 @@ Tobias Bernard - + True False - 6 + center Cancel True False - True + center center @@ -237,8 +226,8 @@ Tobias Bernard True True True - True Download this episode + center center True @@ -252,8 +241,8 @@ Tobias Bernard False False - end - 2 + 1 + True @@ -262,6 +251,7 @@ Tobias Bernard True True Play this episode + center center @@ -274,15 +264,14 @@ Tobias Bernard False False - end - 3 + 2 + True False False - 6 end 1 diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 5e2bf4e..ed31285 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -1,10 +1,14 @@ +#![allow(warnings)] + use glib; use gtk; use gtk::prelude::*; +use chrono; +use chrono::prelude::*; use crossbeam_channel::Sender; use failure::Error; -use humansize::FileSize; +use humansize::{file_size_opts as size_opts, FileSize}; use open; use take_mut; @@ -22,13 +26,141 @@ use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct EpisodeWidget { pub container: gtk::Box, - date: DateMachine, - duration: DurationMachine, - title: Rc>, - media: Rc>, + info: InfoLabels, + buttons: Buttons, + progressbar: gtk::ProgressBar, +} + +#[derive(Clone, Debug)] +pub struct InfoLabels { + container: gtk::Box, + title: gtk::Label, + date: gtk::Label, + separator1: gtk::Label, + duration: gtk::Label, + separator2: gtk::Label, + local_size: gtk::Label, + size_separator: gtk::Label, + total_size: gtk::Label, +} + +#[derive(Clone, Debug)] +pub struct Buttons { + container: gtk::ButtonBox, + play: gtk::Button, + download: gtk::Button, + cancel: gtk::Button, +} + +impl InfoLabels { + fn init(&self, episode: &EpisodeWidgetQuery) { + // Set the title label state. + self.set_title(episode); + + // Set the date label. + self.set_date(episode.epoch()); + + // Set the duaration label. + self.set_duration(episode.duration()); + + // Set the total_size label. + self.set_size(episode.length()) + } + + fn set_title(&self, episode: &EpisodeWidgetQuery) { + self.title.set_text(episode.title()); + + if episode.played().is_some() { + self.title + .get_style_context() + .map(|c| c.add_class("dim-label")); + } else { + self.title + .get_style_context() + .map(|c| c.remove_class("dim-label")); + } + } + + // Set the date label of the episode widget. + fn set_date(&self, epoch: i32) { + lazy_static! { + static ref NOW: DateTime = Utc::now(); + }; + + let ts = Utc.timestamp(i64::from(epoch), 0); + + // If the episode is from a different year, print year as well + if NOW.year() != ts.year() { + self.date.set_text(ts.format("%e %b %Y").to_string().trim()); + // Else omit the year from the label + } else { + self.date.set_text(ts.format("%e %b").to_string().trim()); + } + } + + // Set the duration label of the episode widget. + fn set_duration(&self, seconds: Option) { + // If lenght is provided + if let Some(s) = seconds { + // Convert seconds to minutes + let minutes = chrono::Duration::seconds(s.into()).num_minutes(); + // If the lenght is 1 or more minutes + if minutes != 0 { + // Set the label and show them. + self.duration.set_text(&format!("{} min", minutes)); + self.duration.show(); + self.separator1.show(); + return; + } + } + + // Else hide the labels + self.separator1.hide(); + self.duration.hide(); + } + + // Set the size label of the episode widget. + fn set_size(&self, bytes: Option) { + lazy_static! { + static ref SIZE_OPTS: Arc = { + // Declare a custom humansize option struct + // See: https://docs.rs/humansize/1.0.2/humansize/file_size_opts/struct.FileSizeOpts.html + Arc::new(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: "", + allow_negative: false, + }) + }; + } + + // Convert the bytes to a String label + let size = || -> Option { + let s = bytes?; + if s == 0 { + return None; + } + + s.file_size(SIZE_OPTS.clone()).ok() + }(); + + if let Some(s) = size { + self.total_size.set_text(&s); + self.total_size.show(); + self.separator2.show(); + } else { + self.total_size.hide(); + self.separator2.hide(); + } + } } impl Default for EpisodeWidget { @@ -36,12 +168,14 @@ impl Default for 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 progressbar: gtk::ProgressBar = builder.get_object("progress_bar").unwrap(); + let buttons_container: gtk::ButtonBox = builder.get_object("button_box").unwrap(); let download: gtk::Button = builder.get_object("download_button").unwrap(); let play: gtk::Button = builder.get_object("play_button").unwrap(); let cancel: gtk::Button = builder.get_object("cancel_button").unwrap(); + let info_container: gtk::Box = builder.get_object("info_container").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(); @@ -50,112 +184,165 @@ impl Default for EpisodeWidget { let separator1: gtk::Label = builder.get_object("separator1").unwrap(); let separator2: gtk::Label = builder.get_object("separator2").unwrap(); - let prog_separator: gtk::Label = builder.get_object("prog_separator").unwrap(); - let date_machine = DateMachine::new(date, 0); - let dur_machine = DurationMachine::new(duration, separator1, None); - let title_machine = Rc::new(RefCell::new(TitleMachine::new(title, false))); - let media = MediaMachine::new( - play, - download, - progress, - cancel, - total_size, - local_size, - separator2, - prog_separator, - ); - let media_machine = Rc::new(RefCell::new(media)); + let size_separator: gtk::Label = builder.get_object("prog_separator").unwrap(); EpisodeWidget { + info: InfoLabels { + container: info_container, + title, + date, + separator1, + duration, + separator2, + local_size, + total_size, + size_separator, + }, + buttons: Buttons { + container: buttons_container, + play, + download, + cancel, + }, + progressbar, container, - title: title_machine, - duration: dur_machine, - date: date_machine, - media: media_machine, } } } impl EpisodeWidget { - pub fn new(episode: EpisodeWidgetQuery, sender: &Sender) -> EpisodeWidget { - let mut widget = EpisodeWidget::default(); - widget.init(episode, sender); + pub fn new(episode: EpisodeWidgetQuery, sender: &Sender) -> Rc { + let widget = Rc::new(Self::default()); + widget.info.init(&episode); + Self::determine_buttons_state(&widget, &episode, sender); widget } - fn init(&mut self, episode: EpisodeWidgetQuery, sender: &Sender) { - // Set the date label. - self.set_date(episode.epoch()); + // fn init(widget: Rc, sender: &Sender) {} - // Set the title label state. - self.set_title(&episode); + // InProgress State: + // * Show ProgressBar and Cancel Button. + // * Show `total_size`, `local_size` labels and `size_separator`. + // * Hide Download and Play Buttons + fn state_prog(&self) { + self.progressbar.show(); + self.buttons.cancel.show(); - // Set the duaration label. - self.set_duration(episode.duration()); + self.info.total_size.show(); + self.info.local_size.show(); + self.info.size_separator.show(); - // Determine what the state of the media widgets should be. - determine_media_state(&self.media, &episode) - .map_err(|err| error!("Error: {}", err)) - .map_err(|_| error!("Could not determine Media State")) - .ok(); - - let episode = Arc::new(Mutex::new(episode)); - self.connect_buttons(&episode, sender); + self.buttons.play.hide(); + self.buttons.download.hide(); } - fn connect_buttons(&self, episode: &Arc>, sender: &Sender) { - let title = self.title.clone(); - if let Ok(media) = self.media.try_borrow_mut() { - media.play_connect_clicked(clone!(episode, sender => move |_| { - if let Ok(mut ep) = episode.lock() { - on_play_bttn_clicked(&mut ep, &title, &sender) - .map_err(|err| error!("Error: {}", err)) - .ok(); - } - })); + // Playable State: + // * Hide ProgressBar and Cancel, Download Buttons. + // * Hide `local_size` labels and `size_separator`. + // * Show Play Button and `total_size` label + fn state_playable(&self) { + self.progressbar.hide(); + self.buttons.cancel.hide(); + self.buttons.download.hide(); + self.info.local_size.hide(); + self.info.size_separator.hide(); - let media_machine = self.media.clone(); - media.download_connect_clicked(clone!(media_machine, episode, sender => move |dl| { - // Make the button insensitive so it won't be pressed twice - dl.set_sensitive(false); - if let Ok(ep) = episode.lock() { - on_download_clicked(&ep, &sender) - .and_then(|_| { - info!("Donwload started succesfully."); - determine_media_state(&media_machine, &ep) - }) - .map_err(|err| error!("Error: {}", err)) - .map_err(|_| error!("Could not determine Media State")) - .ok(); - } + self.info.total_size.show(); + self.buttons.play.show(); + } - // Restore sensitivity after operations above complete - dl.set_sensitive(true); - })); + // ToDownload State: + // * Hide ProgressBar and Cancel, Play Buttons. + // * Hide `local_size` labels and `size_separator`. + // * Show Download Button + // * Determine `total_size` label state (Comes from `episode.lenght`). + fn state_download(&self) { + self.progressbar.hide(); + self.buttons.cancel.hide(); + self.buttons.play.hide(); + + self.info.local_size.hide(); + self.info.size_separator.hide(); + + self.buttons.download.show(); + + // FIXME? + // self.info.set_size(size); + } + + fn determine_buttons_state( + widget: &Rc, + episode: &EpisodeWidgetQuery, + sender: &Sender, + ) -> Result<(), Error> { + // Reset the buttons state no matter the glade file. + // This is just to make it easier to port to relm in the future. + widget.buttons.cancel.hide(); + widget.buttons.play.hide(); + widget.buttons.download.hide(); + + // Check if the episode is being downloaded + let id = episode.rowid(); + let active_dl = || -> Result, Error> { + let m = manager::ACTIVE_DOWNLOADS + .read() + .map_err(|_| format_err!("Failed to get a lock on the mutex."))?; + + Ok(m.get(&id).cloned()) + }()?; + + if let Some(_dl) = active_dl { + // FIXME: Wire cancel button + // FIXME: Wire Total Size label + + widget.state_prog(); + return Ok(()); } + + if let Some(path) = episode.local_uri() { + // FIXME: Wire play button? + widget.state_playable(); + return Ok(()); + } + + widget.state_download(); + Ok(()) } - /// Determine the title state. - fn set_title(&mut self, episode: &EpisodeWidgetQuery) { - let mut machine = self.title.borrow_mut(); - machine.set_title(episode.title()); - take_mut::take(machine.deref_mut(), |title| { - title.determine_state(episode.played().is_some()) - }); - } + // fn connect_buttons( + // widget: Rc, + // episode: Arc>, + // sender: &Sender, + // ) { + // widget + // .buttons + // .play + // .connect_clicked(clone!(widget, episode, sender => move |_| { + // if let Ok(mut ep) = episode.lock() { + // on_play_bttn_clicked(&widget, &mut ep, &sender) + // .map_err(|err| error!("Error: {}", err)) + // .ok(); + // } + // })); - /// Set the date label depending on the current time. - fn set_date(&mut self, epoch: i32) { - let machine = &mut self.date; - take_mut::take(machine, |date| date.determine_state(i64::from(epoch))); - } + // widget + // .buttons + // .download + // .connect_clicked(clone!(widget, episode, sender => move |dl| { + // // Make the button insensitive so it won't be pressed twice + // dl.set_sensitive(false); + // if let Ok(ep) = episode.lock() { + // // FIXME: Change the widget state too + // on_download_clicked(&ep, &sender) + // .map_err(|err| error!("Error: {}", err)) + // .ok(); + // } - /// Set the duration label. - fn set_duration(&mut self, seconds: Option) { - let machine = &mut self.duration; - take_mut::take(machine, |duration| duration.determine_state(seconds)); - } + // // Restore sensitivity after operations above complete + // dl.set_sensitive(true); + // })); + // } } fn determine_media_state( @@ -247,20 +434,17 @@ fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: &Sender) -> Resu } fn on_play_bttn_clicked( + widget: &Rc, episode: &mut EpisodeWidgetQuery, - title: &Rc>, sender: &Sender, ) -> Result<(), Error> { open_uri(episode.rowid())?; episode.set_played_now()?; - let mut machine = title.try_borrow_mut()?; - take_mut::take(machine.deref_mut(), |title| { - title.determine_state(episode.played().is_some()) - }); - - sender.send(Action::RefreshEpisodesViewBGR)?; - Ok(()) + widget.info.set_title(&episode); + sender + .send(Action::RefreshEpisodesViewBGR) + .map_err(From::from) } fn open_uri(rowid: i32) -> Result<(), Error> { diff --git a/hammond-gtk/src/widgets/episode_states.rs b/hammond-gtk/src/widgets/episode_states.rs index a3e27c2..c3aabb0 100644 --- a/hammond-gtk/src/widgets/episode_states.rs +++ b/hammond-gtk/src/widgets/episode_states.rs @@ -5,11 +5,9 @@ // Wrap the types into Struct-tuples and imple deref so it won't be possible to pass // the wrong argument to the wrong position. -use chrono; use glib; use gtk; -use chrono::prelude::*; use gtk::prelude::*; use humansize::{file_size_opts as size_opts, FileSize}; @@ -31,13 +29,8 @@ lazy_static! { allow_negative: false, }) }; - - static ref NOW: DateTime = Utc::now(); } -#[derive(Debug, Clone)] -pub struct UnInitialized; - #[derive(Debug, Clone)] pub struct Shown; #[derive(Debug, Clone)] @@ -48,269 +41,6 @@ pub trait Visibility {} impl Visibility for Shown {} impl Visibility for Hidden {} -#[derive(Debug, Clone)] -pub struct Normal; -#[derive(Debug, Clone)] -pub struct GreyedOut; - -#[derive(Debug, Clone)] -pub struct Title { - title: gtk::Label, - state: S, -} - -impl Title { - #[allow(unused_must_use)] - // This does not need to be &mut since gtk-rs does not model ownership - // But I think it wouldn't hurt if we treat it as a Rust api. - fn set_title(&mut self, s: &str) { - self.title.set_text(s); - } -} - -impl Title { - fn new(title: gtk::Label) -> Self { - Title { - title, - state: Normal {}, - } - } -} - -impl From> for Title { - fn from(f: Title) -> Self { - f.title - .get_style_context() - .map(|c| c.add_class("dim-label")); - - Title { - title: f.title, - state: GreyedOut {}, - } - } -} - -impl From> for Title { - fn from(f: Title) -> Self { - f.title - .get_style_context() - .map(|c| c.remove_class("dim-label")); - - Title { - title: f.title, - state: Normal {}, - } - } -} - -#[derive(Debug, Clone)] -pub enum TitleMachine { - Normal(Title), - GreyedOut(Title), -} - -impl TitleMachine { - pub fn new(label: gtk::Label, is_played: bool) -> Self { - let m = TitleMachine::Normal(Title::::new(label)); - m.determine_state(is_played) - } - - pub fn determine_state(self, is_played: bool) -> Self { - use self::TitleMachine::*; - - match (self, is_played) { - (title @ Normal(_), false) => title, - (title @ GreyedOut(_), true) => title, - (Normal(val), true) => GreyedOut(val.into()), - (GreyedOut(val), false) => Normal(val.into()), - } - } - - pub fn set_title(&mut self, s: &str) { - use self::TitleMachine::*; - - match *self { - Normal(ref mut val) => val.set_title(s), - GreyedOut(ref mut val) => val.set_title(s), - } - } -} - -#[derive(Debug, Clone)] -pub struct Usual; -#[derive(Debug, Clone)] -pub struct YearShown; - -#[derive(Debug, Clone)] -pub struct Date { - date: gtk::Label, - epoch: i64, - state: S, -} - -impl Date { - fn into_usual(self, epoch: i64) -> Date { - let ts = Utc.timestamp(epoch, 0); - self.date.set_text(ts.format("%e %b").to_string().trim()); - - Date { - date: self.date, - epoch: self.epoch, - state: Usual {}, - } - } - - fn into_year_shown(self, epoch: i64) -> Date { - let ts = Utc.timestamp(epoch, 0); - self.date.set_text(ts.format("%e %b %Y").to_string().trim()); - - Date { - date: self.date, - epoch: self.epoch, - state: YearShown {}, - } - } -} - -impl Date { - fn new(date: gtk::Label, epoch: i64) -> Self { - let ts = Utc.timestamp(epoch, 0); - date.set_text(ts.format("%e %b %Y").to_string().trim()); - - Date { - date, - epoch, - state: UnInitialized {}, - } - } -} - -#[derive(Debug, Clone)] -pub enum DateMachine { - UnInitialized(Date), - Usual(Date), - WithYear(Date), -} - -impl DateMachine { - pub fn new(label: gtk::Label, epoch: i64) -> Self { - let m = DateMachine::UnInitialized(Date::::new(label, epoch)); - m.determine_state(epoch) - } - - pub fn determine_state(self, epoch: i64) -> Self { - use self::DateMachine::*; - - let ts = Utc.timestamp(epoch, 0); - let is_old = NOW.year() != ts.year(); - - match (self, is_old) { - // Into Usual - (Usual(val), false) => Usual(val.into_usual(epoch)), - (WithYear(val), false) => Usual(val.into_usual(epoch)), - (UnInitialized(val), false) => Usual(val.into_usual(epoch)), - - // Into Year Shown - (Usual(val), true) => WithYear(val.into_year_shown(epoch)), - (WithYear(val), true) => WithYear(val.into_year_shown(epoch)), - (UnInitialized(val), true) => WithYear(val.into_year_shown(epoch)), - } - } -} - -#[derive(Debug, Clone)] -pub struct Duration { - // TODO: make duration and separator diff types - duration: gtk::Label, - separator: gtk::Label, - state: S, -} - -impl Duration { - // This needs a better name. - // TODO: make me mut - fn set_duration(&self, minutes: i64) { - self.duration.set_text(&format!("{} min", minutes)); - } -} - -impl Duration { - fn new(duration: gtk::Label, separator: gtk::Label) -> Self { - duration.hide(); - separator.hide(); - - Duration { - duration, - separator, - state: Hidden {}, - } - } -} - -impl From> for Duration { - fn from(f: Duration) -> Self { - f.duration.show(); - f.separator.show(); - - Duration { - duration: f.duration, - separator: f.separator, - state: Shown {}, - } - } -} - -impl From> for Duration { - fn from(f: Duration) -> Self { - f.duration.hide(); - f.separator.hide(); - - Duration { - duration: f.duration, - separator: f.separator, - state: Hidden {}, - } - } -} - -#[derive(Debug, Clone)] -pub enum DurationMachine { - Hidden(Duration), - Shown(Duration), -} - -impl DurationMachine { - pub fn new(duration: gtk::Label, separator: gtk::Label, seconds: Option) -> Self { - let m = DurationMachine::Hidden(Duration::::new(duration, separator)); - m.determine_state(seconds) - } - - pub fn determine_state(self, seconds: Option) -> Self { - match (self, seconds) { - (d @ DurationMachine::Hidden(_), None) => d, - (DurationMachine::Shown(val), None) => DurationMachine::Hidden(val.into()), - (DurationMachine::Hidden(val), Some(s)) => { - let minutes = chrono::Duration::seconds(s.into()).num_minutes(); - if minutes == 0 { - DurationMachine::Hidden(val) - } else { - val.set_duration(minutes); - DurationMachine::Shown(val.into()) - } - } - (DurationMachine::Shown(val), Some(s)) => { - let minutes = chrono::Duration::seconds(s.into()).num_minutes(); - if minutes == 0 { - DurationMachine::Hidden(val.into()) - } else { - val.set_duration(minutes); - DurationMachine::Shown(val) - } - } - } - } -} - #[derive(Debug, Clone)] pub struct Size { size: gtk::Label, @@ -356,19 +86,6 @@ impl Size { } } -impl Size { - fn new(size: gtk::Label, separator: gtk::Label) -> Self { - size.hide(); - separator.hide(); - - Size { - size, - separator, - state: UnInitialized {}, - } - } -} - // pub trait Playable {} // impl Playable for Download {} @@ -390,8 +107,7 @@ pub struct DownloadPlay { } impl DownloadPlay { - // https://play.rust-lang.org/?gist=1acffaf62743eeb85be1ae6ecf474784&version=stable - // It might be possible to make a generic definition with Specialization. + // https://play.rust-lang.org/?gist=1acffaf62743eeb85be1ae6ecf474784&version=stable // It might be possible to make a generic definition with Specialization. // https://github.com/rust-lang/rust/issues/31844 fn into_playable(self) -> DownloadPlay { self.play.show(); @@ -425,30 +141,6 @@ impl DownloadPlay { state: Hidden {}, } } - - fn download_connect_clicked( - &self, - f: F, - ) -> glib::SignalHandlerId { - self.download.connect_clicked(f) - } - - fn play_connect_clicked(&self, f: F) -> glib::SignalHandlerId { - self.play.connect_clicked(f) - } -} - -impl DownloadPlay { - fn new(play: gtk::Button, download: gtk::Button) -> Self { - play.hide(); - download.hide(); - - DownloadPlay { - play, - download, - state: UnInitialized {}, - } - } } #[derive(Debug, Clone)] @@ -504,28 +196,6 @@ impl Progress { } } -impl Progress { - fn new( - bar: gtk::ProgressBar, - cancel: gtk::Button, - local_size: gtk::Label, - prog_separator: gtk::Label, - ) -> Self { - bar.hide(); - cancel.hide(); - local_size.hide(); - prog_separator.hide(); - - Progress { - bar, - cancel, - local_size, - prog_separator, - state: UnInitialized {}, - } - } -} - #[derive(Debug, Clone)] pub struct Media { dl: DownloadPlay, @@ -607,24 +277,6 @@ impl Media { } } -impl Media { - fn into_progress(self, size: Option) -> InProgress { - if let Some(s) = size { - Media { - dl: self.dl.into_hidden(), - size: self.size.set_size(&s), - progress: self.progress.into_shown(), - } - } else { - Media { - dl: self.dl.into_hidden(), - size: self.size.set_size("Unkown"), - progress: self.progress.into_shown(), - } - } - } -} - impl InProgress { #[allow(unused_must_use)] // This does not need to be &mut since gtk-rs does not model ownership @@ -703,34 +355,6 @@ impl ButtonsState { } } - pub fn download_connect_clicked( - &self, - f: F, - ) -> glib::SignalHandlerId { - use self::ButtonsState::*; - - match *self { - New(ref val) => val.dl.download_connect_clicked(f), - NewWithoutSize(ref val) => val.dl.download_connect_clicked(f), - Playable(ref val) => val.dl.download_connect_clicked(f), - PlayableWithoutSize(ref val) => val.dl.download_connect_clicked(f), - } - } - - pub fn play_connect_clicked( - &self, - f: F, - ) -> glib::SignalHandlerId { - use self::ButtonsState::*; - - match *self { - New(ref val) => val.dl.play_connect_clicked(f), - NewWithoutSize(ref val) => val.dl.play_connect_clicked(f), - Playable(ref val) => val.dl.play_connect_clicked(f), - PlayableWithoutSize(ref val) => val.dl.play_connect_clicked(f), - } - } - fn cancel_connect_clicked(&self, f: F) -> glib::SignalHandlerId { use self::ButtonsState::*; @@ -745,56 +369,11 @@ impl ButtonsState { #[derive(Debug, Clone)] pub enum MediaMachine { - UnInitialized(Media), Initialized(ButtonsState), InProgress(Media), } impl MediaMachine { - #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] - pub fn new( - play: gtk::Button, - download: gtk::Button, - bar: gtk::ProgressBar, - cancel: gtk::Button, - total_size: gtk::Label, - local_size: gtk::Label, - separator: gtk::Label, - prog_separator: gtk::Label, - ) -> Self { - let dl = DownloadPlay::::new(play, download); - let progress = Progress::::new(bar, cancel, local_size, prog_separator); - let size = Size::::new(total_size, separator); - - MediaMachine::UnInitialized(Media { dl, progress, size }) - } - - pub fn download_connect_clicked( - &self, - f: F, - ) -> glib::SignalHandlerId { - use self::MediaMachine::*; - - match *self { - UnInitialized(ref val) => val.dl.download_connect_clicked(f), - Initialized(ref val) => val.download_connect_clicked(f), - InProgress(ref val) => val.dl.download_connect_clicked(f), - } - } - - pub fn play_connect_clicked( - &self, - f: F, - ) -> glib::SignalHandlerId { - use self::MediaMachine::*; - - match *self { - UnInitialized(ref val) => val.dl.play_connect_clicked(f), - Initialized(ref val) => val.play_connect_clicked(f), - InProgress(ref val) => val.dl.play_connect_clicked(f), - } - } - pub fn cancel_connect_clicked( &self, f: F, @@ -802,7 +381,6 @@ impl MediaMachine { use self::MediaMachine::*; match *self { - UnInitialized(ref val) => val.progress.cancel_connect_clicked(f), Initialized(ref val) => val.cancel_connect_clicked(f), InProgress(ref val) => val.progress.cancel_connect_clicked(f), } @@ -813,20 +391,6 @@ impl MediaMachine { use self::MediaMachine::*; match (self, size_helper(bytes), is_downloaded, is_active) { - (UnInitialized(m), s, _, true) => InProgress(m.into_progress(s)), - - // Into New - (UnInitialized(m), Some(s), false, false) => Initialized(New(m.into_new(&s))), - (UnInitialized(m), None, false, false) => { - Initialized(NewWithoutSize(m.into_new_without())) - } - - // Into Playable - (UnInitialized(m), Some(s), true, false) => Initialized(Playable(m.into_playable(&s))), - (UnInitialized(m), None, true, false) => { - Initialized(PlayableWithoutSize(m.into_playable_without())) - } - (Initialized(bttn), s, dl, false) => Initialized(bttn.determine_state(s, dl)), (Initialized(bttn), _, _, true) => InProgress(bttn.into_progress()), @@ -854,7 +418,6 @@ impl MediaMachine { (Initialized(bttn), s) => Initialized(bttn.set_size(s)), (InProgress(val), Some(s)) => InProgress(val.set_size(&s)), (n @ InProgress(_), None) => n, - (n @ UnInitialized(_), _) => n, } } @@ -863,7 +426,6 @@ impl MediaMachine { match *self { Initialized(_) => (), - UnInitialized(_) => (), InProgress(ref mut val) => val.update_progress(local_size, fraction), } } diff --git a/hammond-gtk/src/widgets/home_view.rs b/hammond-gtk/src/widgets/home_view.rs index 6c9687d..e11c3bc 100644 --- a/hammond-gtk/src/widgets/home_view.rs +++ b/hammond-gtk/src/widgets/home_view.rs @@ -175,6 +175,7 @@ fn split(now: &DateTime, epoch: i64) -> ListSplit { struct HomeEpisode { container: gtk::Box, image: gtk::Image, + // FIXME: Change it to `EpisodeWidget` instead of a `Box`? episode: gtk::Box, } @@ -207,7 +208,7 @@ impl HomeEpisode { let view = HomeEpisode { container, image, - episode: ep.container, + episode: ep.container.clone(), }; view.init(pid); diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs index 1e41fc8..bbd2d98 100644 --- a/hammond-gtk/src/widgets/show.rs +++ b/hammond-gtk/src/widgets/show.rs @@ -207,7 +207,7 @@ fn populate_listbox( let list = show_.episodes.clone(); let constructor = clone!(sender => move |ep| { - EpisodeWidget::new(ep, &sender).container + EpisodeWidget::new(ep, &sender).container.clone() }); let callback = clone!(pd, show_ => move || { From 86d06fa879ffcbb58438cd2afe37861dbebdf79c Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 1 Jun 2018 16:49:06 +0300 Subject: [PATCH 02/12] EpisodeWidget: Wire the play button again. --- hammond-gtk/src/widgets/episode.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index ed31285..5638a16 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -296,16 +296,33 @@ impl EpisodeWidget { // FIXME: Wire cancel button // FIXME: Wire Total Size label + // Change the widget layout/state widget.state_prog(); + return Ok(()); } if let Some(path) = episode.local_uri() { - // FIXME: Wire play button? + // Change the widget layout/state widget.state_playable(); + + // Wire the play button + let id = episode.rowid(); + widget + .buttons + .play + .connect_clicked(clone!(widget, sender => move |_| { + if let Ok(mut ep) = dbqueries::get_episode_widget_from_rowid(id) { + on_play_bttn_clicked(&widget, &mut ep, &sender) + .map_err(|err| error!("Error: {}", err)) + .ok(); + } + })); + return Ok(()); } + // FIXME: Wire the download button widget.state_download(); Ok(()) } @@ -315,16 +332,6 @@ impl EpisodeWidget { // episode: Arc>, // sender: &Sender, // ) { - // widget - // .buttons - // .play - // .connect_clicked(clone!(widget, episode, sender => move |_| { - // if let Ok(mut ep) = episode.lock() { - // on_play_bttn_clicked(&widget, &mut ep, &sender) - // .map_err(|err| error!("Error: {}", err)) - // .ok(); - // } - // })); // widget // .buttons From 1268fcf1cc5427abb086d8223a51677addad9992 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 1 Jun 2018 19:08:56 +0300 Subject: [PATCH 03/12] EpisodeWidget: Wire the download button. --- hammond-gtk/src/widgets/episode.rs | 49 ++++++++++++++---------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 5638a16..c1ba356 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -307,7 +307,6 @@ impl EpisodeWidget { widget.state_playable(); // Wire the play button - let id = episode.rowid(); widget .buttons .play @@ -322,34 +321,32 @@ impl EpisodeWidget { return Ok(()); } - // FIXME: Wire the download button + // Wire the download button + widget + .buttons + .download + .connect_clicked(clone!(widget, sender => move |dl| { + // Make the button insensitive so it won't be pressed twice + dl.set_sensitive(false); + if let Ok(ep) = dbqueries::get_episode_widget_from_rowid(id) { + on_download_clicked(&ep, &sender) + .and_then(|_| { + info!("Donwload started succesfully."); + Self::determine_buttons_state(&widget, &ep, &sender) + }) + .map_err(|err| error!("Error: {}", err)) + .ok(); + } + + // Restore sensitivity after operations above complete + dl.set_sensitive(true); + })); + + // Change the widget state into `ToDownload` widget.state_download(); + Ok(()) } - - // fn connect_buttons( - // widget: Rc, - // episode: Arc>, - // sender: &Sender, - // ) { - - // widget - // .buttons - // .download - // .connect_clicked(clone!(widget, episode, sender => move |dl| { - // // Make the button insensitive so it won't be pressed twice - // dl.set_sensitive(false); - // if let Ok(ep) = episode.lock() { - // // FIXME: Change the widget state too - // on_download_clicked(&ep, &sender) - // .map_err(|err| error!("Error: {}", err)) - // .ok(); - // } - - // // Restore sensitivity after operations above complete - // dl.set_sensitive(true); - // })); - // } } fn determine_media_state( From 9466c5ea102d07949b3a9d4214436f1fbae1c8ce Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 1 Jun 2018 21:24:28 +0300 Subject: [PATCH 04/12] EpisodeWidget: Wire the cancel button. --- hammond-gtk/src/widgets/episode.rs | 57 ++++++++++++++--------- hammond-gtk/src/widgets/episode_states.rs | 28 ----------- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index c1ba356..18a85dd 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -284,16 +284,46 @@ impl EpisodeWidget { // Check if the episode is being downloaded let id = episode.rowid(); - let active_dl = || -> Result, Error> { + let active_dl = move || -> Result, Error> { let m = manager::ACTIVE_DOWNLOADS .read() .map_err(|_| format_err!("Failed to get a lock on the mutex."))?; Ok(m.get(&id).cloned()) - }()?; + }; + + if let Some(prog) = active_dl()? { + // FIXME: Add again the callback ugly hack that makes things work somehow + + // Wire the cancel button + widget + .buttons + .cancel + .connect_clicked(clone!(prog, widget, sender => move |_| { + // Cancel the download + if let Ok(mut m) = prog.lock() { + m.cancel(); + } + + // Cancel is not instant so we have to wait a bit + timeout_add(50, clone!(widget, sender => move || { + if let Ok(thing) = active_dl() { + if thing.is_none() { + // Recalculate the widget state + dbqueries::get_episode_widget_from_rowid(id) + .map_err(From::from) + .and_then(|ep| Self::determine_buttons_state(&widget, &ep, &sender)) + .map_err(|err| error!("Error: {}", err)) + .ok(); + + return glib::Continue(false) + } + } + + glib::Continue(true) + })); + })); - if let Some(_dl) = active_dl { - // FIXME: Wire cancel button // FIXME: Wire Total Size label // Change the widget layout/state @@ -393,25 +423,6 @@ fn determine_media_state( }); gtk::timeout_add(250, callback); - lock.cancel_connect_clicked(clone!(prog, media_machine => move |_| { - if let Ok(mut m) = prog.lock() { - m.cancel(); - } - - if let Ok(mut lock) = media_machine.try_borrow_mut() { - if let Ok(episode) = dbqueries::get_episode_widget_from_rowid(id) { - take_mut::take(lock.deref_mut(), |media| { - media.determine_state( - episode.length(), - false, - episode.local_uri().is_some(), - ) - }); - } - } - })); - drop(lock); - // Setup a callback that will update the progress bar. update_progressbar_callback(&prog, &media_machine, id); diff --git a/hammond-gtk/src/widgets/episode_states.rs b/hammond-gtk/src/widgets/episode_states.rs index c3aabb0..dc32f2b 100644 --- a/hammond-gtk/src/widgets/episode_states.rs +++ b/hammond-gtk/src/widgets/episode_states.rs @@ -5,7 +5,6 @@ // Wrap the types into Struct-tuples and imple deref so it won't be possible to pass // the wrong argument to the wrong position. -use glib; use gtk; use gtk::prelude::*; @@ -190,10 +189,6 @@ impl Progress { self.local_size.set_text(local_size); self.bar.set_fraction(fraction); } - - fn cancel_connect_clicked(&self, f: F) -> glib::SignalHandlerId { - self.cancel.connect_clicked(f) - } } #[derive(Debug, Clone)] @@ -354,17 +349,6 @@ impl ButtonsState { (PlayableWithoutSize(m), Some(s)) => Playable(m.into_playable(&s)), } } - - fn cancel_connect_clicked(&self, f: F) -> glib::SignalHandlerId { - use self::ButtonsState::*; - - match *self { - New(ref val) => val.progress.cancel_connect_clicked(f), - NewWithoutSize(ref val) => val.progress.cancel_connect_clicked(f), - Playable(ref val) => val.progress.cancel_connect_clicked(f), - PlayableWithoutSize(ref val) => val.progress.cancel_connect_clicked(f), - } - } } #[derive(Debug, Clone)] @@ -374,18 +358,6 @@ pub enum MediaMachine { } impl MediaMachine { - pub fn cancel_connect_clicked( - &self, - f: F, - ) -> glib::SignalHandlerId { - use self::MediaMachine::*; - - match *self { - Initialized(ref val) => val.cancel_connect_clicked(f), - InProgress(ref val) => val.progress.cancel_connect_clicked(f), - } - } - pub fn determine_state(self, bytes: Option, is_active: bool, is_downloaded: bool) -> Self { use self::ButtonsState::*; use self::MediaMachine::*; From c303c697a90e5583d957fbef5ccb54efc132d58b Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 2 Jun 2018 19:25:25 +0300 Subject: [PATCH 05/12] EpisodeWidget: Wire the total_size label again. The size might be provided by the rss feed but not alwasy. Additionally it might be missleading so when a download starts we replace the label with the HTTP ContentLength header. --- hammond-gtk/src/widgets/episode.rs | 29 ++++++++----------- hammond-gtk/src/widgets/episode_states.rs | 34 ----------------------- 2 files changed, 11 insertions(+), 52 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 18a85dd..0fbb046 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -324,7 +324,12 @@ impl EpisodeWidget { })); })); - // FIXME: Wire Total Size label + // Setup a callback that will update the total_size label + // with the http ContentLength header number rather than + // relying to the RSS feed. + update_total_size_callback(&widget, &prog); + + // FIXME: Wire the progress_bar // Change the widget layout/state widget.state_prog(); @@ -425,11 +430,6 @@ fn determine_media_state( // Setup a callback that will update the progress bar. update_progressbar_callback(&prog, &media_machine, id); - - // Setup a callback that will update the total_size label - // with the http ContentLength header number rather than - // relying to the RSS feed. - update_total_size_callback(&prog, &media_machine); } Ok(()) @@ -541,19 +541,16 @@ fn progress_bar_helper( // with the http ContentLength header number rather than // relying to the RSS feed. #[inline] -fn update_total_size_callback( - prog: &Arc>, - media: &Rc>, -) { - let callback = clone!(prog, media => move || { - total_size_helper(&prog, &media).unwrap_or(glib::Continue(true)) +fn update_total_size_callback(widget: &Rc, prog: &Arc>) { + let callback = clone!(prog, widget => move || { + total_size_helper(&widget, &prog).unwrap_or(glib::Continue(true)) }); timeout_add(500, callback); } fn total_size_helper( + widget: &Rc, prog: &Arc>, - media: &Rc>, ) -> Result { // Get the total_bytes. let total_bytes = { @@ -566,11 +563,7 @@ fn total_size_helper( debug!("Total Size: {}", total_bytes); if total_bytes != 0 { // Update the total_size label - if let Ok(mut m) = media.try_borrow_mut() { - take_mut::take(m.deref_mut(), |machine| { - machine.set_size(Some(total_bytes as i32)) - }); - } + widget.info.set_size(Some(total_bytes as i32)); // Do not call again the callback Ok(glib::Continue(false)) diff --git a/hammond-gtk/src/widgets/episode_states.rs b/hammond-gtk/src/widgets/episode_states.rs index dc32f2b..9c4f1b9 100644 --- a/hammond-gtk/src/widgets/episode_states.rs +++ b/hammond-gtk/src/widgets/episode_states.rs @@ -203,14 +203,6 @@ type Playable = Media