1
0
mirror of https://github.com/elegaanz/mobydick synced 2025-02-19 20:50:36 +01:00

Thread pool everywhere

This commit is contained in:
Baptiste Gelez 2019-02-08 13:35:58 +01:00
parent 3ccf7daff6
commit 03b02f54d4
6 changed files with 232 additions and 275 deletions

View File

@ -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<Vec<Upload>>,
}
#[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<Vec<Upload>>,
}
#[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<Upload> {
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()
}
}

View File

@ -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<String>,
username: Option<String>,
password: Option<String>,
token: Option<String>,
client: reqwest::Client,
search_result: Option<api::SearchResult>,
stack: Stack,
err_revealer: Revealer,
err_label: Label,
downloads: Arc<RefCell<Vec<Download>>>,
}
pub type State = Rc<RefCell<AppState>>;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Download {
url: String,
done: bool,
output: PathBuf,
}
lazy_static! {
static ref DOWNLOADS: Arc<Mutex<Vec<Download>>> = 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();
}

View File

@ -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<T: CardModel> {
model: T,
state: State,
}
pub fn render<T>(model: T) -> Rc<RefCell<Grid>> where T: CardModel + 'static {
let card = Grid::new();
card.set_column_spacing(12);
card.set_valign(Align::Start);
impl<T: 'static> Card<T> where T: CardModel {
pub fn new(model: T, state: State) -> Card<T> {
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<Download>;
fn downloads(&self) -> Vec<Download>;
}
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<Download> {
fn downloads(&self) -> Vec<Download> {
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<Download> {
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<Download> {
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<Download> {
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<Download> {
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,
}]
}

View File

@ -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::<ApiResult<LoginInfo>>().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<S: AsRef<str>>(self, def: S) -> Input<'a> {
self.entry.set_text(def.as_ref());
self
}
fn get_text(&self) -> Option<String> {
self.entry.get_text()
}

View File

@ -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: &gtk::Box) {
fn update_results(res: api::SearchResult, cont: &gtk::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();
}

View File

@ -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<RefCell<Image>>,
@ -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(),
}