diff --git a/src/api.rs b/src/api.rs index 42121fc..60d2fa6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,11 +2,10 @@ use serde_derive::*; use lazy_static::*; use workerpool::Worker; use std::{ - cell::RefCell, sync::{ Arc, mpsc::{channel, Receiver}, - Mutex, MutexGuard + Mutex, }, }; @@ -54,6 +53,13 @@ impl RequestContext { self.client .post(&format!("https://{}{}", self.instance, url.as_ref())) } + + pub fn to_json(&self) -> serde_json::Value { + serde_json::json!({ + "token": self.token, + "instance": self.instance, + }) + } } #[derive(Default)] @@ -151,7 +157,6 @@ pub struct Track { pub album: Album, pub artist: ArtistPreview, pub listen_url: String, - pub uploads: Option>, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -165,23 +170,5 @@ pub struct AlbumTrack { pub title: String, pub artist: ArtistPreview, pub listen_url: String, - pub uploads: Option>, } -#[derive(Deserialize, Serialize, Debug, Clone)] -pub struct Upload { - pub extension: String, - pub listen_url: String, -} - -impl Upload { - pub fn get_for_track(track_id: i32, instance: String, jwt: String) -> Option { - let track: Track = reqwest::Client::new() - .get(&format!("https://{}/api/v1/tracks/{}/", instance, track_id)) - .header(reqwest::header::AUTHORIZATION, format!("JWT {}", jwt.clone())) - .send().unwrap() - .json().unwrap(); - println!("uploads : {:#?}", track); - track.uploads.unwrap_or_default().into_iter().next() - } -} diff --git a/src/main.rs b/src/main.rs index b5ba048..63a9dfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,10 @@ use gtk::{self, prelude::*, *}; use std::{ cell::RefCell, rc::Rc, - sync::Arc, + sync::{Arc, Mutex}, fs, path::PathBuf, }; -use serde_json::json; macro_rules! clone { (@param _) => ( _ ); @@ -23,9 +22,30 @@ macro_rules! clone { move |$(clone!(@param $p),)+| $body } ); + ($($n:ident),+) => ( + $( let $n = $n.clone(); )+ + ) +} + +macro_rules! rc { + ($($n:ident),+) => ( + $( let $n = std::rc::Rc::new(std::cell::RefCell::new($n)); )+ + ) } macro_rules! wait { + ($exp:expr => | const $res:ident | $then:block) => { + let rx = $exp; + gtk::idle_add(move || { + match rx.try_recv() { + Err(_) => glib::Continue(true), + Ok($res) => { + $then; + glib::Continue(false) + }, + } + }) + }; ($exp:expr => | $res:ident | $then:block) => { let rx = $exp; gtk::idle_add(move || { @@ -40,37 +60,34 @@ macro_rules! wait { } } +macro_rules! client { + () => (crate::api::API.lock().unwrap().as_ref().unwrap()) +} + mod api; mod ui; #[derive(Debug)] pub struct AppState { - instance: Option, - username: Option, - password: Option, - - token: Option, - - client: reqwest::Client, - - search_result: Option, - stack: Stack, err_revealer: Revealer, err_label: Label, - downloads: Arc>>, } pub type State = Rc>; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Download { url: String, done: bool, output: PathBuf, } +lazy_static! { + static ref DOWNLOADS: Arc>> = Arc::new(Mutex::new(vec![])); +} + fn main() { if gtk::init().is_err() { println!("Failed to initialize GTK."); @@ -81,24 +98,20 @@ fn main() { window.set_title("Funkload"); window.set_default_size(1080, 720); - let (token, instance, user) = fs::read("data.json").ok().and_then(|f| - serde_json::from_slice(&f).map(|json: serde_json::Value| ( - json["token"].as_str().map(ToString::to_string), - json["instance"].as_str().map(ToString::to_string), - json["username"].as_str().map(ToString::to_string), - )).ok() - ).unwrap_or((None, None, None)); + let connected = fs::read("data.json").ok().and_then(|f| { + let json: serde_json::Value = serde_json::from_slice(&f).ok()?; + let mut api_ctx = crate::api::API.lock().ok()?; + let mut ctx = api::RequestContext::new(json["instance"].as_str()?.to_string()); + ctx.auth(json["token"].as_str()?.to_string()); + *api_ctx = Some(ctx); + + Some(()) + }).is_some(); let err_revealer = Revealer::new(); let err_label = Label::new("Error"); err_revealer.add(&err_label); let state = Rc::new(RefCell::new(AppState { - client: reqwest::Client::new(), - token: token, - instance: instance, - username: user, - password: None, - search_result: None, stack: Stack::new(), err_revealer: err_revealer, err_label: err_label, @@ -113,24 +126,19 @@ fn main() { window.add(&scrolled); window.show_all(); - if state.borrow().instance.is_some() && state.borrow().token.is_some() { - let main_page = ui::main_page::render(state.clone()); + if connected { + let main_page = ui::main_page::render(); state.borrow().stack.add_named(&main_page, "main"); state.borrow().stack.set_visible_child_name("main"); - // state.borrow_mut().stack.show_all(); } - window.connect_delete_event(clone!(state => move |_, _| { + window.connect_delete_event(move |_, _| { gtk::main_quit(); - fs::write("data.json", serde_json::to_string(&json!({ - "token": state.borrow().token.clone(), - "instance": state.borrow().instance.clone(), - "username": state.borrow().username.clone(), - })).unwrap()).unwrap(); + fs::write("data.json", serde_json::to_string(&client!().to_json()).unwrap()).unwrap(); Inhibit(false) - })); + }); gtk::main(); } diff --git a/src/ui/card.rs b/src/ui/card.rs index 1b64225..1baeea0 100644 --- a/src/ui/card.rs +++ b/src/ui/card.rs @@ -1,84 +1,92 @@ use gtk::*; -use std::{fs, thread}; -use crate::{Download, State, api, ui::network_image::NetworkImage}; +use std::{fs, thread, sync::mpsc::channel, rc::Rc, cell::RefCell}; +use crate::{Download, api::{self, execute}, ui::network_image::NetworkImage}; -pub struct Card { - model: T, - state: State, -} +pub fn render(model: T) -> Rc> where T: CardModel + 'static { + let card = Grid::new(); + card.set_column_spacing(12); + card.set_valign(Align::Start); -impl Card where T: CardModel { - pub fn new(model: T, state: State) -> Card { - Card { - model, - state, - } + if let Some(url) = model.image_url() { + let img = NetworkImage::new(url); + card.attach(&*img.img.borrow(), 0, 0, 1, 2); } - pub fn render(&self) -> Grid { - let card = Grid::new(); - card.set_column_spacing(12); - card.set_valign(Align::Start); + let main_text = Label::new(model.text().as_ref()); + main_text.get_style_context().map(|c| c.add_class("h3")); + main_text.set_hexpand(true); + main_text.set_halign(Align::Start); + let sub_text = Label::new(model.subtext().as_ref()); + sub_text.get_style_context().map(|c| c.add_class("dim-label")); + sub_text.set_hexpand(true); + sub_text.set_halign(Align::Start); - if let Some(url) = self.model.image_url() { - let img = NetworkImage::new(format!("https://{}{}", self.state.borrow().instance.clone().unwrap(), url)); - card.attach(&*img.img.borrow(), 0, 0, 1, 2); - } + let dl_bt = Button::new_with_label("Download"); + dl_bt.set_valign(Align::Center); + dl_bt.set_vexpand(true); + dl_bt.get_style_context().map(|c| c.add_class("suggested-action")); - let main_text = Label::new(self.model.text().as_ref()); - main_text.get_style_context().map(|c| c.add_class("h3")); - main_text.set_hexpand(true); - main_text.set_halign(Align::Start); - let sub_text = Label::new(self.model.subtext().as_ref()); - sub_text.get_style_context().map(|c| c.add_class("dim-label")); - sub_text.set_hexpand(true); - sub_text.set_halign(Align::Start); + rc!(dl_bt, card); + { + clone!(dl_bt, card); + wait!({ // Fetch the list of files to download + let (tx, rx) = channel(); + thread::spawn(move || { + let dl_list = model.downloads(); + tx.send(dl_list).unwrap(); + }); + rx + } => | const dl_list | { + let dl_bt = dl_bt.borrow(); + println!("DLs: {:?}", dl_list); + if dl_list.is_empty() { // Nothing to download + dl_bt.set_label("Not available"); + dl_bt.set_sensitive(false); + } else { + clone!(dl_list); + dl_bt.connect_clicked(move |_| { + for dl in dl_list.clone() { + thread::spawn(move || { + let mut res = client!().get(&dl.url).send().unwrap(); - let dl_bt = Button::new_with_label("Download"); - dl_bt.set_valign(Align::Center); - dl_bt.set_vexpand(true); - dl_bt.get_style_context().map(|c| c.add_class("suggested-action")); + let ext = res.headers() + .get(reqwest::header::CONTENT_DISPOSITION).and_then(|h| h.to_str().ok()) + .unwrap_or(".mp3") + .rsplitn(2, ".").next().unwrap_or("mp3"); - let dl_list = self.model.downloads(self.state.clone()); - if dl_list.len() > 1 { // Not only one song - let more_bt = Button::new_with_label("Details"); - more_bt.set_valign(Align::Center); - more_bt.set_vexpand(true); - card.attach(&more_bt, 2, 0, 1, 2); - } + fs::create_dir_all(dl.output.clone().parent().unwrap()).unwrap(); + let mut out = dl.output.clone(); + out.set_extension(ext); + let mut file = fs::File::create(out).unwrap(); - let state = self.state.clone(); - let model = self.model.clone(); - dl_bt.connect_clicked(clone!(state, model => move |_| { - let downloads = state.borrow().downloads.clone(); - for dl in model.downloads(state.clone()) { - let token = state.borrow().token.clone().unwrap_or_default(); - thread::spawn(move || { - fs::create_dir_all(dl.output.clone().parent().unwrap()).unwrap(); - let mut file = fs::File::create(dl.output.clone()).unwrap(); - println!("saving {} in {:?}", dl.url.clone(), dl.output.clone()); - reqwest::Client::new() - .get(&dl.url) - .header(reqwest::header::AUTHORIZATION, format!("JWT {}", token.clone())) - .query(&[( "jwt", token )]) - .send() - .unwrap() - .copy_to(&mut file) - .unwrap(); - println!("saved {:?}", dl.output); + println!("saving {} in {:?}", dl.url.clone(), dl.output.clone()); + res.copy_to(&mut file).unwrap(); + println!("saved {:?}", dl.output); + }); + } }); } - })); + if dl_list.len() > 1 { // Not only one song + let more_bt = Button::new_with_label("Details"); + more_bt.set_valign(Align::Center); + more_bt.set_vexpand(true); + card.borrow().attach(&more_bt, 2, 0, 1, 2); + } + }); + } + + { + let card = card.borrow(); card.attach(&main_text, 1, 0, 1, 1); card.attach(&sub_text, 1, 1, 1, 1); - card.attach(&dl_bt, 3, 0, 1, 2); - - card + card.attach(&*dl_bt.borrow(), 3, 0, 1, 2); } + + card } -pub trait CardModel: Clone { +pub trait CardModel: Clone + Send + Sync { fn text(&self) -> String; fn subtext(&self) -> String { String::new() @@ -87,7 +95,7 @@ pub trait CardModel: Clone { None } - fn downloads(&self, state: State) -> Vec; + fn downloads(&self) -> Vec; } impl CardModel for api::Artist { @@ -105,25 +113,20 @@ impl CardModel for api::Artist { .and_then(|album| album.cover.medium_square_crop.clone()) } - fn downloads(&self, state: State) -> Vec { + fn downloads(&self) -> Vec { let mut dls = vec![]; for album in self.albums.clone().unwrap_or_default() { - let album: api::Album = reqwest::Client::new() - .get(&format!("https://{}/api/v1/albums/{}/", state.borrow().instance.clone().unwrap(), album.id)) - .header(reqwest::header::AUTHORIZATION, format!("JWT {}", state.borrow().token.clone().unwrap_or_default())) - .send() - .unwrap() - .json() - .unwrap(); + let album: api::Album = client!().get(&format!("/api/v1/albums/{}/", album.id)) + .send().unwrap() + .json().unwrap(); for track in album.tracks.unwrap_or_default() { - let upload = match api::Upload::get_for_track(track.id, state.borrow().instance.clone().unwrap(), state.borrow().token.clone().unwrap()) { - Some(u) => u, - _ => continue, - }; dls.push(Download { - url: format!("https://{}{}", state.borrow().instance.clone().unwrap(), upload.listen_url), - output: dirs::audio_dir().unwrap().join(self.name.clone()).join(album.title.clone()).join(format!("{}.{}", track.title.clone(), upload.extension)), + url: track.listen_url.clone(), + output: dirs::audio_dir().unwrap() + .join(self.name.clone()) + .join(album.title.clone()) + .join(format!("{}.mp3", track.title.clone())), done: false, }); } @@ -145,13 +148,16 @@ impl CardModel for api::Album { self.cover.medium_square_crop.clone() } - fn downloads(&self, state: State) -> Vec { - self.tracks.clone().unwrap_or_default().iter().filter_map(|track| - api::Upload::get_for_track(track.id, state.borrow().instance.clone().unwrap(), state.borrow().token.clone().unwrap()).map(|u| Download { - url: format!("https://{}{}", state.borrow().instance.clone().unwrap(), u.listen_url), - output: dirs::audio_dir().unwrap().join(self.artist.name.clone()).join(self.title.clone()).join(format!("{}.{}", track.title.clone(), u.extension)), + fn downloads(&self) -> Vec { + self.tracks.clone().unwrap_or_default().iter().map(|track| + Download { + url: track.listen_url.clone(), + output: dirs::audio_dir().unwrap() + .join(self.artist.name.clone()) + .join(self.title.clone()) + .join(format!("{}.mp3", track.title.clone())), done: false, - }) + } ).collect() } } @@ -169,16 +175,13 @@ impl CardModel for api::Track { self.album.cover.medium_square_crop.clone() } - fn downloads(&self, state: State) -> Vec { - println!("yoy"); - let upload = match api::Upload::get_for_track(self.id, state.borrow().instance.clone().unwrap(), state.borrow().token.clone().unwrap()) { - Some(u) => u, - _ => return vec![] - }; - println!("yay"); + fn downloads(&self) -> Vec { vec![Download { - url: format!("https://{}{}", state.borrow().instance.clone().unwrap(), upload.listen_url), - output: dirs::audio_dir().unwrap().join(self.artist.name.clone()).join(self.album.title.clone()).join(format!("{}.{}", self.title.clone(), upload.extension)), + url: self.listen_url.clone(), + output: dirs::audio_dir().unwrap() + .join(self.artist.name.clone()) + .join(self.album.title.clone()) + .join(format!("{}.mp3", self.title.clone())), done: false, }] } diff --git a/src/ui/login_page.rs b/src/ui/login_page.rs index 414de42..92001ee 100644 --- a/src/ui/login_page.rs +++ b/src/ui/login_page.rs @@ -2,7 +2,6 @@ use gtk::*; use std::{ rc::Rc, cell::RefCell, - ops::Deref }; use crate::{State, api::*, ui::title}; @@ -14,10 +13,8 @@ pub fn render(state: State) -> gtk::Box { let title = title("Login"); let instance = Input::new("Instance URL") - .with_placeholder("demo.funkwhale.audio") - .with_default(state.borrow().instance.clone().unwrap_or_default()); - let username = Input::new("Username") - .with_default(state.borrow().username.clone().unwrap_or_default()); + .with_placeholder("demo.funkwhale.audio"); + let username = Input::new("Username"); let password = Input::new_password("Password"); let login_bt = Button::new_with_label("Login"); @@ -27,7 +24,7 @@ pub fn render(state: State) -> gtk::Box { instance, username, password ))); login_bt.connect_clicked(clone!(state, widgets => move |_| { - let mut api_ctx = crate::api::API.lock().expect("1"); + let mut api_ctx = crate::api::API.lock().unwrap(); *api_ctx = Some(RequestContext::new( widgets.borrow().0.get_text().unwrap() )); @@ -39,11 +36,11 @@ pub fn render(state: State) -> gtk::Box { })) => |res| { let res: Result<_, _> = res.json::>().unwrap().into(); - if let Some(ref mut api) = *crate::api::API.lock().expect("3") { - api.auth(res.unwrap().token.clone()); + if let Some(ref mut client) = *crate::api::API.lock().unwrap() { + client.auth(res.unwrap().token.clone()); } - state.borrow_mut().stack.add_named(&crate::ui::main_page::render(state.clone()), "main"); + state.borrow_mut().stack.add_named(&crate::ui::main_page::render(), "main"); state.borrow_mut().stack.set_visible_child_name("main"); state.borrow_mut().stack.show_all(); }); @@ -84,11 +81,6 @@ impl<'a> Input<'a> { self } - fn with_default>(self, def: S) -> Input<'a> { - self.entry.set_text(def.as_ref()); - self - } - fn get_text(&self) -> Option { self.entry.get_text() } diff --git a/src/ui/main_page.rs b/src/ui/main_page.rs index d54acbe..d1f47d9 100644 --- a/src/ui/main_page.rs +++ b/src/ui/main_page.rs @@ -2,49 +2,23 @@ use gdk::ContextExt; use gdk_pixbuf::PixbufExt; use gtk::*; use std::{ - cell::RefCell, - rc::Rc, - ops::Deref, fs, }; -use crate::{State, api, ui::{title, card::Card}}; +use crate::{api::{self, execute}, ui::{title, card}}; -pub fn render(state: State) -> gtk::Box { +pub fn render() -> gtk::Box { let cont = gtk::Box::new(Orientation::Vertical, 12); cont.set_margin_top(48); cont.set_margin_bottom(48); cont.set_margin_start(96); cont.set_margin_end(96); - let avatar_path = dirs::cache_dir().unwrap().join("funkload-avatar.png"); - /*let user: api::UserInfo = reqwest::Client::new() - .get(&format!("https://{}/api/v1/users/users/me/", state.borrow().instance.clone().unwrap())) - .header(reqwest::header::AUTHORIZATION, format!("JWT {}", state.borrow().token.clone().unwrap_or_default())) - .send() - .unwrap() - .json() - .unwrap(); - let pb = match user.avatar.medium_square_crop { - Some(url) => { - let mut avatar_file = fs::File::create(avatar_path.clone()).unwrap(); - reqwest::Client::new() - .get(&format!("https://{}{}", state.borrow().instance.clone().unwrap(), url)) - .header(reqwest::header::AUTHORIZATION, format!("JWT {}", state.borrow().token.clone().unwrap_or_default())) - .send() - .unwrap() - .copy_to(&mut avatar_file) - .unwrap(); - gdk_pixbuf::Pixbuf::new_from_file_at_scale(avatar_path, 128, 128, true).unwrap() - }, - None => { - IconTheme::get_default().unwrap().load_icon("avatar-default", 128, IconLookupFlags::empty()).unwrap().unwrap() - } - }; + let avatar_path = dirs::cache_dir().unwrap().join("funkload").join("avatar.png"); - let avatar = DrawingArea::new(); + let avatar = DrawingArea::new(); avatar.set_size_request(128, 128); avatar.set_halign(Align::Center); - avatar.connect_draw(move |da, g| { // More or less stolen from Fractal (https://gitlab.gnome.org/GNOME/fractal/blob/master/fractal-gtk/src/widgets/avatar.rs) + avatar.connect_draw(clone!(avatar_path => move |da, g| { // More or less stolen from Fractal (https://gitlab.gnome.org/GNOME/fractal/blob/master/fractal-gtk/src/widgets/avatar.rs) use std::f64::consts::PI; let width = 128.0f64; let height = 128.0f64; @@ -63,6 +37,9 @@ pub fn render(state: State) -> gtk::Box { ); g.clip(); + let pb = gdk_pixbuf::Pixbuf::new_from_file_at_scale(avatar_path.clone(), 128, 128, true) + .unwrap_or_else(|_| IconTheme::get_default().unwrap().load_icon("avatar-default", 128, IconLookupFlags::empty()).unwrap().unwrap()); + let hpos: f64 = (width - (pb.get_height()) as f64) / 2.0; g.set_source_pixbuf(&pb, 0.0, hpos); @@ -70,12 +47,11 @@ pub fn render(state: State) -> gtk::Box { g.fill(); Inhibit(false) - }); - + })); cont.add(&avatar); - let lbl = Label::new(format!("Welcome {}.", user.username).as_ref()); - lbl.get_style_context().map(|c| c.add_class("h1")); - cont.add(&lbl);*/ + let welcome = Label::new("Welcome."); + welcome.get_style_context().map(|c| c.add_class("h1")); + cont.add(&welcome); let search = SearchEntry::new(); search.set_placeholder_text("Search"); @@ -85,67 +61,64 @@ pub fn render(state: State) -> gtk::Box { results.set_valign(Align::Start); cont.add(&results); - let widgets = Rc::new(RefCell::new( - (search, results) - )); - let state = state.clone(); - widgets.clone().borrow().0.connect_activate(move |_| { - let res: api::SearchResult = reqwest::Client::new() - .get(&format!("https://{}/api/v1/search", state.borrow().instance.clone().unwrap())) - .header(reqwest::header::AUTHORIZATION, format!("JWT {}", state.borrow().token.clone().unwrap_or_default())) - .query(&api::SearchQuery { - query: widgets.borrow().deref().0.get_text().unwrap_or_default() - }) - .send() - .unwrap() - .json() - .unwrap(); + rc!(welcome, avatar, results); + clone!(welcome, avatar, results, avatar_path); + wait!(execute(client!().get("/api/v1/users/users/me")) => |res| { + let res: api::UserInfo = res.json().unwrap(); - state.borrow_mut().search_result = Some(res.clone()); - println!("{:#?}", res); - update_results(state.clone(), &widgets.borrow().1); + welcome.borrow().set_text(format!("Welcome {}.", res.username).as_ref()); + + clone!(avatar_path, avatar); + wait!(execute(client!().get(&res.avatar.medium_square_crop.unwrap_or_default())) => |avatar_dl| { + let mut avatar_file = fs::File::create(avatar_path.clone()).unwrap(); + avatar_dl.copy_to(&mut avatar_file).unwrap(); + avatar.borrow().queue_draw(); + }); + }); + + search.connect_activate(move |s| { + let results = results.clone(); + wait!(execute(client!().get("/api/v1/search").query(&api::SearchQuery { + query: s.get_text().unwrap_or_default() + })) => |res| { + update_results(res.json().unwrap(), &results.borrow()); + }); }); cont.show_all(); cont } -fn update_results(state: State, cont: >k::Box) { +fn update_results(res: api::SearchResult, cont: >k::Box) { for ch in cont.get_children() { cont.remove(&ch); } - match &state.borrow().search_result { - Some(res) => { - if res.artists.is_empty() && res.albums.is_empty() && res.tracks.is_empty() { - cont.add(&Label::new("No results. Try something else.")); - } + if res.artists.is_empty() && res.albums.is_empty() && res.tracks.is_empty() { + cont.add(&Label::new("No results. Try something else.")); + } - if !res.artists.is_empty() { - cont.add(&title("Artists")); - for artist in res.artists.clone() { - cont.add(&Card::new(artist, state.clone()).render()); - } - } - - if !res.albums.is_empty() { - cont.add(&title("Albums")); - for album in res.albums.clone() { - cont.add(&Card::new(album, state.clone()).render()); - } - } - - if !res.tracks.is_empty() { - cont.add(&title("Songs")); - for track in res.tracks.clone() { - cont.add(&Card::new(track, state.clone()).render()); - } - } - }, - None => { - cont.add(&Label::new("Try to search something")); + if !res.artists.is_empty() { + cont.add(&title("Artists")); + for artist in res.artists.clone() { + cont.add(&*card::render(artist).borrow()); } } + + if !res.albums.is_empty() { + cont.add(&title("Albums")); + for album in res.albums.clone() { + cont.add(&*card::render(album).borrow()); + } + } + + if !res.tracks.is_empty() { + cont.add(&title("Songs")); + for track in res.tracks.clone() { + cont.add(&*card::render(track).borrow()); + } + } + cont.show_all(); } diff --git a/src/ui/network_image.rs b/src/ui/network_image.rs index b7a2c59..593a52d 100644 --- a/src/ui/network_image.rs +++ b/src/ui/network_image.rs @@ -1,8 +1,9 @@ use gtk::*; use std::{ - sync::mpsc, cell::RefCell, - fs, thread, rc::Rc, time::Duration, + cell::RefCell, + fs, rc::Rc, }; +use crate::api::execute; pub struct NetworkImage { pub img: Rc>, @@ -10,34 +11,27 @@ pub struct NetworkImage { impl NetworkImage { pub fn new(url: String) -> NetworkImage { - let image = Rc::new(RefCell::new( - Image::new_from_icon_name("image-loading", 4) - )); + let image = Image::new_from_icon_name("image-loading", 4); + rc!(image); + let dest_file = url.split("/media/").last().unwrap().replace('/', "-"); let dest = dirs::cache_dir().unwrap().join(env!("CARGO_PKG_NAME")).join(dest_file.to_string()); - let (tx, rx) = mpsc::channel(); - thread::spawn(clone!(dest => move || { - fs::create_dir_all(dest.parent().unwrap()).unwrap(); - let mut file = fs::File::create(dest.clone()).unwrap(); // TODO: check if it exists - reqwest::Client::new() - .get(&url) - .send() - .unwrap() - .copy_to(&mut file) - .unwrap(); - tx.send(dest).unwrap(); - })); - gtk::idle_add(clone!(image => move || { // Check every 0.5s - match rx.recv_timeout(Duration::from_millis(500)) { - Err(_) => glib::Continue(true), - Ok(res) => { - let pb = gdk_pixbuf::Pixbuf::new_from_file_at_scale(res, 64, 64, true).unwrap(); - image.borrow().set_from_pixbuf(&pb); - glib::Continue(false) - } - } - })); + if dest.exists() { + let pb = gdk_pixbuf::Pixbuf::new_from_file_at_scale(dest, 64, 64, true).unwrap(); + image.borrow().set_from_pixbuf(&pb); + } else { + clone!(image); + wait!(execute(client!().get(&url)) => |res| { + fs::create_dir_all(dest.parent().unwrap()).unwrap(); + let mut file = fs::File::create(dest.clone()).unwrap(); + res.copy_to(&mut file).unwrap(); + + let pb = gdk_pixbuf::Pixbuf::new_from_file_at_scale(dest.clone(), 64, 64, true).unwrap(); + image.borrow().set_from_pixbuf(&pb); + }); + } + NetworkImage { img: image.clone(), }