diff --git a/Cargo.lock b/Cargo.lock index edaa3e4..e940ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,6 +260,16 @@ dependencies = [ "thread-scoped 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.2.0" @@ -278,6 +288,19 @@ dependencies = [ "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-epoch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-epoch" version = "0.3.1" @@ -696,6 +719,7 @@ name = "hammond-gtk" version = "0.1.0" dependencies = [ "chrono 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -969,6 +993,11 @@ dependencies = [ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memoffset" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memoffset" version = "0.2.1" @@ -1145,6 +1174,14 @@ dependencies = [ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pango" version = "0.4.0" @@ -1170,6 +1207,26 @@ dependencies = [ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1563,6 +1620,16 @@ name = "smallvec" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smallvec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stable_deref_trait" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "string_cache" version = "0.7.1" @@ -2051,8 +2118,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum criterion 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4556caa5b5e69626a0b69c4892baa6ab518cf2e802384a5eeef15dd4416d4771" "checksum criterion-plot 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "029914bacd6bb0a521429ff8df0c6c79be1d99f5c4bb85475caabb78f5e06da6" "checksum criterion-stats 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a57a868c589ef2208c0f441e816810e16bfd9cf6a6ea6548f53938b8a530d362" +"checksum crossbeam-channel 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d7b07a3084d8718d95338443d5a46aab38ce16d5f991d4027a0906b369f70a3" "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c1bdc73742c36f7f35ebcda81dbb33a7e0d33757d03a06d9ddca762712ec5ea2" +"checksum crossbeam-epoch 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9898f21d6d647793e163c804944941fb19aecd1f4a1a4c254bbb0bee15ccdea5" "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" "checksum crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4e2817eb773f770dcb294127c011e22771899c21d18fce7dd739c0b9832e81" "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" @@ -2118,6 +2187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum markup5ever 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfedc97d5a503e96816d10fedcd5b42f760b2e525ce2f7ec71f6a41780548475" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum memoffset 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e163e5baece1a039e71e75b074de17a9b4114982aa109921fc20253bdf91a53c" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum migrations_internals 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd916de6df9ac7e811e7e1ac28e0abfebe5205f3b29a7bda9ec8a41ee980a4eb" "checksum migrations_macros 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a550cfd76f6cfdf15a7b541893d7c79b68277b0b309f12179211a373a56e617" @@ -2137,8 +2207,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c281318d992e4432cfa799969467003d05921582a7489a8325e37f8a450d5113" "checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985" "checksum openssl-sys 0.9.27 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdc5c4a02e69ce65046f1763a0181107038e02176233acb0b3351d7cc588f9" +"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45374801e224373c3c0393cd48073c81093494c8735721e81d1dbaa4096b2767" "checksum pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1" +"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +"checksum parking_lot_core 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "538ef00b7317875071d5e00f603f24d16f0b474c1a5fc0ccb8b454ca72eafa79" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0fce5d8b5cc33983fc74f78ad552b5522ab41442c4ca91606e4236eb4b5ceefc" "checksum pest_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ab94faafeb93f4c5e3ce81ca0e5a779529a602ad5d09ae6d21996bfb8b6a52bf" @@ -2187,6 +2260,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" "checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" +"checksum smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44db0ecb22921ef790d17ae13a3f6d15784183ff5f2a01aa32098c7498d2b4b9" +"checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" "checksum string_cache 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39cb4173bcbd1319da31faa5468a7e3870683d7a237150b0b0aaafd546f6ad12" "checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 4923310..3bf982d 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -175,6 +175,16 @@ pub fn get_pd_episodes(parent: &Podcast) -> Result, DataError> { .map_err(From::from) } +pub fn get_pd_episodes_count(parent: &Podcast) -> Result { + let db = connection(); + let con = db.get()?; + + Episode::belonging_to(parent) + .count() + .get_result(&con) + .map_err(From::from) +} + pub fn get_pd_episodeswidgets(parent: &Podcast) -> Result, DataError> { use schema::episode::dsl::*; let db = connection(); diff --git a/hammond-gtk/Cargo.toml b/hammond-gtk/Cargo.toml index 93d8a40..f0ff5c6 100644 --- a/hammond-gtk/Cargo.toml +++ b/hammond-gtk/Cargo.toml @@ -7,6 +7,7 @@ workspace = "../" [dependencies] chrono = "0.4.1" +crossbeam-channel = "0.1.2" gdk = "0.8.0" gdk-pixbuf = "0.4.0" glib = "0.5.0" diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index 276ae35..f9c3c6c 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -24,6 +24,7 @@ extern crate log; extern crate pretty_assertions; extern crate chrono; +extern crate crossbeam_channel; extern crate hammond_data; extern crate hammond_downloader; extern crate html2pango; diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs index 81a9bad..998e49e 100644 --- a/hammond-gtk/src/utils.rs +++ b/hammond-gtk/src/utils.rs @@ -4,6 +4,7 @@ use gdk_pixbuf::Pixbuf; use gio::{Settings, SettingsExt}; use glib; use gtk; +use gtk::{IsA, Widget}; use gtk::prelude::*; use failure::Error; @@ -30,6 +31,55 @@ use app::Action; use chrono::Duration; use chrono::prelude::*; +/// Lazy evaluates and loads widgets to the parent `container` widget. +/// +/// Accepts an IntoIterator, `T`, as the source from which each widget +/// will be constructed. An `FnMut` function that returns the desired +/// widget should be passed as the widget `constructor`. +/// +/// ```no_run +/// # struct Message; +/// # struct MessageWidget(gtk::Label); +/// +/// # impl MessageWidget { +/// # fn new(_: Message) -> Self { +/// # MessageWidget(gtk::Label::new("A message")) +/// # } +/// # } +/// +/// let messages: Vec = Vec::new(); +/// let list = gtk::ListBox::new(); +/// let constructor = |m| { MessageWidget::new(m).0}; +/// lazy_load(messages, list, constructor); +/// ``` +/// +/// If you have already constructed the widgets and only want to +/// load them to the parent you can pass a closure that returns it's +/// own argument to the constructor. +/// +/// ```no_run +/// # use std::collections::binary_heap::BinaryHeap; +/// let widgets: BinaryHeap = BinaryHeap::new(); +/// let list = gtk::ListBox::new(); +/// lazy_load(widgets, list, |w| w); +/// ``` +pub fn lazy_load(data: T, container: C, mut contructor: F) +where + T: IntoIterator + 'static, + T::Item: 'static, + C: ContainerExt + 'static, + F: FnMut(T::Item) -> W + 'static, + W: IsA, +{ + let mut data = data.into_iter(); + gtk::idle_add(move || { + data.next() + .map(|x| container.add(&contructor(x))) + .map(|_| glib::Continue(true)) + .unwrap_or(glib::Continue(false)) + }); +} + lazy_static! { static ref IGNORESHOWS: Arc>> = Arc::new(Mutex::new(HashSet::new())); } diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 57ea477..ec4a595 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -5,6 +5,7 @@ use gtk::prelude::*; use failure::Error; use humansize::FileSize; use open; +use rayon; use take_mut; use hammond_data::{EpisodeWidgetQuery, Podcast}; @@ -13,6 +14,7 @@ use hammond_data::utils::get_download_folder; use app::Action; use manager; +use utils::lazy_load; use widgets::episode_states::*; use std::cell::RefCell; @@ -350,23 +352,44 @@ fn total_size_helper( // delete_local_content(&mut ep).map_err(From::from).map(|_| ()) // } -pub fn episodes_listbox(pd: &Podcast, sender: Sender) -> Result { - let episodes = dbqueries::get_pd_episodeswidgets(pd)?; +pub fn episodes_listbox(pd: Arc, sender: Sender) -> Result { + use crossbeam_channel::TryRecvError::*; + use crossbeam_channel::bounded; + + let count = dbqueries::get_pd_episodes_count(&pd)?; + + let (sender_, receiver) = bounded(1); + rayon::spawn(move || { + let episodes = dbqueries::get_pd_episodeswidgets(&pd).unwrap(); + sender_ + .send(episodes) + .expect("Something terrible happened to the channnel"); + }); let list = gtk::ListBox::new(); list.set_visible(true); list.set_selection_mode(gtk::SelectionMode::None); - if episodes.is_empty() { + if count == 0 { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/empty_show.ui"); let container: gtk::Box = builder.get_object("empty_show").unwrap(); list.add(&container); return Ok(list); } - episodes.into_iter().for_each(|ep| { - let widget = EpisodeWidget::new(ep, sender.clone()); - list.add(&widget.container); - }); + gtk::idle_add(clone!(list => move || { + let episodes = match receiver.try_recv() { + Ok(e) => e, + Err(Empty) => return glib::Continue(true), + Err(Disconnected) => return glib::Continue(false), + }; + + lazy_load(episodes, list.clone(), clone!(sender => move |ep| { + EpisodeWidget::new(ep, sender.clone()).container + })); + + glib::Continue(false) + })); + Ok(list) } diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs index bf90607..c02e652 100644 --- a/hammond-gtk/src/widgets/show.rs +++ b/hammond-gtk/src/widgets/show.rs @@ -73,7 +73,7 @@ impl ShowWidget { } })); - self.setup_listbox(&pd, sender.clone()); + self.setup_listbox(pd.clone(), sender.clone()); self.set_description(pd.description()); if let Err(err) = self.set_cover(pd.clone()) { @@ -105,7 +105,7 @@ impl ShowWidget { } /// Populate the listbox with the shows episodes. - fn setup_listbox(&self, pd: &Podcast, sender: Sender) { + fn setup_listbox(&self, pd: Arc, sender: Sender) { let listbox = episodes_listbox(pd, sender.clone()); listbox.ok().map(|l| self.episodes.add(&l)); }