323 lines
9.4 KiB
Rust
323 lines
9.4 KiB
Rust
use glib;
|
|
use gtk;
|
|
|
|
use gtk::prelude::*;
|
|
use chrono::prelude::*;
|
|
|
|
use open;
|
|
use humansize::{file_size_opts as size_opts, FileSize};
|
|
|
|
use hammond_data::dbqueries;
|
|
use hammond_data::{EpisodeWidgetQuery, Podcast};
|
|
use hammond_data::utils::*;
|
|
use hammond_data::errors::*;
|
|
use hammond_downloader::downloader;
|
|
|
|
use std::thread;
|
|
use std::cell::RefCell;
|
|
use std::sync::mpsc::{channel, Receiver};
|
|
use std::path::Path;
|
|
|
|
type Foo = RefCell<
|
|
Option<
|
|
(
|
|
gtk::Button,
|
|
gtk::Button,
|
|
gtk::Button,
|
|
gtk::Button,
|
|
gtk::ProgressBar,
|
|
Receiver<bool>,
|
|
),
|
|
>,
|
|
>;
|
|
|
|
thread_local!(static GLOBAL: Foo = RefCell::new(None));
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct EpisodeWidget {
|
|
pub container: gtk::Box,
|
|
play: gtk::Button,
|
|
delete: gtk::Button,
|
|
download: gtk::Button,
|
|
cancel: gtk::Button,
|
|
title: gtk::Label,
|
|
date: gtk::Label,
|
|
duration: gtk::Label,
|
|
size: gtk::Label,
|
|
progress: gtk::ProgressBar,
|
|
progress_label: gtk::Label,
|
|
separator1: gtk::Label,
|
|
}
|
|
|
|
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 download: gtk::Button = builder.get_object("download_button").unwrap();
|
|
let play: gtk::Button = builder.get_object("play_button").unwrap();
|
|
let delete: gtk::Button = builder.get_object("delete_button").unwrap();
|
|
let cancel: gtk::Button = builder.get_object("cancel_button").unwrap();
|
|
|
|
let title: gtk::Label = builder.get_object("title_label").unwrap();
|
|
let date: gtk::Label = builder.get_object("date_label").unwrap();
|
|
let duration: gtk::Label = builder.get_object("duration_label").unwrap();
|
|
let size: gtk::Label = builder.get_object("size_label").unwrap();
|
|
let progress_label: gtk::Label = builder.get_object("progress_label").unwrap();
|
|
|
|
let separator1: gtk::Label = builder.get_object("separator1").unwrap();
|
|
|
|
EpisodeWidget {
|
|
container,
|
|
progress,
|
|
download,
|
|
play,
|
|
cancel,
|
|
delete,
|
|
title,
|
|
duration,
|
|
size,
|
|
date,
|
|
progress_label,
|
|
separator1,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EpisodeWidget {
|
|
pub fn new(episode: &mut EpisodeWidgetQuery) -> EpisodeWidget {
|
|
let widget = EpisodeWidget::default();
|
|
widget.init(episode);
|
|
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());
|
|
|
|
if episode.played().is_some() {
|
|
self.title
|
|
.get_style_context()
|
|
.map(|c| c.add_class("dim-label"));
|
|
}
|
|
|
|
// 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 {
|
|
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: "",
|
|
};
|
|
|
|
if let Some(size) = episode.length() {
|
|
let s = size.file_size(custom_options);
|
|
if let Ok(s) = s {
|
|
self.size.set_text(&s);
|
|
} else {
|
|
self.size.hide();
|
|
}
|
|
};
|
|
|
|
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();
|
|
self.delete.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 play = &self.play;
|
|
let download = &self.download;
|
|
self.delete
|
|
.connect_clicked(clone!(episode, play, download => move |del| {
|
|
on_delete_bttn_clicked(episode.rowid());
|
|
del.hide();
|
|
play.hide();
|
|
download.show();
|
|
}));
|
|
|
|
let play = &self.play;
|
|
let delete = &self.delete;
|
|
let cancel = &self.cancel;
|
|
let progress = self.progress.clone();
|
|
self.download.connect_clicked(
|
|
clone!(play, delete, episode, cancel, progress => move |dl| {
|
|
on_download_clicked(
|
|
&mut episode.clone(),
|
|
dl,
|
|
&play,
|
|
&delete,
|
|
&cancel,
|
|
progress.clone()
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
// TODO: show notification when dl is finished.
|
|
fn on_download_clicked(
|
|
ep: &mut EpisodeWidgetQuery,
|
|
download_bttn: >k::Button,
|
|
play_bttn: >k::Button,
|
|
del_bttn: >k::Button,
|
|
cancel_bttn: >k::Button,
|
|
progress_bar: gtk::ProgressBar,
|
|
) {
|
|
let progress = progress_bar.clone();
|
|
|
|
// Start the proggress_bar pulse.
|
|
timeout_add(200, move || {
|
|
progress_bar.pulse();
|
|
glib::Continue(true)
|
|
});
|
|
|
|
// Create a async channel.
|
|
let (sender, receiver) = channel();
|
|
|
|
// Pass the desired arguments into the Local Thread Storage.
|
|
GLOBAL.with(
|
|
clone!(download_bttn, play_bttn, del_bttn, cancel_bttn, progress => move |global| {
|
|
*global.borrow_mut() = Some((
|
|
download_bttn,
|
|
play_bttn,
|
|
del_bttn,
|
|
cancel_bttn,
|
|
progress,
|
|
receiver));
|
|
}),
|
|
);
|
|
|
|
let pd = dbqueries::get_podcast_from_id(ep.podcast_id()).unwrap();
|
|
let pd_title = pd.title().to_owned();
|
|
let mut ep = ep.clone();
|
|
cancel_bttn.show();
|
|
progress.show();
|
|
download_bttn.hide();
|
|
thread::spawn(move || {
|
|
let download_fold = downloader::get_download_folder(&pd_title).unwrap();
|
|
let e = downloader::get_episode(&mut ep, download_fold.as_str());
|
|
if let Err(err) = e {
|
|
error!("Error while trying to download: {:?}", ep.uri());
|
|
error!("Error: {}", err);
|
|
};
|
|
sender.send(true).expect("Couldn't send data to channel");;
|
|
glib::idle_add(receive);
|
|
});
|
|
}
|
|
|
|
fn on_play_bttn_clicked(episode_id: i32) {
|
|
let local_uri = dbqueries::get_episode_local_uri_from_id(episode_id).unwrap();
|
|
|
|
if let Some(uri) = local_uri {
|
|
if Path::new(&uri).exists() {
|
|
info!("Opening {}", uri);
|
|
let e = open::that(&uri);
|
|
if let Err(err) = e {
|
|
error!("Error while trying to open file: {}", uri);
|
|
error!("Error: {}", err);
|
|
};
|
|
}
|
|
} else {
|
|
error!(
|
|
"Something went wrong evaluating the following path: {:?}",
|
|
local_uri
|
|
);
|
|
}
|
|
}
|
|
|
|
fn on_delete_bttn_clicked(episode_id: i32) {
|
|
let mut ep = dbqueries::get_episode_from_rowid(episode_id)
|
|
.unwrap()
|
|
.into();
|
|
|
|
let e = delete_local_content(&mut ep);
|
|
if let Err(err) = e {
|
|
error!("Error while trying to delete file: {:?}", ep.local_uri());
|
|
error!("Error: {}", err);
|
|
};
|
|
}
|
|
|
|
fn receive() -> glib::Continue {
|
|
GLOBAL.with(|global| {
|
|
if let Some((
|
|
ref download_bttn,
|
|
ref play_bttn,
|
|
ref del_bttn,
|
|
ref cancel_bttn,
|
|
ref progress_bar,
|
|
ref reciever,
|
|
)) = *global.borrow()
|
|
{
|
|
if reciever.try_recv().is_ok() {
|
|
download_bttn.hide();
|
|
play_bttn.show();
|
|
del_bttn.show();
|
|
cancel_bttn.hide();
|
|
progress_bar.hide();
|
|
}
|
|
}
|
|
});
|
|
glib::Continue(false)
|
|
}
|
|
|
|
pub fn episodes_listbox(pd: &Podcast) -> Result<gtk::ListBox> {
|
|
let episodes = dbqueries::get_pd_episodeswidgets(pd)?;
|
|
|
|
let list = gtk::ListBox::new();
|
|
|
|
episodes.into_iter().for_each(|mut ep| {
|
|
let widget = EpisodeWidget::new(&mut ep);
|
|
list.add(&widget.container);
|
|
|
|
let sep = gtk::Separator::new(gtk::Orientation::Vertical);
|
|
sep.set_sensitive(false);
|
|
sep.set_can_focus(false);
|
|
|
|
list.add(&sep);
|
|
sep.show()
|
|
});
|
|
|
|
list.set_vexpand(false);
|
|
list.set_hexpand(false);
|
|
list.set_visible(true);
|
|
list.set_selection_mode(gtk::SelectionMode::None);
|
|
Ok(list)
|
|
}
|