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 {
orientation: vertical;
$AudreyUiAlbumCarousel carousel1 {
$AudreyUiAlbumCarousel {
title: _("Explore from your library");
type: random;
client: bind template.client;
}
$AudreyUiAlbumCarousel carousel2 {
$AudreyUiAlbumCarousel {
title: _("Newly added releases");
type: newest;
client: bind template.client;
}
$AudreyUiAlbumCarousel carousel3 {
$AudreyUiAlbumCarousel {
title: _("Recently played");
type: recent;
client: bind template.client;
}
$AudreyUiAlbumCarousel carousel4 {
$AudreyUiAlbumCarousel {
title: _("Most played");
type: frequent;
client: bind template.client;
}
};
};

View file

@ -18,6 +18,7 @@ use gtk::{gio, glib};
pub mod mpv;
pub mod util;
pub mod wrappers;
fn init_tracing() {
#[cfg(debug_assertions)]

View file

@ -74,8 +74,7 @@ glib::wrapper! {
}
impl Song {
pub fn from_child(window: &crate::ui::Window, song: &subsonic::schema::Child) -> Self {
let api = window.api();
pub fn from_child(client: &crate::wrappers::Client, song: &subsonic::schema::Child) -> Self {
let song: Self = Object::builder()
.property("id", &song.id)
.property("title", &song.title)
@ -84,20 +83,20 @@ impl Song {
.property("genre", &song.genre)
.property("duration", song.duration as i64)
.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)
.build();
song
}
pub fn need_thumbnail(&self, window: &crate::ui::Window) {
let api = window.api();
pub fn need_thumbnail(&self, client: &crate::wrappers::Client, scale_factor: u32) {
let api = client.clone();
let mut thumbnail_loading = self.imp().thumbnail_loading.borrow_mut();
if thumbnail_loading.is_none() {
let id = self.id();
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 {
let _permit = SEM.acquire().await.unwrap();

View file

@ -4,7 +4,6 @@ use adw::subclass::prelude::*;
use adw::{gio, glib};
use glib::subclass::InitializingObject;
use std::cell::{Cell, OnceCell, RefCell};
use std::rc::Rc;
use std::sync::OnceLock;
use tracing::{event, Level};
@ -29,10 +28,10 @@ mod imp {
#[derive(gtk::CompositeTemplate, Default)]
#[template(resource = "/eu/callcc/audrey/album_carousel.ui")]
pub struct AlbumCarousel {
pub api: RefCell<Option<Rc<crate::subsonic::Client>>>,
title: RefCell<String>,
model: OnceCell<gio::ListStore>,
r#type: Cell<Type>,
client: RefCell<Option<crate::wrappers::Client>>,
}
#[glib::object_subclass]
@ -61,6 +60,7 @@ mod imp {
glib::ParamSpecObject::builder::<gio::ListModel>("model")
.read_only()
.build(),
glib::ParamSpecObject::builder::<crate::wrappers::Client>("client").build(),
]
})
}
@ -70,6 +70,7 @@ mod imp {
1 => self.title.borrow().clone().into(),
2 => self.r#type.get().into(),
3 => self.model().into(),
4 => self.client.borrow().clone().into(),
_ => unreachable!(),
}
}
@ -83,6 +84,10 @@ mod imp {
self.r#type.replace(value.get_owned().unwrap());
self.update_model();
}
4 => {
self.client.replace(value.get_owned().unwrap());
self.update_model();
}
_ => unreachable!(),
}
}
@ -102,13 +107,13 @@ mod imp {
pub fn update_model(&self) {
self.model().remove_all();
let api = self.api.borrow();
let api = self.client.borrow();
let api = match api.as_ref() {
Some(api) => api,
None => return,
};
let api = Rc::clone(&api);
let api = api.clone();
let carousel = self.obj().downgrade();
let type_ = match self.r#type.get() {
@ -145,10 +150,3 @@ glib::wrapper! {
@extends adw::Bin, gtk::Widget,
@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_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) {}

View file

@ -1,5 +1,4 @@
use crate::model::Song;
use crate::ui::AlbumCarousel;
use crate::util::buffering::BufferingPulseController;
use crate::{mpris, mpv};
use adw::prelude::*;
@ -49,7 +48,8 @@ mod imp {
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,
#[property(get)]
@ -91,15 +91,6 @@ mod imp {
refreshing_albums: Cell<bool>,
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 {
@ -131,7 +122,7 @@ mod imp {
background: Default::default(),
_song: (),
setup: Default::default(),
api: Default::default(),
client: Default::default(),
mpv,
playlist_model: gio::ListStore::new::<Song>(),
@ -162,11 +153,6 @@ mod imp {
refreshing_albums: Cell::new(true),
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);
let api = {
let api = self.api.borrow();
Rc::clone(api.as_ref().unwrap())
let api = self.client.borrow();
api.as_ref().unwrap().clone()
};
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
.command(["loadfile", &song.stream_url(), "append-play"])
.unwrap();
@ -757,7 +743,7 @@ mod imp {
if let Some(handle) = self
.loading_cover_handle
.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
.cover_art(&song_id, Some(800 * scale_factor)) // 800px times ui scale
.await
@ -1032,14 +1018,15 @@ impl Window {
self.set_can_click_shuffle_all(true);
self.imp().carousel1.set_api(Some(&api));
self.imp().carousel2.set_api(Some(&api));
self.imp().carousel3.set_api(Some(&api));
self.imp().carousel4.set_api(Some(&api));
if self.imp().api.replace(Some(api)).is_some() {
if self
.imp()
.client
.replace(Some(crate::wrappers::Client::new((*api).clone())))
.is_some()
{
unimplemented!("changing the api object");
}
self.notify("client");
}
pub fn playlist_next(&self) {
@ -1153,8 +1140,4 @@ impl Window {
self.imp().mpv.command(["stop"]).unwrap();
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")
}
}