feat: add playlist browse/search discovery and follow controls
Some checks failed
Build for Windows / build-windows (push) Has been cancelled

This commit is contained in:
joren
2026-03-31 00:23:56 +02:00
parent 07d6c8a88d
commit 96bb21adff
13 changed files with 833 additions and 26 deletions

View File

@@ -49,6 +49,11 @@ pub const EV_MOST_POPULAR_OK: c_int = 26;
pub const EV_GENRES_OK: c_int = 27;
pub const EV_FEATURED_ALBUMS_OK: c_int = 28;
pub const EV_DYNAMIC_SUGGEST_OK: c_int = 29;
pub const EV_FEATURED_PLAYLISTS_OK: c_int = 30;
pub const EV_DISCOVER_PLAYLISTS_OK: c_int = 31;
pub const EV_PLAYLIST_SEARCH_OK: c_int = 32;
pub const EV_PLAYLIST_SUBSCRIBED: c_int = 33;
pub const EV_PLAYLIST_UNSUBSCRIBED: c_int = 34;
// ---------- Callback ----------
@@ -481,7 +486,7 @@ pub unsafe extern "C" fn qobuz_backend_get_genres(ptr: *mut Backend) {
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_get_featured_albums(
ptr: *mut Backend,
genre_id: i64,
genre_ids: *const c_char,
kind: *const c_char,
limit: u32,
offset: u32,
@@ -490,13 +495,14 @@ pub unsafe extern "C" fn qobuz_backend_get_featured_albums(
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
let genre_ids_str = CStr::from_ptr(genre_ids).to_string_lossy().into_owned();
let kind_str = CStr::from_ptr(kind).to_string_lossy().into_owned();
spawn(inner, async move {
let result = client
.lock()
.await
.get_featured_albums(genre_id, &kind_str, limit, offset)
.get_featured_albums(&genre_ids_str, &kind_str, limit, offset)
.await;
match result {
Ok(r) => {
@@ -506,7 +512,7 @@ pub unsafe extern "C" fn qobuz_backend_get_featured_albums(
"items": items,
"total": total,
"type": kind_str,
"genre_id": genre_id,
"genre_ids": genre_ids_str,
});
call_cb(
cb,
@@ -520,6 +526,132 @@ pub unsafe extern "C" fn qobuz_backend_get_featured_albums(
});
}
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_get_featured_playlists(
ptr: *mut Backend,
genre_ids: *const c_char,
kind: *const c_char,
limit: u32,
offset: u32,
) {
let inner = &(*ptr).0;
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
let kind_str = CStr::from_ptr(kind).to_string_lossy().into_owned();
let genre_ids_str = CStr::from_ptr(genre_ids).to_string_lossy().into_owned();
spawn(inner, async move {
let result = client
.lock()
.await
.get_featured_playlists(&genre_ids_str, &kind_str, limit, offset)
.await;
match result {
Ok(r) => {
let items = r["playlists"]["items"].clone();
let total = r["playlists"]["total"].as_i64().unwrap_or(0);
let out = serde_json::json!({
"items": items,
"total": total,
"type": kind_str,
"genre_ids": genre_ids_str,
});
call_cb(
cb,
ud,
EV_FEATURED_PLAYLISTS_OK,
&serde_json::to_string(&out).unwrap_or_default(),
);
}
Err(e) => call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string())),
}
});
}
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_discover_playlists(
ptr: *mut Backend,
genre_ids: *const c_char,
tags: *const c_char,
limit: u32,
offset: u32,
) {
let inner = &(*ptr).0;
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
let genre_ids_str = CStr::from_ptr(genre_ids).to_string_lossy().into_owned();
let tags_str = CStr::from_ptr(tags).to_string_lossy().into_owned();
spawn(inner, async move {
let result = client
.lock()
.await
.discover_playlists(&genre_ids_str, &tags_str, limit, offset)
.await;
match result {
Ok(r) => {
let items = r["items"].clone();
let total = r["total"].as_i64().unwrap_or(0);
let out = serde_json::json!({
"items": items,
"total": total,
"genre_ids": genre_ids_str,
"tags": tags_str,
});
call_cb(
cb,
ud,
EV_DISCOVER_PLAYLISTS_OK,
&serde_json::to_string(&out).unwrap_or_default(),
);
}
Err(e) => call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string())),
}
});
}
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_search_playlists(
ptr: *mut Backend,
query: *const c_char,
limit: u32,
offset: u32,
) {
let inner = &(*ptr).0;
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
let query_str = CStr::from_ptr(query).to_string_lossy().into_owned();
spawn(inner, async move {
let result = client
.lock()
.await
.search_playlists(&query_str, limit, offset)
.await;
match result {
Ok(r) => {
let items = r["playlists"]["items"].clone();
let total = r["playlists"]["total"].as_i64().unwrap_or(0);
let out = serde_json::json!({
"items": items,
"total": total,
"query": query_str,
});
call_cb(
cb,
ud,
EV_PLAYLIST_SEARCH_OK,
&serde_json::to_string(&out).unwrap_or_default(),
);
}
Err(e) => call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string())),
}
});
}
// ---------- Playlist ----------
#[no_mangle]
@@ -1129,3 +1261,41 @@ pub unsafe extern "C" fn qobuz_backend_delete_track_from_playlist(
}
});
}
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_subscribe_playlist(ptr: *mut Backend, playlist_id: i64) {
let inner = &(*ptr).0;
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
spawn(inner, async move {
match client.lock().await.subscribe_playlist(playlist_id).await {
Ok(()) => call_cb(
cb,
ud,
EV_PLAYLIST_SUBSCRIBED,
&serde_json::json!({"playlist_id": playlist_id}).to_string(),
),
Err(e) => call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string())),
}
});
}
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_unsubscribe_playlist(ptr: *mut Backend, playlist_id: i64) {
let inner = &(*ptr).0;
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
spawn(inner, async move {
match client.lock().await.unsubscribe_playlist(playlist_id).await {
Ok(()) => call_cb(
cb,
ud,
EV_PLAYLIST_UNSUBSCRIBED,
&serde_json::json!({"playlist_id": playlist_id}).to_string(),
),
Err(e) => call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string())),
}
});
}