add wrapper class for api client
This commit is contained in:
parent
a249a00120
commit
8c61f7545b
8 changed files with 87 additions and 53 deletions
|
@ -41,24 +41,28 @@ template $AudreyUiWindow: Adw.ApplicationWindow {
|
||||||
child: Box {
|
child: Box {
|
||||||
orientation: vertical;
|
orientation: vertical;
|
||||||
|
|
||||||
$AudreyUiAlbumCarousel carousel1 {
|
$AudreyUiAlbumCarousel {
|
||||||
title: _("Explore from your library");
|
title: _("Explore from your library");
|
||||||
type: random;
|
type: random;
|
||||||
|
client: bind template.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
$AudreyUiAlbumCarousel carousel2 {
|
$AudreyUiAlbumCarousel {
|
||||||
title: _("Newly added releases");
|
title: _("Newly added releases");
|
||||||
type: newest;
|
type: newest;
|
||||||
|
client: bind template.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
$AudreyUiAlbumCarousel carousel3 {
|
$AudreyUiAlbumCarousel {
|
||||||
title: _("Recently played");
|
title: _("Recently played");
|
||||||
type: recent;
|
type: recent;
|
||||||
|
client: bind template.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
$AudreyUiAlbumCarousel carousel4 {
|
$AudreyUiAlbumCarousel {
|
||||||
title: _("Most played");
|
title: _("Most played");
|
||||||
type: frequent;
|
type: frequent;
|
||||||
|
client: bind template.client;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ use gtk::{gio, glib};
|
||||||
pub mod mpv;
|
pub mod mpv;
|
||||||
|
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
pub mod wrappers;
|
||||||
|
|
||||||
fn init_tracing() {
|
fn init_tracing() {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|
|
@ -74,8 +74,7 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Song {
|
impl Song {
|
||||||
pub fn from_child(window: &crate::ui::Window, song: &subsonic::schema::Child) -> Self {
|
pub fn from_child(client: &crate::wrappers::Client, song: &subsonic::schema::Child) -> Self {
|
||||||
let api = window.api();
|
|
||||||
let song: Self = Object::builder()
|
let song: Self = Object::builder()
|
||||||
.property("id", &song.id)
|
.property("id", &song.id)
|
||||||
.property("title", &song.title)
|
.property("title", &song.title)
|
||||||
|
@ -84,20 +83,20 @@ impl Song {
|
||||||
.property("genre", &song.genre)
|
.property("genre", &song.genre)
|
||||||
.property("duration", song.duration as i64)
|
.property("duration", song.duration as i64)
|
||||||
.property("track", song.track.map(i64::from).unwrap_or(-1))
|
.property("track", song.track.map(i64::from).unwrap_or(-1))
|
||||||
.property("stream-url", api.stream_url(&song.id).as_str())
|
.property("stream-url", client.stream_url(&song.id).as_str())
|
||||||
.property("cover-art", &song.cover_art)
|
.property("cover-art", &song.cover_art)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
song
|
song
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn need_thumbnail(&self, window: &crate::ui::Window) {
|
pub fn need_thumbnail(&self, client: &crate::wrappers::Client, scale_factor: u32) {
|
||||||
let api = window.api();
|
let api = client.clone();
|
||||||
let mut thumbnail_loading = self.imp().thumbnail_loading.borrow_mut();
|
let mut thumbnail_loading = self.imp().thumbnail_loading.borrow_mut();
|
||||||
if thumbnail_loading.is_none() {
|
if thumbnail_loading.is_none() {
|
||||||
let id = self.id();
|
let id = self.id();
|
||||||
let song_weak = self.downgrade();
|
let song_weak = self.downgrade();
|
||||||
let scale_factor = window.scale_factor() as u32; // NOTE: assumed constant
|
let scale_factor = scale_factor; // NOTE: assumed constant
|
||||||
*thumbnail_loading = Some(glib::spawn_future_local(async move {
|
*thumbnail_loading = Some(glib::spawn_future_local(async move {
|
||||||
let _permit = SEM.acquire().await.unwrap();
|
let _permit = SEM.acquire().await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ use adw::subclass::prelude::*;
|
||||||
use adw::{gio, glib};
|
use adw::{gio, glib};
|
||||||
use glib::subclass::InitializingObject;
|
use glib::subclass::InitializingObject;
|
||||||
use std::cell::{Cell, OnceCell, RefCell};
|
use std::cell::{Cell, OnceCell, RefCell};
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use tracing::{event, Level};
|
use tracing::{event, Level};
|
||||||
|
|
||||||
|
@ -29,10 +28,10 @@ mod imp {
|
||||||
#[derive(gtk::CompositeTemplate, Default)]
|
#[derive(gtk::CompositeTemplate, Default)]
|
||||||
#[template(resource = "/eu/callcc/audrey/album_carousel.ui")]
|
#[template(resource = "/eu/callcc/audrey/album_carousel.ui")]
|
||||||
pub struct AlbumCarousel {
|
pub struct AlbumCarousel {
|
||||||
pub api: RefCell<Option<Rc<crate::subsonic::Client>>>,
|
|
||||||
title: RefCell<String>,
|
title: RefCell<String>,
|
||||||
model: OnceCell<gio::ListStore>,
|
model: OnceCell<gio::ListStore>,
|
||||||
r#type: Cell<Type>,
|
r#type: Cell<Type>,
|
||||||
|
client: RefCell<Option<crate::wrappers::Client>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -61,6 +60,7 @@ mod imp {
|
||||||
glib::ParamSpecObject::builder::<gio::ListModel>("model")
|
glib::ParamSpecObject::builder::<gio::ListModel>("model")
|
||||||
.read_only()
|
.read_only()
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecObject::builder::<crate::wrappers::Client>("client").build(),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ mod imp {
|
||||||
1 => self.title.borrow().clone().into(),
|
1 => self.title.borrow().clone().into(),
|
||||||
2 => self.r#type.get().into(),
|
2 => self.r#type.get().into(),
|
||||||
3 => self.model().into(),
|
3 => self.model().into(),
|
||||||
|
4 => self.client.borrow().clone().into(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +84,10 @@ mod imp {
|
||||||
self.r#type.replace(value.get_owned().unwrap());
|
self.r#type.replace(value.get_owned().unwrap());
|
||||||
self.update_model();
|
self.update_model();
|
||||||
}
|
}
|
||||||
|
4 => {
|
||||||
|
self.client.replace(value.get_owned().unwrap());
|
||||||
|
self.update_model();
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,13 +107,13 @@ mod imp {
|
||||||
pub fn update_model(&self) {
|
pub fn update_model(&self) {
|
||||||
self.model().remove_all();
|
self.model().remove_all();
|
||||||
|
|
||||||
let api = self.api.borrow();
|
let api = self.client.borrow();
|
||||||
let api = match api.as_ref() {
|
let api = match api.as_ref() {
|
||||||
Some(api) => api,
|
Some(api) => api,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let api = Rc::clone(&api);
|
let api = api.clone();
|
||||||
let carousel = self.obj().downgrade();
|
let carousel = self.obj().downgrade();
|
||||||
|
|
||||||
let type_ = match self.r#type.get() {
|
let type_ = match self.r#type.get() {
|
||||||
|
@ -145,10 +150,3 @@ glib::wrapper! {
|
||||||
@extends adw::Bin, gtk::Widget,
|
@extends adw::Bin, gtk::Widget,
|
||||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlbumCarousel {
|
|
||||||
pub fn set_api(&self, api: Option<&Rc<crate::subsonic::Client>>) {
|
|
||||||
self.imp().api.replace(api.map(Rc::clone));
|
|
||||||
self.imp().update_model();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -200,7 +200,10 @@ impl Song {
|
||||||
self.set_position(position);
|
self.set_position(position);
|
||||||
self.set_playlist_pos(window.playlist_pos());
|
self.set_playlist_pos(window.playlist_pos());
|
||||||
|
|
||||||
song.need_thumbnail(window);
|
song.need_thumbnail(
|
||||||
|
window.client().as_ref().unwrap(),
|
||||||
|
window.scale_factor() as u32,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn unbind(&self) {}
|
pub const fn unbind(&self) {}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::model::Song;
|
use crate::model::Song;
|
||||||
use crate::ui::AlbumCarousel;
|
|
||||||
use crate::util::buffering::BufferingPulseController;
|
use crate::util::buffering::BufferingPulseController;
|
||||||
use crate::{mpris, mpv};
|
use crate::{mpris, mpv};
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
|
@ -49,7 +48,8 @@ mod imp {
|
||||||
|
|
||||||
pub(super) setup: crate::ui::Setup,
|
pub(super) setup: crate::ui::Setup,
|
||||||
|
|
||||||
pub(super) api: RefCell<Option<Rc<crate::subsonic::Client>>>,
|
#[property(get, set, nullable)]
|
||||||
|
pub client: RefCell<Option<crate::wrappers::Client>>,
|
||||||
|
|
||||||
pub(super) mpv: mpv::Handle,
|
pub(super) mpv: mpv::Handle,
|
||||||
#[property(get)]
|
#[property(get)]
|
||||||
|
@ -91,15 +91,6 @@ mod imp {
|
||||||
refreshing_albums: Cell<bool>,
|
refreshing_albums: Cell<bool>,
|
||||||
|
|
||||||
css_provider: gtk::CssProvider,
|
css_provider: gtk::CssProvider,
|
||||||
|
|
||||||
#[template_child]
|
|
||||||
pub carousel1: TemplateChild<AlbumCarousel>,
|
|
||||||
#[template_child]
|
|
||||||
pub carousel2: TemplateChild<AlbumCarousel>,
|
|
||||||
#[template_child]
|
|
||||||
pub carousel3: TemplateChild<AlbumCarousel>,
|
|
||||||
#[template_child]
|
|
||||||
pub carousel4: TemplateChild<AlbumCarousel>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Window {
|
impl Default for Window {
|
||||||
|
@ -131,7 +122,7 @@ mod imp {
|
||||||
background: Default::default(),
|
background: Default::default(),
|
||||||
_song: (),
|
_song: (),
|
||||||
setup: Default::default(),
|
setup: Default::default(),
|
||||||
api: Default::default(),
|
client: Default::default(),
|
||||||
mpv,
|
mpv,
|
||||||
|
|
||||||
playlist_model: gio::ListStore::new::<Song>(),
|
playlist_model: gio::ListStore::new::<Song>(),
|
||||||
|
@ -162,11 +153,6 @@ mod imp {
|
||||||
refreshing_albums: Cell::new(true),
|
refreshing_albums: Cell::new(true),
|
||||||
|
|
||||||
css_provider: gtk::CssProvider::new(),
|
css_provider: gtk::CssProvider::new(),
|
||||||
|
|
||||||
carousel1: Default::default(),
|
|
||||||
carousel2: Default::default(),
|
|
||||||
carousel3: Default::default(),
|
|
||||||
carousel4: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -416,11 +402,11 @@ mod imp {
|
||||||
self.set_pause(false);
|
self.set_pause(false);
|
||||||
|
|
||||||
let api = {
|
let api = {
|
||||||
let api = self.api.borrow();
|
let api = self.client.borrow();
|
||||||
Rc::clone(api.as_ref().unwrap())
|
api.as_ref().unwrap().clone()
|
||||||
};
|
};
|
||||||
for song in api.random_songs(50).await.unwrap().into_iter() {
|
for song in api.random_songs(50).await.unwrap().into_iter() {
|
||||||
let song = Song::from_child(&self.obj(), &song);
|
let song = Song::from_child(&api, &song);
|
||||||
self.mpv
|
self.mpv
|
||||||
.command(["loadfile", &song.stream_url(), "append-play"])
|
.command(["loadfile", &song.stream_url(), "append-play"])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -757,7 +743,7 @@ mod imp {
|
||||||
if let Some(handle) = self
|
if let Some(handle) = self
|
||||||
.loading_cover_handle
|
.loading_cover_handle
|
||||||
.replace(Some(glib::spawn_future_local(async move {
|
.replace(Some(glib::spawn_future_local(async move {
|
||||||
let api = window.imp().api.borrow().as_ref().unwrap().clone();
|
let api = window.imp().client.borrow().as_ref().unwrap().clone();
|
||||||
let image = match api
|
let image = match api
|
||||||
.cover_art(&song_id, Some(800 * scale_factor)) // 800px times ui scale
|
.cover_art(&song_id, Some(800 * scale_factor)) // 800px times ui scale
|
||||||
.await
|
.await
|
||||||
|
@ -1032,14 +1018,15 @@ impl Window {
|
||||||
|
|
||||||
self.set_can_click_shuffle_all(true);
|
self.set_can_click_shuffle_all(true);
|
||||||
|
|
||||||
self.imp().carousel1.set_api(Some(&api));
|
if self
|
||||||
self.imp().carousel2.set_api(Some(&api));
|
.imp()
|
||||||
self.imp().carousel3.set_api(Some(&api));
|
.client
|
||||||
self.imp().carousel4.set_api(Some(&api));
|
.replace(Some(crate::wrappers::Client::new((*api).clone())))
|
||||||
|
.is_some()
|
||||||
if self.imp().api.replace(Some(api)).is_some() {
|
{
|
||||||
unimplemented!("changing the api object");
|
unimplemented!("changing the api object");
|
||||||
}
|
}
|
||||||
|
self.notify("client");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn playlist_next(&self) {
|
pub fn playlist_next(&self) {
|
||||||
|
@ -1153,8 +1140,4 @@ impl Window {
|
||||||
self.imp().mpv.command(["stop"]).unwrap();
|
self.imp().mpv.command(["stop"]).unwrap();
|
||||||
self.playlist_model().remove_all();
|
self.playlist_model().remove_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api(&self) -> Rc<crate::subsonic::Client> {
|
|
||||||
Rc::clone(self.imp().api.borrow().as_ref().unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
2
src/wrappers.rs
Normal file
2
src/wrappers.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod client;
|
||||||
|
pub use client::Client;
|
44
src/wrappers/client.rs
Normal file
44
src/wrappers/client.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// simple wrapper class so we can use the client object as a glib property
|
||||||
|
|
||||||
|
use crate::subsonic;
|
||||||
|
use glib::subclass::prelude::*;
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Client(pub OnceCell<subsonic::Client>);
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for Client {
|
||||||
|
const NAME: &'static str = "AudreyClient";
|
||||||
|
type Type = super::Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for Client {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct Client(ObjectSubclass<imp::Client>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn new(inner: subsonic::Client) -> Self {
|
||||||
|
let client: Self = glib::Object::new();
|
||||||
|
client.imp().0.set(inner).map_err(|_| ()).unwrap();
|
||||||
|
client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Client {
|
||||||
|
type Target = subsonic::Client;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.imp()
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.expect("tried to deref uninitialized client object")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue