diff --git a/Cargo.lock b/Cargo.lock
index 6a119f8..9a789da 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -741,7 +741,6 @@ dependencies = [
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
"send-cell 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1799,11 +1798,6 @@ name = "take"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-[[package]]
-name = "take_mut"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
[[package]]
name = "tempdir"
version = "0.3.7"
@@ -2403,7 +2397,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
-"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
"checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561"
diff --git a/hammond-gtk/Cargo.toml b/hammond-gtk/Cargo.toml
index 316ebd8..d8cd1bd 100644
--- a/hammond-gtk/Cargo.toml
+++ b/hammond-gtk/Cargo.toml
@@ -21,7 +21,6 @@ send-cell = "0.1.3"
url = "1.7.0"
failure = "0.1.1"
failure_derive = "0.1.1"
-take_mut = "0.2.2"
regex = "1.0.0"
reqwest = "0.8.5"
serde_json = "1.0.17"
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
- 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/main.rs b/hammond-gtk/src/main.rs
index 5e11002..083f19c 100644
--- a/hammond-gtk/src/main.rs
+++ b/hammond-gtk/src/main.rs
@@ -38,7 +38,6 @@ extern crate regex;
extern crate reqwest;
extern crate send_cell;
extern crate serde_json;
-extern crate take_mut;
extern crate url;
use log::Level;
diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs
index 5e2bf4e..582839d 100644
--- a/hammond-gtk/src/widgets/episode.rs
+++ b/hammond-gtk/src/widgets/episode.rs
@@ -2,11 +2,12 @@ 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;
use hammond_data::dbqueries;
use hammond_data::utils::get_download_folder;
@@ -14,223 +15,417 @@ use hammond_data::EpisodeWidgetQuery;
use app::Action;
use manager;
-use widgets::episode_states::*;
-use std::cell::RefCell;
-use std::ops::DerefMut;
use std::path::Path;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
-#[derive(Debug)]
+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,
+ })
+ };
+}
+
+#[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)]
+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)]
+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) {
+ // 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 {
fn default() -> Self {
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 container = builder.get_object("episode_container").unwrap();
+ let progressbar = builder.get_object("progress_bar").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 buttons_container = builder.get_object("button_box").unwrap();
+ let download = builder.get_object("download_button").unwrap();
+ let play = builder.get_object("play_button").unwrap();
+ let cancel = 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 local_size: gtk::Label = builder.get_object("local_size").unwrap();
- let total_size: gtk::Label = builder.get_object("total_size").unwrap();
+ let info_container = builder.get_object("info_container").unwrap();
+ let title = builder.get_object("title_label").unwrap();
+ let date = builder.get_object("date_label").unwrap();
+ let duration = builder.get_object("duration_label").unwrap();
+ let local_size = builder.get_object("local_size").unwrap();
+ let total_size = builder.get_object("total_size").unwrap();
- 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 separator1 = builder.get_object("separator1").unwrap();
+ let separator2 = builder.get_object("separator2").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 = 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)
+ .map_err(|err| error!("Error: {}", err))
+ .ok();
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();
+
+ self.info.total_size.show();
+ self.buttons.play.show();
+ }
+
+ // 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();
+ }
+
+ fn update_progress(&self, local_size: &str, fraction: f64) {
+ self.info.local_size.set_text(local_size);
+ self.progressbar.set_fraction(fraction);
+ }
+
+ /// Change the state of the `EpisodeWidget`.
+ ///
+ /// Function Flowchart:
+ ///
+ /// ------------------- --------------
+ /// | Is the Episode | YES | State: |
+ /// | currently being | ----> | InProgress |
+ /// | downloaded? | | |
+ /// ------------------- --------------
+ /// |
+ /// | NO
+ /// |
+ /// \_/
+ /// ------------------- --------------
+ /// | is the episode | YES | State: |
+ /// | downloaded | ----> | Playable |
+ /// | already? | | |
+ /// ------------------- --------------
+ /// |
+ /// | NO
+ /// |
+ /// \_/
+ /// -------------------
+ /// | State: |
+ /// | ToDownload |
+ /// -------------------
+ 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 = move || -> Result