Merge branch 'state-machines-experiements' into 'master'
EpisodeWidget as a state machine See merge request alatiera/Hammond!18
This commit is contained in:
commit
4535c3005d
@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
* Ability to mark all episodes of a Show as watched. [#47](https://gitlab.gnome.org/alatiera/Hammond/issues/47)
|
* Ability to mark all episodes of a Show as watched. [#47](https://gitlab.gnome.org/alatiera/Hammond/issues/47)
|
||||||
* Now you are able to subscribe to itunes™ podcasts by using the itunes link of the show.[#49](https://gitlab.gnome.org/alatiera/Hammond/issues/49)
|
* Now you are able to subscribe to itunes™ podcasts by using the itunes link of the show. [#49](https://gitlab.gnome.org/alatiera/Hammond/issues/49)
|
||||||
|
* EpisdeWidget has been reimplemented as a compile time state machine. [!18](https://gitlab.gnome.org/alatiera/Hammond/merge_requests/18)
|
||||||
|
|
||||||
## [0.3.0] - 2018-02-11
|
## [0.3.0] - 2018-02-11
|
||||||
|
|
||||||
|
|||||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -711,6 +711,7 @@ dependencies = [
|
|||||||
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"send-cell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"send-cell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"take_mut 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1654,6 +1655,11 @@ name = "take"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "take_mut"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempdir"
|
name = "tempdir"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -2108,6 +2114,7 @@ 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 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 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 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
||||||
|
"checksum take_mut 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50b910a1174df4aeb5738e8a0e7253883cf7801de40d094175a5a557e487f4c5"
|
||||||
"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e"
|
"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e"
|
||||||
"checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
|
"checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
|
||||||
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
|
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
|
||||||
|
|||||||
@ -22,6 +22,7 @@ send-cell = "0.1.2"
|
|||||||
url = "1.6.0"
|
url = "1.6.0"
|
||||||
failure = "0.1.1"
|
failure = "0.1.1"
|
||||||
failure_derive = "0.1.1"
|
failure_derive = "0.1.1"
|
||||||
|
take_mut = "0.2.0"
|
||||||
regex = "0.2.6"
|
regex = "0.2.6"
|
||||||
reqwest = "0.8.5"
|
reqwest = "0.8.5"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#![cfg_attr(feature = "cargo-clippy",
|
#![cfg_attr(feature = "cargo-clippy",
|
||||||
allow(clone_on_ref_ptr, needless_pass_by_value, useless_format))]
|
allow(clone_on_ref_ptr, needless_pass_by_value, useless_format, blacklisted_name,
|
||||||
|
match_same_arms))]
|
||||||
#![allow(unknown_lints)]
|
#![allow(unknown_lints)]
|
||||||
#![deny(unused_extern_crates, unused)]
|
#![deny(unused_extern_crates, unused)]
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ extern crate regex;
|
|||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
extern crate send_cell;
|
extern crate send_cell;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
extern crate take_mut;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
// extern crate rayon;
|
// extern crate rayon;
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
use glib;
|
use glib;
|
||||||
use gtk;
|
use gtk;
|
||||||
|
|
||||||
use chrono::prelude::*;
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
use chrono::Duration;
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use humansize::{file_size_opts as size_opts, FileSize};
|
use humansize::FileSize;
|
||||||
use open;
|
use open;
|
||||||
|
use take_mut;
|
||||||
|
|
||||||
use hammond_data::{EpisodeWidgetQuery, Podcast};
|
use hammond_data::{EpisodeWidgetQuery, Podcast};
|
||||||
use hammond_data::dbqueries;
|
use hammond_data::dbqueries;
|
||||||
@ -15,46 +13,22 @@ use hammond_data::utils::get_download_folder;
|
|||||||
|
|
||||||
use app::Action;
|
use app::Action;
|
||||||
use manager;
|
use manager;
|
||||||
|
use widgets::episode_states::*;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
lazy_static! {
|
#[derive(Debug)]
|
||||||
static ref SIZE_OPTS: Arc<size_opts::FileSizeOpts> = {
|
|
||||||
// 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,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
static ref NOW: DateTime<Utc> = Utc::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EpisodeWidget {
|
pub struct EpisodeWidget {
|
||||||
pub container: gtk::Box,
|
pub container: gtk::Box,
|
||||||
play: gtk::Button,
|
date: RefCell<DateMachine>,
|
||||||
download: gtk::Button,
|
duration: RefCell<DurationMachine>,
|
||||||
cancel: gtk::Button,
|
title: Rc<RefCell<TitleMachine>>,
|
||||||
title: gtk::Label,
|
media: Arc<Mutex<MediaMachine>>,
|
||||||
date: gtk::Label,
|
|
||||||
duration: gtk::Label,
|
|
||||||
progress: gtk::ProgressBar,
|
|
||||||
total_size: gtk::Label,
|
|
||||||
local_size: gtk::Label,
|
|
||||||
separator1: gtk::Label,
|
|
||||||
separator2: gtk::Label,
|
|
||||||
prog_separator: gtk::Label,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EpisodeWidget {
|
impl Default for EpisodeWidget {
|
||||||
@ -78,75 +52,72 @@ impl Default for EpisodeWidget {
|
|||||||
let separator2: gtk::Label = builder.get_object("separator2").unwrap();
|
let separator2: gtk::Label = builder.get_object("separator2").unwrap();
|
||||||
let prog_separator: gtk::Label = builder.get_object("prog_separator").unwrap();
|
let prog_separator: gtk::Label = builder.get_object("prog_separator").unwrap();
|
||||||
|
|
||||||
EpisodeWidget {
|
let date_machine = RefCell::new(DateMachine::new(date, 0));
|
||||||
container,
|
let dur_machine = RefCell::new(DurationMachine::new(duration, separator1, None));
|
||||||
progress,
|
let title_machine = Rc::new(RefCell::new(TitleMachine::new(title, false)));
|
||||||
download,
|
let media = MediaMachine::new(
|
||||||
play,
|
play,
|
||||||
|
download,
|
||||||
|
progress,
|
||||||
cancel,
|
cancel,
|
||||||
title,
|
|
||||||
duration,
|
|
||||||
date,
|
|
||||||
total_size,
|
total_size,
|
||||||
local_size,
|
local_size,
|
||||||
separator1,
|
|
||||||
separator2,
|
separator2,
|
||||||
prog_separator,
|
prog_separator,
|
||||||
|
);
|
||||||
|
let media_machine = Arc::new(Mutex::new(media));
|
||||||
|
|
||||||
|
EpisodeWidget {
|
||||||
|
container,
|
||||||
|
title: title_machine,
|
||||||
|
duration: dur_machine,
|
||||||
|
date: date_machine,
|
||||||
|
media: media_machine,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EpisodeWidget {
|
impl EpisodeWidget {
|
||||||
pub fn new(episode: EpisodeWidgetQuery, sender: Sender<Action>) -> EpisodeWidget {
|
pub fn new(episode: EpisodeWidgetQuery, sender: Sender<Action>) -> EpisodeWidget {
|
||||||
let widget = EpisodeWidget::default();
|
let mut widget = EpisodeWidget::default();
|
||||||
widget.init(episode, sender);
|
widget.init(episode, sender);
|
||||||
widget
|
widget
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&self, episode: EpisodeWidgetQuery, sender: Sender<Action>) {
|
fn init(&mut self, episode: EpisodeWidgetQuery, sender: Sender<Action>) {
|
||||||
WidgetExt::set_name(&self.container, &episode.rowid().to_string());
|
WidgetExt::set_name(&self.container, &episode.rowid().to_string());
|
||||||
|
|
||||||
|
// Set the date label.
|
||||||
|
self.set_date(episode.epoch());
|
||||||
|
|
||||||
// Set the title label state.
|
// Set the title label state.
|
||||||
self.set_title(&episode);
|
self.set_title(&episode);
|
||||||
|
|
||||||
// Set the duaration label.
|
// Set the duaration label.
|
||||||
self.set_duration(episode.duration());
|
self.set_duration(episode.duration());
|
||||||
|
|
||||||
// Set the date label.
|
// Determine what the state of the media widgets should be.
|
||||||
self.set_date(episode.epoch());
|
if let Err(err) = self.determine_media_state(&episode) {
|
||||||
|
error!("Something went wrong determining the Media State.");
|
||||||
// Show or hide the play/delete/download buttons upon widget initialization.
|
|
||||||
if let Err(err) = self.show_buttons(episode.local_uri()) {
|
|
||||||
debug!("Failed to determine play/download button state.");
|
|
||||||
debug!("Error: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the size label.
|
|
||||||
if let Err(err) = self.set_total_size(episode.length()) {
|
|
||||||
error!("Failed to set the Size label.");
|
|
||||||
error!("Error: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine what the state of the progress bar should be.
|
|
||||||
if let Err(err) = self.determine_progess_bar() {
|
|
||||||
error!("Something went wrong determining the ProgressBar State.");
|
|
||||||
error!("Error: {}", err);
|
error!("Error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let episode = Arc::new(Mutex::new(episode));
|
let episode = Arc::new(Mutex::new(episode));
|
||||||
|
self.connect_buttons(episode, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_buttons(&self, episode: Arc<Mutex<EpisodeWidgetQuery>>, sender: Sender<Action>) {
|
||||||
let title = self.title.clone();
|
let title = self.title.clone();
|
||||||
self.play
|
if let Ok(media) = self.media.lock() {
|
||||||
.connect_clicked(clone!(episode, sender => move |_| {
|
media.play_connect_clicked(clone!(episode, sender => move |_| {
|
||||||
if let Ok(mut ep) = episode.lock() {
|
if let Ok(mut ep) = episode.lock() {
|
||||||
if let Err(err) = on_play_bttn_clicked(&mut ep, &title, sender.clone()){
|
if let Err(err) = on_play_bttn_clicked(&mut ep, title.clone(), sender.clone()){
|
||||||
error!("Error: {}", err);
|
error!("Error: {}", err);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.download
|
media.download_connect_clicked(clone!(episode, sender => move |dl| {
|
||||||
.connect_clicked(clone!(episode, sender => move |dl| {
|
|
||||||
dl.set_sensitive(false);
|
dl.set_sensitive(false);
|
||||||
if let Ok(ep) = episode.lock() {
|
if let Ok(ep) = episode.lock() {
|
||||||
if let Err(err) = on_download_clicked(&ep, sender.clone()) {
|
if let Err(err) = on_download_clicked(&ep, sender.clone()) {
|
||||||
@ -156,74 +127,32 @@ impl EpisodeWidget {
|
|||||||
info!("Donwload started succesfully.");
|
info!("Donwload started succesfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
/// Show or hide the play/delete/download buttons upon widget
|
|
||||||
/// initialization.
|
|
||||||
fn show_buttons(&self, local_uri: Option<&str>) -> Result<(), Error> {
|
|
||||||
let path = local_uri.ok_or_else(|| format_err!("Path is None"))?;
|
|
||||||
if Path::new(path).exists() {
|
|
||||||
self.download.hide();
|
|
||||||
self.play.show();
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the title state.
|
/// Determine the title state.
|
||||||
fn set_title(&self, episode: &EpisodeWidgetQuery) {
|
fn set_title(&mut self, episode: &EpisodeWidgetQuery) {
|
||||||
self.title.set_text(episode.title());
|
let mut machine = self.title.borrow_mut();
|
||||||
|
machine.set_title(episode.title());
|
||||||
// Grey out the title if the episode is played.
|
take_mut::take(machine.deref_mut(), |title| {
|
||||||
if episode.played().is_some() {
|
title.determine_state(episode.played().is_some())
|
||||||
self.title
|
});
|
||||||
.get_style_context()
|
|
||||||
.map(|c| c.add_class("dim-label"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the date label depending on the current time.
|
/// Set the date label depending on the current time.
|
||||||
fn set_date(&self, epoch: i32) {
|
fn set_date(&mut self, epoch: i32) {
|
||||||
let date = Utc.timestamp(i64::from(epoch), 0);
|
let machine = self.date.get_mut();
|
||||||
if NOW.year() == date.year() {
|
take_mut::take(machine, |date| date.determine_state(i64::from(epoch)));
|
||||||
self.date.set_text(date.format("%e %b").to_string().trim());
|
|
||||||
} else {
|
|
||||||
self.date
|
|
||||||
.set_text(date.format("%e %b %Y").to_string().trim());
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the duration label.
|
/// Set the duration label.
|
||||||
fn set_duration(&self, seconds: Option<i32>) -> Option<()> {
|
fn set_duration(&mut self, seconds: Option<i32>) {
|
||||||
let minutes = Duration::seconds(seconds?.into()).num_minutes();
|
let machine = self.duration.get_mut();
|
||||||
if minutes == 0 {
|
take_mut::take(machine, |duration| duration.determine_state(seconds));
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.duration.set_text(&format!("{} min", minutes));
|
|
||||||
self.duration.show();
|
|
||||||
self.separator1.show();
|
|
||||||
Some(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the Episode label dependings on its size
|
fn determine_media_state(&self, episode: &EpisodeWidgetQuery) -> Result<(), Error> {
|
||||||
fn set_total_size(&self, bytes: Option<i32>) -> Result<(), Error> {
|
|
||||||
let size = bytes.ok_or_else(|| format_err!("Size is None."))?;
|
|
||||||
if size == 0 {
|
|
||||||
bail!("Size is 0.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = size.file_size(SIZE_OPTS.clone())
|
|
||||||
.map_err(|err| format_err!("{}", err))?;
|
|
||||||
self.total_size.set_text(&s);
|
|
||||||
self.total_size.show();
|
|
||||||
self.separator2.show();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: REFACTOR ME
|
|
||||||
// Something Something State-Machine?
|
|
||||||
fn determine_progess_bar(&self) -> Result<(), Error> {
|
|
||||||
let id = WidgetExt::get_name(&self.container)
|
let id = WidgetExt::get_name(&self.container)
|
||||||
.ok_or_else(|| format_err!("Failed to get widget Name"))?
|
.ok_or_else(|| format_err!("Failed to get widget Name"))?
|
||||||
.parse::<i32>()?;
|
.parse::<i32>()?;
|
||||||
@ -236,40 +165,34 @@ impl EpisodeWidget {
|
|||||||
Ok(m.get(&id).cloned())
|
Ok(m.get(&id).cloned())
|
||||||
}()?;
|
}()?;
|
||||||
|
|
||||||
if let Some(prog) = active_dl {
|
let mut lock = self.media.lock().map_err(|err| format_err!("{}", err))?;
|
||||||
// FIXME: Document me?
|
take_mut::take(lock.deref_mut(), |media| {
|
||||||
self.download.hide();
|
media.determine_state(
|
||||||
self.progress.show();
|
episode.length(),
|
||||||
self.local_size.show();
|
active_dl.is_some(),
|
||||||
self.total_size.show();
|
episode.local_uri().is_some(),
|
||||||
self.separator2.show();
|
)
|
||||||
self.prog_separator.show();
|
});
|
||||||
self.cancel.show();
|
|
||||||
|
|
||||||
let progress_bar = self.progress.clone();
|
// Show or hide the play/delete/download buttons upon widget initialization.
|
||||||
let total_size = self.total_size.clone();
|
if let Some(prog) = active_dl {
|
||||||
let local_size = self.local_size.clone();
|
lock.cancel_connect_clicked(prog.clone());
|
||||||
|
drop(lock);
|
||||||
|
|
||||||
// Setup a callback that will update the progress bar.
|
// Setup a callback that will update the progress bar.
|
||||||
update_progressbar_callback(prog.clone(), id, &progress_bar, &local_size);
|
update_progressbar_callback(prog.clone(), self.media.clone(), id);
|
||||||
|
|
||||||
// Setup a callback that will update the total_size label
|
// Setup a callback that will update the total_size label
|
||||||
// with the http ContentLength header number rather than
|
// with the http ContentLength header number rather than
|
||||||
// relying to the RSS feed.
|
// relying to the RSS feed.
|
||||||
update_total_size_callback(prog.clone(), &total_size);
|
update_total_size_callback(prog.clone(), self.media.clone());
|
||||||
|
|
||||||
self.cancel.connect_clicked(clone!(prog => move |cancel| {
|
|
||||||
if let Ok(mut m) = prog.lock() {
|
|
||||||
m.cancel();
|
|
||||||
cancel.set_sensitive(false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) -> Result<(), Error> {
|
fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) -> Result<(), Error> {
|
||||||
let pd = dbqueries::get_podcast_from_id(ep.podcast_id())?;
|
let pd = dbqueries::get_podcast_from_id(ep.podcast_id())?;
|
||||||
let download_fold = get_download_folder(&pd.title().to_owned())?;
|
let download_fold = get_download_folder(&pd.title().to_owned())?;
|
||||||
@ -284,18 +207,21 @@ fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) -> Resul
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn on_play_bttn_clicked(
|
fn on_play_bttn_clicked(
|
||||||
episode: &mut EpisodeWidgetQuery,
|
episode: &mut EpisodeWidgetQuery,
|
||||||
title: >k::Label,
|
title: Rc<RefCell<TitleMachine>>,
|
||||||
sender: Sender<Action>,
|
sender: Sender<Action>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
open_uri(episode.rowid())?;
|
open_uri(episode.rowid())?;
|
||||||
|
episode.set_played_now()?;
|
||||||
|
|
||||||
if episode.set_played_now().is_ok() {
|
let mut machine = title.try_borrow_mut()?;
|
||||||
title.get_style_context().map(|c| c.add_class("dim-label"));
|
take_mut::take(machine.deref_mut(), |title| {
|
||||||
sender.send(Action::RefreshEpisodesViewBGR)?;
|
title.determine_state(episode.played().is_some())
|
||||||
};
|
});
|
||||||
|
|
||||||
|
sender.send(Action::RefreshEpisodesViewBGR)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,28 +240,28 @@ fn open_uri(rowid: i32) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup a callback that will update the progress bar.
|
// Setup a callback that will update the progress bar.
|
||||||
|
#[inline]
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))]
|
#[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))]
|
||||||
fn update_progressbar_callback(
|
fn update_progressbar_callback(
|
||||||
prog: Arc<Mutex<manager::Progress>>,
|
prog: Arc<Mutex<manager::Progress>>,
|
||||||
|
media: Arc<Mutex<MediaMachine>>,
|
||||||
episode_rowid: i32,
|
episode_rowid: i32,
|
||||||
progress_bar: >k::ProgressBar,
|
|
||||||
local_size: >k::Label,
|
|
||||||
) {
|
) {
|
||||||
timeout_add(
|
timeout_add(
|
||||||
400,
|
400,
|
||||||
clone!(prog, progress_bar, progress_bar, local_size=> move || {
|
clone!(prog, media => move || {
|
||||||
progress_bar_helper(prog.clone(), episode_rowid, &progress_bar, &local_size)
|
progress_bar_helper(prog.clone(), media.clone(), episode_rowid)
|
||||||
.unwrap_or(glib::Continue(false))
|
.unwrap_or(glib::Continue(false))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[allow(if_same_then_else)]
|
#[allow(if_same_then_else)]
|
||||||
fn progress_bar_helper(
|
fn progress_bar_helper(
|
||||||
prog: Arc<Mutex<manager::Progress>>,
|
prog: Arc<Mutex<manager::Progress>>,
|
||||||
|
media: Arc<Mutex<MediaMachine>>,
|
||||||
episode_rowid: i32,
|
episode_rowid: i32,
|
||||||
progress_bar: >k::ProgressBar,
|
|
||||||
local_size: >k::Label,
|
|
||||||
) -> Result<glib::Continue, Error> {
|
) -> Result<glib::Continue, Error> {
|
||||||
let (fraction, downloaded) = {
|
let (fraction, downloaded) = {
|
||||||
let m = prog.lock()
|
let m = prog.lock()
|
||||||
@ -343,16 +269,16 @@ fn progress_bar_helper(
|
|||||||
(m.get_fraction(), m.get_downloaded())
|
(m.get_fraction(), m.get_downloaded())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update local_size label
|
|
||||||
downloaded
|
|
||||||
.file_size(SIZE_OPTS.clone())
|
|
||||||
.map_err(|err| format_err!("{}", err))
|
|
||||||
.map(|x| local_size.set_text(&x))?;
|
|
||||||
|
|
||||||
// I hate floating points.
|
// I hate floating points.
|
||||||
// Update the progress_bar.
|
// Update the progress_bar.
|
||||||
if (fraction >= 0.0) && (fraction <= 1.0) && (!fraction.is_nan()) {
|
if (fraction >= 0.0) && (fraction <= 1.0) && (!fraction.is_nan()) {
|
||||||
progress_bar.set_fraction(fraction);
|
// Update local_size label
|
||||||
|
let size = downloaded
|
||||||
|
.file_size(SIZE_OPTS.clone())
|
||||||
|
.map_err(|err| format_err!("{}", err))?;
|
||||||
|
|
||||||
|
let mut m = media.lock().unwrap();
|
||||||
|
m.update_progress(&size, fraction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// info!("Fraction: {}", progress_bar.get_fraction());
|
// info!("Fraction: {}", progress_bar.get_fraction());
|
||||||
@ -378,18 +304,23 @@ fn progress_bar_helper(
|
|||||||
// Setup a callback that will update the total_size label
|
// Setup a callback that will update the total_size label
|
||||||
// with the http ContentLength header number rather than
|
// with the http ContentLength header number rather than
|
||||||
// relying to the RSS feed.
|
// relying to the RSS feed.
|
||||||
fn update_total_size_callback(prog: Arc<Mutex<manager::Progress>>, total_size: >k::Label) {
|
#[inline]
|
||||||
|
fn update_total_size_callback(
|
||||||
|
prog: Arc<Mutex<manager::Progress>>,
|
||||||
|
media: Arc<Mutex<MediaMachine>>,
|
||||||
|
) {
|
||||||
timeout_add(
|
timeout_add(
|
||||||
500,
|
500,
|
||||||
clone!(prog, total_size => move || {
|
clone!(prog, media => move || {
|
||||||
total_size_helper(prog.clone(), &total_size).unwrap_or(glib::Continue(true))
|
total_size_helper(prog.clone(), media.clone()).unwrap_or(glib::Continue(true))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn total_size_helper(
|
fn total_size_helper(
|
||||||
prog: Arc<Mutex<manager::Progress>>,
|
prog: Arc<Mutex<manager::Progress>>,
|
||||||
total_size: >k::Label,
|
media: Arc<Mutex<MediaMachine>>,
|
||||||
) -> Result<glib::Continue, Error> {
|
) -> Result<glib::Continue, Error> {
|
||||||
// Get the total_bytes.
|
// Get the total_bytes.
|
||||||
let total_bytes = {
|
let total_bytes = {
|
||||||
@ -401,10 +332,12 @@ fn total_size_helper(
|
|||||||
debug!("Total Size: {}", total_bytes);
|
debug!("Total Size: {}", total_bytes);
|
||||||
if total_bytes != 0 {
|
if total_bytes != 0 {
|
||||||
// Update the total_size label
|
// Update the total_size label
|
||||||
total_bytes
|
if let Ok(mut m) = media.lock() {
|
||||||
.file_size(SIZE_OPTS.clone())
|
take_mut::take(m.deref_mut(), |machine| {
|
||||||
.map_err(|err| format_err!("{}", err))
|
machine.set_size(Some(total_bytes as i32))
|
||||||
.map(|x| total_size.set_text(&x))?;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Do not call again the callback
|
// Do not call again the callback
|
||||||
Ok(glib::Continue(false))
|
Ok(glib::Continue(false))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
917
hammond-gtk/src/widgets/episode_states.rs
Normal file
917
hammond-gtk/src/widgets/episode_states.rs
Normal file
@ -0,0 +1,917 @@
|
|||||||
|
// TODO: Things that should be done.
|
||||||
|
//
|
||||||
|
// * Wherever there's a function that take 2 or more arguments of the same type,
|
||||||
|
// eg: fn new(total_size: gtk::Label, local_size: gtk::Label ..)
|
||||||
|
// 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};
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use manager::Progress as OtherProgress;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref SIZE_OPTS: Arc<size_opts::FileSizeOpts> = {
|
||||||
|
// 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,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
static ref NOW: DateTime<Utc> = Utc::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct UnInitialized;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Shown;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Hidden;
|
||||||
|
|
||||||
|
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<S> {
|
||||||
|
title: gtk::Label,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Title<S> {
|
||||||
|
#[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<Normal> {
|
||||||
|
fn new(title: gtk::Label) -> Self {
|
||||||
|
Title {
|
||||||
|
title,
|
||||||
|
state: Normal {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Title<Normal>> for Title<GreyedOut> {
|
||||||
|
fn from(f: Title<Normal>) -> Self {
|
||||||
|
f.title
|
||||||
|
.get_style_context()
|
||||||
|
.map(|c| c.add_class("dim-label"));
|
||||||
|
|
||||||
|
Title {
|
||||||
|
title: f.title,
|
||||||
|
state: GreyedOut {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Title<GreyedOut>> for Title<Normal> {
|
||||||
|
fn from(f: Title<GreyedOut>) -> 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<Normal>),
|
||||||
|
GreyedOut(Title<GreyedOut>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TitleMachine {
|
||||||
|
pub fn new(label: gtk::Label, is_played: bool) -> Self {
|
||||||
|
let m = TitleMachine::Normal(Title::<Normal>::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<S> {
|
||||||
|
date: gtk::Label,
|
||||||
|
epoch: i64,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Date<S> {
|
||||||
|
fn into_usual(self, epoch: i64) -> Date<Usual> {
|
||||||
|
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<YearShown> {
|
||||||
|
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<UnInitialized> {
|
||||||
|
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<UnInitialized>),
|
||||||
|
Usual(Date<Usual>),
|
||||||
|
WithYear(Date<YearShown>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DateMachine {
|
||||||
|
pub fn new(label: gtk::Label, epoch: i64) -> Self {
|
||||||
|
let m = DateMachine::UnInitialized(Date::<UnInitialized>::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<S: Visibility> {
|
||||||
|
// TODO: make duration and separator diff types
|
||||||
|
duration: gtk::Label,
|
||||||
|
separator: gtk::Label,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Visibility> Duration<S> {
|
||||||
|
// This needs a better name.
|
||||||
|
// TODO: make me mut
|
||||||
|
fn set_duration(&self, minutes: i64) {
|
||||||
|
self.duration.set_text(&format!("{} min", minutes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Duration<Hidden> {
|
||||||
|
fn new(duration: gtk::Label, separator: gtk::Label) -> Self {
|
||||||
|
duration.hide();
|
||||||
|
separator.hide();
|
||||||
|
|
||||||
|
Duration {
|
||||||
|
duration,
|
||||||
|
separator,
|
||||||
|
state: Hidden {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Duration<Hidden>> for Duration<Shown> {
|
||||||
|
fn from(f: Duration<Hidden>) -> Self {
|
||||||
|
f.duration.show();
|
||||||
|
f.separator.show();
|
||||||
|
|
||||||
|
Duration {
|
||||||
|
duration: f.duration,
|
||||||
|
separator: f.separator,
|
||||||
|
state: Shown {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Duration<Shown>> for Duration<Hidden> {
|
||||||
|
fn from(f: Duration<Shown>) -> Self {
|
||||||
|
f.duration.hide();
|
||||||
|
f.separator.hide();
|
||||||
|
|
||||||
|
Duration {
|
||||||
|
duration: f.duration,
|
||||||
|
separator: f.separator,
|
||||||
|
state: Hidden {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DurationMachine {
|
||||||
|
Hidden(Duration<Hidden>),
|
||||||
|
Shown(Duration<Shown>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DurationMachine {
|
||||||
|
pub fn new(duration: gtk::Label, separator: gtk::Label, seconds: Option<i32>) -> Self {
|
||||||
|
let m = DurationMachine::Hidden(Duration::<Hidden>::new(duration, separator));
|
||||||
|
m.determine_state(seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn determine_state(self, seconds: Option<i32>) -> 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<S> {
|
||||||
|
size: gtk::Label,
|
||||||
|
separator: gtk::Label,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Size<S> {
|
||||||
|
fn set_size(self, s: &str) -> Size<Shown> {
|
||||||
|
self.size.set_text(s);
|
||||||
|
self.size.show();
|
||||||
|
self.separator.show();
|
||||||
|
Size {
|
||||||
|
size: self.size,
|
||||||
|
separator: self.separator,
|
||||||
|
state: Shown {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_shown(self) -> Size<Shown> {
|
||||||
|
self.size.show();
|
||||||
|
self.separator.show();
|
||||||
|
|
||||||
|
Size {
|
||||||
|
size: self.size,
|
||||||
|
separator: self.separator,
|
||||||
|
state: Shown {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_hidden(self) -> Size<Hidden> {
|
||||||
|
self.size.hide();
|
||||||
|
self.separator.hide();
|
||||||
|
|
||||||
|
Size {
|
||||||
|
size: self.size,
|
||||||
|
separator: self.separator,
|
||||||
|
state: Hidden {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Size<UnInitialized> {
|
||||||
|
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 {}
|
||||||
|
// impl Playable for Play {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Download;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Play;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
// FIXME: Needs better name.
|
||||||
|
// Should each button also has it's own type and machine?
|
||||||
|
pub struct DownloadPlay<S> {
|
||||||
|
play: gtk::Button,
|
||||||
|
download: gtk::Button,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> DownloadPlay<S> {
|
||||||
|
// 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<Play> {
|
||||||
|
self.play.show();
|
||||||
|
self.download.hide();
|
||||||
|
|
||||||
|
DownloadPlay {
|
||||||
|
play: self.play,
|
||||||
|
download: self.download,
|
||||||
|
state: Play {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_fetchable(self) -> DownloadPlay<Download> {
|
||||||
|
self.play.hide();
|
||||||
|
self.download.show();
|
||||||
|
|
||||||
|
DownloadPlay {
|
||||||
|
play: self.play,
|
||||||
|
download: self.download,
|
||||||
|
state: Download {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_hidden(self) -> DownloadPlay<Hidden> {
|
||||||
|
self.play.hide();
|
||||||
|
self.download.hide();
|
||||||
|
|
||||||
|
DownloadPlay {
|
||||||
|
play: self.play,
|
||||||
|
download: self.download,
|
||||||
|
state: Hidden {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||||
|
&self,
|
||||||
|
f: F,
|
||||||
|
) -> glib::SignalHandlerId {
|
||||||
|
self.download.connect_clicked(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn play_connect_clicked<F: Fn(>k::Button) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||||
|
self.play.connect_clicked(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DownloadPlay<UnInitialized> {
|
||||||
|
fn new(play: gtk::Button, download: gtk::Button) -> Self {
|
||||||
|
play.hide();
|
||||||
|
download.hide();
|
||||||
|
|
||||||
|
DownloadPlay {
|
||||||
|
play,
|
||||||
|
download,
|
||||||
|
state: UnInitialized {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Progress<S> {
|
||||||
|
bar: gtk::ProgressBar,
|
||||||
|
cancel: gtk::Button,
|
||||||
|
local_size: gtk::Label,
|
||||||
|
prog_separator: gtk::Label,
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Progress<S> {
|
||||||
|
fn into_shown(self) -> Progress<Shown> {
|
||||||
|
self.bar.show();
|
||||||
|
self.cancel.show();
|
||||||
|
self.local_size.show();
|
||||||
|
self.prog_separator.show();
|
||||||
|
|
||||||
|
Progress {
|
||||||
|
bar: self.bar,
|
||||||
|
cancel: self.cancel,
|
||||||
|
local_size: self.local_size,
|
||||||
|
prog_separator: self.prog_separator,
|
||||||
|
state: Shown {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_hidden(self) -> Progress<Hidden> {
|
||||||
|
self.bar.hide();
|
||||||
|
self.cancel.hide();
|
||||||
|
self.local_size.hide();
|
||||||
|
self.prog_separator.hide();
|
||||||
|
|
||||||
|
Progress {
|
||||||
|
bar: self.bar,
|
||||||
|
cancel: self.cancel,
|
||||||
|
local_size: self.local_size,
|
||||||
|
prog_separator: self.prog_separator,
|
||||||
|
state: Hidden {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 update_progress(&mut self, local_size: &str, fraction: f64) {
|
||||||
|
self.local_size.set_text(local_size);
|
||||||
|
self.bar.set_fraction(fraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_connect_clicked(&self, prog: Arc<Mutex<OtherProgress>>) -> glib::SignalHandlerId {
|
||||||
|
self.cancel.connect_clicked(move |cancel| {
|
||||||
|
if let Ok(mut m) = prog.lock() {
|
||||||
|
m.cancel();
|
||||||
|
cancel.set_sensitive(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Progress<UnInitialized> {
|
||||||
|
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<X, Y, Z> {
|
||||||
|
dl: DownloadPlay<X>,
|
||||||
|
size: Size<Y>,
|
||||||
|
progress: Progress<Z>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type New<Y> = Media<Download, Y, Hidden>;
|
||||||
|
type Playable<Y> = Media<Play, Y, Hidden>;
|
||||||
|
type InProgress = Media<Hidden, Shown, Shown>;
|
||||||
|
type MediaUnInitialized = Media<UnInitialized, UnInitialized, UnInitialized>;
|
||||||
|
|
||||||
|
impl From<New<Shown>> for InProgress {
|
||||||
|
fn from(f: New<Shown>) -> Self {
|
||||||
|
f.into_progress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<New<Hidden>> for InProgress {
|
||||||
|
fn from(f: New<Hidden>) -> Self {
|
||||||
|
f.into_progress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Playable<Shown>> for InProgress {
|
||||||
|
fn from(f: Playable<Shown>) -> Self {
|
||||||
|
f.into_progress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Playable<Hidden>> for InProgress {
|
||||||
|
fn from(f: Playable<Hidden>) -> Self {
|
||||||
|
f.into_progress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Y: Visibility> From<Playable<Y>> for New<Y> {
|
||||||
|
fn from(f: Playable<Y>) -> Self {
|
||||||
|
Media {
|
||||||
|
dl: f.dl.into_fetchable(),
|
||||||
|
size: f.size,
|
||||||
|
progress: f.progress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Y: Visibility> From<New<Y>> for Playable<Y> {
|
||||||
|
fn from(f: New<Y>) -> Self {
|
||||||
|
Media {
|
||||||
|
dl: f.dl.into_playable(),
|
||||||
|
size: f.size,
|
||||||
|
progress: f.progress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MediaUnInitialized> for New<Hidden> {
|
||||||
|
fn from(f: MediaUnInitialized) -> Self {
|
||||||
|
Media {
|
||||||
|
dl: f.dl.into_fetchable(),
|
||||||
|
size: f.size.into_hidden(),
|
||||||
|
progress: f.progress.into_hidden(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MediaUnInitialized> for Playable<Hidden> {
|
||||||
|
fn from(f: MediaUnInitialized) -> Self {
|
||||||
|
Media {
|
||||||
|
dl: f.dl.into_playable(),
|
||||||
|
size: f.size.into_hidden(),
|
||||||
|
progress: f.progress.into_hidden(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<X, Y, Z> Media<X, Y, Z> {
|
||||||
|
fn set_size(self, s: &str) -> Media<X, Shown, Z> {
|
||||||
|
Media {
|
||||||
|
dl: self.dl,
|
||||||
|
size: self.size.set_size(s),
|
||||||
|
progress: self.progress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_size(self) -> Media<X, Hidden, Z> {
|
||||||
|
Media {
|
||||||
|
dl: self.dl,
|
||||||
|
size: self.size.into_hidden(),
|
||||||
|
progress: self.progress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_new(self, size: &str) -> New<Shown> {
|
||||||
|
Media {
|
||||||
|
dl: self.dl.into_fetchable(),
|
||||||
|
size: self.size.set_size(size),
|
||||||
|
progress: self.progress.into_hidden(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_playable(self, size: &str) -> Playable<Shown> {
|
||||||
|
Media {
|
||||||
|
dl: self.dl.into_playable(),
|
||||||
|
size: self.size.set_size(size),
|
||||||
|
progress: self.progress.into_hidden(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<X, Z> Media<X, Shown, Z> {
|
||||||
|
fn into_progress(self) -> InProgress {
|
||||||
|
Media {
|
||||||
|
dl: self.dl.into_hidden(),
|
||||||
|
size: self.size.into_shown(),
|
||||||
|
progress: self.progress.into_shown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<X, Z> Media<X, Hidden, Z> {
|
||||||
|
fn into_progress(self) -> InProgress {
|
||||||
|
Media {
|
||||||
|
dl: self.dl.into_hidden(),
|
||||||
|
size: self.size.set_size("Unkown"),
|
||||||
|
progress: self.progress.into_shown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<X, Z> Media<X, UnInitialized, Z> {
|
||||||
|
fn into_progress(self, size: Option<String>) -> 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
|
||||||
|
// But I think it wouldn't hurt if we treat it as a Rust api.
|
||||||
|
fn update_progress(&mut self, local_size: &str, fraction: f64) {
|
||||||
|
self.progress.update_progress(local_size, fraction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ButtonsState {
|
||||||
|
New(Media<Download, Shown, Hidden>),
|
||||||
|
NewWithoutSize(Media<Download, Hidden, Hidden>),
|
||||||
|
Playable(Media<Play, Shown, Hidden>),
|
||||||
|
PlayableWithoutSize(Media<Play, Hidden, Hidden>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonsState {
|
||||||
|
pub fn determine_state(self, size: Option<String>, is_downloaded: bool) -> Self {
|
||||||
|
use self::ButtonsState::*;
|
||||||
|
|
||||||
|
match (self, size, is_downloaded) {
|
||||||
|
// From whatever to New
|
||||||
|
(New(m), Some(s), false) => New(m.into_new(&s)),
|
||||||
|
(Playable(m), Some(s), false) => New(m.into_new(&s)),
|
||||||
|
|
||||||
|
(NewWithoutSize(m), Some(s), false) => New(m.into_new(&s)),
|
||||||
|
(PlayableWithoutSize(m), Some(s), false) => New(m.into_new(&s)),
|
||||||
|
|
||||||
|
// From whatever to Playable
|
||||||
|
(New(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||||
|
(Playable(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||||
|
|
||||||
|
(NewWithoutSize(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||||
|
(PlayableWithoutSize(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||||
|
|
||||||
|
// From whatever to NewWithoutSize
|
||||||
|
(New(m), None, false) => NewWithoutSize(m.hide_size()),
|
||||||
|
(Playable(m), None, false) => NewWithoutSize(Media::from(m).hide_size()),
|
||||||
|
|
||||||
|
(b @ NewWithoutSize(_), None, false) => b,
|
||||||
|
(PlayableWithoutSize(m), None, false) => NewWithoutSize(m.into()),
|
||||||
|
|
||||||
|
// From whatever to PlayableWithoutSize
|
||||||
|
(New(m), None, true) => PlayableWithoutSize(Media::from(m).hide_size()),
|
||||||
|
(Playable(m), None, true) => PlayableWithoutSize(m.hide_size()),
|
||||||
|
|
||||||
|
(NewWithoutSize(val), None, true) => PlayableWithoutSize(val.into()),
|
||||||
|
(b @ PlayableWithoutSize(_), None, true) => b,
|
||||||
|
// _ => unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_progress(self) -> InProgress {
|
||||||
|
use self::ButtonsState::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
New(m) => m.into(),
|
||||||
|
Playable(m) => m.into(),
|
||||||
|
NewWithoutSize(m) => m.into(),
|
||||||
|
PlayableWithoutSize(m) => m.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_size(self, size: Option<String>) -> Self {
|
||||||
|
use self::ButtonsState::*;
|
||||||
|
|
||||||
|
match (self, size) {
|
||||||
|
(New(m), Some(s)) => New(m.set_size(&s)),
|
||||||
|
(New(m), None) => NewWithoutSize(m.hide_size()),
|
||||||
|
(Playable(m), Some(s)) => Playable(m.set_size(&s)),
|
||||||
|
(Playable(m), None) => PlayableWithoutSize(m.hide_size()),
|
||||||
|
(bttn @ NewWithoutSize(_), None) => bttn,
|
||||||
|
(bttn @ PlayableWithoutSize(_), None) => bttn,
|
||||||
|
(NewWithoutSize(m), Some(s)) => New(m.into_new(&s)),
|
||||||
|
(PlayableWithoutSize(m), Some(s)) => Playable(m.into_playable(&s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||||
|
&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<F: Fn(>k::Button) + 'static>(
|
||||||
|
&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, prog: Arc<Mutex<OtherProgress>>) -> glib::SignalHandlerId {
|
||||||
|
use self::ButtonsState::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
New(ref val) => val.progress.cancel_connect_clicked(prog),
|
||||||
|
NewWithoutSize(ref val) => val.progress.cancel_connect_clicked(prog),
|
||||||
|
Playable(ref val) => val.progress.cancel_connect_clicked(prog),
|
||||||
|
PlayableWithoutSize(ref val) => val.progress.cancel_connect_clicked(prog),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum MediaMachine {
|
||||||
|
UnInitialized(Media<UnInitialized, UnInitialized, UnInitialized>),
|
||||||
|
Initialized(ButtonsState),
|
||||||
|
InProgress(Media<Hidden, Shown, Shown>),
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<UnInitialized>::new(play, download);
|
||||||
|
let progress = Progress::<UnInitialized>::new(bar, cancel, local_size, prog_separator);
|
||||||
|
let size = Size::<UnInitialized>::new(total_size, separator);
|
||||||
|
|
||||||
|
MediaMachine::UnInitialized(Media { dl, progress, size })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||||
|
&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<F: Fn(>k::Button) + 'static>(
|
||||||
|
&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, prog: Arc<Mutex<OtherProgress>>) -> glib::SignalHandlerId {
|
||||||
|
use self::MediaMachine::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
UnInitialized(ref val) => val.progress.cancel_connect_clicked(prog),
|
||||||
|
Initialized(ref val) => val.cancel_connect_clicked(prog),
|
||||||
|
InProgress(ref val) => val.progress.cancel_connect_clicked(prog),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn determine_state(self, bytes: Option<i32>, is_active: bool, is_downloaded: bool) -> Self {
|
||||||
|
use self::ButtonsState::*;
|
||||||
|
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())),
|
||||||
|
|
||||||
|
// Into Playable
|
||||||
|
(UnInitialized(m), Some(s), true, false) => Initialized(Playable(m.into_playable(&s))),
|
||||||
|
(UnInitialized(m), None, true, false) => Initialized(PlayableWithoutSize(m.into())),
|
||||||
|
|
||||||
|
(Initialized(bttn), s, dl, false) => Initialized(bttn.determine_state(s, dl)),
|
||||||
|
(Initialized(bttn), _, _, true) => InProgress(bttn.into_progress()),
|
||||||
|
(i @ InProgress(_), _, _, _) => i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_size(self, bytes: Option<i32>) -> Self {
|
||||||
|
use self::MediaMachine::*;
|
||||||
|
let size = size_helper(bytes);
|
||||||
|
|
||||||
|
match (self, size) {
|
||||||
|
(Initialized(bttn), s) => Initialized(bttn.set_size(s)),
|
||||||
|
(InProgress(val), Some(s)) => InProgress(val.set_size(&s)),
|
||||||
|
(n @ InProgress(_), None) => n,
|
||||||
|
(n @ UnInitialized(_), _) => n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_progress(&mut self, local_size: &str, fraction: f64) {
|
||||||
|
use self::MediaMachine::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
Initialized(_) => (),
|
||||||
|
UnInitialized(_) => (),
|
||||||
|
InProgress(ref mut val) => val.update_progress(local_size, fraction),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_helper(bytes: Option<i32>) -> Option<String> {
|
||||||
|
let s = bytes?;
|
||||||
|
if s == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.file_size(SIZE_OPTS.clone()).ok()
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
mod show;
|
mod show;
|
||||||
mod episode;
|
mod episode;
|
||||||
|
mod episode_states;
|
||||||
|
|
||||||
pub use self::episode::EpisodeWidget;
|
pub use self::episode::EpisodeWidget;
|
||||||
pub use self::show::ShowWidget;
|
pub use self::show::ShowWidget;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user