Compare commits
No commits in common. "5d3c22aaad34ac3bbff239e4899fbabf7dfebed1" and "e52adda2edb0776e899238c64bda852ca7246136" have entirely different histories.
5d3c22aaad
...
e52adda2ed
7 changed files with 99 additions and 188 deletions
|
@ -27,6 +27,8 @@ mod imp {
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
track: Cell<i64>,
|
track: Cell<i64>,
|
||||||
|
|
||||||
|
#[property(get, set)]
|
||||||
|
cover_art_url: RefCell<String>,
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
stream_url: RefCell<String>,
|
stream_url: RefCell<String>,
|
||||||
|
|
||||||
|
@ -81,7 +83,8 @@ impl Song {
|
||||||
.property("album", &song.album)
|
.property("album", &song.album)
|
||||||
.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)
|
||||||
|
.property("cover-art-url", api.cover_art_url(&song.id, 0).as_str())
|
||||||
.property("stream-url", api.stream_url(&song.id).as_str())
|
.property("stream-url", api.stream_url(&song.id).as_str())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -93,7 +96,7 @@ impl Song {
|
||||||
.thumbnail_loading
|
.thumbnail_loading
|
||||||
.replace(Some(glib::spawn_future_local(async move {
|
.replace(Some(glib::spawn_future_local(async move {
|
||||||
let bytes = api
|
let bytes = api
|
||||||
.cover_art(&id, Some(50)) // TODO: WidgetExt::scale_factor
|
.cover_art(&id, 50) // TODO: constify thumbnail size, maybe take dpi into account etc
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let song = match song_weak.upgrade() {
|
let song = match song_weak.upgrade() {
|
||||||
|
|
106
src/subsonic.rs
106
src/subsonic.rs
|
@ -1,8 +1,5 @@
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
|
||||||
mod album_list;
|
|
||||||
pub use album_list::AlbumListType;
|
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use md5::Digest;
|
use md5::Digest;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -52,13 +49,13 @@ impl From<reqwest::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
base_url: reqwest::Url,
|
base_url: reqwest::Url,
|
||||||
|
token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn random_salt(length: usize) -> String {
|
fn get_random_salt(length: usize) -> String {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
std::iter::repeat(())
|
std::iter::repeat(())
|
||||||
// 0.9: s/distributions/distr
|
// 0.9: s/distributions/distr
|
||||||
|
@ -73,7 +70,7 @@ impl Client {
|
||||||
const SALT_BYTES: usize = 8;
|
const SALT_BYTES: usize = 8;
|
||||||
|
|
||||||
// subsonic docs say to generate a salt per request, but that's completely unnecessary
|
// subsonic docs say to generate a salt per request, but that's completely unnecessary
|
||||||
let salt_hex = random_salt(SALT_BYTES);
|
let salt_hex = get_random_salt(SALT_BYTES);
|
||||||
|
|
||||||
let mut hasher = md5::Md5::new();
|
let mut hasher = md5::Md5::new();
|
||||||
hasher.update(password);
|
hasher.update(password);
|
||||||
|
@ -107,9 +104,14 @@ impl Client {
|
||||||
.user_agent(crate::USER_AGENT)
|
.user_agent(crate::USER_AGENT)
|
||||||
.build()?,
|
.build()?,
|
||||||
base_url,
|
base_url,
|
||||||
|
token: token.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn strip_token(&self, string: &str) -> String {
|
||||||
|
string.replace(&self.token, "[authentication token]")
|
||||||
|
}
|
||||||
|
|
||||||
async fn send<T: serde::de::DeserializeOwned + Send + 'static>(
|
async fn send<T: serde::de::DeserializeOwned + Send + 'static>(
|
||||||
&self,
|
&self,
|
||||||
request: reqwest::RequestBuilder,
|
request: reqwest::RequestBuilder,
|
||||||
|
@ -151,51 +153,10 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url(&self, path: &[&str], query: &[(&str, &str)]) -> url::Url {
|
async fn send_bytes(&self, request: reqwest::RequestBuilder) -> Result<Bytes, Error> {
|
||||||
let mut url = self.base_url.clone();
|
|
||||||
url.path_segments_mut()
|
|
||||||
// literally can't fail
|
|
||||||
.unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() })
|
|
||||||
.extend(path);
|
|
||||||
url.query_pairs_mut().extend_pairs(query);
|
|
||||||
url
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get<T: serde::de::DeserializeOwned + Send + 'static>(
|
|
||||||
&self,
|
|
||||||
path: &[&str],
|
|
||||||
query: &[(&str, &str)],
|
|
||||||
) -> Result<T, Error> {
|
|
||||||
self.send(self.client.get(self.url(path, query))).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn ping(&self) -> Result<(), Error> {
|
|
||||||
self.get(&["rest", "ping"], &[]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn random_songs(&self, size: u32) -> Result<Vec<schema::Child>, Error> {
|
|
||||||
self.get::<schema::RandomSongsOuter>(
|
|
||||||
&["rest", "getRandomSongs"],
|
|
||||||
&[("size", &size.to_string())],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|response| response.random_songs.song)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cover_art_url(&self, id: &str, size: Option<u32>) -> url::Url {
|
|
||||||
match size {
|
|
||||||
None => self.url(&["rest", "getCoverArt"], &[("id", id)]),
|
|
||||||
Some(size) => self.url(
|
|
||||||
&["rest", "getCoverArt"],
|
|
||||||
&[("id", id), ("size", &size.to_string())],
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn cover_art(&self, id: &str, size: Option<u32>) -> Result<Bytes, Error> {
|
|
||||||
let (sender, receiver) = async_channel::bounded(1);
|
let (sender, receiver) = async_channel::bounded(1);
|
||||||
|
|
||||||
let future = self.client.get(self.cover_art_url(id, size)).send();
|
let future = request.send();
|
||||||
runtime().spawn(async move {
|
runtime().spawn(async move {
|
||||||
async fn perform(
|
async fn perform(
|
||||||
response: Result<reqwest::Response, reqwest::Error>,
|
response: Result<reqwest::Response, reqwest::Error>,
|
||||||
|
@ -236,6 +197,53 @@ impl Client {
|
||||||
.expect("could not receive cover art bytes from tokio")
|
.expect("could not receive cover art bytes from tokio")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn url(&self, path: &[&str], query: &[(&str, &str)]) -> url::Url {
|
||||||
|
let mut url = self.base_url.clone();
|
||||||
|
url.path_segments_mut()
|
||||||
|
// literally can't fail
|
||||||
|
.unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() })
|
||||||
|
.extend(path);
|
||||||
|
url.query_pairs_mut().extend_pairs(query);
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get<T: serde::de::DeserializeOwned + Send + 'static>(
|
||||||
|
&self,
|
||||||
|
path: &[&str],
|
||||||
|
query: &[(&str, &str)],
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
self.send(self.client.get(self.url(path, query))).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ping(&self) -> Result<(), Error> {
|
||||||
|
self.get(&["rest", "ping"], &[]).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_random_songs(&self, size: u32) -> Result<Vec<schema::Child>, Error> {
|
||||||
|
self.get::<schema::RandomSongsOuter>(
|
||||||
|
&["rest", "getRandomSongs"],
|
||||||
|
&[("size", &size.to_string())],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|response| response.random_songs.song)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cover_art_url(&self, id: &str, size: u32) -> url::Url {
|
||||||
|
if size == 0 {
|
||||||
|
self.url(&["rest", "getCoverArt"], &[("id", id)])
|
||||||
|
} else {
|
||||||
|
self.url(
|
||||||
|
&["rest", "getCoverArt"],
|
||||||
|
&[("id", id), ("size", &size.to_string())],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cover_art(&self, id: &str, size: u32) -> Result<Bytes, Error> {
|
||||||
|
self.send_bytes(self.client.get(self.cover_art_url(id, size)))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stream_url(&self, id: &str) -> url::Url {
|
pub fn stream_url(&self, id: &str) -> url::Url {
|
||||||
self.url(&["rest", "stream"], &[("id", id)])
|
self.url(&["rest", "stream"], &[("id", id)])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
use super::{schema, Client, Error};
|
|
||||||
use futures::{Stream, TryStreamExt};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum AlbumListType {
|
|
||||||
Random,
|
|
||||||
Newest,
|
|
||||||
Highest,
|
|
||||||
Frequent,
|
|
||||||
Recent,
|
|
||||||
AlphabeticalByName,
|
|
||||||
AlphabeticalByArtist,
|
|
||||||
ByYear { from: u32, to: u32 },
|
|
||||||
ByGenre(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub async fn album_list(
|
|
||||||
&self,
|
|
||||||
type_: &AlbumListType,
|
|
||||||
size: u32,
|
|
||||||
offset: u32,
|
|
||||||
) -> Result<Vec<schema::AlbumID3>, Error> {
|
|
||||||
match type_ {
|
|
||||||
AlbumListType::Newest => {
|
|
||||||
self.get::<schema::AlbumList2Outer>(
|
|
||||||
&["rest", "getAlbumList2"],
|
|
||||||
&[
|
|
||||||
("type", "newest"),
|
|
||||||
("size", &size.to_string()),
|
|
||||||
("offset", &offset.to_string()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => todo!("{type_:?}"),
|
|
||||||
}
|
|
||||||
.map(|response| match response.album_list2.album {
|
|
||||||
Some(albums) => albums,
|
|
||||||
None => vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn album_list_full(
|
|
||||||
&self,
|
|
||||||
type_: AlbumListType,
|
|
||||||
) -> impl Stream<Item = Result<schema::AlbumID3, Error>> + use<'_> {
|
|
||||||
futures::stream::try_unfold(0usize, move |offset| {
|
|
||||||
let type_ = type_.clone();
|
|
||||||
async move {
|
|
||||||
match self.album_list(&type_, 500, offset as u32).await {
|
|
||||||
Ok(albums) if albums.len() == 0 => Ok(None),
|
|
||||||
Ok(albums) => {
|
|
||||||
let next_offset = offset + albums.len();
|
|
||||||
Ok(Some((
|
|
||||||
futures::stream::iter(albums.into_iter().map(Result::Ok)),
|
|
||||||
next_offset,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.try_flatten()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
use chrono::{offset::Utc, DateTime};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -45,40 +44,10 @@ pub struct Child {
|
||||||
pub artist: String,
|
pub artist: String,
|
||||||
pub track: Option<u32>,
|
pub track: Option<u32>,
|
||||||
pub year: Option<u32>,
|
pub year: Option<u32>,
|
||||||
pub starred: Option<DateTime<Utc>>, // TODO: check which is best
|
pub starred: Option<chrono::DateTime<chrono::offset::Utc>>, // TODO: check which is best
|
||||||
// applicable
|
// applicable
|
||||||
pub duration: u32,
|
pub duration: u64,
|
||||||
//pub play_count: Option<u32>,
|
//pub play_count: Option<u32>,
|
||||||
pub genre: Option<String>,
|
pub genre: Option<String>,
|
||||||
pub cover_art: String,
|
pub cover_art: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AlbumList2Outer {
|
|
||||||
pub album_list2: AlbumList2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct AlbumList2 {
|
|
||||||
pub album: Option<Vec<AlbumID3>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AlbumID3 {
|
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub artist: Option<String>,
|
|
||||||
pub artist_id: Option<String>,
|
|
||||||
pub cover_art: Option<String>,
|
|
||||||
pub song_count: u32,
|
|
||||||
pub duration: u32,
|
|
||||||
pub play_count: Option<u32>,
|
|
||||||
pub created: DateTime<Utc>,
|
|
||||||
pub starred: Option<DateTime<Utc>>,
|
|
||||||
pub year: Option<u32>,
|
|
||||||
pub genre: Option<String>,
|
|
||||||
pub played: Option<DateTime<Utc>>,
|
|
||||||
// TODO: opensubsonic extensions?
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod imp {
|
||||||
#[template(resource = "/eu/callcc/audrey/play_queue_song.ui")]
|
#[template(resource = "/eu/callcc/audrey/play_queue_song.ui")]
|
||||||
#[properties(wrapper_type = super::Song)]
|
#[properties(wrapper_type = super::Song)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
#[property(type = i64, set = Self::set_playlist_pos)]
|
#[property(type = i32, set = Self::set_playlist_pos)]
|
||||||
_playlist_pos: (),
|
_playlist_pos: (),
|
||||||
|
|
||||||
#[property(set, get)]
|
#[property(set, get)]
|
||||||
|
@ -167,9 +167,9 @@ mod imp {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_playlist_pos(&self, playlist_pos: i64) {
|
fn set_playlist_pos(&self, playlist_pos: i32) {
|
||||||
self.obj()
|
self.obj()
|
||||||
.set_current(playlist_pos == self.position.get() as i64);
|
.set_current(playlist_pos == self.position.get() as i32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ mod imp {
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_skip_forward_clicked(&self) {
|
fn on_skip_forward_clicked(&self) {
|
||||||
if self.window().playlist_pos() + 1 < self.window().playlist_count() {
|
if self.window().playlist_pos() + 1 < self.window().playlist_count() as i32 {
|
||||||
self.window().playlist_next();
|
self.window().playlist_next();
|
||||||
} else {
|
} else {
|
||||||
self.window().playlist_play_index(None);
|
self.window().playlist_play_index(None);
|
||||||
|
|
|
@ -38,13 +38,13 @@ mod imp {
|
||||||
#[property(get)]
|
#[property(get)]
|
||||||
playlist_model: gio::ListStore,
|
playlist_model: gio::ListStore,
|
||||||
|
|
||||||
#[property(type = i64, get = Self::volume, set = Self::set_volume, minimum = 0, maximum = 100)]
|
#[property(type = u32, get = Self::volume, set = Self::set_volume)]
|
||||||
_volume: (),
|
_volume: (),
|
||||||
#[property(type = bool, get = Self::mute, set = Self::set_mute)]
|
#[property(type = bool, get = Self::mute, set = Self::set_mute)]
|
||||||
_mute: (),
|
_mute: (),
|
||||||
#[property(type = bool, get = Self::pause, set = Self::set_pause)]
|
#[property(type = bool, get = Self::pause, set = Self::set_pause)]
|
||||||
_pause: (),
|
_pause: (),
|
||||||
#[property(type = i64, get = Self::playlist_pos)]
|
#[property(type = i32, get = Self::playlist_pos)]
|
||||||
_playlist_pos: (),
|
_playlist_pos: (),
|
||||||
#[property(type = f64, get = Self::time_pos)]
|
#[property(type = f64, get = Self::time_pos)]
|
||||||
_time_pos: (),
|
_time_pos: (),
|
||||||
|
@ -52,7 +52,7 @@ mod imp {
|
||||||
_duration: (), // as reported by mpv, compare with song.duration
|
_duration: (), // as reported by mpv, compare with song.duration
|
||||||
#[property(type = bool, get = Self::idle_active)]
|
#[property(type = bool, get = Self::idle_active)]
|
||||||
_idle_active: (),
|
_idle_active: (),
|
||||||
#[property(type = i64, get = Self::playlist_count)]
|
#[property(type = u32, get = Self::playlist_count)]
|
||||||
_playlist_count: (),
|
_playlist_count: (),
|
||||||
|
|
||||||
pub(super) queued_seek: Cell<Option<f64>>,
|
pub(super) queued_seek: Cell<Option<f64>>,
|
||||||
|
@ -275,7 +275,7 @@ mod imp {
|
||||||
let api = self.api.borrow();
|
let api = self.api.borrow();
|
||||||
Rc::clone(api.as_ref().unwrap())
|
Rc::clone(api.as_ref().unwrap())
|
||||||
};
|
};
|
||||||
for song in api.random_songs(10).await.unwrap().into_iter() {
|
for song in api.get_random_songs(10).await.unwrap().into_iter() {
|
||||||
let song = Song::from_child(&api, &song, true);
|
let song = Song::from_child(&api, &song, true);
|
||||||
self.mpv
|
self.mpv
|
||||||
.command(["loadfile", &song.stream_url(), "append-play"])
|
.command(["loadfile", &song.stream_url(), "append-play"])
|
||||||
|
@ -290,7 +290,7 @@ mod imp {
|
||||||
self.setup.present(Some(self.obj().as_ref()));
|
self.setup.present(Some(self.obj().as_ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn volume(&self) -> i64 {
|
fn volume(&self) -> u32 {
|
||||||
self.mpv
|
self.mpv
|
||||||
.get_property::<i64>("volume")
|
.get_property::<i64>("volume")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -298,7 +298,7 @@ mod imp {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_volume(&self, volume: i64) {
|
fn set_volume(&self, volume: u32) {
|
||||||
self.mpv.set_property("volume", volume as i64).unwrap();
|
self.mpv.set_property("volume", volume as i64).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,8 +318,12 @@ mod imp {
|
||||||
self.mpv.set_property("pause", pause).unwrap();
|
self.mpv.set_property("pause", pause).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn playlist_pos(&self) -> i64 {
|
fn playlist_pos(&self) -> i32 {
|
||||||
self.mpv.get_property("playlist-pos").unwrap()
|
self.mpv
|
||||||
|
.get_property::<i64>("playlist-pos")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time_pos(&self) -> f64 {
|
fn time_pos(&self) -> f64 {
|
||||||
|
@ -364,7 +368,7 @@ mod imp {
|
||||||
self.mpv.get_property("idle-active").unwrap()
|
self.mpv.get_property("idle-active").unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn playlist_count(&self) -> i64 {
|
fn playlist_count(&self) -> u32 {
|
||||||
self.mpv
|
self.mpv
|
||||||
.get_property::<i64>("playlist-count")
|
.get_property::<i64>("playlist-count")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -373,16 +377,16 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn song(&self) -> Option<Song> {
|
fn song(&self) -> Option<Song> {
|
||||||
match self.obj().playlist_pos().try_into() {
|
match self.obj().playlist_pos() {
|
||||||
Ok(playlist_pos) => Some(
|
playlist_pos if playlist_pos < 0 => None,
|
||||||
|
playlist_pos => Some(
|
||||||
self.obj()
|
self.obj()
|
||||||
.playlist_model()
|
.playlist_model()
|
||||||
.item(playlist_pos)
|
.item(playlist_pos as u32)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.dynamic_cast()
|
.dynamic_cast()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
),
|
||||||
Err(_) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +482,7 @@ mod imp {
|
||||||
.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().api.borrow().as_ref().unwrap().clone();
|
||||||
let bytes = api
|
let bytes = api
|
||||||
.cover_art(&song_id, None) // full size
|
.cover_art(&song_id, 0) // 0: full size
|
||||||
.await
|
.await
|
||||||
.expect("could not load cover art for song {song_id}");
|
.expect("could not load cover art for song {song_id}");
|
||||||
|
|
||||||
|
@ -518,22 +522,29 @@ mod imp {
|
||||||
fn on_log_message(&self, event: crate::mpv::event::LogMessageEvent) {
|
fn on_log_message(&self, event: crate::mpv::event::LogMessageEvent) {
|
||||||
let span = span!(Level::DEBUG, "mpv_log", prefix = event.prefix);
|
let span = span!(Level::DEBUG, "mpv_log", prefix = event.prefix);
|
||||||
let _guard = span.enter();
|
let _guard = span.enter();
|
||||||
|
|
||||||
|
let event_text = event.text.trim_end_matches("\n"); // trim training newline
|
||||||
|
let event_text = match self.api.borrow().as_ref() {
|
||||||
|
None => event_text.to_string(),
|
||||||
|
Some(api) => api.strip_token(event_text), // hide auth token if printing url
|
||||||
|
};
|
||||||
|
|
||||||
match event.log_level {
|
match event.log_level {
|
||||||
// level has to be 'static so this sux
|
// level has to be 'static so this sux
|
||||||
l if l <= 20 => {
|
l if l <= 20 => {
|
||||||
event!(target: "mpv_event", Level::ERROR, "{}", event.text.trim())
|
event!(target: "mpv_event", Level::ERROR, "{}", event_text)
|
||||||
}
|
}
|
||||||
l if l <= 30 => {
|
l if l <= 30 => {
|
||||||
event!(target: "mpv_event", Level::WARN, "{}", event.text.trim())
|
event!(target: "mpv_event", Level::WARN, "{}", event_text)
|
||||||
}
|
}
|
||||||
l if l <= 40 => {
|
l if l <= 40 => {
|
||||||
event!(target: "mpv_event", Level::INFO, "{}", event.text.trim())
|
event!(target: "mpv_event", Level::INFO, "{}", event_text)
|
||||||
}
|
}
|
||||||
l if l <= 60 => {
|
l if l <= 60 => {
|
||||||
event!(target: "mpv_event", Level::DEBUG, "{}", event.text.trim())
|
event!(target: "mpv_event", Level::DEBUG, "{}", event_text)
|
||||||
}
|
}
|
||||||
l if l <= 70 => {
|
l if l <= 70 => {
|
||||||
event!(target: "mpv_event", Level::TRACE, "{}", event.text.trim())
|
event!(target: "mpv_event", Level::TRACE, "{}", event_text)
|
||||||
}
|
}
|
||||||
// should be unused
|
// should be unused
|
||||||
_ => event!(
|
_ => event!(
|
||||||
|
@ -541,7 +552,7 @@ mod imp {
|
||||||
Level::DEBUG,
|
Level::DEBUG,
|
||||||
log_level = event.log_level,
|
log_level = event.log_level,
|
||||||
"{}",
|
"{}",
|
||||||
event.text.trim(),
|
event_text,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -656,19 +667,6 @@ impl Window {
|
||||||
pub fn setup_connected(&self, api: crate::subsonic::Client) {
|
pub fn setup_connected(&self, api: crate::subsonic::Client) {
|
||||||
self.imp().api.replace(Some(Rc::new(api)));
|
self.imp().api.replace(Some(Rc::new(api)));
|
||||||
self.set_can_click_shuffle_all(true);
|
self.set_can_click_shuffle_all(true);
|
||||||
|
|
||||||
let api = Rc::clone(self.imp().api.borrow().as_ref().unwrap());
|
|
||||||
glib::spawn_future_local(async move {
|
|
||||||
use futures::TryStreamExt;
|
|
||||||
|
|
||||||
let mut count = 0;
|
|
||||||
let mut albums =
|
|
||||||
std::pin::pin!(api.album_list_full(crate::subsonic::AlbumListType::Newest));
|
|
||||||
while let Some(_) = albums.try_next().await.unwrap() {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
println!("gathered {count} albums");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn playlist_next(&self) {
|
pub fn playlist_next(&self) {
|
||||||
|
|
Loading…
Reference in a new issue