add wrapper class for api client

This commit is contained in:
Erica Z 2024-11-26 13:57:54 +01:00
parent a249a00120
commit 8c61f7545b
8 changed files with 87 additions and 53 deletions

View file

@ -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;
} }
}; };
}; };

View file

@ -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)]

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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) {}

View file

@ -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
View file

@ -0,0 +1,2 @@
pub mod client;
pub use client::Client;

44
src/wrappers/client.rs Normal file
View 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")
}
}