refactor(qbz-qt): split QbzLibrary out of the God-object

library_bridge.rs has sat in the tree unreferenced since the phase-23 work
stopped: no mod declaration, not in build.rs, zero call sites, and duplicating
members that were still live in bridge.rs. It could not have been wired as it
stood — its module was named qbz_library, which is ambiguous with the
qbz-library crate this binary depends on, so it never compiled. The module is
now qbz_library_bridge; the QML type name comes from the QObject, so QML is
unaffected.

Registering it and deleting the duplicates had to happen together. Wiring it
alone would have compiled cleanly and produced two independent Library states —
QML reading one object while Rust published to the other — with the view simply
frozen and nothing in the log.

Moved with it: four properties, five invokables, three signals, the seven
library-only ui() hops in main.rs, and thirty-four QML references. The
Connections blocks in LibraryView, Sidebar, QueuePanel, AlbumView and
PlaylistCollage retarget wholesale because every handler in them is a library
signal. ArtistView's does not: it mixes onLibraryArtworkReady with
onReleaseSectionReady, so it becomes two blocks — retargeting it whole would
have orphaned the releases pager, and QML resolves handlers lazily enough that
the discography would just have stopped loading with nothing in the log.

bridge.rs is down to 389 lines and five domains.

cc
This commit is contained in:
vicrodh committed 2026-07-28 14:07:40 -06:00
1 parent 0799d59e01
commit 5c631000b7
18 files changed
+65 -122

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ fn main() {
.qt_module("QuickControls2") .qt_module("QuickControls2")
.qml_module(QmlModule { .qml_module(QmlModule {
uri: "com.blitzfc.qbz", uri: "com.blitzfc.qbz",
rust_files: &["src/bridge.rs", "src/session_bridge.rs", "src/shell_bridge.rs", "src/player_bridge.rs", "src/queue_bridge.rs", "src/home_bridge.rs", "src/viz_bridge.rs", "src/local_bridge.rs"], rust_files: &["src/bridge.rs", "src/session_bridge.rs", "src/shell_bridge.rs", "src/player_bridge.rs", "src/queue_bridge.rs", "src/home_bridge.rs", "src/viz_bridge.rs", "src/local_bridge.rs", "src/library_bridge.rs"],
qml_files: &[ qml_files: &[
"qml/LoginScreen.qml", "qml/LoginScreen.qml",
"qml/Main.qml", "qml/Main.qml",
+1
View File
@@ -52,6 +52,7 @@ ApplicationWindow {
QbzHome.boot() QbzHome.boot()
QbzViz.boot() QbzViz.boot()
QbzLocal.boot() QbzLocal.boot()
QbzLibrary.boot()
QbzBridge.boot() QbzBridge.boot()
} }
+2 -2
View File
@@ -62,11 +62,11 @@ Rectangle {
function toggleFavorite() { function toggleFavorite() {
root.isFavorite = !root.isFavorite root.isFavorite = !root.isFavorite
QbzBridge.libraryToggleFavorite("album", root.albumId) QbzLibrary.libraryToggleFavorite("album", root.albumId)
} }
function togglePin() { function togglePin() {
root.isPinned = !root.isPinned root.isPinned = !root.isPinned
QbzBridge.togglePin("album", root.albumId, root.title, root.artist, "") QbzLibrary.togglePin("album", root.albumId, root.title, root.artist, "")
} }
Column { Column {
+1 -1
View File
@@ -181,7 +181,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.isPinned = !root.isPinned root.isPinned = !root.isPinned
QbzBridge.togglePin("artist", root.item.id, root.item.title, QbzLibrary.togglePin("artist", root.item.id, root.item.title,
root.item.subtitle || "", root.artworkUrl) root.item.subtitle || "", root.artworkUrl)
} }
} }
+3 -3
View File
@@ -121,7 +121,7 @@ Rectangle {
onClicked: { onClicked: {
if (root.item.playlistOwned) { if (root.item.playlistOwned) {
root.item.isFavorite = !root.item.isFavorite root.item.isFavorite = !root.item.isFavorite
QbzBridge.libraryToggleFavorite("playlist", root.item.id) QbzLibrary.libraryToggleFavorite("playlist", root.item.id)
} else { } else {
root.item.playlistFollowing = !root.item.playlistFollowing root.item.playlistFollowing = !root.item.playlistFollowing
QbzBridge.playlistSetFollowById(root.item.id, root.item.playlistFollowing) QbzBridge.playlistSetFollowById(root.item.id, root.item.playlistFollowing)
@@ -166,7 +166,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.isPinned = !root.isPinned root.isPinned = !root.isPinned
QbzBridge.togglePin("playlist", root.item.id, root.item.title, QbzLibrary.togglePin("playlist", root.item.id, root.item.title,
root.item.subtitle || "", root.artworkUrl) root.item.subtitle || "", root.artworkUrl)
} }
} }
@@ -196,7 +196,7 @@ Rectangle {
else if (a === "favorite") { else if (a === "favorite") {
if (root.item.playlistOwned) { if (root.item.playlistOwned) {
root.item.isFavorite = !root.item.isFavorite root.item.isFavorite = !root.item.isFavorite
QbzBridge.libraryToggleFavorite("playlist", root.item.id) QbzLibrary.libraryToggleFavorite("playlist", root.item.id)
} else { } else {
root.item.playlistFollowing = !root.item.playlistFollowing root.item.playlistFollowing = !root.item.playlistFollowing
QbzBridge.playlistSetFollowById(root.item.id, root.item.playlistFollowing) QbzBridge.playlistSetFollowById(root.item.id, root.item.playlistFollowing)
+1 -1
View File
@@ -109,7 +109,7 @@ Item {
} }
Connections { Connections {
target: QbzBridge target: QbzLibrary
// Shared with the feed's artKey-keyed emissions — ignore anything // Shared with the feed's artKey-keyed emissions — ignore anything
// that is not one of OUR urls. // that is not one of OUR urls.
function onLibraryArtworkReady(key, path) { function onLibraryArtworkReady(key, path) {
+2 -2
View File
@@ -80,7 +80,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
onClicked: { onClicked: {
root.item.isFavorite = !root.item.isFavorite root.item.isFavorite = !root.item.isFavorite
QbzBridge.libraryToggleFavorite("track", root.item.id) QbzLibrary.libraryToggleFavorite("track", root.item.id)
} }
} }
CardOverlayButton { CardOverlayButton {
@@ -193,7 +193,7 @@ Rectangle {
else if (a === "go-album") QbzBridge.openAlbum(root.item.albumId) else if (a === "go-album") QbzBridge.openAlbum(root.item.albumId)
else if (a === "favorite") { else if (a === "favorite") {
root.item.isFavorite = !root.item.isFavorite root.item.isFavorite = !root.item.isFavorite
QbzBridge.libraryToggleFavorite("track", root.item.id) QbzLibrary.libraryToggleFavorite("track", root.item.id)
} }
} }
} }
+2 -2
View File
@@ -257,7 +257,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.item.isFavorite = !root.item.isFavorite root.item.isFavorite = !root.item.isFavorite
QbzBridge.libraryToggleFavorite("track", root.item.id) QbzLibrary.libraryToggleFavorite("track", root.item.id)
} }
} }
} }
@@ -329,7 +329,7 @@ Rectangle {
else if (a === "go-album") QbzBridge.openAlbum(root.item.albumId) else if (a === "go-album") QbzBridge.openAlbum(root.item.albumId)
else if (a === "favorite") { else if (a === "favorite") {
root.item.isFavorite = !root.item.isFavorite root.item.isFavorite = !root.item.isFavorite
QbzBridge.libraryToggleFavorite("track", root.item.id) QbzLibrary.libraryToggleFavorite("track", root.item.id)
} else if (a === "remove") root.removeRequested() } else if (a === "remove") root.removeRequested()
} }
} }
@@ -501,7 +501,7 @@ Rectangle {
if (id !== "") QbzPlayer.enqueueTrack(id, a) if (id !== "") QbzPlayer.enqueueTrack(id, a)
} else if (a === "album-favorite") { } else if (a === "album-favorite") {
if (QbzPlayer.npAlbumId !== "") if (QbzPlayer.npAlbumId !== "")
QbzBridge.libraryToggleFavorite("album", QbzPlayer.npAlbumId) QbzLibrary.libraryToggleFavorite("album", QbzPlayer.npAlbumId)
} }
// TODO(qt-bridge): "playlist" (add-to-playlist modal) and // TODO(qt-bridge): "playlist" (add-to-playlist modal) and
// "mixtape" have no invokable in the Qt port yet — the rows are // "mixtape" have no invokable in the Qt port yet — the rows are
+1 -1
View File
@@ -755,7 +755,7 @@ Rectangle {
if (id !== "") QbzPlayer.enqueueTrack(id, a) if (id !== "") QbzPlayer.enqueueTrack(id, a)
} else if (a === "album-favorite") { } else if (a === "album-favorite") {
if (QbzPlayer.npAlbumId !== "") if (QbzPlayer.npAlbumId !== "")
QbzBridge.libraryToggleFavorite("album", QbzPlayer.npAlbumId) QbzLibrary.libraryToggleFavorite("album", QbzPlayer.npAlbumId)
} }
// TODO(qt-bridge): "playlist" (add-to-playlist modal) and // TODO(qt-bridge): "playlist" (add-to-playlist modal) and
// "mixtape" have no invokable in the Qt port yet — the rows are // "mixtape" have no invokable in the Qt port yet — the rows are
+1 -1
View File
@@ -42,7 +42,7 @@ Rectangle {
QbzTheme { id: theme } QbzTheme { id: theme }
Connections { Connections {
target: QbzBridge target: QbzLibrary
function onLibraryArtworkReady(key, path) { function onLibraryArtworkReady(key, path) {
var m = root.coverMap var m = root.coverMap
m[key] = path m[key] = path
+1 -1
View File
@@ -62,7 +62,7 @@ Rectangle {
} }
Connections { Connections {
target: QbzBridge target: QbzLibrary
function onLibraryArtworkReady(key, path) { function onLibraryArtworkReady(key, path) {
var m = root.coverMap var m = root.coverMap
m[key] = path m[key] = path
+4 -4
View File
@@ -67,7 +67,7 @@ Rectangle {
} }
Connections { Connections {
target: QbzBridge target: QbzLibrary
function onLibraryArtworkReady(key, path) { function onLibraryArtworkReady(key, path) {
var m = root.coverMap var m = root.coverMap
m[key] = path m[key] = path
@@ -350,7 +350,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
onClicked: { onClicked: {
header.isFavorite = !header.isFavorite header.isFavorite = !header.isFavorite
QbzBridge.libraryToggleFavorite("album", header.id) QbzLibrary.libraryToggleFavorite("album", header.id)
} }
} }
// Radio / Mixtape / Info — INERT stubs (POC-NOTE: // Radio / Mixtape / Info — INERT stubs (POC-NOTE:
@@ -710,10 +710,10 @@ Rectangle {
else if (a === "queue") QbzPlayer.enqueueAlbum(header.id, "later") else if (a === "queue") QbzPlayer.enqueueAlbum(header.id, "later")
else if (a === "favorite") { else if (a === "favorite") {
header.isFavorite = !header.isFavorite header.isFavorite = !header.isFavorite
QbzBridge.libraryToggleFavorite("album", header.id) QbzLibrary.libraryToggleFavorite("album", header.id)
} else if (a === "pin") { } else if (a === "pin") {
header.isPinned = !header.isPinned header.isPinned = !header.isPinned
QbzBridge.togglePin("album", header.id, header.title, header.artist, header.artUrl) QbzLibrary.togglePin("album", header.id, header.title, header.artist, header.artUrl)
} }
} }
} }
+13 -5
View File
@@ -66,13 +66,21 @@ Rectangle {
return tabs return tabs
} }
// Two blocks, not one: artwork is QbzLibrary's signal and the releases
// pager is still QbzBridge's. Retargeting a mixed block wholesale would
// silently orphan the other half — QML resolves handlers lazily, so the
// discography would just stop loading with nothing in the log.
Connections { Connections {
target: QbzBridge target: QbzLibrary
function onLibraryArtworkReady(key, path) { function onLibraryArtworkReady(key, path) {
var m = root.coverMap var m = root.coverMap
m[key] = path m[key] = path
root.coverMap = Object.assign({}, m) root.coverMap = Object.assign({}, m)
} }
}
Connections {
target: QbzBridge
function onReleaseSectionReady(releaseType, cardsJson, hasMore) { function onReleaseSectionReady(releaseType, cardsJson, hasMore) {
var cards = JSON.parse(cardsJson) var cards = JSON.parse(cardsJson)
var sections = root.releaseSections var sections = root.releaseSections
@@ -306,7 +314,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
row.isFavorite = !row.isFavorite row.isFavorite = !row.isFavorite
QbzBridge.libraryToggleFavorite("track", row.id) QbzLibrary.libraryToggleFavorite("track", row.id)
} }
} }
} }
@@ -596,7 +604,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
onClicked: { onClicked: {
artist.isFollowing = !artist.isFollowing artist.isFollowing = !artist.isFollowing
QbzBridge.libraryToggleFavorite("artist", artist.id) QbzLibrary.libraryToggleFavorite("artist", artist.id)
} }
} }
QbzCircleAction { QbzCircleAction {
@@ -1124,7 +1132,7 @@ Rectangle {
// Library feed access (the phase-5 document, parsed in LibraryView). // Library feed access (the phase-5 document, parsed in LibraryView).
function libraryFeed() { function libraryFeed() {
return JSON.parse(QbzBridge.libraryJson) return JSON.parse(QbzLibrary.libraryJson)
} }
// --- Network sidebar (300px, surface-card + 1px left border) --------- // --- Network sidebar (300px, surface-card + 1px left border) ---------
@@ -1385,7 +1393,7 @@ Rectangle {
overflowMenu.close() overflowMenu.close()
if (modelData.action === "pin") { if (modelData.action === "pin") {
artist.isPinned = !artist.isPinned artist.isPinned = !artist.isPinned
QbzBridge.togglePin("artist", artist.id, artist.name, "", artist.artUrl) QbzLibrary.togglePin("artist", artist.id, artist.name, "", artist.artUrl)
} }
} }
} }
+13 -13
View File
@@ -1,7 +1,7 @@
// Library view — QML port of crates/qbz-ui/ui/favorites/FavoritesView.slint // Library view — QML port of crates/qbz-ui/ui/favorites/FavoritesView.slint
// + the Library "All" mixed feed (library_all.rs semantics). // + the Library "All" mixed feed (library_all.rs semantics).
// //
// Data: QbzBridge.libraryJson (ONE JSON document — the full merged feed; // Data: QbzLibrary.libraryJson (ONE JSON document — the full merged feed;
// tabs/search/sort/source-filters derive HERE in JS, measured per the // tabs/search/sort/source-filters derive HERE in JS, measured per the
// phase brief) + libraryCountsJson (tab badges). Artwork is id-keyed // phase brief) + libraryCountsJson (tab badges). Artwork is id-keyed
// through the libraryArtworkReady signal into `artMap` (never a // through the libraryArtworkReady signal into `artMap` (never a
@@ -44,9 +44,9 @@ Rectangle {
// ============================ state ================================== // ============================ state ==================================
property string activeTab: "all" property string activeTab: "all"
readonly property var counts: JSON.parse(QbzBridge.libraryCountsJson) readonly property var counts: JSON.parse(QbzLibrary.libraryCountsJson)
// Full merged feed (parsed once per publish — timed for the report). // Full merged feed (parsed once per publish — timed for the report).
readonly property var feed: parseFeed(QbzBridge.libraryJson) readonly property var feed: parseFeed(QbzLibrary.libraryJson)
function parseFeed(json) { function parseFeed(json) {
var t = Date.now() var t = Date.now()
var f = JSON.parse(json) var f = JSON.parse(json)
@@ -162,11 +162,11 @@ Rectangle {
if (!keep[key]) { delete m[key]; changed = true } if (!keep[key]) { delete m[key]; changed = true }
} }
if (changed) artMap = Object.assign({}, m) if (changed) artMap = Object.assign({}, m)
QbzBridge.libraryArtworkWindow(JSON.stringify(keys)) QbzLibrary.libraryArtworkWindow(JSON.stringify(keys))
} }
Connections { Connections {
target: QbzBridge target: QbzLibrary
function onLibraryArtworkReady(key, path) { function onLibraryArtworkReady(key, path) {
var m = root.artMap var m = root.artMap
m[key] = path m[key] = path
@@ -315,7 +315,7 @@ Rectangle {
else if (a === "go-album") QbzBridge.openAlbum(item.albumId) else if (a === "go-album") QbzBridge.openAlbum(item.albumId)
else if (a === "favorite") { else if (a === "favorite") {
item.isFavorite = !item.isFavorite item.isFavorite = !item.isFavorite
QbzBridge.libraryToggleFavorite("track", item.id) QbzLibrary.libraryToggleFavorite("track", item.id)
} }
} }
@@ -557,7 +557,7 @@ Rectangle {
QbzBridge.openArtist(item.id) QbzBridge.openArtist(item.id)
} else if (a === "favorite") { } else if (a === "favorite") {
item.isFavorite = !item.isFavorite item.isFavorite = !item.isFavorite
QbzBridge.libraryToggleFavorite(item.kind, item.id) QbzLibrary.libraryToggleFavorite(item.kind, item.id)
} }
} }
} }
@@ -871,12 +871,12 @@ Rectangle {
QbzSpinner { QbzSpinner {
anchors.centerIn: parent anchors.centerIn: parent
size: 36 size: 36
visible: QbzBridge.libraryLoading visible: QbzLibrary.libraryLoading
} }
// Error + retry. // Error + retry.
Column { Column {
visible: !QbzBridge.libraryLoading && QbzBridge.libraryError !== "" visible: !QbzLibrary.libraryLoading && QbzLibrary.libraryError !== ""
anchors.centerIn: parent anchors.centerIn: parent
spacing: 10 spacing: 10
Text { Text {
@@ -886,7 +886,7 @@ Rectangle {
font.weight: theme.weightSemibold font.weight: theme.weightSemibold
} }
Text { Text {
text: QbzBridge.libraryError text: QbzLibrary.libraryError
color: theme.textMuted color: theme.textMuted
font.pixelSize: theme.fontLegal font.pixelSize: theme.fontLegal
} }
@@ -910,7 +910,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: QbzBridge.reloadLibrary() onClicked: QbzLibrary.reloadLibrary()
} }
} }
} }
@@ -922,7 +922,7 @@ Rectangle {
anchors.leftMargin: 32 anchors.leftMargin: 32
anchors.rightMargin: 32 anchors.rightMargin: 32
anchors.topMargin: 16 anchors.topMargin: 16
visible: !QbzBridge.libraryLoading && QbzBridge.libraryError === "" visible: !QbzLibrary.libraryLoading && QbzLibrary.libraryError === ""
&& (root.activeTab !== "all" || root.viewMode === "grid") && (root.activeTab !== "all" || root.viewMode === "grid")
&& root.activeTab !== "tracks" && root.activeTab !== "tracks"
cellWidth: 220 cellWidth: 220
@@ -1007,7 +1007,7 @@ Rectangle {
anchors.leftMargin: 32 anchors.leftMargin: 32
anchors.rightMargin: 32 anchors.rightMargin: 32
anchors.topMargin: 10 anchors.topMargin: 10
visible: !QbzBridge.libraryLoading && QbzBridge.libraryError === "" visible: !QbzLibrary.libraryLoading && QbzLibrary.libraryError === ""
&& ((root.activeTab === "all" && root.viewMode === "list") && ((root.activeTab === "all" && root.viewMode === "list")
|| root.activeTab === "tracks") || root.activeTab === "tracks")
cacheBuffer: 44 * 10 cacheBuffer: 44 * 10
-73
View File
@@ -24,16 +24,6 @@ pub mod qbz_bridge {
#[qobject] #[qobject]
#[qml_element] #[qml_element]
#[qml_singleton] #[qml_singleton]
// --- Library view --------------------------------------------------
#[qproperty(bool, library_loading)]
#[qproperty(QString, library_error)]
// One JSON document: the FULL merged feed (tabs/search/sort/source
// filters derive QML-side from the parsed array — see library_qt.rs
// for the rationale + the measured parse cost).
#[qproperty(QString, library_json)]
// {tracks, albums, artists, playlists, labels, all} — tab badges.
#[qproperty(QString, library_counts_json)]
// --- Album / Artist detail views (phase 8) ------------------------- // --- Album / Artist detail views (phase 8) -------------------------
#[qproperty(bool, album_loading)] #[qproperty(bool, album_loading)]
#[qproperty(QString, album_json)] #[qproperty(QString, album_json)]
@@ -75,39 +65,6 @@ pub mod qbz_bridge {
#[qinvokable] #[qinvokable]
fn boot(self: Pin<&mut QbzBridge>); fn boot(self: Pin<&mut QbzBridge>);
/// Library retry button / manual refresh.
#[qinvokable]
fn reload_library(self: Pin<&mut QbzBridge>);
/// Windowed artwork: the grid/list reports the mounted window as a
/// JSON array of artKeys (tab views filter the feed, so raw indices
/// don't map); Rust dispatches covers for those keys (id-keyed via
/// `libraryArtworkReady`).
#[qinvokable]
fn library_artwork_window(self: Pin<&mut QbzBridge>, keys_json: QString);
/// Card heart: toggle favorite (Qobuz API or the local store,
/// routed by id shape); the result arrives via
/// `libraryFavoriteChanged`.
#[qinvokable]
fn library_toggle_favorite(self: Pin<&mut QbzBridge>, kind: QString, id: QString);
/// Emitted when a dispatched cover lands on disk (id-keyed —
/// `{kind}:{id}`); QML updates its artwork map.
#[qsignal]
fn library_artwork_ready(self: Pin<&mut QbzBridge>, key: QString, path: QString);
/// Emitted after a heart toggle (optimistic flip + rollback).
#[qsignal]
fn library_favorite_changed(self: Pin<&mut QbzBridge>, key: QString, value: bool);
/// AlbumCard pin badge: toggle pin (album/artist/playlist).
#[qinvokable]
fn toggle_pin(self: Pin<&mut QbzBridge>, kind: QString, id: QString, title: QString, subtitle: QString, artwork_url: QString);
/// Emitted after a pin toggle (`{kind}:{id}` key like artKey).
#[qsignal]
fn pin_changed(self: Pin<&mut QbzBridge>, key: QString, value: bool);
/// Open the album detail view (pushes "album" on the nav stack). /// Open the album detail view (pushes "album" on the nav stack).
#[qinvokable] #[qinvokable]
fn open_album(self: Pin<&mut QbzBridge>, album_id: QString); fn open_album(self: Pin<&mut QbzBridge>, album_id: QString);
@@ -237,10 +194,6 @@ use cxx_qt_lib::QString;
/// generated `set_*` methods on the Qt thread; the struct itself is plain /// generated `set_*` methods on the Qt thread; the struct itself is plain
/// storage (as required by cxx-qt's Default-constructed qobjects). /// storage (as required by cxx-qt's Default-constructed qobjects).
pub struct QbzBridgeRust { pub struct QbzBridgeRust {
library_loading: bool,
library_error: QString,
library_json: QString,
library_counts_json: QString,
album_loading: bool, album_loading: bool,
album_json: QString, album_json: QString,
artist_loading: bool, artist_loading: bool,
@@ -260,10 +213,6 @@ pub struct QbzBridgeRust {
impl Default for QbzBridgeRust { impl Default for QbzBridgeRust {
fn default() -> Self { fn default() -> Self {
Self { Self {
library_loading: false,
library_error: QString::default(),
library_json: QString::from("[]"),
library_counts_json: QString::from("{}"),
album_loading: false, album_loading: false,
album_json: QString::from("{}"), album_json: QString::from("{}"),
artist_loading: false, artist_loading: false,
@@ -287,28 +236,6 @@ impl qbz_bridge::QbzBridge {
crate::register_qt_thread(self.qt_thread()); crate::register_qt_thread(self.qt_thread());
} }
pub fn reload_library(self: Pin<&mut Self>) {
crate::reload_library();
}
pub fn library_artwork_window(self: Pin<&mut Self>, keys_json: QString) {
crate::library_artwork_window(keys_json.to_string());
}
pub fn library_toggle_favorite(self: Pin<&mut Self>, kind: QString, id: QString) {
crate::library_toggle_favorite(kind.to_string(), id.to_string());
}
pub fn toggle_pin(self: Pin<&mut Self>, kind: QString, id: QString, title: QString, subtitle: QString, artwork_url: QString) {
crate::toggle_pin(
kind.to_string(),
id.to_string(),
title.to_string(),
subtitle.to_string(),
artwork_url.to_string(),
);
}
pub fn open_album(self: Pin<&mut Self>, album_id: QString) { pub fn open_album(self: Pin<&mut Self>, album_id: QString) {
crate::open_album(album_id.to_string()); crate::open_album(album_id.to_string());
} }
+10 -4
View File
@@ -1,4 +1,10 @@
//! QbzLibrary — Library view domain bridge (phase 23 split of the QbzBridge //! QbzLibrary — Library view domain bridge.
//!
//! The module is `qbz_library_bridge`, NOT `qbz_library`: this crate depends
//! on the `qbz-library` crate, whose Rust name is `qbz_library`, and a bridge
//! module by that name is ambiguous at every use site. The QML type name comes
//! from the QObject (`QbzLibrary`), not from the module, so QML is unaffected.
//! Original note: Library view domain bridge (phase 23 split of the QbzBridge
//! God-object; the pattern is documented in main.rs). Props: the merged-feed //! God-object; the pattern is documented in main.rs). Props: the merged-feed
//! document + counts + loading/error. Invokables: reload, windowed artwork //! document + counts + loading/error. Invokables: reload, windowed artwork
//! dispatch, favorite/pin toggles. Signals: artwork-ready / favorite-changed //! dispatch, favorite/pin toggles. Signals: artwork-ready / favorite-changed
@@ -12,7 +18,7 @@ use cxx_qt::Threading as _;
use cxx_qt_lib::QString; use cxx_qt_lib::QString;
#[cxx_qt::bridge] #[cxx_qt::bridge]
pub mod qbz_library { pub mod qbz_library_bridge {
extern "C++" { extern "C++" {
include!("cxx-qt-lib/qstring.h"); include!("cxx-qt-lib/qstring.h");
type QString = cxx_qt_lib::QString; type QString = cxx_qt_lib::QString;
@@ -77,7 +83,7 @@ pub mod qbz_library {
impl cxx_qt::Threading for QbzLibrary {} impl cxx_qt::Threading for QbzLibrary {}
} }
use qbz_library::QbzLibrary; use qbz_library_bridge::QbzLibrary;
/// Rust side of the library bridge (plain storage, phase-1 pattern). /// Rust side of the library bridge (plain storage, phase-1 pattern).
pub struct QbzLibraryRust { pub struct QbzLibraryRust {
@@ -112,7 +118,7 @@ pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzLibrary>) + Send + 'static) {
} }
} }
impl qbz_library::QbzLibrary { impl qbz_library_bridge::QbzLibrary {
pub fn boot(self: Pin<&mut Self>) { pub fn boot(self: Pin<&mut Self>) {
if QT_THREAD.set(self.qt_thread()).is_err() { if QT_THREAD.set(self.qt_thread()).is_err() {
log::warn!("[qbz-qt] library Qt thread already registered"); log::warn!("[qbz-qt] library Qt thread already registered");
+8 -7
View File
@@ -27,6 +27,7 @@ mod queue_bridge;
mod home_bridge; mod home_bridge;
mod viz_bridge; mod viz_bridge;
mod local_bridge; mod local_bridge;
mod library_bridge;
mod artwork_qt; mod artwork_qt;
mod bridge; mod bridge;
mod home_qt; mod home_qt;
@@ -282,7 +283,7 @@ pub(crate) fn do_logout() {
b.as_mut().set_home_error(QString::from("")); b.as_mut().set_home_error(QString::from(""));
b.as_mut().set_home_loading(false); b.as_mut().set_home_loading(false);
}); });
ui(|mut b| { library_bridge::ui(|mut b| {
b.as_mut().set_library_json(QString::from("[]")); b.as_mut().set_library_json(QString::from("[]"));
b.as_mut().set_library_counts_json(QString::from("{}")); b.as_mut().set_library_counts_json(QString::from("{}"));
b.as_mut().set_library_error(QString::from("")); b.as_mut().set_library_error(QString::from(""));
@@ -516,7 +517,7 @@ pub(crate) fn enqueue_album(album_id: String, mode: String) {
pub(crate) fn toggle_pin(kind: String, id: String, title: String, subtitle: String, artwork_url: String) { pub(crate) fn toggle_pin(kind: String, id: String, title: String, subtitle: String, artwork_url: String) {
if let Some(value) = sidebar_qt::toggle_pin(&kind, &id, &title, &subtitle, &artwork_url) { if let Some(value) = sidebar_qt::toggle_pin(&kind, &id, &title, &subtitle, &artwork_url) {
let key = format!("{kind}:{id}"); let key = format!("{kind}:{id}");
ui(move |mut b| { library_bridge::ui(move |mut b| {
b.as_mut().pin_changed(QString::from(key.as_str()), value); b.as_mut().pin_changed(QString::from(key.as_str()), value);
}); });
} }
@@ -1183,7 +1184,7 @@ pub(crate) fn reload_library() {
return; return;
} }
log_rss("library load start"); log_rss("library load start");
ui(|mut b| { library_bridge::ui(|mut b| {
b.as_mut().set_library_loading(true); b.as_mut().set_library_loading(true);
b.as_mut().set_library_error(QString::from("")); b.as_mut().set_library_error(QString::from(""));
}); });
@@ -1209,7 +1210,7 @@ pub(crate) fn reload_library() {
"[qbz-qt][perf] library load total: {:?} ({total} items)", "[qbz-qt][perf] library load total: {:?} ({total} items)",
t.elapsed(), t.elapsed(),
); );
ui(move |mut b| { library_bridge::ui(move |mut b| {
b.as_mut().set_library_json(QString::from(feed_json.as_str())); b.as_mut().set_library_json(QString::from(feed_json.as_str()));
b.as_mut() b.as_mut()
.set_library_counts_json(QString::from(counts_json.as_str())); .set_library_counts_json(QString::from(counts_json.as_str()));
@@ -1219,7 +1220,7 @@ pub(crate) fn reload_library() {
} }
Err(e) => { Err(e) => {
log::warn!("[qbz-qt] library load failed: {e}"); log::warn!("[qbz-qt] library load failed: {e}");
ui(move |mut b| { library_bridge::ui(move |mut b| {
b.as_mut().set_library_error(QString::from(e.as_str())); b.as_mut().set_library_error(QString::from(e.as_str()));
b.as_mut().set_library_loading(false); b.as_mut().set_library_loading(false);
}); });
@@ -1285,7 +1286,7 @@ pub(crate) fn library_artwork_window(keys_json: String) {
} }
fn emit_library_artwork(key: String, path: String) { fn emit_library_artwork(key: String, path: String) {
ui(move |mut b| { library_bridge::ui(move |mut b| {
b.as_mut() b.as_mut()
.library_artwork_ready(QString::from(key.as_str()), QString::from(path.as_str())); .library_artwork_ready(QString::from(key.as_str()), QString::from(path.as_str()));
}); });
@@ -1298,7 +1299,7 @@ pub(crate) fn library_toggle_favorite(kind: String, id: String) {
let runtime = app(); let runtime = app();
spawn(async move { spawn(async move {
if let Some(value) = library_qt::toggle_favorite(&runtime, &kind, &id).await { if let Some(value) = library_qt::toggle_favorite(&runtime, &kind, &id).await {
ui(move |mut b| { library_bridge::ui(move |mut b| {
b.as_mut() b.as_mut()
.library_favorite_changed(QString::from(key.as_str()), value); .library_favorite_changed(QString::from(key.as_str()), value);
}); });