Merge branch 'master' into 33-downloader-re-work

This commit is contained in:
Jordan Petridis
2018-01-05 21:43:41 +02:00
18 changed files with 532 additions and 412 deletions
+96 -58
View File
@@ -15,6 +15,10 @@ use hammond_downloader::downloader;
use app::DOWNLOADS_MANAGER;
use app::Action;
use std::thread;
use std::sync::mpsc::Sender;
use std::path::Path;
#[derive(Debug, Clone)]
@@ -71,24 +75,30 @@ impl Default for EpisodeWidget {
}
impl EpisodeWidget {
pub fn new(episode: &mut EpisodeWidgetQuery) -> EpisodeWidget {
pub fn new(episode: &mut EpisodeWidgetQuery, sender: Sender<Action>) -> EpisodeWidget {
let widget = EpisodeWidget::default();
widget.init(episode);
widget.init(episode, sender);
widget
}
// TODO: calculate lenght.
// TODO: wire the progress_bar to the downloader.
// TODO: wire the cancel button.
fn init(&self, episode: &mut EpisodeWidgetQuery) {
self.title.set_xalign(0.0);
self.title.set_text(episode.title());
fn init(&self, episode: &mut EpisodeWidgetQuery, sender: Sender<Action>) {
// Set the title label state.
self.set_title(episode);
if episode.played().is_some() {
self.title
.get_style_context()
.map(|c| c.add_class("dim-label"));
}
// Set the size label.
self.set_size(episode.length());
// Set the duaration label.
self.set_duration(episode.duration());
// Set the date label.
self.set_date(episode.epoch());
// Show or hide the play/delete/download buttons upon widget initialization.
self.show_buttons(episode.local_uri());
{
let m = DOWNLOADS_MANAGER.lock().unwrap();
@@ -98,6 +108,77 @@ impl EpisodeWidget {
};
}
let title = &self.title;
self.play
.connect_clicked(clone!(episode, title, sender => move |_| {
let mut episode = episode.clone();
on_play_bttn_clicked(episode.rowid());
if episode.set_played_now().is_ok() {
title
.get_style_context()
.map(|c| c.add_class("dim-label"));
sender.send(Action::RefreshEpisodesViewBGR).unwrap();
};
}));
let cancel = &self.cancel;
let progress = self.progress.clone();
self.download
.connect_clicked(clone!(episode, cancel, progress, sender => move |dl| {
on_download_clicked(
&mut episode.clone(),
dl,
&cancel,
progress.clone(),
sender.clone()
);
}));
}
/// Show or hide the play/delete/download buttons upon widget initialization.
fn show_buttons(&self, local_uri: Option<&str>) {
if local_uri.is_some() && Path::new(local_uri.unwrap()).exists() {
self.download.hide();
self.play.show();
}
}
/// Determine the title state.
fn set_title(&self, episode: &EpisodeWidgetQuery) {
self.title.set_xalign(0.0);
self.title.set_text(episode.title());
// Grey out the title if the episode is played.
if 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.
fn set_date(&self, epoch: i32) {
let now = Utc::now();
let date = Utc.timestamp(i64::from(epoch), 0);
if now.year() == date.year() {
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.
fn set_duration(&self, seconds: Option<i32>) {
if let Some(secs) = seconds {
self.duration.set_text(&format!("{} min", secs / 60));
self.duration.show();
self.separator1.show();
}
}
/// Set the Episode label dependings on its size
fn set_size(&self, bytes: Option<i32>) {
// Declare a custom humansize option struct
// See: https://docs.rs/humansize/1.0.2/humansize/file_size_opts/struct.FileSizeOpts.html
let custom_options = size_opts::FileSizeOpts {
@@ -112,7 +193,7 @@ impl EpisodeWidget {
allow_negative: false,
};
if let Some(size) = episode.length() {
if let Some(size) = bytes {
if size != 0 {
let s = size.file_size(custom_options);
if let Ok(s) = s {
@@ -122,51 +203,6 @@ impl EpisodeWidget {
}
}
};
if let Some(secs) = episode.duration() {
self.duration.set_text(&format!("{} min", secs / 60));
self.duration.show();
self.separator1.show();
};
let now = Utc::now();
let date = Utc.timestamp(i64::from(episode.epoch()), 0);
if now.year() == date.year() {
self.date.set_text(&date.format("%e %b").to_string());
} else {
self.date.set_text(&date.format("%e %b %Y").to_string());
};
// Show or hide the play/delete/download buttons upon widget initialization.
let local_uri = episode.local_uri();
if local_uri.is_some() && Path::new(local_uri.unwrap()).exists() {
self.download.hide();
self.play.show();
}
let title = &self.title;
self.play
.connect_clicked(clone!(episode, title => move |_| {
let mut episode = episode.clone();
on_play_bttn_clicked(episode.rowid());
if episode.set_played_now().is_ok() {
title
.get_style_context()
.map(|c| c.add_class("dim-label"));
};
}));
let cancel = &self.cancel;
let progress = self.progress.clone();
self.download
.connect_clicked(clone!(episode, cancel, progress => move |dl| {
on_download_clicked(
&episode,
dl,
&cancel,
progress.clone()
);
}));
}
fn show_progess_bar(&self) {
@@ -187,6 +223,7 @@ fn on_download_clicked(
download_bttn: &gtk::Button,
cancel_bttn: &gtk::Button,
progress_bar: gtk::ProgressBar,
sender: Sender<Action>,
) {
let progress = progress_bar.clone();
@@ -206,6 +243,7 @@ fn on_download_clicked(
let man = DOWNLOADS_MANAGER.lock().unwrap();
man.add(ep.rowid(), &download_fold);
}
sender.send(Action::RefreshEpisodesViewBGR).unwrap();
}
fn on_play_bttn_clicked(episode_id: i32) {
@@ -240,13 +278,13 @@ fn on_play_bttn_clicked(episode_id: i32) {
// };
// }
pub fn episodes_listbox(pd: &Podcast) -> Result<gtk::ListBox> {
pub fn episodes_listbox(pd: &Podcast, sender: Sender<Action>) -> Result<gtk::ListBox> {
let mut episodes = dbqueries::get_pd_episodeswidgets(pd)?;
let list = gtk::ListBox::new();
episodes.iter_mut().for_each(|ep| {
let widget = EpisodeWidget::new(ep);
let widget = EpisodeWidget::new(ep, sender.clone());
list.add(&widget.container);
});
+73 -40
View File
@@ -12,14 +12,17 @@ use hammond_downloader::downloader;
use widgets::episode::episodes_listbox;
use utils::get_pixbuf_from_path;
use content::ShowStack;
use headerbar::Header;
use app::Action;
use std::rc::Rc;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::thread;
use std::fs;
#[derive(Debug, Clone)]
pub struct ShowWidget {
pub container: gtk::Box,
scrolled_window: gtk::ScrolledWindow,
cover: gtk::Image,
description: gtk::Label,
link: gtk::Button,
@@ -32,6 +35,7 @@ impl Default for ShowWidget {
fn default() -> Self {
let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/show_widget.ui");
let container: gtk::Box = builder.get_object("container").unwrap();
let scrolled_window: gtk::ScrolledWindow = builder.get_object("scrolled_window").unwrap();
let episodes: gtk::Frame = builder.get_object("episodes").unwrap();
let cover: gtk::Image = builder.get_object("cover").unwrap();
@@ -42,6 +46,7 @@ impl Default for ShowWidget {
ShowWidget {
container,
scrolled_window,
cover,
description,
unsub,
@@ -53,67 +58,95 @@ impl Default for ShowWidget {
}
impl ShowWidget {
pub fn new(shows: Rc<ShowStack>, header: Rc<Header>, pd: &Podcast) -> ShowWidget {
pub fn new(shows: Arc<ShowStack>, pd: &Podcast, sender: Sender<Action>) -> ShowWidget {
let pdw = ShowWidget::default();
pdw.init(shows, header, pd);
pdw.init(shows, pd, sender);
pdw
}
pub fn init(&self, shows: Rc<ShowStack>, header: Rc<Header>, pd: &Podcast) {
pub fn init(&self, shows: Arc<ShowStack>, pd: &Podcast, sender: Sender<Action>) {
// Hacky workaround so the pd.id() can be retrieved from the `ShowStack`.
WidgetExt::set_name(&self.container, &pd.id().to_string());
self.unsub.connect_clicked(clone!(shows, pd => move |bttn| {
on_unsub_button_clicked(shows.clone(), &pd, bttn);
header.switch_to_normal();
self.unsub
.connect_clicked(clone!(shows, pd, sender => move |bttn| {
on_unsub_button_clicked(shows.clone(), &pd, bttn, sender.clone());
sender.send(Action::HeaderBarNormal).unwrap();
}));
let listbox = episodes_listbox(pd);
if let Ok(l) = listbox {
self.episodes.add(&l);
}
// TODO: Temporary solution until we render html urls/bold/italic probably with markup.
let desc = dissolve::strip_html_tags(pd.description()).join(" ");
self.description.set_text(&replace_extra_spaces(&desc));
let img = get_pixbuf_from_path(&pd.clone().into(), 128);
if let Some(i) = img {
self.cover.set_from_pixbuf(&i);
}
self.setup_listbox(pd, sender.clone());
self.set_cover(pd);
self.set_description(pd.description());
let link = pd.link().to_owned();
self.link.set_tooltip_text(Some(link.as_str()));
self.link.connect_clicked(move |_| {
info!("Opening link: {}", &link);
let _ = open::that(&link);
});
}
// self.played.connect_clicked(clone!(shows, pd => move |_| {
// on_played_button_clicked(shows.clone(), &pd);
// }));
/// Populate the listbox with the shows episodes.
fn setup_listbox(&self, pd: &Podcast, sender: Sender<Action>) {
let listbox = episodes_listbox(pd, sender.clone());
if let Ok(l) = listbox {
self.episodes.add(&l);
}
}
/// Set the show cover.
fn set_cover(&self, pd: &Podcast) {
let img = get_pixbuf_from_path(&pd.clone().into(), 128);
if let Some(i) = img {
self.cover.set_from_pixbuf(&i);
}
}
/// Set the descripton text.
fn set_description(&self, text: &str) {
// TODO: Temporary solution until we render html urls/bold/italic probably with markup.
let desc = dissolve::strip_html_tags(text).join(" ");
self.description.set_text(&replace_extra_spaces(&desc));
}
/// Set scrolled window vertical adjustment.
pub fn set_vadjustment(&self, vadjustment: &gtk::Adjustment) {
self.scrolled_window.set_vadjustment(vadjustment)
}
}
fn on_unsub_button_clicked(shows: Rc<ShowStack>, pd: &Podcast, unsub_button: &gtk::Button) {
let res = dbqueries::remove_feed(pd);
if res.is_ok() {
info!("{} was removed succesfully.", pd.title());
// hack to get away without properly checking for none.
// if pressed twice would panic.
unsub_button.hide();
fn on_unsub_button_clicked(
shows: Arc<ShowStack>,
pd: &Podcast,
unsub_button: &gtk::Button,
sender: Sender<Action>,
) {
// hack to get away without properly checking for none.
// if pressed twice would panic.
unsub_button.hide();
// Spawn a thread so it won't block the ui.
thread::spawn(clone!(pd => move || {
let res = dbqueries::remove_feed(&pd);
if res.is_ok() {
info!("{} was removed succesfully.", pd.title());
let dl_fold = downloader::get_download_folder(pd.title());
if let Ok(fold) = dl_fold {
let res3 = fs::remove_dir_all(&fold);
if res3.is_ok() {
info!("All the content at, {} was removed succesfully", &fold);
}
};
}
let dl_fold = downloader::get_download_folder(pd.title());
if let Ok(fold) = dl_fold {
let res3 = fs::remove_dir_all(&fold);
// TODO: Show errors?
if res3.is_ok() {
info!("All the content at, {} was removed succesfully", &fold);
}
};
}
}));
shows.switch_podcasts_animated();
// Queue a refresh after the switch to avoid blocking the db.
sender.send(Action::RefreshViews).unwrap();
}
#[allow(dead_code)]
fn on_played_button_clicked(shows: Rc<ShowStack>, pd: &Podcast) {
fn on_played_button_clicked(shows: Arc<ShowStack>, pd: &Podcast) {
let _ = dbqueries::update_none_to_played_now(pd);
shows.update_widget();