feat(wails): move the frontend onto v3's generated bindings
frontend/wailsjs/ is deleted and frontend/bindings/ takes its place — a real TypeScript module tree nested by Go import path, generated by wails3's static analyser rather than by building the app and running it. The @go alias absorbs the constant prefix, so a call site imports '@go/library/library.js' and the codemod over all 93 sites was a specifier rewrite plus splitting @go/models' namespaces into one import per package. The 12 SetContext bindings and the fake `context` model are gone, as Phase 2's ServiceStartup port promised: 272 methods across 12 services, none of them plumbing. @runtime/runtime is now a local shim (src/wails/runtime.ts) over @wailsio/runtime, so the 22 EventsOn imports are untouched. It unwraps v3's WailsEvent into v2's callback shape, which is exact here: nothing in backend/events passes more than one data argument, and v3 only packs arguments into a slice when there is more than one. v3 tells the truth about two things v2 lied about, and that is most of the diff. A Go nil slice really does arrive as JSON null, and a Go named string type really is an enum; v2 typed them as T[] and string. utils/binding.ts states the app's actual contract — an absent list is an empty list — once, at the boundary where it is true, and also drops the CancellablePromise the app never cancels. Four test fixtures widen an enum field back to its value union. Not done, and Phase 5's to fix: frontend/test/support/wails-fake.ts still fakes window.go, which v3 does not have, so `make ui-test` is broken and harness.test.ts fails to compile on EventsEmit. That test also asserts v2 ordering that no longer holds — v3's Events.Emit calls the backend and does not notify in-page listeners at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDCbcCZQepnpSQYJ6SxxZm
This commit is contained in:
@@ -100,14 +100,9 @@ ui-visual-update: ## Re-record the screenshot baselines
|
||||
ui-setup: ## Install the Vitest browser provider's own Chromium (once)
|
||||
@cd frontend && pnpm install && npx playwright install chromium
|
||||
|
||||
# Bindings are generated by `wails`, NOT by `go generate`, so the
|
||||
# Bindings are generated by `wails3`, NOT by `go generate`, so the
|
||||
# pre-commit codegen check does not cover them: a renamed Go struct
|
||||
# field currently surfaces at runtime, in a window.
|
||||
#
|
||||
# NOTE (plan 009 phase 4): this still checks v2's frontend/wailsjs,
|
||||
# which nothing regenerates now that the v2 CLI is gone. It is
|
||||
# excluded from the pre-commit hook until the frontend moves to
|
||||
# frontend/bindings.
|
||||
# field would otherwise surface at runtime, inside a window.
|
||||
bindings-check: ## Fail if the generated bindings are stale
|
||||
@./scripts/bindings-check.sh
|
||||
|
||||
@@ -130,14 +125,13 @@ commit-check: ## Fail if a commit subject is not a Conventional Commit
|
||||
@./scripts/commit-check.sh $(if $(RANGE),--range $(RANGE))
|
||||
|
||||
# v3 generates TypeScript into frontend/bindings/, nested by Go import
|
||||
# path, rather than v2's frontend/wailsjs/. The 93 frontend import
|
||||
# sites still point at the old tree, so this writes the new one beside
|
||||
# it until plan 009 phase 4 moves them; the chmod dance that used to
|
||||
# follow is gone with v2's generator, which wrote its runtime files 755.
|
||||
# path, rather than v2's frontend/wailsjs/. The `@go` alias absorbs the
|
||||
# constant prefix, so a call site imports '@go/library/library.js'.
|
||||
#
|
||||
# The tag set is pinned deliberately: the generator is a static
|
||||
# analyser, so it only sees the configuration it is told about, and the
|
||||
# one that matters is the one users run.
|
||||
# No -f flag: the tag set is the default one, deliberately, because the
|
||||
# generator is a static analyser that sees only the configuration it is
|
||||
# told about and the one that matters is the one users run. See
|
||||
# scripts/bindings-check.sh for why the other two do not apply.
|
||||
bindings: ## Regenerate frontend/bindings from the bound Go services
|
||||
go tool wails3 generate bindings -clean=true -ts -i
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//@ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Create as $Create } from "@wailsio/runtime";
|
||||
|
||||
Object.freeze($Create.Events);
|
||||
@@ -0,0 +1,2 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
Executable → Regular
+3
-2
@@ -1,5 +1,6 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {home} from '../models';
|
||||
|
||||
export function GetShelves():Promise<Array<home.Shelf>>;
|
||||
export {
|
||||
Duration
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,38 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* A Duration represents the elapsed time between two instants
|
||||
* as an int64 nanosecond count. The representation limits the
|
||||
* largest representable duration to approximately 290 years.
|
||||
*/
|
||||
export enum Duration {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = 0,
|
||||
|
||||
minDuration = -9223372036854775808,
|
||||
maxDuration = 9223372036854775807,
|
||||
|
||||
/**
|
||||
* Common durations. There is no definition for units of Day or larger
|
||||
* to avoid confusion across daylight savings time zone transitions.
|
||||
*
|
||||
* To count the number of units in a [Duration], divide:
|
||||
*
|
||||
* second := time.Second
|
||||
* fmt.Print(int64(second/time.Millisecond)) // prints 1000
|
||||
*
|
||||
* To convert an integer number of units to a Duration, multiply:
|
||||
*
|
||||
* seconds := 10
|
||||
* fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
|
||||
*/
|
||||
Nanosecond = 1,
|
||||
Microsecond = 1000,
|
||||
Millisecond = 1000000,
|
||||
Second = 1000000000,
|
||||
Minute = 60000000000,
|
||||
Hour = 3600000000000,
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export type {
|
||||
AlignmentView,
|
||||
ApplyResultView,
|
||||
CandidateView,
|
||||
FailureView,
|
||||
LocalTrackView,
|
||||
PendingItem,
|
||||
ScoreBreakdownView,
|
||||
ScoreView,
|
||||
SearchHitView
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,187 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* AlignmentView mirrors autotag.TrackAlignment. LocalIndex of -1
|
||||
* means "candidate has this track, folder doesn't" (status=missing).
|
||||
*/
|
||||
export interface AlignmentView {
|
||||
"localIndex": number;
|
||||
"localTitle": string;
|
||||
"localLengthMillis": number;
|
||||
"candidatePosition": number;
|
||||
"candidateDiscNumber": number;
|
||||
"candidateTitle": string;
|
||||
"candidateMbid": string;
|
||||
"candidateLength": number;
|
||||
"titleScore": number;
|
||||
"lengthDeltaMs": number;
|
||||
"trackNumberOk": boolean;
|
||||
"status": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApplyResultView mirrors autotag.ApplyResult for the frontend.
|
||||
*/
|
||||
export interface ApplyResultView {
|
||||
"groupKey": string;
|
||||
"succeeded": number;
|
||||
"failed": number;
|
||||
"failures": FailureView[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CandidateView mirrors autotag.Candidate + its alignments. The
|
||||
* CoverArtURL is populated eagerly only for the top-ranked
|
||||
* candidate (eager network fetch); the frontend calls
|
||||
* GetCandidateCoverArt to lazy-load art for the others when the
|
||||
* user selects them. Date is this release's own date (re-issue
|
||||
* year for remasters); OriginalDate is the release-group's
|
||||
* first-release-date (the original album year).
|
||||
*/
|
||||
export interface CandidateView {
|
||||
"releaseMbid": string;
|
||||
"releaseGroupMbid": string;
|
||||
"title": string;
|
||||
"artistCredit": string;
|
||||
"date": string;
|
||||
"originalDate": string;
|
||||
"country": string;
|
||||
"status": string;
|
||||
"primaryType": string;
|
||||
"trackCount": number;
|
||||
"score": number;
|
||||
"breakdown": ScoreBreakdownView;
|
||||
"source": string;
|
||||
"provenance": string;
|
||||
"coverArtUrl": string;
|
||||
"alignments": AlignmentView[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* FailureView carries one failed track for the frontend's toast.
|
||||
*/
|
||||
export interface FailureView {
|
||||
"filePath": string;
|
||||
"error": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalTrackView mirrors autotag.LocalTrack.
|
||||
*/
|
||||
export interface LocalTrackView {
|
||||
"audioFileId": number;
|
||||
"filePath": string;
|
||||
"title": string;
|
||||
"artist": string;
|
||||
"trackNumber": number;
|
||||
"discNumber": number;
|
||||
"lengthMillis": number;
|
||||
"recordingMbid": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* PendingItem is a projection of tagging_items that's safe to hand
|
||||
* to the frontend. Score is dereferenced to 0 when NULL so TS sees
|
||||
* a plain number.
|
||||
*/
|
||||
export interface PendingItem {
|
||||
"groupKey": string;
|
||||
"libraryId": number;
|
||||
"libraryName": string;
|
||||
|
||||
/**
|
||||
* FolderSubPath is the album folder's path relative to its
|
||||
* library root (e.g. "Beatles/Abbey Road"). Empty when the
|
||||
* path can't be derived (no audio files yet, missing library
|
||||
* row, etc.). Populated by ListPendingFolders and
|
||||
* GetPendingFolder; not present on rows returned by other
|
||||
* list/get queries that haven't been extended.
|
||||
*/
|
||||
"folderSubPath": string;
|
||||
"trackCount": number;
|
||||
"albumName": string;
|
||||
"albumArtist": string;
|
||||
"discNumber": number;
|
||||
"bestMatchReleaseMbid": string;
|
||||
"score": number;
|
||||
"status": string;
|
||||
|
||||
/**
|
||||
* Synthetic marks a group SplitMixedFolder carved out of a
|
||||
* bigger folder by matching tags rather than a directory — the
|
||||
* review UI labels these distinctly since several may share the
|
||||
* same FolderSubPath.
|
||||
*/
|
||||
"synthetic": boolean;
|
||||
|
||||
/**
|
||||
* LikelyMixedBag is a cheap SQL-side approximation of autotag.
|
||||
* IsMixedBag, computed for the whole library in one pass by
|
||||
* ListPendingFolders (see ListLikelyMixedBagGroupKeys) rather
|
||||
* than hydrating every group's tracks in Go. It's a badge hint,
|
||||
* not a guarantee — ScoreView.MixedBag (computed from the real
|
||||
* track list when a folder is opened) is the authoritative check
|
||||
* that gates the SplitMixedFolder action itself.
|
||||
*/
|
||||
"likelyMixedBag": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ScoreBreakdownView mirrors autotag.ScoreBreakdown for display.
|
||||
* Each field is in [0, 1].
|
||||
*/
|
||||
export interface ScoreBreakdownView {
|
||||
"titleAvg": number;
|
||||
"lengthAvg": number;
|
||||
"artistFit": number;
|
||||
"albumFit": number;
|
||||
"trackCountFit": number;
|
||||
"releaseMeta": number;
|
||||
"evidence": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* ScoreView is the Wails-friendly projection of autotag.GroupScore.
|
||||
*/
|
||||
export interface ScoreView {
|
||||
"groupKey": string;
|
||||
"localTracks": LocalTrackView[] | null;
|
||||
"candidates": CandidateView[] | null;
|
||||
|
||||
/**
|
||||
* Recommendation is the qualitative confidence tier for the
|
||||
* ranked list: "none", "low", "medium", or "strong". Unlike the
|
||||
* raw score it accounts for ambiguity (a rival release group
|
||||
* scoring nearly as high) and alignment defects.
|
||||
*/
|
||||
"recommendation": string;
|
||||
|
||||
/**
|
||||
* MixedBag is true when this group's tracks look like an
|
||||
* unrelated pile rather than one release (autotag.IsMixedBag) —
|
||||
* the review UI offers SplitMixedFolder when set. Always false
|
||||
* for a group that's already Synthetic; a split group doesn't
|
||||
* get split again.
|
||||
*/
|
||||
"mixedBag": boolean;
|
||||
|
||||
/**
|
||||
* Synthetic mirrors PendingItem.Synthetic for the currently
|
||||
* open group.
|
||||
*/
|
||||
"synthetic": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchHitView is one in-app MusicBrainz search result surfaced to
|
||||
* the review UI. Kind is "releasegroup" or "recording" so the
|
||||
* frontend knows which resolve path SelectSearchCandidate must take.
|
||||
*/
|
||||
export interface SearchHitView {
|
||||
"mbid": string;
|
||||
"kind": string;
|
||||
"title": string;
|
||||
"artist": string;
|
||||
"detail": string;
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service is the Wails-bound surface for the review UI. All
|
||||
* exported methods become TypeScript stubs that the frontend calls.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as jobs$0 from "../jobs/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* AckLibraryWarning records that the user has seen the first-
|
||||
* time-apply irreversibility warning for this library.
|
||||
*/
|
||||
export function AckLibraryWarning(libraryID: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(1523853598, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply runs the apply pipeline for the given group + chosen
|
||||
* release MBID. Uses the cached candidate list from the last
|
||||
* GetCandidates / GetCandidatesForPasteURL call so the user
|
||||
* always applies the exact release they saw in the UI.
|
||||
* Empty releaseMBID picks the top-scored candidate.
|
||||
*/
|
||||
export function Apply(groupKey: string, releaseMBID: string): $CancellablePromise<$models.ApplyResultView | null> {
|
||||
return $Call.ByID(2015906074, groupKey, releaseMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* ApplyAsync dispatches an Apply for the given group in a
|
||||
* background goroutine and returns immediately. Progress is
|
||||
* reported to the frontend via three Wails events:
|
||||
*
|
||||
* - AutotagApplyStarted {groupKey, total}
|
||||
* - AutotagApplyProgress {groupKey, current, total, succeeded, failed}
|
||||
* - AutotagApplyFinished {groupKey, succeeded, failed, error}
|
||||
*
|
||||
* The candidate list and locals are snapshot synchronously before
|
||||
* returning so a concurrent GetCandidates can't shuffle the
|
||||
* selected release out from under the running job. Returns
|
||||
* ErrApplyInFlight when a job for the same groupKey is already
|
||||
* running; otherwise nil. Job-level errors land in the Finished
|
||||
* event payload, not the return value.
|
||||
*/
|
||||
export function ApplyAsync(groupKey: string, releaseMBID: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2246879134, groupKey, releaseMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* ClearCompletedEntries marks every confirmed item in the given
|
||||
* library (or all libraries when libraryID = 0) as cleared. The
|
||||
* rows stay in the table — so a re-scan of the same folder
|
||||
* won't bounce them back into 'pending' — but they no longer
|
||||
* appear in the review queue. Returns the number of rows
|
||||
* affected so the frontend can show a quick toast.
|
||||
*/
|
||||
export function ClearCompletedEntries(libraryID: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(1606786204, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetCandidateCoverArt is the lazy-load entrypoint for cover art
|
||||
* when the user picks a non-top candidate. Goes through the
|
||||
* CAA-only chain (disk cache → network) — never the local
|
||||
* library-by-name index, since that index conflates embedded ID3
|
||||
* art with externally-fetched art and the embedded path produces
|
||||
* stale or wrong-version covers for autotag review.
|
||||
*/
|
||||
export function GetCandidateCoverArt(releaseMBID: string, releaseGroupMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(1410264131, releaseMBID, releaseGroupMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetCandidates runs the scorer for a group and returns the full
|
||||
* candidate list (ordered best-first) along with the local tracks.
|
||||
* Caches the scored candidate list so Apply can operate on the
|
||||
* exact release the user saw — even if a concurrent rescore would
|
||||
* have shuffled the ranking. Also writes the top candidate's
|
||||
* score back to the tagging_items row so the sidebar's match-%
|
||||
* pill and the score-desc list sort reflect the freshly-computed
|
||||
* ranking instead of the scanner's pre-review estimate.
|
||||
*/
|
||||
export function GetCandidates(groupKey: string): $CancellablePromise<$models.ScoreView | null> {
|
||||
return $Call.ByID(873001524, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetCandidatesForPasteURL resolves a MusicBrainz release-or-
|
||||
* release-group URL into a ScoreView by fetching the pasted entity
|
||||
* from MB, running track alignment against the local group, and
|
||||
* prepending a fully-scored candidate to the existing list. The
|
||||
* pasted candidate's provenance is "paste" so the UI can label it.
|
||||
*/
|
||||
export function GetCandidatesForPasteURL(groupKey: string, mbReleaseURL: string): $CancellablePromise<$models.ScoreView | null> {
|
||||
return $Call.ByID(2470223543, groupKey, mbReleaseURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetLocalCoverArt returns a data-URI for the artwork associated
|
||||
* with the group's local files, so the review UI can show "what the
|
||||
* folder already looks like" beside the fetched candidate art.
|
||||
* It first checks each file for embedded ID3/Vorbis pictures, then
|
||||
* falls back to a sidecar cover image (cover.jpg, folder.png, …)
|
||||
* sitting in the album directory. Untagged folders carry one or the
|
||||
* other far more often than they have a DB release-group cover, so we
|
||||
* read the files directly. Returns "" when nothing is found.
|
||||
*/
|
||||
export function GetLocalCoverArt(groupKey: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(2603982821, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetNextPending returns the next pending tagging item after the
|
||||
* internal cursor, or nil when the queue is empty. Advances the
|
||||
* cursor on success.
|
||||
*/
|
||||
export function GetNextPending(): $CancellablePromise<$models.PendingItem | null> {
|
||||
return $Call.ByID(1751929138);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetPendingFolder returns the tagging item for a specific group
|
||||
* key — used by the sidebar after the user clicks an entry, so
|
||||
* the header can render even when the cursor-based queue iteration
|
||||
* hasn't visited that key yet.
|
||||
*/
|
||||
export function GetPendingFolder(groupKey: string): $CancellablePromise<$models.PendingItem | null> {
|
||||
return $Call.ByID(3455545217, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* LeaveAsIs is the "local tags are correct, don't rescore"
|
||||
* acknowledgement — bumps status to 'confirmed' without touching
|
||||
* any files.
|
||||
*/
|
||||
export function LeaveAsIs(groupKey: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(3897348801, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* ListPendingFolders returns every tagging item in the given
|
||||
* library (or all libraries when libraryID = 0) that has not been
|
||||
* explicitly cleared. Includes items in all review states —
|
||||
* pending, skipped, and confirmed — so the sidebar can group
|
||||
* them into sections (the user wanted skipped/completed to stay
|
||||
* visible at the bottom rather than vanish). Sorted by score
|
||||
* descending so the UI surfaces the most confident matches first
|
||||
* (NULL scores fall to the bottom); the frontend re-groups by
|
||||
* status for display.
|
||||
*/
|
||||
export function ListPendingFolders(libraryID: number): $CancellablePromise<$models.PendingItem[] | null> {
|
||||
return $Call.ByID(617511590, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* RetagGroup flips a group back to 'pending' so the user can
|
||||
* re-review after an apply or skip. Drops the durably-cached
|
||||
* candidate list too, so the next open recomputes against fresh
|
||||
* MusicBrainz data rather than reusing the stale stored result.
|
||||
*/
|
||||
export function RetagGroup(groupKey: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2552507548, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchCandidates runs an in-app MusicBrainz search — the "suggest a
|
||||
* new candidate" escape hatch when the automatic cascade misses.
|
||||
* kind is "recording" (title + artist, for singletons) or anything
|
||||
* else (album + artist release-group search). Returns lightweight
|
||||
* hits; SelectSearchCandidate resolves the picked one into a scored
|
||||
* candidate.
|
||||
*/
|
||||
export function SearchCandidates(kind: string, query: string, artist: string): $CancellablePromise<$models.SearchHitView[] | null> {
|
||||
return $Call.ByID(4062823000, kind, query, artist);
|
||||
}
|
||||
|
||||
/**
|
||||
* SelectSearchCandidate resolves a picked search hit into a fully-
|
||||
* scored candidate and splices it to the top of the group's candidate
|
||||
* list — the same shape as the paste-URL path, so Apply works
|
||||
* unchanged. A "recording" hit is resolved to a representative
|
||||
* release first.
|
||||
*/
|
||||
export function SelectSearchCandidate(groupKey: string, kind: string, mbid: string): $CancellablePromise<$models.ScoreView | null> {
|
||||
return $Call.ByID(2540832563, groupKey, kind, mbid);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetJobRegistry wires the background job registry so an apply reports
|
||||
* progress and offers a cancel like every other long-running operation.
|
||||
*
|
||||
* Before this, apply was a bare goroutine whose progress lived in a
|
||||
* component field that navigation discarded, with no cancel and no
|
||||
* record of where it stopped (errors.C3). Everything routed through the
|
||||
* registry gets progress, cancel and the global indicator for free; the
|
||||
* three subsystems that lacked them were the three that were not
|
||||
* registered.
|
||||
*/
|
||||
export function SetJobRegistry(reg: jobs$0.Registry | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(3755409662, reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip marks the current group as skipped — it stays in the
|
||||
* queue but renders in the "Skipped" section at the bottom of
|
||||
* the sidebar, separate from pending items, so the user can
|
||||
* revisit it later if they change their mind.
|
||||
*/
|
||||
export function Skip(groupKey: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(1248497081, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* SplitMixedFolder is the "this folder is a pile of unrelated
|
||||
* tracks" escape hatch: it partitions the group's local tracks via
|
||||
* autotag.SplitPlan — tag-matched sub-albums (see
|
||||
* autotag.ClusterByAlbumArtist) plus a one-track cluster for every
|
||||
* track that didn't share an (album, album-artist) pair with
|
||||
* anything else — and carves each piece out into its own synthetic
|
||||
* tagging group, reassigning just those audio_files rows (no files
|
||||
* move on disk). Every track leaves the original group; nothing is
|
||||
* left behind to be scored as "extra tracks" of whichever piece
|
||||
* happens to match first. The synthetic groups are scored with
|
||||
* relaxed missing-track handling (rank.go, recommend.go), since
|
||||
* they're expected to be an incomplete subset of whatever release
|
||||
* they belong to.
|
||||
*
|
||||
* Returns the resulting PendingItems — the leftover original group
|
||||
* first (if anything remains in it), then the new synthetic groups
|
||||
* — so the frontend can splice them into the sidebar without a full
|
||||
* reload. Errors with errNothingToSplit when the folder is already
|
||||
* one coherent unit (SplitPlan produces a single cluster covering
|
||||
* every track); callers should treat that as "nothing to show", not
|
||||
* a failure.
|
||||
*/
|
||||
export function SplitMixedFolder(groupKey: string): $CancellablePromise<$models.PendingItem[] | null> {
|
||||
return $Call.ByID(3640008069, groupKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* StartAutotagQueue resets the cursor for a new review session
|
||||
* and kicks off the background prefetch worker so sidebar pills
|
||||
* populate while the user reviews the first folder. Cancels any
|
||||
* previous prefetch (library filter may have changed). Pass
|
||||
* libraryID=0 to cover all libraries.
|
||||
*/
|
||||
export function StartAutotagQueue(libraryID: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(568310002, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* StartBackgroundPrefetch kicks off (or restarts) the prefetch
|
||||
* worker over all libraries. Called from app startup hooks so
|
||||
* unscored pending items get their match-% backfilled while the
|
||||
* user does other things — by the time they navigate to the
|
||||
* autotag page, the sidebar pills are already populated.
|
||||
* Idempotent: items with an existing score are skipped, so it's
|
||||
* safe to call after every library scan and on every app launch.
|
||||
*/
|
||||
export function StartBackgroundPrefetch(): $CancellablePromise<void> {
|
||||
return $Call.ByID(428378477);
|
||||
}
|
||||
|
||||
/**
|
||||
* WritesInFlight reports whether an apply is currently rewriting tags
|
||||
* on disk. Quitting mid-apply leaves a folder half-retagged, so the app
|
||||
* asks before closing (errors.p4).
|
||||
*/
|
||||
export function WritesInFlight(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(1561223347);
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Config represents the application configuration.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as download$0 from "../download/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as tracklist$0 from "../tracklist/models.js";
|
||||
|
||||
/**
|
||||
* GetDefaultPage returns the view the app opens to on launch.
|
||||
*/
|
||||
export function GetDefaultPage(): $CancellablePromise<string> {
|
||||
return $Call.ByID(3209119019);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetDownloadPreferences returns the configured auto-download
|
||||
* guardrails.
|
||||
*/
|
||||
export function GetDownloadPreferences(): $CancellablePromise<download$0.AutoDownloadPrefs> {
|
||||
return $Call.ByID(4104118319);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetFavoritesIconStyle returns the configured icon style.
|
||||
*/
|
||||
export function GetFavoritesIconStyle(): $CancellablePromise<string> {
|
||||
return $Call.ByID(3222927230);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetFavoritesPlaylistID returns the configured default playlist ID.
|
||||
*/
|
||||
export function GetFavoritesPlaylistID(): $CancellablePromise<number> {
|
||||
return $Call.ByID(4116868495);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetLibraryDirectory returns the currently configured library directory path.
|
||||
*/
|
||||
export function GetLibraryDirectory(): $CancellablePromise<string> {
|
||||
return $Call.ByID(2123565657);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetPinDefaultPlaylist returns whether the default playlist
|
||||
* is pinned to the top of the playlist view.
|
||||
*/
|
||||
export function GetPinDefaultPlaylist(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(3818283301);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetQueueFallback returns what plays, if anything, once the queue
|
||||
* runs out.
|
||||
*/
|
||||
export function GetQueueFallback(): $CancellablePromise<string> {
|
||||
return $Call.ByID(1504773860);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetScanConcurrency returns the configured scan concurrency mode.
|
||||
*/
|
||||
export function GetScanConcurrency(): $CancellablePromise<string> {
|
||||
return $Call.ByID(3233312385);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetShortcuts returns the current shortcut bindings map.
|
||||
*/
|
||||
export function GetShortcuts(): $CancellablePromise<{ [_ in string]?: string } | null> {
|
||||
return $Call.ByID(1616717634);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetThemeAccentColor returns the configured accent colour.
|
||||
*/
|
||||
export function GetThemeAccentColor(): $CancellablePromise<string> {
|
||||
return $Call.ByID(4264374057);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetThemeBackgroundShade returns the configured background shade.
|
||||
*/
|
||||
export function GetThemeBackgroundShade(): $CancellablePromise<string> {
|
||||
return $Call.ByID(578772653);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTrackListColumns returns the configured track-list columns.
|
||||
*/
|
||||
export function GetTrackListColumns(): $CancellablePromise<tracklist$0.Column[] | null> {
|
||||
return $Call.ByID(3426289065);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load reads and parses the config file from disk.
|
||||
*/
|
||||
export function Load(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2408891113);
|
||||
}
|
||||
|
||||
/**
|
||||
* ResetShortcuts resets all shortcuts to defaults.
|
||||
*/
|
||||
export function ResetShortcuts(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3197898083);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save writes the config to disk. Refuses to write if the config
|
||||
* was never successfully loaded — prevents overwriting user config
|
||||
* with defaults during abnormal startup/shutdown sequences.
|
||||
*/
|
||||
export function Save(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1988945736);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetDefaultPage validates and saves a new launch page.
|
||||
*/
|
||||
export function SetDefaultPage(page: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2714957423, page);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetDownloadPreferences saves new auto-download guardrails. This only
|
||||
* persists them; the download package cannot depend on config (config
|
||||
* already depends on download for UserConfig), so making the change
|
||||
* live without a restart is the caller's job — the frontend settings
|
||||
* save calls this and download.Service.SetPreferences in the same
|
||||
* action, and app.go's initDownloadRuntime applies the saved value to
|
||||
* the running Manager at startup.
|
||||
*/
|
||||
export function SetDownloadPreferences(prefs: download$0.AutoDownloadPrefs): $CancellablePromise<void> {
|
||||
return $Call.ByID(2655203755, prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetFavoritesIconStyle validates and saves a new icon style.
|
||||
*/
|
||||
export function SetFavoritesIconStyle(style: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(812590378, style);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetFavoritesPlaylistID saves a new default playlist ID.
|
||||
*/
|
||||
export function SetFavoritesPlaylistID(id: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2117079163, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetLibraryDirectory validates and saves a new library directory,
|
||||
* then emits the LibraryConfigChanged event so listeners (e.g. the
|
||||
* Library scanner) can react.
|
||||
*/
|
||||
export function SetLibraryDirectory(dir: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(1352505021, dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetPinDefaultPlaylist saves whether the default playlist
|
||||
* should be pinned to the top of the playlist view.
|
||||
*/
|
||||
export function SetPinDefaultPlaylist(pin: boolean): $CancellablePromise<void> {
|
||||
return $Call.ByID(372446849, pin);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetQueueFallback validates and saves a new queue-fallback mode.
|
||||
*/
|
||||
export function SetQueueFallback(mode: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2824429312, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetScanConcurrency validates and saves a new scan concurrency
|
||||
* mode. The change takes effect on the next scan.
|
||||
*/
|
||||
export function SetScanConcurrency(mode: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(742893445, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetShortcut saves a single shortcut binding.
|
||||
*/
|
||||
export function SetShortcut(action: string, key: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(275130705, action, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetShortcuts saves the entire shortcut bindings map.
|
||||
*/
|
||||
export function SetShortcuts(bindings: { [_ in string]?: string } | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(4073898118, bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetThemeAccentColor validates and saves a new accent colour.
|
||||
*/
|
||||
export function SetThemeAccentColor(color: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(3536665861, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetThemeBackgroundShade validates and saves a new background shade.
|
||||
*/
|
||||
export function SetThemeBackgroundShade(shade: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2709290065, shade);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetTrackListColumns validates and saves a new column layout.
|
||||
*/
|
||||
export function SetTrackListColumns(columns: tracklist$0.Column[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(4226159685, columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate returns errors if there is a breaking issue with the config.
|
||||
*/
|
||||
export function Validate(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3087982155);
|
||||
}
|
||||
Executable → Regular
+4
-4
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function GetShelves() {
|
||||
return window['go']['home']['Service']['GetShelves']();
|
||||
}
|
||||
import * as Config from "./config.js";
|
||||
export {
|
||||
Config
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export type {
|
||||
Library
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export interface Library {
|
||||
"ID": number;
|
||||
"Name": string;
|
||||
"Path": string;
|
||||
"CreatedAt": string;
|
||||
"AutotagWarningAcked": number;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export {
|
||||
Entity,
|
||||
Format,
|
||||
Kind,
|
||||
Protocol,
|
||||
RequestScope,
|
||||
RequestState,
|
||||
State
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
AutoDownloadPrefs,
|
||||
Candidate,
|
||||
CandidateFile,
|
||||
Caps,
|
||||
Config,
|
||||
Descriptor,
|
||||
DownloadItem,
|
||||
DownloadView,
|
||||
ExpectedTrack,
|
||||
Field,
|
||||
MatchScore,
|
||||
QualityScore,
|
||||
Reconciler,
|
||||
Request,
|
||||
RequestInput,
|
||||
SearchRequest,
|
||||
StartResult,
|
||||
Summary
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,732 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* AutoDownloadPrefs gates and scores what AutoPickable may choose
|
||||
* without asking. Zero values are permissive: no size window and no
|
||||
* format restriction.
|
||||
*/
|
||||
export interface AutoDownloadPrefs {
|
||||
/**
|
||||
* MinSizeMB and MaxSizeMB bound what auto-pick will grab. Zero
|
||||
* means no bound on that side. A candidate outside the window is
|
||||
* filtered out of auto-pick entirely, not merely scored down — a
|
||||
* tiny "sampler" torrent or a boxset ten times the expected size is
|
||||
* usually the wrong thing entirely, not a worse copy of the right
|
||||
* thing.
|
||||
*/
|
||||
"minSizeMb": number;
|
||||
"maxSizeMb": number;
|
||||
|
||||
/**
|
||||
* PreferredSizeMB nudges the score toward a target size within the
|
||||
* min/max window (a lossless rip and a heavily-padded lossless rip
|
||||
* can both pass the window). Zero disables the nudge; sizeFit then
|
||||
* returns a neutral value that does not affect ranking.
|
||||
*/
|
||||
"preferredSizeMb": number;
|
||||
|
||||
/**
|
||||
* AllowedFormats restricts auto-pick to candidates whose audio
|
||||
* files are all in one of these formats. Empty means no
|
||||
* restriction.
|
||||
*/
|
||||
"allowedFormats": Format[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Candidate is one acquirable thing a provider found: a Soulseek user's
|
||||
* folder, a torrent, a YouTube playlist. Providers fill the descriptive
|
||||
* fields; the ranker fills Match, Quality and Score.
|
||||
*/
|
||||
export interface Candidate {
|
||||
/**
|
||||
* ID is unique within the provider that produced it, and is what
|
||||
* gets handed back to Grab.
|
||||
*/
|
||||
"id": string;
|
||||
"providerId": number;
|
||||
"kind": Kind;
|
||||
|
||||
/**
|
||||
* Protocol determines which transport can fetch this.
|
||||
*/
|
||||
"protocol": Protocol;
|
||||
|
||||
/**
|
||||
* Descriptive.
|
||||
*/
|
||||
"title": string;
|
||||
"artist"?: string;
|
||||
|
||||
/**
|
||||
* peer username, indexer name, channel
|
||||
*/
|
||||
"origin"?: string;
|
||||
"files": CandidateFile[] | null;
|
||||
"totalSize": number;
|
||||
|
||||
/**
|
||||
* Health is the provider's own availability signal, normalized to
|
||||
* 0..1: seeder count for torrents, free upload slots and queue
|
||||
* length for Soulseek. 0.5 when the provider has no signal.
|
||||
*/
|
||||
"health": number;
|
||||
|
||||
/**
|
||||
* Scores, filled by the ranker.
|
||||
*/
|
||||
"match": MatchScore;
|
||||
"quality": QualityScore;
|
||||
"score": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* CandidateFile is one file inside a candidate. Soulseek and torrent
|
||||
* results give paths and sizes but no tags, so Format and duration are
|
||||
* inferred from the path and size where possible.
|
||||
*/
|
||||
export interface CandidateFile {
|
||||
"path": string;
|
||||
"size": number;
|
||||
"format": Format;
|
||||
|
||||
/**
|
||||
* kbps, 0 when unknown
|
||||
*/
|
||||
"bitrate"?: number;
|
||||
"isAudio": boolean;
|
||||
|
||||
/**
|
||||
* expected track position
|
||||
*/
|
||||
"matchedTo"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caps declares which roles a provider fills and which optional
|
||||
* behaviours it supports. The frontend renders controls from this
|
||||
* rather than switching on Kind, so a provider that gains resume
|
||||
* support later needs no frontend change.
|
||||
*/
|
||||
export interface Caps {
|
||||
/**
|
||||
* Roles.
|
||||
*/
|
||||
"canSearch": boolean;
|
||||
"canTransport": boolean;
|
||||
"canDelegate": boolean;
|
||||
|
||||
/**
|
||||
* CanList marks a provider that keeps a persistent wanted list of
|
||||
* its own, which the reconciler mirrors this app's list into.
|
||||
*/
|
||||
"canList": boolean;
|
||||
|
||||
/**
|
||||
* Optional behaviours.
|
||||
*/
|
||||
"canResume": boolean;
|
||||
"canCancel": boolean;
|
||||
"reportsSize": boolean;
|
||||
|
||||
/**
|
||||
* Protocols this provider can transport. Empty for providers that
|
||||
* only fetch their own search results.
|
||||
*/
|
||||
"transports": Protocol[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Config is a provider's stored settings. Secret values are not held
|
||||
* here — they live in the secrets store keyed by provider ID, so a
|
||||
* config blob can be logged or shown in the UI without redaction.
|
||||
*/
|
||||
export interface Config {
|
||||
"id": number;
|
||||
"kind": Kind;
|
||||
"name": string;
|
||||
"enabled": boolean;
|
||||
"priority": number;
|
||||
"settings": { [_ in string]?: string } | null;
|
||||
|
||||
/**
|
||||
* SetSecrets names which of the descriptor's secret fields already
|
||||
* have a stored value, without exposing it. Populated only when a
|
||||
* Config is built for the frontend (see Service.withSecretFlags);
|
||||
* empty when read from or written to the store.
|
||||
*/
|
||||
"setSecrets"?: { [_ in string]?: boolean } | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptor is the static, instance-independent description of a
|
||||
* provider kind: what it is called, what it can do, and which settings
|
||||
* it needs. The settings page renders its form from this, so a new
|
||||
* provider gets a config UI without any frontend work.
|
||||
*/
|
||||
export interface Descriptor {
|
||||
"kind": Kind;
|
||||
"name": string;
|
||||
|
||||
/**
|
||||
* Summary is one line explaining what connecting this gets you.
|
||||
*/
|
||||
"summary": string;
|
||||
|
||||
/**
|
||||
* Caps are the kind's inherent capabilities, before configuration.
|
||||
*/
|
||||
"caps": Caps;
|
||||
|
||||
/**
|
||||
* Fields are the settings the user must supply.
|
||||
*/
|
||||
"fields": Field[] | null;
|
||||
|
||||
/**
|
||||
* RequiresExternal names the software the user must run themselves
|
||||
* (a slskd daemon, a Lidarr instance), or is empty for providers
|
||||
* that need nothing but a binary on PATH.
|
||||
*/
|
||||
"requiresExternal"?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DownloadItem is one grab attempt, as stored.
|
||||
*
|
||||
* deliberate: distinguishes it from download.Download (the attempt) and
|
||||
* download.Request (the durable record) at every call site, which a bare
|
||||
* "Item" would not.
|
||||
*/
|
||||
export interface DownloadItem {
|
||||
"id": string;
|
||||
"downloadId": string;
|
||||
"providerId": number;
|
||||
"transportId"?: number;
|
||||
"externalId"?: string;
|
||||
"candidate": Candidate;
|
||||
"state": State;
|
||||
"bytesDone": number;
|
||||
"bytesTotal": number;
|
||||
"error"?: string;
|
||||
"createdAt": string;
|
||||
"updatedAt": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DownloadView is one row of the downloads list.
|
||||
*
|
||||
* would stop reading as "one row of the Downloads list" the moment this
|
||||
* package also has a Requests list — see RequestInput/Request nearby.
|
||||
*/
|
||||
export interface DownloadView {
|
||||
"id": string;
|
||||
|
||||
/**
|
||||
* Anchors. Any may be empty; all empty means free-text.
|
||||
*/
|
||||
"releaseMbid"?: string;
|
||||
"releaseGroupMbid"?: string;
|
||||
|
||||
/**
|
||||
* RecordingMBID anchors a single-track download. Its Expected holds
|
||||
* exactly that one track, which is what lets a track download be
|
||||
* scored — and therefore auto-picked — on the same footing as an
|
||||
* album.
|
||||
*/
|
||||
"recordingMbid"?: string;
|
||||
|
||||
/**
|
||||
* RequestID links back to the durable Request row this download was
|
||||
* raised for or attached to, or 0 for a free-text download with
|
||||
* nothing stable to attach to. The reconciler and manual anchored
|
||||
* downloads both write the outcome back through it.
|
||||
*/
|
||||
"requestId"?: number;
|
||||
|
||||
/**
|
||||
* Source records where the download came from, for the downloads
|
||||
* list. Empty means "manual".
|
||||
*/
|
||||
"source"?: string;
|
||||
|
||||
/**
|
||||
* Display and query text. Artist/Album are what searches are built
|
||||
* from; Query overrides them when the user typed something raw.
|
||||
*/
|
||||
"artist": string;
|
||||
"album": string;
|
||||
"query"?: string;
|
||||
|
||||
/**
|
||||
* Expected is the tracklist the anchor resolves to, used for
|
||||
* completeness scoring and for the autotag match at import. Empty
|
||||
* for free-text downloads.
|
||||
*/
|
||||
"expected"?: ExpectedTrack[] | null;
|
||||
|
||||
/**
|
||||
* LibraryID is the library imported files belong to.
|
||||
*/
|
||||
"libraryId": number;
|
||||
"createdAt": string;
|
||||
"state": State;
|
||||
"error"?: string;
|
||||
"items": DownloadItem[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity says what a request's MBID names, and is the only type
|
||||
* distinction the durable request list makes.
|
||||
*/
|
||||
export enum Entity {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Request entity types.
|
||||
*
|
||||
* EntityArtist is a subscription rather than a thing to fetch: it
|
||||
* is never satisfied, and each reconcile expands the artist's
|
||||
* discography into child requests.
|
||||
*/
|
||||
EntityArtist = "artist",
|
||||
|
||||
/**
|
||||
* EntityReleaseGroup is an album in the abstract — any release of
|
||||
* it satisfies the request, which is what a user means by "I want
|
||||
* this album".
|
||||
*/
|
||||
EntityReleaseGroup = "release-group",
|
||||
|
||||
/**
|
||||
* EntityRelease is one specific edition, used when the user picked
|
||||
* a particular pressing.
|
||||
*/
|
||||
EntityRelease = "release",
|
||||
|
||||
/**
|
||||
* EntityRecording is a single track.
|
||||
*/
|
||||
EntityRecording = "recording",
|
||||
};
|
||||
|
||||
/**
|
||||
* ExpectedTrack is one track of the release the user asked for.
|
||||
*/
|
||||
export interface ExpectedTrack {
|
||||
"position": number;
|
||||
"discNumber": number;
|
||||
"title": string;
|
||||
"artist": string;
|
||||
"lengthMillis": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field describes one provider setting for the settings form.
|
||||
*/
|
||||
export interface Field {
|
||||
"key": string;
|
||||
"label": string;
|
||||
"placeholder"?: string;
|
||||
"help"?: string;
|
||||
|
||||
/**
|
||||
* Secret marks a value stored in the secrets store rather than the
|
||||
* provider config row, and rendered as a password input.
|
||||
*/
|
||||
"secret": boolean;
|
||||
|
||||
/**
|
||||
* Path marks a value that names a local filesystem directory, so
|
||||
* the settings form can offer a native folder picker beside the
|
||||
* text input rather than making the user type or paste it.
|
||||
*/
|
||||
"path": boolean;
|
||||
"required": boolean;
|
||||
"default"?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format is a normalized audio container/codec name.
|
||||
*/
|
||||
export enum Format {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Audio formats, ordered by the quality ranking in formatRank.
|
||||
*/
|
||||
FormatUnknown = "",
|
||||
FormatFLAC = "flac",
|
||||
FormatALAC = "alac",
|
||||
FormatWAV = "wav",
|
||||
FormatMP3 = "mp3",
|
||||
FormatAAC = "aac",
|
||||
FormatOGG = "ogg",
|
||||
FormatOpus = "opus",
|
||||
FormatWMA = "wma",
|
||||
};
|
||||
|
||||
/**
|
||||
* Kind identifies a provider implementation. It is stored in the
|
||||
* database and used to look up the constructor in the registry, so
|
||||
* values are stable strings and never renamed.
|
||||
*/
|
||||
export enum Kind {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Provider kinds.
|
||||
*/
|
||||
KindSlskd = "slskd",
|
||||
KindYtDlp = "yt-dlp",
|
||||
KindLidarr = "lidarr",
|
||||
KindProwlarr = "prowlarr",
|
||||
KindQBittorrent = "qbittorrent",
|
||||
KindSABnzbd = "sabnzbd",
|
||||
|
||||
/**
|
||||
* KindFake is an in-memory provider used by tests. It is never
|
||||
* offered in the UI.
|
||||
*/
|
||||
KindFake = "fake",
|
||||
};
|
||||
|
||||
/**
|
||||
* MatchScore answers "is this the release the user asked for?" It is
|
||||
* deliberately separate from QualityScore: a perfect match at 128kbps
|
||||
* and a mediocre match in FLAC are different failures, and collapsing
|
||||
* them into one number makes the ranking impossible to explain.
|
||||
*/
|
||||
export interface MatchScore {
|
||||
/**
|
||||
* Overall is 0..1.
|
||||
*/
|
||||
"overall": number;
|
||||
|
||||
/**
|
||||
* filenames vs expected titles
|
||||
*/
|
||||
"titleFit": number;
|
||||
|
||||
/**
|
||||
* path/origin vs expected artist
|
||||
*/
|
||||
"artistFit": number;
|
||||
|
||||
/**
|
||||
* folder name vs album title
|
||||
*/
|
||||
"albumFit": number;
|
||||
|
||||
/**
|
||||
* audio files vs expected count
|
||||
*/
|
||||
"completeness": number;
|
||||
|
||||
/**
|
||||
* Anchored records whether an MBID drove this score. Unanchored
|
||||
* matches are capped, because there is nothing to be right about.
|
||||
*/
|
||||
"anchored": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protocol is how a candidate's bytes are moved. Search-only providers
|
||||
* report it so the pipeline can pick a compatible transport; providers
|
||||
* that transport their own results use ProtocolDirect.
|
||||
*/
|
||||
export enum Protocol {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Transport protocols.
|
||||
*
|
||||
* ProtocolDirect means the finding provider also does the fetch.
|
||||
*/
|
||||
ProtocolDirect = "direct",
|
||||
ProtocolTorrent = "torrent",
|
||||
ProtocolUsenet = "usenet",
|
||||
};
|
||||
|
||||
/**
|
||||
* QualityScore answers "is this a good copy?".
|
||||
*/
|
||||
export interface QualityScore {
|
||||
/**
|
||||
* Overall is 0..1.
|
||||
*/
|
||||
"overall": number;
|
||||
|
||||
/**
|
||||
* FLAC > V0 > 320 > lower
|
||||
*/
|
||||
"formatRank": number;
|
||||
"bitrate": number;
|
||||
|
||||
/**
|
||||
* seeders, free slots
|
||||
*/
|
||||
"health": number;
|
||||
|
||||
/**
|
||||
* user's per-provider preference
|
||||
*/
|
||||
"priority": number;
|
||||
|
||||
/**
|
||||
* closeness to the preferred download size
|
||||
*/
|
||||
"sizeFit": number;
|
||||
|
||||
/**
|
||||
* Mixed marks a candidate whose files are not all the same format,
|
||||
* which usually means a hand-assembled folder rather than a rip.
|
||||
*/
|
||||
"mixed": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconciler works the request list.
|
||||
*/
|
||||
export interface Reconciler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request is one row of the durable request list.
|
||||
*/
|
||||
export interface Request {
|
||||
"id": number;
|
||||
"mbid": string;
|
||||
"entity": Entity;
|
||||
"libraryId": number;
|
||||
|
||||
/**
|
||||
* Artist and Title are display cache only. Matching always uses
|
||||
* the MBID.
|
||||
*/
|
||||
"artist": string;
|
||||
"title": string;
|
||||
"scope": RequestScope;
|
||||
|
||||
/**
|
||||
* Secondary includes compilations, live albums and remixes in an
|
||||
* artist request's expansion.
|
||||
*/
|
||||
"secondary": boolean;
|
||||
"state": RequestState;
|
||||
|
||||
/**
|
||||
* ParentID is set on requests the reconciler derived from an artist
|
||||
* subscription. A request the user pinned directly has none, so
|
||||
* removing the artist leaves it alone.
|
||||
*/
|
||||
"parentId"?: number;
|
||||
"attempts": number;
|
||||
"lastError"?: string;
|
||||
"lastTriedAt"?: string;
|
||||
"nextTryAt"?: string;
|
||||
|
||||
/**
|
||||
* ExternalIDs maps provider row ID (as a string, because JSON
|
||||
* object keys are strings) to that provider's own identifier for
|
||||
* this request. Only set for providers that keep a persistent
|
||||
* list of their own.
|
||||
*/
|
||||
"externalIds"?: { [_ in string]?: string } | null;
|
||||
"createdAt": string;
|
||||
"updatedAt": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RequestInput is what the frontend submits to request something. It
|
||||
* is one MBID and the type of thing it names, because that is
|
||||
* genuinely all a durable request is.
|
||||
*/
|
||||
export interface RequestInput {
|
||||
"mbid": string;
|
||||
"entity": string;
|
||||
"libraryId": number;
|
||||
|
||||
/**
|
||||
* Artist and Title are display text only, and optional: the
|
||||
* reconciler fills them in from the catalog when the caller has
|
||||
* nothing but an MBID.
|
||||
*/
|
||||
"artist": string;
|
||||
"title": string;
|
||||
|
||||
/**
|
||||
* Scope and Secondary apply to artist requests.
|
||||
*/
|
||||
"scope": string;
|
||||
"secondary": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* RequestScope applies to artist requests only.
|
||||
*/
|
||||
export enum RequestScope {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Artist request scopes.
|
||||
*
|
||||
* ScopeFuture takes only releases first published after the artist
|
||||
* was added. Default, because subscribing to an artist should not
|
||||
* silently queue their entire back catalogue.
|
||||
*/
|
||||
ScopeFuture = "future",
|
||||
|
||||
/**
|
||||
* ScopeAll backfills the whole discography as well.
|
||||
*/
|
||||
ScopeAll = "all",
|
||||
};
|
||||
|
||||
/**
|
||||
* RequestState is where a request sits. There is deliberately no
|
||||
* "failed": an attempt can fail, a request cannot. A request that has
|
||||
* tried and not found anything is still wanted, with attempts and
|
||||
* last_error recording why it is taking a while.
|
||||
*/
|
||||
export enum RequestState {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Request states.
|
||||
*
|
||||
* RequestStateWanted is the active state: due for another attempt
|
||||
* when its backoff elapses.
|
||||
*/
|
||||
RequestStateWanted = "wanted",
|
||||
|
||||
/**
|
||||
* RequestStateSatisfied means the library owns it. How it got
|
||||
* there — downloaded here, ripped, bought elsewhere — does not
|
||||
* matter.
|
||||
*/
|
||||
RequestStateSatisfied = "satisfied",
|
||||
|
||||
/**
|
||||
* RequestStatePaused is the user saying "keep this on the list but
|
||||
* stop trying".
|
||||
*/
|
||||
RequestStatePaused = "paused",
|
||||
};
|
||||
|
||||
/**
|
||||
* SearchRequest is what the frontend submits to start a download.
|
||||
*/
|
||||
export interface SearchRequest {
|
||||
"libraryId": number;
|
||||
"releaseMbid": string;
|
||||
"releaseGroupMbid": string;
|
||||
"artist": string;
|
||||
"album": string;
|
||||
"query": string;
|
||||
"expected": ExpectedTrack[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* StartResult is what the picker needs after a search.
|
||||
*/
|
||||
export interface StartResult {
|
||||
"downloadId": string;
|
||||
"candidates": Candidate[] | null;
|
||||
|
||||
/**
|
||||
* AutoPicked reports that the pipeline already chose and is
|
||||
* downloading, so the picker should show progress rather than a
|
||||
* list of choices.
|
||||
*/
|
||||
"autoPicked": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* State is the lifecycle position of a download item.
|
||||
*/
|
||||
export enum State {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Download item states. Searching through Importing are live;
|
||||
* Complete, Cancelled and Failed are terminal.
|
||||
*/
|
||||
StateSearching = "searching",
|
||||
StateFound = "found",
|
||||
StateQueued = "queued",
|
||||
StateGrabbing = "grabbing",
|
||||
StateVerifying = "verifying",
|
||||
StateTagging = "tagging",
|
||||
StateImporting = "importing",
|
||||
StateComplete = "complete",
|
||||
StateCancelled = "cancelled",
|
||||
StateFailed = "failed",
|
||||
};
|
||||
|
||||
/**
|
||||
* Summary reports what a pass did, for logging and for the UI.
|
||||
*/
|
||||
export interface Summary {
|
||||
/**
|
||||
* Expanded is how many child requests artist subscriptions produced.
|
||||
*/
|
||||
"expanded": number;
|
||||
|
||||
/**
|
||||
* Satisfied is how many requests the library turned out to own.
|
||||
*/
|
||||
"satisfied": number;
|
||||
|
||||
/**
|
||||
* Attempted is how many requests were searched for.
|
||||
*/
|
||||
"attempted": number;
|
||||
|
||||
/**
|
||||
* Started is how many of those found a clear enough winner to
|
||||
* download unattended.
|
||||
*/
|
||||
"started": number;
|
||||
|
||||
/**
|
||||
* Synced is how many requests were pushed to an external list.
|
||||
*/
|
||||
"synced": number;
|
||||
|
||||
/**
|
||||
* Waiting is how many requests are on the list and still being
|
||||
* looked for. A pass that did nothing is the normal case, and the
|
||||
* UI can only say so honestly if it knows the list was not empty.
|
||||
*/
|
||||
"waiting": number;
|
||||
|
||||
/**
|
||||
* NoProviders reports that nothing could be searched because no
|
||||
* download client is enabled — the one "nothing happened" the user
|
||||
* can actually fix.
|
||||
*/
|
||||
"noProviders": boolean;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service is the frontend-facing surface of the download subsystem.
|
||||
* Its methods are bound into Wails and called from TypeScript, so
|
||||
* signatures use plain types and return errors the UI can render.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* AddProvider creates a provider and stores any secret settings
|
||||
* separately. Secrets arrive in the same map as ordinary settings
|
||||
* because that is what the form submits; they are split out here and
|
||||
* never written to the provider row.
|
||||
*/
|
||||
export function AddProvider(kind: string, name: string, settings: { [_ in string]?: string } | null): $CancellablePromise<number> {
|
||||
return $Call.ByID(2479360532, kind, name, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* AddRequest puts something on the request list and asks for a
|
||||
* reconcile pass, so the user sees something happen rather than
|
||||
* waiting six hours for the next scheduled one.
|
||||
*/
|
||||
export function AddRequest(req: $models.RequestInput): $CancellablePromise<number> {
|
||||
return $Call.ByID(2621441882, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel aborts a live download.
|
||||
*/
|
||||
export function Cancel(downloadID: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(1726338100, downloadID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Candidates returns the ranked candidates of a live download, so the
|
||||
* picker can be reopened without searching again.
|
||||
*/
|
||||
export function Candidates(downloadID: string): $CancellablePromise<$models.Candidate[] | null> {
|
||||
return $Call.ByID(2436454548, downloadID);
|
||||
}
|
||||
|
||||
/**
|
||||
* ClearFinished removes terminal downloads from the list.
|
||||
*/
|
||||
export function ClearFinished(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3757713449);
|
||||
}
|
||||
|
||||
/**
|
||||
* ClearSatisfiedRequests drops everything already owned.
|
||||
*/
|
||||
export function ClearSatisfiedRequests(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2681765363);
|
||||
}
|
||||
|
||||
/**
|
||||
* DeleteProvider removes a provider and its credentials.
|
||||
*/
|
||||
export function DeleteProvider(id: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(495153424, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* ImportExternalRequests adopts a provider's own list — "import the
|
||||
* artists Lidarr is already monitoring".
|
||||
*/
|
||||
export function ImportExternalRequests(providerID: number, libraryID: number): $CancellablePromise<number> {
|
||||
return $Call.ByID(4055326022, providerID, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* IsRequested answers the Explore pages' question — should this album
|
||||
* show "want" or "wanted?" — without making them load the whole list.
|
||||
*/
|
||||
export function IsRequested(mbid: string, libraryID: number): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(3695628158, mbid, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* ListDownloads returns recent downloads, newest first.
|
||||
*/
|
||||
export function ListDownloads(limit: number): $CancellablePromise<$models.DownloadView[] | null> {
|
||||
return $Call.ByID(2505337995, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* ListProviders returns the user's configured download clients.
|
||||
*/
|
||||
export function ListProviders(): $CancellablePromise<$models.Config[] | null> {
|
||||
return $Call.ByID(913508028);
|
||||
}
|
||||
|
||||
/**
|
||||
* ListRequests returns the whole durable request list.
|
||||
*/
|
||||
export function ListRequests(): $CancellablePromise<$models.Request[] | null> {
|
||||
return $Call.ByID(3593452892);
|
||||
}
|
||||
|
||||
/**
|
||||
* PauseRequest stops attempts without forgetting the request.
|
||||
*/
|
||||
export function PauseRequest(id: number, paused: boolean): $CancellablePromise<void> {
|
||||
return $Call.ByID(2652842089, id, paused);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick starts the transfer for the candidate the user chose.
|
||||
*/
|
||||
export function Pick(downloadID: string, candidateID: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(40308223, downloadID, candidateID);
|
||||
}
|
||||
|
||||
/**
|
||||
* ProviderKinds returns every provider type that can be added, with the
|
||||
* settings each one needs. The settings page renders its forms from
|
||||
* this, so a new adapter needs no frontend change.
|
||||
*/
|
||||
export function ProviderKinds(): $CancellablePromise<$models.Descriptor[] | null> {
|
||||
return $Call.ByID(2902376358);
|
||||
}
|
||||
|
||||
/**
|
||||
* ReconcileRequests runs a pass now and reports what it did. This
|
||||
* backs the "check now" button, so it runs synchronously: the user
|
||||
* pressed it and is waiting for an answer.
|
||||
*/
|
||||
export function ReconcileRequests(): $CancellablePromise<$models.Summary> {
|
||||
return $Call.ByID(593439868);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveRequest takes something off the list. Removing an artist takes
|
||||
* its derived albums with it, by cascade; an album the user pinned
|
||||
* themselves has no parent and survives.
|
||||
*/
|
||||
export function RemoveRequest(id: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2423963479, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetPreferences pushes the auto-download guardrails straight into the
|
||||
* running Manager, without persisting them. Persistence is
|
||||
* config.Config's job (GetDownloadPreferences/SetDownloadPreferences);
|
||||
* this package cannot depend on config, since config already depends on
|
||||
* download for UserConfig. The frontend settings save is expected to
|
||||
* call the config setter and this method in the same action, the way
|
||||
* UpdateProvider already achieves "live without a restart" by touching
|
||||
* storage and the running Manager together.
|
||||
*/
|
||||
export function SetPreferences(prefs: $models.AutoDownloadPrefs): $CancellablePromise<void> {
|
||||
return $Call.ByID(3789717356, prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetReconciler wires the request-list loop. Optional: without it the
|
||||
* request list still stores and lists requests, it just never acts on
|
||||
* them.
|
||||
*/
|
||||
export function SetReconciler(r: $models.Reconciler | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(2390832784, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* StartDownload searches for a release and either auto-picks a clear
|
||||
* winner or returns ranked candidates for the user to choose from.
|
||||
*
|
||||
* When the search carries a MusicBrainz anchor, it also resolves or
|
||||
* creates the durable Request the anchor names and attaches it, via
|
||||
* ensureRequest — so a manual download that fails or finds nothing
|
||||
* right now leaves a durable record behind instead of just vanishing,
|
||||
* and the reconciler picks it up on its normal schedule exactly as if
|
||||
* the user had explicitly added it to the request list.
|
||||
*/
|
||||
export function StartDownload(req: $models.SearchRequest): $CancellablePromise<$models.StartResult> {
|
||||
return $Call.ByID(1191306198, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* TestProvider backs the "test connection" button. It builds the
|
||||
* provider from its stored config and asks it to check itself, so the
|
||||
* result reflects exactly what a real search would use.
|
||||
*/
|
||||
export function TestProvider(id: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2688601475, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateProvider saves changes to a provider. A secret field left
|
||||
* blank keeps its stored value rather than clearing it — the form does
|
||||
* not echo secrets back, so an empty box means "unchanged", not
|
||||
* "delete".
|
||||
*/
|
||||
export function UpdateProvider(id: number, name: string, enabled: boolean, priority: number, settings: { [_ in string]?: string } | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(42359606, id, name, enabled, priority, settings);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export {
|
||||
ShelfKind
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
AlbumCompleteFunc,
|
||||
IndexStatus,
|
||||
LBSimilarArtist,
|
||||
LBTopRecording,
|
||||
LBTopReleaseGroup,
|
||||
LyricsResult,
|
||||
MBArtist,
|
||||
MBRecording,
|
||||
MBRelease,
|
||||
MBReleaseGroup,
|
||||
MBSearchResult,
|
||||
MBTrack,
|
||||
MusicBrainzClient,
|
||||
RateLimiter,
|
||||
Shelf,
|
||||
ShelfPage,
|
||||
ThumbnailRequest,
|
||||
TierStatus,
|
||||
TopResult,
|
||||
TrackLyrics,
|
||||
TrackThumbnailRequest
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,469 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* AlbumCompleteFunc answers "is the local album with this id complete",
|
||||
* i.e. do its files declare a track total the library actually has.
|
||||
*/
|
||||
export type AlbumCompleteFunc = any;
|
||||
|
||||
/**
|
||||
* IndexStatus is the full index build status, exposed to the frontend.
|
||||
*/
|
||||
export interface IndexStatus {
|
||||
"building": boolean;
|
||||
"ready": boolean;
|
||||
|
||||
/**
|
||||
* RFC3339 timestamp of last complete build
|
||||
*/
|
||||
"lastBuilt"?: string;
|
||||
"tiers": TierStatus[] | null;
|
||||
"artists": number;
|
||||
"recordings": number;
|
||||
"releaseGroups": number;
|
||||
"totalRows": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* LBSimilarArtist represents a similar artist from the
|
||||
* ListenBrainz labs API.
|
||||
*/
|
||||
export interface LBSimilarArtist {
|
||||
"artistMbid": string;
|
||||
"name": string;
|
||||
"score": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* LBTopRecording represents a popular recording from the
|
||||
* ListenBrainz popularity API.
|
||||
*
|
||||
* JSON tags use camelCase for Wails→frontend serialization.
|
||||
* The API response uses snake_case, so we unmarshal into
|
||||
* lbTopRecordingWire first, then convert.
|
||||
*/
|
||||
export interface LBTopRecording {
|
||||
"recordingMbid": string;
|
||||
"artistName": string;
|
||||
"trackName": string;
|
||||
"totalListenCount": number;
|
||||
"caaReleaseMbid": string;
|
||||
|
||||
/**
|
||||
* ReleaseGroupMBID is resolved from CAAReleaseMBID so a top-track
|
||||
* row can link to its album page with the track highlighted.
|
||||
*/
|
||||
"releaseGroupMbid"?: string;
|
||||
"releaseName": string;
|
||||
|
||||
/**
|
||||
* milliseconds (from LB API)
|
||||
*/
|
||||
"length": number;
|
||||
"inLibrary": boolean;
|
||||
"localId"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* LBTopReleaseGroup represents a popular release group from the
|
||||
* ListenBrainz popularity API.
|
||||
*/
|
||||
export interface LBTopReleaseGroup {
|
||||
"releaseGroupMbid": string;
|
||||
"title": string;
|
||||
"artistName": string;
|
||||
"type": string;
|
||||
"date": string;
|
||||
"totalListenCount": number;
|
||||
"caaReleaseMbid": string;
|
||||
"inLibrary": boolean;
|
||||
"localId"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* LyricsResult is a single lyric-search hit, mapped from the DB layer
|
||||
* into the camelCase shape the frontend consumes.
|
||||
*/
|
||||
export interface LyricsResult {
|
||||
"recordingId": number;
|
||||
"filePath": string;
|
||||
"lengthMs": number;
|
||||
"title": string;
|
||||
"artist": string;
|
||||
"album": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* MBArtist is a Wails-friendly projection of a MusicBrainz artist.
|
||||
*/
|
||||
export interface MBArtist {
|
||||
"mbid": string;
|
||||
"name": string;
|
||||
"sortName": string;
|
||||
"englishName"?: string;
|
||||
"type": string;
|
||||
"country": string;
|
||||
"disambiguation": string;
|
||||
"score": number;
|
||||
|
||||
/**
|
||||
* raw LB listen count (0 if unknown)
|
||||
*/
|
||||
"popularity": number;
|
||||
"listenerCount": number;
|
||||
|
||||
/**
|
||||
* true if the user owns music by this artist
|
||||
*/
|
||||
"inLibrary": boolean;
|
||||
|
||||
/**
|
||||
* local artist row ID for navigation
|
||||
*/
|
||||
"localId"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MBRecording is a Wails-friendly projection of a MusicBrainz
|
||||
* recording.
|
||||
*/
|
||||
export interface MBRecording {
|
||||
"mbid": string;
|
||||
"title": string;
|
||||
"length": number;
|
||||
"artistCredit": string;
|
||||
|
||||
/**
|
||||
* for linking the artist to its detail page
|
||||
*/
|
||||
"artistMbid"?: string;
|
||||
"score": number;
|
||||
|
||||
/**
|
||||
* raw LB listen count (0 if unknown)
|
||||
*/
|
||||
"popularity": number;
|
||||
"listenerCount": number;
|
||||
|
||||
/**
|
||||
* parent release, for album navigation
|
||||
*/
|
||||
"caaReleaseMbid"?: string;
|
||||
|
||||
/**
|
||||
* ReleaseGroupMBID is resolved from CAAReleaseMBID so a track can
|
||||
* link to its album page with the track highlighted, matching how
|
||||
* tracks behave everywhere else.
|
||||
*/
|
||||
"releaseGroupMbid"?: string;
|
||||
|
||||
/**
|
||||
* album title
|
||||
*/
|
||||
"releaseName"?: string;
|
||||
|
||||
/**
|
||||
* true if the user owns this recording
|
||||
*/
|
||||
"inLibrary": boolean;
|
||||
|
||||
/**
|
||||
* local recording row ID
|
||||
*/
|
||||
"localId"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MBRelease is a Wails-friendly projection of a MusicBrainz release.
|
||||
*/
|
||||
export interface MBRelease {
|
||||
"mbid": string;
|
||||
"title": string;
|
||||
"date": string;
|
||||
"country": string;
|
||||
"status": string;
|
||||
"artistCredit"?: string;
|
||||
"tracks"?: MBTrack[] | null;
|
||||
|
||||
/**
|
||||
* ReleaseGroupMBID is the parent release group's MBID. Empty unless
|
||||
* the lookup requested the "release-groups" include (LookupRelease
|
||||
* does); used to resolve a release-level MBID (what many taggers
|
||||
* write) back to the release-group MBID everything else on the
|
||||
* album page is keyed by.
|
||||
*/
|
||||
"releaseGroupMbid"?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* MBReleaseGroup is a Wails-friendly projection of a MusicBrainz
|
||||
* release group.
|
||||
*/
|
||||
export interface MBReleaseGroup {
|
||||
"mbid": string;
|
||||
"title": string;
|
||||
"primaryType": string;
|
||||
"secondaryTypes"?: string[] | null;
|
||||
"firstReleaseDate": string;
|
||||
"artistCredit": string;
|
||||
|
||||
/**
|
||||
* for linking the artist to its detail page
|
||||
*/
|
||||
"artistMbid"?: string;
|
||||
|
||||
/**
|
||||
* raw LB listen count (0 if unknown)
|
||||
*/
|
||||
"popularity": number;
|
||||
"listenerCount": number;
|
||||
|
||||
/**
|
||||
* true if the user owns this album
|
||||
*/
|
||||
"inLibrary": boolean;
|
||||
|
||||
/**
|
||||
* local release_group row ID
|
||||
*/
|
||||
"localId"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MBSearchResult aggregates the three searchable entity types
|
||||
* returned by the MusicBrainz search API.
|
||||
*/
|
||||
export interface MBSearchResult {
|
||||
"artists"?: MBArtist[] | null;
|
||||
"releaseGroups"?: MBReleaseGroup[] | null;
|
||||
"recordings"?: MBRecording[] | null;
|
||||
"topResults"?: TopResult[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* MBTrack is a Wails-friendly projection of a MusicBrainz track.
|
||||
*/
|
||||
export interface MBTrack {
|
||||
"position": number;
|
||||
"discNumber": number;
|
||||
"title": string;
|
||||
"length": number;
|
||||
"mbid": string;
|
||||
"inLibrary": boolean;
|
||||
"localId"?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MusicBrainzClient wraps the musicbrainzws2 library with a local
|
||||
* response cache. Every API call checks the cache first and stores
|
||||
* successful responses for future hits.
|
||||
*
|
||||
* A proactive rate limiter gates all outgoing requests at 1 req/sec
|
||||
* to avoid triggering MusicBrainz 429 responses. The underlying
|
||||
* musicbrainzws2.Client still retries on 429 as a safety net, but
|
||||
* the limiter should prevent most rate-limit hits.
|
||||
*/
|
||||
export interface MusicBrainzClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* RateLimiter enforces a maximum request rate using a token bucket.
|
||||
* MusicBrainz requires ≤1 request per second and rejects ALL
|
||||
* requests (not just excess) when the rate is exceeded, so callers
|
||||
* block proactively via Wait rather than retrying reactively.
|
||||
*
|
||||
* A limiter may carry a second, slower **background lane** (see
|
||||
* WithBackgroundLane). A caller marked by WithBackgroundPriority is
|
||||
* paced by that lane *and* yields to interactive callers: while any
|
||||
* interactive Wait is outstanding, background waits do not take a
|
||||
* token at all. This is what keeps a multi-thousand-request backfill
|
||||
* from putting the album page the user is looking at right now behind
|
||||
* hours of queued work.
|
||||
*
|
||||
* RateLimiter is safe for concurrent use.
|
||||
*/
|
||||
export interface RateLimiter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shelf is one horizontal row on the Explore page.
|
||||
*
|
||||
* A shelf carries albums or artists, never both: they route to
|
||||
* different pages and render as different cards, and a row that is
|
||||
* sometimes one and sometimes the other is two components pretending to
|
||||
* be one.
|
||||
*/
|
||||
export interface Shelf {
|
||||
"id": string;
|
||||
"kind": ShelfKind;
|
||||
|
||||
/**
|
||||
* Title is the row heading.
|
||||
*/
|
||||
"title": string;
|
||||
|
||||
/**
|
||||
* Subtitle says why these are here. As on Home it is not
|
||||
* decoration: without it a shelf is indistinguishable from a
|
||||
* random grid.
|
||||
*/
|
||||
"subtitle": string;
|
||||
"albums"?: MBReleaseGroup[] | null;
|
||||
"artists"?: MBArtist[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ShelfKind identifies what a shelf is built from, so the frontend can
|
||||
* pick an icon and a spec can assert on a shelf without matching
|
||||
* display copy.
|
||||
*/
|
||||
export enum ShelfKind {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Shelf kinds.
|
||||
*/
|
||||
ShelfPopularAlbums = "popular-albums",
|
||||
ShelfPopularArtists = "popular-artists",
|
||||
ShelfMoreFromOwned = "more-from-owned",
|
||||
};
|
||||
|
||||
/**
|
||||
* ShelfPage is what Explore renders before a query.
|
||||
*
|
||||
* State exists because "no shelves" has three different causes here and
|
||||
* the page must not present them identically — a blank panel is the bug
|
||||
* this whole feature is fixing, and a blank panel that says nothing
|
||||
* about why is the same bug with more code behind it.
|
||||
*/
|
||||
export interface ShelfPage {
|
||||
"shelves": Shelf[] | null;
|
||||
|
||||
/**
|
||||
* State is one of:
|
||||
*
|
||||
* "ready" — the catalog is here and the shelves are below.
|
||||
* "building" — it is being fetched or built right now.
|
||||
* "no-index" — there is no catalog. Search still works over
|
||||
* whatever is in the index, which may be nothing;
|
||||
* the page says so and points at Settings.
|
||||
*/
|
||||
"state": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ThumbnailRequest is a single item in a batch thumbnail request.
|
||||
*/
|
||||
export interface ThumbnailRequest {
|
||||
"mbid": string;
|
||||
"albumName": string;
|
||||
"artistName": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TierStatus represents the state of a single index tier.
|
||||
*/
|
||||
export interface TierStatus {
|
||||
"name": string;
|
||||
|
||||
/**
|
||||
* "pending", "running", "complete", "error", "skipped"
|
||||
*/
|
||||
"state": string;
|
||||
"total": number;
|
||||
"completed": number;
|
||||
"error"?: string;
|
||||
|
||||
/**
|
||||
* Detail is a human-readable progress line for stages whose raw
|
||||
* completed/total numbers say little on their own — the listens
|
||||
* stream reports "42.3 / 205.1 GB · 18 MB/s · ~3h20m left" here.
|
||||
*/
|
||||
"detail"?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TopResult represents a single top-result card shown above the
|
||||
* categorized search lists. Computed by intent scoring after all
|
||||
* reranking is complete.
|
||||
*/
|
||||
export interface TopResult {
|
||||
/**
|
||||
* "artist", "release_group", "recording"
|
||||
*/
|
||||
"entityType": string;
|
||||
"mbid": string;
|
||||
"name": string;
|
||||
|
||||
/**
|
||||
* for tracks/albums
|
||||
*/
|
||||
"artistCredit"?: string;
|
||||
|
||||
/**
|
||||
* for linking the artist subtitle
|
||||
*/
|
||||
"artistMbid"?: string;
|
||||
"intentScore": number;
|
||||
|
||||
/**
|
||||
* Artist-specific
|
||||
* "Group", "Person"
|
||||
*/
|
||||
"artistType"?: string;
|
||||
"country"?: string;
|
||||
|
||||
/**
|
||||
* Album-specific
|
||||
*/
|
||||
"primaryType"?: string;
|
||||
"year"?: string;
|
||||
|
||||
/**
|
||||
* Track-specific. ReleaseGroupMBID is resolved (from CAAReleaseMBID)
|
||||
* so a track click can open its album page with the track highlighted,
|
||||
* matching how tracks behave everywhere else. ReleaseName is the album
|
||||
* title used for the album page header.
|
||||
*/
|
||||
"length"?: number;
|
||||
"caaReleaseMbid"?: string;
|
||||
"releaseGroupMbid"?: string;
|
||||
"releaseName"?: string;
|
||||
|
||||
/**
|
||||
* Library status — populated from index cross-reference columns.
|
||||
*/
|
||||
"inLibrary": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TrackLyrics is the stored or freshly-fetched lyrics for one track.
|
||||
* Source is "embedded" (from the file's tags / library DB), "lrclib"
|
||||
* (fetched on demand), or "" when none are available.
|
||||
*/
|
||||
export interface TrackLyrics {
|
||||
"plain": string;
|
||||
"synced": string;
|
||||
"instrumental": boolean;
|
||||
"source": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TrackThumbnailRequest is a single item in a batch track thumbnail
|
||||
* request. Either ReleaseMBID or ReleaseGroupMBID may be empty;
|
||||
* the proxy tries whichever is present.
|
||||
*/
|
||||
export interface TrackThumbnailRequest {
|
||||
/**
|
||||
* stable key used in the returned map
|
||||
*/
|
||||
"key": string;
|
||||
"releaseMbid": string;
|
||||
"releaseGroupMbid": string;
|
||||
"albumName": string;
|
||||
"artistName": string;
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service is the Wails-bound service for the explore feature.
|
||||
* It owns the lifecycle of all explore-related components: the
|
||||
* MusicBrainz client, ListenBrainz client, rate limiter, and
|
||||
* response cache. Its exported methods form the binding surface
|
||||
* that the frontend calls via generated TypeScript stubs.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as time$0 from "../../../time/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as jobs$0 from "../jobs/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* AdoptPausedIndexBuild re-registers a build paused in a previous
|
||||
* session so it appears in the jobs panel, still paused.
|
||||
*/
|
||||
export function AdoptPausedIndexBuild(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3754928399);
|
||||
}
|
||||
|
||||
/**
|
||||
* BackfillLibraryDiscographies enriches owned artists that have not had
|
||||
* their discography fetched yet, in the background. Idempotent and
|
||||
* bounded — the query only returns unenriched artists and each is marked
|
||||
* discog_fetched on success, so this is cheap (an empty query) once every
|
||||
* owned artist is covered and safe to call on every scan and launch.
|
||||
*/
|
||||
export function BackfillLibraryDiscographies(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3678857155);
|
||||
}
|
||||
|
||||
/**
|
||||
* BackfillLibraryLyrics fetches lyrics from LRCLIB for library tracks
|
||||
* that don't have them, in the background. Idempotent and bounded —
|
||||
* each recording is tried once (a miss is cached), and a run stops
|
||||
* after a fixed number of passes, resuming on the next launch.
|
||||
*/
|
||||
export function BackfillLibraryLyrics(): $CancellablePromise<void> {
|
||||
return $Call.ByID(4204427482);
|
||||
}
|
||||
|
||||
/**
|
||||
* BackfillReleaseGroupMBIDs resolves release groups whose scan only
|
||||
* found a release-level MBID (MUSICBRAINZ_ALBUMID — many taggers write
|
||||
* this instead of, or in addition to, MUSICBRAINZ_RELEASEGROUPID) into
|
||||
* the release-group MBID everything else on the album page is keyed
|
||||
* by. Bounded and resumable, in the background: a scan can't afford a
|
||||
* live MusicBrainz call, so `library.updateMBIDs` stashes the release
|
||||
* MBID in `pending_release_mbid` instead, and this is what resolves it
|
||||
* — the same "defer the network call out of the scan path" shape as
|
||||
* BackfillLibraryDiscographies.
|
||||
*/
|
||||
export function BackfillReleaseGroupMBIDs(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2594807082);
|
||||
}
|
||||
|
||||
/**
|
||||
* BrowseReleaseGroups fetches release groups for a given artist MBID.
|
||||
* Checks the local index first for instant results, then fetches from
|
||||
* MusicBrainz for complete data (secondary types, precise dates).
|
||||
* Also adds results to the search index (Tier 5: organic growth).
|
||||
*/
|
||||
export function BrowseReleaseGroups(artistMBID: string): $CancellablePromise<$models.MBReleaseGroup[] | null> {
|
||||
return $Call.ByID(404562912, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* BrowseReleases fetches releases for a given release group MBID.
|
||||
*
|
||||
* Local-first, non-blocking: a warm response cache is served instantly;
|
||||
* on a miss the request does NOT block on a live MusicBrainz browse
|
||||
* (which pulls every version's full tracklist and can take seconds).
|
||||
* Instead it kicks off a background fetch and returns empty — the
|
||||
* AlbumReleasesReady event signals the caller to re-fetch once the cache
|
||||
* is warm.
|
||||
*/
|
||||
export function BrowseReleases(releaseGroupMBID: string): $CancellablePromise<$models.MBRelease[] | null> {
|
||||
return $Call.ByID(2551207897, releaseGroupMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* CAALimiter returns the shared Cover Art Archive rate limiter.
|
||||
* Consumers must respect it for any fresh CAA HTTP GETs.
|
||||
*/
|
||||
export function CAALimiter(): $CancellablePromise<$models.RateLimiter | null> {
|
||||
return $Call.ByID(1239092428);
|
||||
}
|
||||
|
||||
/**
|
||||
* CheckLibraryMBIDs returns which of the given MBIDs exist in the
|
||||
* local music library. Returns a map of MBID → entity type
|
||||
* ("artist", "release_group", "recording").
|
||||
*
|
||||
* It has no frontend caller — `downloadcatalog.go` is the one consumer,
|
||||
* asking about a single MBID at a time.
|
||||
*/
|
||||
export function CheckLibraryMBIDs(mbids: string[] | null): $CancellablePromise<{ [_ in string]?: string } | null> {
|
||||
return $Call.ByID(3168338597, mbids);
|
||||
}
|
||||
|
||||
/**
|
||||
* CoreCatalogImported reports whether a prebuilt catalog artifact has
|
||||
* been merged into this index.
|
||||
*/
|
||||
export function CoreCatalogImported(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(186364153);
|
||||
}
|
||||
|
||||
/**
|
||||
* CoverArtGroupURL returns the Cover Art Archive URL for a release
|
||||
* group's front cover at the default 250px size. This is the
|
||||
* correct endpoint for search results, which return release group
|
||||
* MBIDs rather than individual release MBIDs.
|
||||
*/
|
||||
export function CoverArtGroupURL(releaseGroupMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(2102228641, releaseGroupMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* CoverArtURL returns the Cover Art Archive URL for a release's
|
||||
* front cover at the default 250px size.
|
||||
*/
|
||||
export function CoverArtURL(releaseMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(3231496554, releaseMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GenerateMix returns the next batch of tracks for a dynamic-mix queue
|
||||
* fallback, built by expanding the seed's artists to their similar
|
||||
* artists (weighted by how often each appears in the seed, sharpened
|
||||
* by shared genre tags) and restricting candidates to what is actually
|
||||
* in the library — a queue can only play files that exist.
|
||||
*
|
||||
* continuing extends the current mix session — regenerating from its
|
||||
* original seed rather than seedPaths — instead of starting a fresh
|
||||
* one. Pass false whenever the queue that just exhausted was not
|
||||
* itself a mix batch (a real selection just ran out); pass true when
|
||||
* it was (the mix keeps going indefinitely). label names the batch
|
||||
* after its most-represented seed artist, for the "Playing from" UI.
|
||||
*/
|
||||
export function GenerateMix(seedPaths: string[] | null, continuing: boolean): $CancellablePromise<[string[] | null, string]> {
|
||||
return $Call.ByID(4293623744, seedPaths, continuing);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetArtistImageCached returns a base64 data URL for the artist's
|
||||
* photo ONLY if it's already on disk — no MB/Wikidata resolution
|
||||
* or Wikimedia fetch. Returns "" if not cached.
|
||||
*/
|
||||
export function GetArtistImageCached(artistMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(670428351, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetArtistImageCachedPath returns the asset-handler URL path for
|
||||
* the artist's cached medium thumbnail, e.g.
|
||||
* "/artist-images/b1/b10bbbfc-.../primary_md.jpg". No base64, no
|
||||
* network calls — just a disk existence check. Returns "" if no
|
||||
* image is cached.
|
||||
*/
|
||||
export function GetArtistImageCachedPath(artistMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(589683480, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetArtistImageURL returns a base64 data URL for the artist's
|
||||
* photo. Cached on disk — first call resolves via MB/Wikidata and
|
||||
* fetches from Wikimedia Commons, subsequent calls are instant.
|
||||
* Returns "" if no image is available.
|
||||
*/
|
||||
export function GetArtistImageURL(artistMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(1780431074, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetArtistImagesCachedPaths resolves artist portraits for many MBIDs
|
||||
* in one call, returning MBID → asset-handler path for the medium
|
||||
* thumbnail. Disk existence checks only: no MusicBrainz, no Wikidata,
|
||||
* no Wikimedia, no network of any kind. MBIDs with no cached portrait
|
||||
* are omitted rather than returned empty.
|
||||
*
|
||||
* It exists because the resolving entry point (GetArtistImageURL) was
|
||||
* being used where a cache check belonged: a page rendering a dozen
|
||||
* search results paid a full MB → Wikidata → Wikipedia → Wikimedia
|
||||
* resolution for every artist whose portrait was already on disk. Its
|
||||
* predecessor could not serve that — it keyed on artist *name* through
|
||||
* the library's own MBID map, so it only ever answered for artists the
|
||||
* user already owned, which on a catalog search is nearly none of them.
|
||||
*
|
||||
* Paths rather than base64: a portrait is ~128 kB as a data URL, and
|
||||
* the asset handler serves the same bytes without crossing the IPC
|
||||
* boundary or being retained by a JS string.
|
||||
*/
|
||||
export function GetArtistImagesCachedPaths(mbids: string[] | null): $CancellablePromise<{ [_ in string]?: string } | null> {
|
||||
return $Call.ByID(2104788604, mbids);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetArtistMBID returns the MusicBrainz ID for a local library
|
||||
* artist by name, or "" if not found or no MBID tagged.
|
||||
*/
|
||||
export function GetArtistMBID(artistName: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(944736774, artistName);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetArtistPlayCount returns the total LB listen count for an artist.
|
||||
* Returns 0 if unknown.
|
||||
*/
|
||||
export function GetArtistPlayCount(artistMBID: string): $CancellablePromise<number> {
|
||||
return $Call.ByID(143535277, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetCandidateThumbnail returns CAA-only cover art for an autotag
|
||||
* candidate, skipping the library-by-name index so embedded ID3
|
||||
* art on the user's existing files doesn't pollute the candidate
|
||||
* preview. Disk cache → network on RG → network on release.
|
||||
*/
|
||||
export function GetCandidateThumbnail(releaseMBID: string, releaseGroupMBID: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(1946932424, releaseMBID, releaseGroupMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetExploreShelves builds the page Explore shows before a query.
|
||||
*
|
||||
* One call rather than one per shelf, for `home`'s reason: the shelves
|
||||
* share nothing expensive, but the page has nothing useful to render
|
||||
* until it knows which rows exist, and rows that pop in one at a time
|
||||
* reflow under the cursor.
|
||||
*/
|
||||
export function GetExploreShelves(): $CancellablePromise<$models.ShelfPage> {
|
||||
return $Call.ByID(144329614);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetIndexStatus returns the current search index build status.
|
||||
*/
|
||||
export function GetIndexStatus(): $CancellablePromise<$models.IndexStatus> {
|
||||
return $Call.ByID(3481063523);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetLibrarySimilarArtists returns similar artists to the given
|
||||
* MBID that are also in the user's local library. Uses the
|
||||
* pre-computed similar_artist_map table (populated during Tier 4
|
||||
* index build) joined with the artists table. No API calls.
|
||||
*
|
||||
* The artists table allows multiple rows with the same MBID
|
||||
* (different artist credits like "A feat. B" that resolve to the
|
||||
* same MB artist), so we use EXISTS instead of JOIN to avoid
|
||||
* duplicating similar_artist_map rows.
|
||||
*/
|
||||
export function GetLibrarySimilarArtists(artistMBID: string): $CancellablePromise<$models.LBSimilarArtist[] | null> {
|
||||
return $Call.ByID(104313157, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetThumbnail returns a base64 data URL for the release group's
|
||||
* cover art. Checks local library art first (by album+artist
|
||||
* name), then disk cache, then Cover Art Archive.
|
||||
* Returns "" if no cover art is available.
|
||||
*/
|
||||
export function GetThumbnail(releaseGroupMBID: string, albumName: string, artistName: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(4046192225, releaseGroupMBID, albumName, artistName);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetThumbnails fetches multiple thumbnails in one call and returns
|
||||
* a map of MBID → base64 data URL. Entries with no art are omitted.
|
||||
* GetThumbnails returns ONLY cached/local art instantly — no network
|
||||
* fetches. For items missing from the cache, the frontend should
|
||||
* call GetThumbnail() individually so results stream in rather than
|
||||
* blocking on a batch.
|
||||
*/
|
||||
export function GetThumbnails(requests: $models.ThumbnailRequest[] | null): $CancellablePromise<{ [_ in string]?: string } | null> {
|
||||
return $Call.ByID(3124819542, requests);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTrackLyrics returns lyrics for a recording. If the library
|
||||
* already has them (from embedded tags) they're returned as-is;
|
||||
* otherwise it fetches from LRCLIB, persists them (updating the FTS
|
||||
* index), and returns them. Never returns an error to the frontend —
|
||||
* a miss just yields an empty result.
|
||||
*/
|
||||
export function GetTrackLyrics(recordingID: number): $CancellablePromise<$models.TrackLyrics> {
|
||||
return $Call.ByID(1131284622, recordingID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTrackThumbnail returns cover art for a track. Accepts both
|
||||
* the track's CAA release MBID and the resolved parent release
|
||||
* group MBID (either may be empty). Tries the RG first to reuse
|
||||
* discography cache; falls back to the release-level CAA endpoint
|
||||
* when the RG isn't known — useful when the track's preferred CAA
|
||||
* release doesn't belong to any RG currently in the index.
|
||||
*/
|
||||
export function GetTrackThumbnail(releaseMBID: string, releaseGroupMBID: string, albumName: string, artistName: string): $CancellablePromise<string> {
|
||||
return $Call.ByID(4191599666, releaseMBID, releaseGroupMBID, albumName, artistName);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTrackThumbnails returns ONLY cached/local art for track
|
||||
* requests, keyed by the caller-provided Key so callers can map
|
||||
* results back to rows in their UI.
|
||||
*/
|
||||
export function GetTrackThumbnails(requests: $models.TrackThumbnailRequest[] | null): $CancellablePromise<{ [_ in string]?: string } | null> {
|
||||
return $Call.ByID(2383043155, requests);
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexBaselineSeries returns the incremental listens series the index's
|
||||
* popularity is caught up to. A change across a refresh means new data
|
||||
* was folded in.
|
||||
*/
|
||||
export function IndexBaselineSeries(): $CancellablePromise<number> {
|
||||
return $Call.ByID(3317566669);
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexImportComplete reports whether the dump import has finished all
|
||||
* of its stages. Distinct from IsIndexReady, which only means the index
|
||||
* holds enough rows to answer queries — a partially imported index is
|
||||
* ready but not complete. Used by the headless builder to decide
|
||||
* whether another run is needed.
|
||||
*/
|
||||
export function IndexImportComplete(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(3872948181);
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexLastImported returns when the dump import last completed, or the
|
||||
* zero time if it never has.
|
||||
*/
|
||||
export function IndexLastImported(): $CancellablePromise<string> {
|
||||
return $Call.ByID(1889359471);
|
||||
}
|
||||
|
||||
/**
|
||||
* InvalidateIndexDiscographies clears the discography build
|
||||
* timestamp so the next index build re-runs Tiers 2-4. Call
|
||||
* after a library rescan that may have populated new MBIDs.
|
||||
*/
|
||||
export function InvalidateIndexDiscographies(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2573191521);
|
||||
}
|
||||
|
||||
/**
|
||||
* InvalidateLibrarySync clears the "ready" markers guarding the gated
|
||||
* library-sync steps so they re-run on the next launch. Call after a
|
||||
* mutation that changes owned content outside a scan (e.g. removing a
|
||||
* library), which would otherwise leave stale in_library flags and
|
||||
* orphaned lyric-index rows.
|
||||
*/
|
||||
export function InvalidateLibrarySync(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2446309672);
|
||||
}
|
||||
|
||||
/**
|
||||
* IsIndexReady returns true once the index has been populated.
|
||||
*/
|
||||
export function IsIndexReady(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(1366461294);
|
||||
}
|
||||
|
||||
/**
|
||||
* LookupArtist fetches a single MusicBrainz artist by MBID.
|
||||
* Checks the local index first — has name, type, country,
|
||||
* disambiguation, sort_name for indexed artists. Falls back to
|
||||
* MB API for unknown artists and backfills the index for next time.
|
||||
*/
|
||||
export function LookupArtist(mbid: string): $CancellablePromise<$models.MBArtist | null> {
|
||||
return $Call.ByID(3986196952, mbid);
|
||||
}
|
||||
|
||||
/**
|
||||
* LookupReleaseGroup fetches a single MusicBrainz release group by MBID.
|
||||
*/
|
||||
export function LookupReleaseGroup(mbid: string): $CancellablePromise<$models.MBReleaseGroup | null> {
|
||||
return $Call.ByID(2946174711, mbid);
|
||||
}
|
||||
|
||||
/**
|
||||
* MusicBrainz returns the shared cached MB client so other services
|
||||
* (e.g. autotag) can reuse it without spinning up a second limiter.
|
||||
*/
|
||||
export function MusicBrainz(): $CancellablePromise<$models.MusicBrainzClient | null> {
|
||||
return $Call.ByID(3453528034);
|
||||
}
|
||||
|
||||
/**
|
||||
* PopulateLocalCrossReferences updates the local_*_id columns on
|
||||
* explore_index after a library scan.
|
||||
*/
|
||||
export function PopulateLocalCrossReferences(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1058376024);
|
||||
}
|
||||
|
||||
/**
|
||||
* PopulateLocalCrossReferencesIfNeeded runs the library→index sync only
|
||||
* when it has not run since the last library change. Use it on the
|
||||
* unchanged-library launch path so the write-heavy re-sync is skipped in
|
||||
* steady state; the scan-completion path calls the unconditional form.
|
||||
*/
|
||||
export function PopulateLocalCrossReferencesIfNeeded(): $CancellablePromise<void> {
|
||||
return $Call.ByID(814782146);
|
||||
}
|
||||
|
||||
/**
|
||||
* PrefetchReleases warms the local response cache for a set of release
|
||||
* groups in the background so opening any of them is instant. Called from
|
||||
* the artist page once its top-releases / discography render — album
|
||||
* navigation almost always originates there. Already-cached groups are
|
||||
* skipped; a cap bounds how many live fetches a single artist view can
|
||||
* trigger so the MusicBrainz rate limiter isn't flooded.
|
||||
* A release group the user owns *completely* is skipped outright: since
|
||||
* tag-derived completeness landed, such an album opens with no catalog
|
||||
* call at all — identity from its MBID, tracklist from its own files —
|
||||
* so warming the most expensive request in the app on its behalf buys
|
||||
* nothing. The skip is not merely an optimisation; those slots go to
|
||||
* albums that will actually need the browse.
|
||||
*/
|
||||
export function PrefetchReleases(releaseGroupMBIDs: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(306872972, releaseGroupMBIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* PrepareIndexRebuild clears the completion marker so the next build
|
||||
* re-imports from the newest published dump.
|
||||
*/
|
||||
export function PrepareIndexRebuild(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2772457219);
|
||||
}
|
||||
|
||||
/**
|
||||
* RebuildLyricsIndex rebuilds the FTS lyrics index from the current
|
||||
* library. Cheap; safe to call after every scan.
|
||||
*/
|
||||
export function RebuildLyricsIndex(): $CancellablePromise<void> {
|
||||
return $Call.ByID(300335776);
|
||||
}
|
||||
|
||||
/**
|
||||
* RebuildLyricsIndexIfNeeded rebuilds the lyrics FTS only when it has not
|
||||
* been built since the last library change. The backfill keeps the index
|
||||
* in sync incrementally thereafter, so on an unchanged library the full
|
||||
* rebuild is redundant; the scan-completion path calls the unconditional
|
||||
* form.
|
||||
*/
|
||||
export function RebuildLyricsIndexIfNeeded(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1447291626);
|
||||
}
|
||||
|
||||
/**
|
||||
* RecordSearchClick records that the user clicked a search result.
|
||||
* Called from the frontend when any search result is clicked.
|
||||
*/
|
||||
export function RecordSearchClick(query: string, mbid: string, entityType: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(824485756, query, mbid, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* RefreshIndexNow folds newly published incremental listens dumps into
|
||||
* the index synchronously. Pass 0 to bypass the cadence gate.
|
||||
*/
|
||||
export function RefreshIndexNow(minInterval: time$0.Duration): $CancellablePromise<void> {
|
||||
return $Call.ByID(4165220556, minInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* RefreshListenCounts folds any newly-published incremental listen dumps
|
||||
* into the index's popularity numbers, in the background. No-op when
|
||||
* offline, when a full build is running, when there is no baseline
|
||||
* import, or when the last refresh was within the weekly cadence. Fully
|
||||
* local — downloads the small daily dumps but makes no ListenBrainz API
|
||||
* calls.
|
||||
*/
|
||||
export function RefreshListenCounts(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3813092323);
|
||||
}
|
||||
|
||||
/**
|
||||
* ResolveReleaseGroupMBIDs takes a list of CAA release MBIDs (from
|
||||
* recording metadata) and returns a map of release MBID → release
|
||||
* group MBID. The frontend uses this to fetch track cover art via
|
||||
* the parent release group, reusing whatever cache exists for the
|
||||
* album already.
|
||||
*/
|
||||
export function ResolveReleaseGroupMBIDs(caaReleaseMBIDs: string[] | null): $CancellablePromise<{ [_ in string]?: string } | null> {
|
||||
return $Call.ByID(325093206, caaReleaseMBIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchLocal queries only the local FTS5 index and returns fully
|
||||
* ranked results instantly with no network calls. This is the
|
||||
* primary interactive search path: now that the index is populated
|
||||
* from the MetaBrainz dumps it covers essentially every popular
|
||||
* entity, so the frontend drives search entirely from here. The
|
||||
* old MusicBrainz network pipeline (Search) is retained for a future
|
||||
* opt-in "search online" affordance but is no longer called on the
|
||||
* hot path.
|
||||
*
|
||||
* Returns nil if the index has no hits for the query, so the caller
|
||||
* can fall back to whatever owned-library matches it already has.
|
||||
*/
|
||||
export function SearchLocal(query: string): $CancellablePromise<$models.MBSearchResult | null> {
|
||||
return $Call.ByID(189423736, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchLyrics finds library tracks whose lyrics contain the given
|
||||
* fragment, ranked by relevance. Pure local FTS — no network.
|
||||
*/
|
||||
export function SearchLyrics(query: string): $CancellablePromise<$models.LyricsResult[] | null> {
|
||||
return $Call.ByID(4153164053, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetAlbumComplete injects the completeness check. It is injected
|
||||
* rather than imported because `library` and `explore` do not depend on
|
||||
* each other in either direction today, and one prefetch heuristic is
|
||||
* not a reason to introduce that edge — the alternative, re-deriving
|
||||
* "complete" from SQL here, would be a second definition of it.
|
||||
*/
|
||||
export function SetAlbumComplete(fn: $models.AlbumCompleteFunc): $CancellablePromise<void> {
|
||||
return $Call.ByID(942474493, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetJobRegistry wires the background job registry into the search
|
||||
* index so its build reports progress and controls to the frontend.
|
||||
*/
|
||||
export function SetJobRegistry(reg: jobs$0.Registry | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(4291900709, reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* SimilarArtists returns artists similar to the given artist MBID.
|
||||
*/
|
||||
export function SimilarArtists(artistMBID: string): $CancellablePromise<$models.LBSimilarArtist[] | null> {
|
||||
return $Call.ByID(2048211426, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* StartIndexBuild kicks off the background search index build.
|
||||
* Call this after the library scan completes so the indexer doesn't
|
||||
* starve the scan for DB access.
|
||||
*/
|
||||
export function StartIndexBuild(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1444396415);
|
||||
}
|
||||
|
||||
/**
|
||||
* StopIndexBuild cancels the background search index build.
|
||||
* Call before a full rescan to free the DB for the scan.
|
||||
*/
|
||||
export function StopIndexBuild(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3056245285);
|
||||
}
|
||||
|
||||
/**
|
||||
* TopRecordingsForArtist returns the most-listened recordings for an
|
||||
* artist. Serves instantly from the local index when available; when the
|
||||
* artist isn't indexed yet it returns empty immediately and fetches the
|
||||
* discography in the background, emitting ArtistDiscographyReady so the
|
||||
* caller can re-fetch — the request never blocks on a live fetch.
|
||||
*/
|
||||
export function TopRecordingsForArtist(artistMBID: string): $CancellablePromise<$models.LBTopRecording[] | null> {
|
||||
return $Call.ByID(2077579960, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* TopReleaseGroupsForArtist returns the most-listened release groups for
|
||||
* an artist. Same non-blocking contract as TopRecordingsForArtist: index
|
||||
* hit is instant, a miss kicks off a background discography fetch and
|
||||
* returns empty, and ArtistDiscographyReady signals when to re-fetch.
|
||||
*/
|
||||
export function TopReleaseGroupsForArtist(artistMBID: string): $CancellablePromise<$models.LBTopReleaseGroup[] | null> {
|
||||
return $Call.ByID(602197643, artistMBID);
|
||||
}
|
||||
|
||||
/**
|
||||
* WaitForIndexIdle blocks until no index build or artist indexing
|
||||
* goroutine is running. Does not cancel a running build.
|
||||
*/
|
||||
export function WaitForIndexIdle(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3200929511);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* FrontendUtil provides frontend-bound Go functions.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* DirectoryPicker opens a directory selection dialog.
|
||||
*
|
||||
* v3 has no separate directory dialog: it is the file dialog told to
|
||||
* choose directories and not files.
|
||||
*/
|
||||
export function DirectoryPicker(): $CancellablePromise<string> {
|
||||
return $Call.ByID(3245034282);
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageFilePicker opens a file selection dialog filtered to image
|
||||
* files (JPEG, PNG). Returns the selected file path, or empty
|
||||
* string if the user cancelled.
|
||||
*/
|
||||
export function ImageFilePicker(): $CancellablePromise<string> {
|
||||
return $Call.ByID(3408786006);
|
||||
}
|
||||
|
||||
/**
|
||||
* PlaylistFilePicker opens a file selection dialog filtered
|
||||
* to M3U/M3U8 playlist files. Multiple files may be selected.
|
||||
*/
|
||||
export function PlaylistFilePicker(): $CancellablePromise<string[] | null> {
|
||||
return $Call.ByID(2858498591);
|
||||
}
|
||||
|
||||
/**
|
||||
* ReadFile reads a file from disk and returns its contents.
|
||||
* Used by the frontend to read cover art image files selected
|
||||
* via ImageFilePicker.
|
||||
*/
|
||||
export function ReadFile(path: string): $CancellablePromise<string | null> {
|
||||
return $Call.ByID(3350710867, path);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as FrontendUtil from "./frontendutil.js";
|
||||
export {
|
||||
FrontendUtil
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export {
|
||||
Kind
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
Shelf
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,53 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as library$0 from "../library/models.js";
|
||||
|
||||
/**
|
||||
* Kind identifies what a shelf is built from, so the frontend can pick
|
||||
* an icon and the e2e suite can assert on a shelf without matching
|
||||
* display copy.
|
||||
*/
|
||||
export enum Kind {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Shelf kinds.
|
||||
*/
|
||||
KindRecentlyPlayed = "recently-played",
|
||||
KindRecentlyAdded = "recently-added",
|
||||
KindMostPlayed = "most-played",
|
||||
KindUnplayed = "unplayed",
|
||||
KindStale = "stale",
|
||||
KindArtist = "artist",
|
||||
KindGenre = "genre",
|
||||
KindRandom = "random",
|
||||
};
|
||||
|
||||
/**
|
||||
* Shelf is one horizontal row on the home page.
|
||||
*/
|
||||
export interface Shelf {
|
||||
/**
|
||||
* ID is stable within a response, for list keying.
|
||||
*/
|
||||
"id": string;
|
||||
"kind": Kind;
|
||||
|
||||
/**
|
||||
* Title is the row heading.
|
||||
*/
|
||||
"title": string;
|
||||
|
||||
/**
|
||||
* Subtitle says why these albums are here. It is not decoration:
|
||||
* without it a shelf is indistinguishable from a random grid.
|
||||
*/
|
||||
"subtitle": string;
|
||||
"albums": library$0.Album[] | null;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service builds the home page's shelves.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* GetShelves returns the home page's rows, in display order.
|
||||
*
|
||||
* It is a single call rather than one per shelf because the shelves
|
||||
* share an album lookup and because the page has nothing useful to
|
||||
* render until it knows which rows exist — a page that pops rows in one
|
||||
* at a time reflows under the user's cursor.
|
||||
*/
|
||||
export function GetShelves(): $CancellablePromise<$models.Shelf[] | null> {
|
||||
return $Call.ByID(2822423495);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export {
|
||||
Kind,
|
||||
Level,
|
||||
State
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
Caps,
|
||||
Job,
|
||||
LogEntry,
|
||||
Registry,
|
||||
Stage,
|
||||
Stat
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,159 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Caps describes which controls a job supports. The frontend renders
|
||||
* buttons from these rather than switching on Kind, so a job that gains
|
||||
* pause support later needs no frontend change.
|
||||
*/
|
||||
export interface Caps {
|
||||
"pausable": boolean;
|
||||
"cancellable": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job is the frontend-facing snapshot of a single background job.
|
||||
*/
|
||||
export interface Job {
|
||||
"id": string;
|
||||
"kind": Kind;
|
||||
"title": string;
|
||||
"subtitle"?: string;
|
||||
"state": State;
|
||||
"phase"?: string;
|
||||
|
||||
/**
|
||||
* Current/Total drive the progress bar. Total == 0 means the job
|
||||
* is indeterminate and the frontend should render a spinner.
|
||||
*/
|
||||
"current": number;
|
||||
"total": number;
|
||||
"caps": Caps;
|
||||
"stages": Stage[] | null;
|
||||
"stats": Stat[] | null;
|
||||
"error"?: string;
|
||||
|
||||
/**
|
||||
* unix milliseconds
|
||||
*/
|
||||
"startedAt": number;
|
||||
"updatedAt": number;
|
||||
"endedAt"?: number;
|
||||
"logCount": number;
|
||||
"warnCount": number;
|
||||
"errorCount": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kind identifies the subsystem that owns a job. The frontend uses it
|
||||
* to pick an icon and to route "view details" to the right panel.
|
||||
*/
|
||||
export enum Kind {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Job kinds.
|
||||
*/
|
||||
KindLibraryScan = "library-scan",
|
||||
KindIndexBuild = "index-build",
|
||||
KindDownload = "download",
|
||||
KindAutotagApply = "autotag-apply",
|
||||
|
||||
/**
|
||||
* KindCatalogEnrich is background catalog work for content the user
|
||||
* already owns — the discography backfills. It is distinct from
|
||||
* KindIndexBuild because the two differ in what cancelling costs:
|
||||
* an index build discards hours of downloading and the frontend
|
||||
* confirms before stopping one, where a backfill is resumable per
|
||||
* artist and stopping it is free.
|
||||
*/
|
||||
KindCatalogEnrich = "catalog-enrich",
|
||||
};
|
||||
|
||||
/**
|
||||
* Level is the severity of a job log entry.
|
||||
*/
|
||||
export enum Level {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Log levels.
|
||||
*/
|
||||
LevelInfo = "info",
|
||||
LevelWarn = "warn",
|
||||
LevelError = "error",
|
||||
};
|
||||
|
||||
/**
|
||||
* LogEntry is one line of a job's output log.
|
||||
*/
|
||||
export interface LogEntry {
|
||||
/**
|
||||
* unix milliseconds
|
||||
*/
|
||||
"time": number;
|
||||
"level": Level;
|
||||
"message": string;
|
||||
"detail"?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry owns every known job and pushes coalesced snapshots to the
|
||||
* frontend. It is safe for concurrent use.
|
||||
*/
|
||||
export interface Registry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage is one named sub-step of a multi-stage job, such as an index
|
||||
* build tier. Jobs with a single linear phase leave Stages empty.
|
||||
*/
|
||||
export interface Stage {
|
||||
"name": string;
|
||||
|
||||
/**
|
||||
* pending, running, complete, error, skipped
|
||||
*/
|
||||
"state": string;
|
||||
"current": number;
|
||||
"total": number;
|
||||
"error"?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stat is a display-only key/value pair shown in the job detail panel
|
||||
* (e.g. "Added" / "1,204").
|
||||
*/
|
||||
export interface Stat {
|
||||
"label": string;
|
||||
"value": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* State is the lifecycle position of a job.
|
||||
*/
|
||||
export enum State {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Job states. Queued, Running, Paused and Pausing are live; Complete,
|
||||
* Cancelled and Error are terminal.
|
||||
*/
|
||||
StateQueued = "queued",
|
||||
StateRunning = "running",
|
||||
StatePausing = "pausing",
|
||||
StatePaused = "paused",
|
||||
StateCancelling = "cancelling",
|
||||
StateComplete = "complete",
|
||||
StateCancelled = "cancelled",
|
||||
StateError = "error",
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service is the Wails-bound facade over the registry. It deliberately
|
||||
* exposes only the read and control surface — producers get a *Handle
|
||||
* through the registry instead, so the frontend cannot invent jobs.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* CancelJob abandons a job.
|
||||
*/
|
||||
export function CancelJob(id: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(680141523, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* ClearFinishedJobs removes every terminal job from the list.
|
||||
*/
|
||||
export function ClearFinishedJobs(): $CancellablePromise<void> {
|
||||
return $Call.ByID(4219744941);
|
||||
}
|
||||
|
||||
/**
|
||||
* DismissJob removes a single finished job from the list.
|
||||
*/
|
||||
export function DismissJob(id: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(1904307161, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetJobLog returns the retained log tail for one job.
|
||||
*/
|
||||
export function GetJobLog(id: string): $CancellablePromise<$models.LogEntry[] | null> {
|
||||
return $Call.ByID(441057229, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetJobs returns every known job — active first by registration order,
|
||||
* including recently finished ones so the panel can show outcomes.
|
||||
*/
|
||||
export function GetJobs(): $CancellablePromise<$models.Job[] | null> {
|
||||
return $Call.ByID(3979035366);
|
||||
}
|
||||
|
||||
/**
|
||||
* PauseJob asks the owning subsystem to pause a job. Returns
|
||||
* immediately; the job reports "paused" once it actually stops.
|
||||
*/
|
||||
export function PauseJob(id: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(4094353111, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* ResumeJob continues a paused job.
|
||||
*/
|
||||
export function ResumeJob(id: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(3937701508, id);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Library from "./library.js";
|
||||
export {
|
||||
Library
|
||||
};
|
||||
|
||||
export type {
|
||||
Album,
|
||||
AlbumCompleteness,
|
||||
Artist,
|
||||
GenreWithCount,
|
||||
Info,
|
||||
RemovalHooks,
|
||||
RemovalImpact,
|
||||
RemovalResult,
|
||||
RemovalSummary,
|
||||
RescanHooks,
|
||||
ScanHooks,
|
||||
ScanMetrics,
|
||||
ScanWarning,
|
||||
Track,
|
||||
TrackMBIDs
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,454 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Library manages scanning and querying the music collection.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as sqlcgen$0 from "../database/sql/sqlcgen/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as jobs$0 from "../jobs/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* AcquirePipelineLock acquires the pipeline mutex for a tag write
|
||||
* operation. The caller must call ReleasePipelineLock when done.
|
||||
* If a scan is currently in progress, AcquirePipelineLock blocks
|
||||
* until it completes (and vice versa).
|
||||
*/
|
||||
export function AcquirePipelineLock(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2056761494);
|
||||
}
|
||||
|
||||
/**
|
||||
* AddLibrary creates a new library from a directory path, emits a
|
||||
* LibraryAdded event, and starts an asynchronous scan.
|
||||
*/
|
||||
export function AddLibrary(path: string): $CancellablePromise<sqlcgen$0.Library | null> {
|
||||
return $Call.ByID(4258719739, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelAllScans cancels the current scan and clears the entire
|
||||
* queue so no further libraries are scanned.
|
||||
*/
|
||||
export function CancelAllScans(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2826471660);
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelCurrentScan cancels only the currently scanning library.
|
||||
* The next queued library (if any) starts automatically when the
|
||||
* current scan's goroutine completes.
|
||||
*/
|
||||
export function CancelCurrentScan(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2190880081);
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelScan cancels an in-progress scan. Returns immediately;
|
||||
* scan goroutines stop at their next checkpoint.
|
||||
*
|
||||
* Deprecated: Use CancelCurrentScan or CancelAllScans for
|
||||
* queue-aware cancellation.
|
||||
*/
|
||||
export function CancelScan(): $CancellablePromise<void> {
|
||||
return $Call.ByID(4160132270);
|
||||
}
|
||||
|
||||
/**
|
||||
* FullRescan clears the queue and player, wipes all library data
|
||||
* (database records and cover art files), and performs a fresh
|
||||
* scan of every library in the database. The returned ScanMetrics
|
||||
* reflects the last library scanned; clear-phase durations are
|
||||
* folded into its totals.
|
||||
*/
|
||||
export function FullRescan(): $CancellablePromise<$models.ScanMetrics | null> {
|
||||
return $Call.ByID(378891236);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAlbumCompleteness answers "do I have all of this album" from the
|
||||
* tags read at scan time, with no network.
|
||||
*
|
||||
* The album page used to ask MusicBrainz, because the only track total
|
||||
* it had was the length of whatever tracklist it was already showing —
|
||||
* which for a library copy is a tautology. The denominator in a file's
|
||||
* "5/12" is a real answer and it is already on disk; this is where it
|
||||
* gets read.
|
||||
*
|
||||
* Complete is deliberately >= rather than ==: bonus and hidden tracks
|
||||
* routinely put a folder over its declared total, and that is a
|
||||
* complete album, not a broken one.
|
||||
*/
|
||||
export function GetAlbumCompleteness(albumID: number): $CancellablePromise<$models.AlbumCompleteness> {
|
||||
return $Call.ByID(2895787150, albumID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAlbumTracks returns all tracks for a given album (release group), ordered by disc and track number.
|
||||
*/
|
||||
export function GetAlbumTracks(albumID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(300451334, albumID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAlbumTracksByLibrary returns tracks for the given album,
|
||||
* scoped to the given library.
|
||||
*/
|
||||
export function GetAlbumTracksByLibrary(albumID: number, libraryID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(3304485554, albumID, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAlbumsByArtist returns all albums where the given artist is the album artist.
|
||||
*/
|
||||
export function GetAlbumsByArtist(artistID: number): $CancellablePromise<$models.Album[] | null> {
|
||||
return $Call.ByID(1456840721, artistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAlbumsByArtistByLibrary returns albums for the given artist
|
||||
* that have tracks in the given library.
|
||||
*/
|
||||
export function GetAlbumsByArtistByLibrary(artistID: number, libraryID: number): $CancellablePromise<$models.Album[] | null> {
|
||||
return $Call.ByID(2809291, artistID, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllAlbums returns all albums with cover art and artist info for the cover grid.
|
||||
*/
|
||||
export function GetAllAlbums(): $CancellablePromise<$models.Album[] | null> {
|
||||
return $Call.ByID(2015458954);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllAlbumsByLibrary returns albums that have tracks in the given library.
|
||||
*/
|
||||
export function GetAllAlbumsByLibrary(libraryID: number): $CancellablePromise<$models.Album[] | null> {
|
||||
return $Call.ByID(4023050470, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllArtists returns artists that are credited as album artists, ordered by name.
|
||||
*/
|
||||
export function GetAllArtists(): $CancellablePromise<$models.Artist[] | null> {
|
||||
return $Call.ByID(2529088294);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllArtistsByLibrary returns artists that have albums with tracks
|
||||
* in the given library.
|
||||
*/
|
||||
export function GetAllArtistsByLibrary(libraryID: number): $CancellablePromise<$models.Artist[] | null> {
|
||||
return $Call.ByID(1170594642, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllGenresWithCounts returns all genres with their track counts.
|
||||
*/
|
||||
export function GetAllGenresWithCounts(): $CancellablePromise<$models.GenreWithCount[] | null> {
|
||||
return $Call.ByID(602231298);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllGenresWithCountsByLibrary returns genres with track counts
|
||||
* scoped to the given library.
|
||||
*/
|
||||
export function GetAllGenresWithCountsByLibrary(libraryID: number): $CancellablePromise<$models.GenreWithCount[] | null> {
|
||||
return $Call.ByID(772684334, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllLibrariesWithTrackCounts returns all libraries with their
|
||||
* audio file counts. Typically 1-5 libraries so the loop is trivial.
|
||||
*/
|
||||
export function GetAllLibrariesWithTrackCounts(): $CancellablePromise<$models.Info[] | null> {
|
||||
return $Call.ByID(3420301148);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllTracks returns an array of track structs of every file in the library.
|
||||
*/
|
||||
export function GetAllTracks(): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(2991050010);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllTracksByLibrary returns tracks scoped to a specific library.
|
||||
*/
|
||||
export function GetAllTracksByLibrary(libraryID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(3882999766, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetFilePathsByAlbums returns the file paths of every track in the
|
||||
* given albums, grouped by album id.
|
||||
*
|
||||
* "Play this artist", "play these albums" and the album drag cache each
|
||||
* resolved paths with one binding call per album, sequentially, and each
|
||||
* asked for whole track rows to read one field off them (perf.m2). This
|
||||
* is that question asked once. The result is grouped rather than
|
||||
* flattened because the caller owns the ordering — an album list is
|
||||
* sorted by name, not by id — and because the drag cache stores it per
|
||||
* album.
|
||||
*
|
||||
* A library id of 0 means "every library", matching the caller's
|
||||
* selected-library filter being unset.
|
||||
*/
|
||||
export function GetFilePathsByAlbums(albumIDs: number[] | null, libraryID: number): $CancellablePromise<{ [_ in `${number}`]?: string[] | null } | null> {
|
||||
return $Call.ByID(1692993358, albumIDs, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetFilePathsByGenres returns the file paths of every track tagged with
|
||||
* the given genres, grouped by genre name. See GetFilePathsByAlbums —
|
||||
* same finding, same shape, and the caller still owns the de-duplication
|
||||
* across genres because it owns the order.
|
||||
*/
|
||||
export function GetFilePathsByGenres(genreNames: string[] | null, libraryID: number): $CancellablePromise<{ [_ in string]?: string[] | null } | null> {
|
||||
return $Call.ByID(1180707302, genreNames, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetFilePathsByRecordingMBIDs returns the file paths of every track
|
||||
* whose recording MBID is in mbids, grouped by MBID.
|
||||
*
|
||||
* This is the catalog side of GetFilePathsByAlbums. An Explore album
|
||||
* page knows what the user owns as a set of recording MBIDs and nothing
|
||||
* else: that is exactly how the backend decides a track's InLibrary
|
||||
* flag (markReleasesInLibrary → CheckMBIDs), and MBTrack.LocalID is a
|
||||
* declared field that nothing writes, so there is no id to ask by.
|
||||
*
|
||||
* Grouped rather than flattened for the same two reasons as its
|
||||
* siblings — the caller owns the order (the tracklist's, not the
|
||||
* database's), and one recording can have more than one file, which is
|
||||
* what this app's duplicate detection exists for.
|
||||
*
|
||||
* A library id of 0 means "every library".
|
||||
*/
|
||||
export function GetFilePathsByRecordingMBIDs(mbids: string[] | null, libraryID: number): $CancellablePromise<{ [_ in string]?: string[] | null } | null> {
|
||||
return $Call.ByID(2789061644, mbids, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetRemovalImpact returns pre-removal counts for the confirmation
|
||||
* dialog. All queries are read-only.
|
||||
*/
|
||||
export function GetRemovalImpact(libraryID: number): $CancellablePromise<$models.RemovalImpact | null> {
|
||||
return $Call.ByID(2715588999, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetScanQueueLength returns the number of libraries waiting in the
|
||||
* scan queue (excludes the currently scanning library).
|
||||
*/
|
||||
export function GetScanQueueLength(): $CancellablePromise<number> {
|
||||
return $Call.ByID(2739382973);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTrackMBIDs returns the MusicBrainz IDs for the track at the
|
||||
* given file path. Returns empty strings for entities without MBIDs.
|
||||
*/
|
||||
export function GetTrackMBIDs(filePath: string): $CancellablePromise<$models.TrackMBIDs> {
|
||||
return $Call.ByID(56752473, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTracksByGenre returns all tracks tagged with the given genre.
|
||||
*/
|
||||
export function GetTracksByGenre(genreName: string): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(1674220245, genreName);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTracksByGenreByLibrary returns tracks tagged with the given
|
||||
* genre, scoped to the given library.
|
||||
*/
|
||||
export function GetTracksByGenreByLibrary(genreName: string, libraryID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(4240564783, genreName, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* IsScanActive returns whether a scan is currently running.
|
||||
*/
|
||||
export function IsScanActive(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(1365062070);
|
||||
}
|
||||
|
||||
/**
|
||||
* IsScanPaused returns whether the scan is currently paused.
|
||||
*/
|
||||
export function IsScanPaused(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(2930130958);
|
||||
}
|
||||
|
||||
/**
|
||||
* LibraryPath resolves a library's root directory by id.
|
||||
*/
|
||||
export function LibraryPath(id: number): $CancellablePromise<string> {
|
||||
return $Call.ByID(1798172787, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* PauseScan pauses an in-progress scan. Workers block at their
|
||||
* next pause checkpoint until ResumeScan is called.
|
||||
*/
|
||||
export function PauseScan(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3522392788);
|
||||
}
|
||||
|
||||
/**
|
||||
* QueuedLibraryNames returns the display names of libraries waiting
|
||||
* in the scan queue, in FIFO order.
|
||||
*/
|
||||
export function QueuedLibraryNames(): $CancellablePromise<string[] | null> {
|
||||
return $Call.ByID(2036951097);
|
||||
}
|
||||
|
||||
/**
|
||||
* ReleasePipelineLock releases the pipeline mutex after a tag write.
|
||||
*/
|
||||
export function ReleasePipelineLock(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2053843147);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveFromLibrary deletes the database rows for the given file paths
|
||||
* and records each path as excluded, so the next scan does not import
|
||||
* it again. **It does not touch the files on disk** — that is the
|
||||
* promise the confirmation dialog makes, and the reason this operation
|
||||
* is safe to put one keystroke from a focused row.
|
||||
*
|
||||
* The exclusion is not an enhancement. Without it the next scan finds
|
||||
* the file, sees no row, and imports it again — a button that undoes
|
||||
* itself, which is worse than no button.
|
||||
*/
|
||||
export function RemoveFromLibrary(filePaths: string[] | null): $CancellablePromise<$models.RemovalResult | null> {
|
||||
return $Call.ByID(825509452, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveLibrary atomically removes a library and all its data,
|
||||
* performing orphan cleanup, phantom metadata conversion, FTS5
|
||||
* rebuild, and queue compaction. Returns a summary of what was removed.
|
||||
*/
|
||||
export function RemoveLibrary(id: number): $CancellablePromise<$models.RemovalSummary | null> {
|
||||
return $Call.ByID(4110443412, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* RenameLibrary validates and updates a library's display name.
|
||||
*/
|
||||
export function RenameLibrary(id: number, newName: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(3949052986, id, newName);
|
||||
}
|
||||
|
||||
/**
|
||||
* RestorePausedScans adopts scans that were paused when the app last
|
||||
* shut down back into the job registry, still paused. Call during
|
||||
* startup before SoftScanAllLibraries so the soft scan does not restart
|
||||
* a library the user deliberately paused.
|
||||
*/
|
||||
export function RestorePausedScans(): $CancellablePromise<void> {
|
||||
return $Call.ByID(4243712755);
|
||||
}
|
||||
|
||||
/**
|
||||
* ResumeScan unblocks a paused scan.
|
||||
*/
|
||||
export function ResumeScan(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3079702539);
|
||||
}
|
||||
|
||||
/**
|
||||
* ScanAllLibraries queries all libraries from the database and queues
|
||||
* each one for scanning. Existing dedup logic ensures no duplicates.
|
||||
*/
|
||||
export function ScanAllLibraries(): $CancellablePromise<void> {
|
||||
return $Call.ByID(266098306);
|
||||
}
|
||||
|
||||
/**
|
||||
* ScanLibrary queues a scan for the library with the given database ID.
|
||||
* If no scan is active the library is scanned immediately; otherwise it
|
||||
* is appended to the queue. Duplicate requests (same library already
|
||||
* scanning or already queued) are silently ignored.
|
||||
*/
|
||||
export function ScanLibrary(id: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2994459743, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchTracks performs an FTS5 full-text search and returns
|
||||
* matching tracks with full metadata.
|
||||
*/
|
||||
export function SearchTracks(query: string): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(3848515709, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchTracksByLibrary performs an FTS5 search scoped to a specific
|
||||
* library and returns matching tracks with full metadata.
|
||||
*/
|
||||
export function SearchTracksByLibrary(query: string, libraryID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(152384327, query, libraryID);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetJobRegistry wires the background job registry so scans report
|
||||
* progress, logs, and pause/cancel controls to the frontend.
|
||||
*/
|
||||
export function SetJobRegistry(reg: jobs$0.Registry | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(4271773525, reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetRemovalHooks provides optional hooks for cross-cutting
|
||||
* orchestration during RemoveLibrary.
|
||||
*/
|
||||
export function SetRemovalHooks(h: $models.RemovalHooks): $CancellablePromise<void> {
|
||||
return $Call.ByID(3190933207, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetRescanHooks provides optional hooks for cross-cutting
|
||||
* orchestration during FullRescan.
|
||||
*/
|
||||
export function SetRescanHooks(h: $models.RescanHooks): $CancellablePromise<void> {
|
||||
return $Call.ByID(1944064333, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetScanHooks provides optional hooks for cross-cutting
|
||||
* orchestration after each library scan.
|
||||
*/
|
||||
export function SetScanHooks(h: $models.ScanHooks): $CancellablePromise<void> {
|
||||
return $Call.ByID(520513414, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* SoftScanAllLibraries performs a lightweight launch-time scan.
|
||||
* First it claims any orphaned tracks (library_id=0) left over from
|
||||
* the pre-multi-library schema. Then for each library it compares
|
||||
* the number of audio files on disk against the track count in the
|
||||
* database. Only libraries where the counts differ (files added or
|
||||
* removed since last scan) are queued for a full scan. Libraries
|
||||
* that are unchanged are silently skipped — no progress bar, no
|
||||
* scan events.
|
||||
*/
|
||||
export function SoftScanAllLibraries(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1401473402);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as time$0 from "../../../time/models.js";
|
||||
|
||||
/**
|
||||
* Album represents an album for the cover grid display.
|
||||
*
|
||||
* Year is the album's preferred display year — the release-group's
|
||||
* original-release-date (MusicBrainz first-release-date) when known,
|
||||
* falling back to the file-tag year. ReleaseYear is the file-tag
|
||||
* year of the specific release in the library; for a 2010 remaster
|
||||
* of a 1973 album, Year=1973 and ReleaseYear=2010.
|
||||
*/
|
||||
export interface Album {
|
||||
"ID": number;
|
||||
"Name": string;
|
||||
"ArtistName": string;
|
||||
"ArtistMBID": string;
|
||||
"MBID": string;
|
||||
"CoverArtPath": string;
|
||||
"CoverArtSmall": string;
|
||||
"CoverArtMedium": string;
|
||||
"CoverArtLarge": string;
|
||||
"Year": number;
|
||||
"ReleaseYear": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* AlbumCompleteness says how much of an album is present, as the files
|
||||
* themselves claim.
|
||||
*
|
||||
* Known is the part that matters: a tag that never declared a total is
|
||||
* not the same as a total that is unmet, and rendering the two alike
|
||||
* would put an "incomplete" mark on most of an untagged library. When
|
||||
* Known is false, Expected means nothing and the caller must say
|
||||
* nothing.
|
||||
*/
|
||||
export interface AlbumCompleteness {
|
||||
"owned": number;
|
||||
"expected": number;
|
||||
"known": boolean;
|
||||
"complete": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Artist represents an artist in the library.
|
||||
*/
|
||||
export interface Artist {
|
||||
"ID": number;
|
||||
"Name": string;
|
||||
"MBID": string;
|
||||
"ImageSmall": string;
|
||||
"ImageMedium": string;
|
||||
"ImageLarge": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GenreWithCount holds a genre name and its associated track count.
|
||||
*/
|
||||
export interface GenreWithCount {
|
||||
"Name": string;
|
||||
"TrackCount": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Info contains library metadata enriched with track count
|
||||
* for the frontend settings UI.
|
||||
*/
|
||||
export interface Info {
|
||||
"id": number;
|
||||
"name": string;
|
||||
"path": string;
|
||||
"trackCount": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RemovalHooks contains callbacks invoked during library removal.
|
||||
* These break circular dependencies between the library, player,
|
||||
* and queue packages.
|
||||
*/
|
||||
export interface RemovalHooks {
|
||||
/**
|
||||
* StopPlayback stops the currently-playing track.
|
||||
*/
|
||||
"StopPlayback": any;
|
||||
|
||||
/**
|
||||
* CompactQueue reloads queue state after cascade deletes.
|
||||
*/
|
||||
"CompactQueue": any;
|
||||
|
||||
/**
|
||||
* PostRemove runs after the removal commits, for cross-cutting
|
||||
* invalidation (e.g. clearing library-sync "ready" markers).
|
||||
*/
|
||||
"PostRemove": any;
|
||||
}
|
||||
|
||||
/**
|
||||
* RemovalImpact contains pre-removal counts for the confirmation dialog.
|
||||
*/
|
||||
export interface RemovalImpact {
|
||||
"trackCount": number;
|
||||
"playlistsAffected": number;
|
||||
"queueItemCount": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RemovalResult reports what one RemoveFromLibrary call did.
|
||||
*/
|
||||
export interface RemovalResult {
|
||||
/**
|
||||
* TracksRemoved is how many audio_files rows were deleted. It can
|
||||
* be lower than len(filePaths) if a path was already gone.
|
||||
*/
|
||||
"tracksRemoved": number;
|
||||
|
||||
/**
|
||||
* PathsExcluded is how many paths the scanner will now skip.
|
||||
*/
|
||||
"pathsExcluded": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RemovalSummary contains post-removal counts for the toast notification.
|
||||
*/
|
||||
export interface RemovalSummary {
|
||||
"tracksDeleted": number;
|
||||
"artistsRemoved": number;
|
||||
"albumsRemoved": number;
|
||||
"genresRemoved": number;
|
||||
"playlistsAffected": number;
|
||||
"queueItemsRemoved": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RescanHooks holds optional callbacks that run before and after
|
||||
* the library-clear-and-scan phase of a full rescan. The app
|
||||
* layer sets these to coordinate cross-cutting concerns (e.g.
|
||||
* clearing the queue, restoring playlists) without the library
|
||||
* needing to know about those packages.
|
||||
*/
|
||||
export interface RescanHooks {
|
||||
/**
|
||||
* PreClear runs before library data is wiped
|
||||
* (e.g. clear queue and stop playback).
|
||||
*/
|
||||
"PreClear": any;
|
||||
|
||||
/**
|
||||
* PostScan runs after the scan completes
|
||||
* (e.g. restore playlists from M3U8 files).
|
||||
*/
|
||||
"PostScan": any;
|
||||
}
|
||||
|
||||
/**
|
||||
* ScanHooks contains callbacks invoked after a library scan
|
||||
* completes. The app layer wires these so the library package
|
||||
* does not depend on the playlist package directly.
|
||||
*/
|
||||
export interface ScanHooks {
|
||||
/**
|
||||
* RepopulatePlaylists re-imports tracks for playlists that
|
||||
* lost their playlist_tracks rows (e.g., from a pre-fix
|
||||
* FullRescan). Runs before ResolvePhantoms.
|
||||
*/
|
||||
"RepopulatePlaylists": any;
|
||||
|
||||
/**
|
||||
* ResolvePhantoms re-links phantom playlist tracks whose
|
||||
* files now exist in the library after scanning.
|
||||
*/
|
||||
"ResolvePhantoms": any;
|
||||
|
||||
/**
|
||||
* OnAllScansComplete runs after ALL queued scans finish
|
||||
* (queue drained).
|
||||
*/
|
||||
"OnAllScansComplete": any;
|
||||
}
|
||||
|
||||
/**
|
||||
* ScanMetrics holds timing and count data collected during a library scan.
|
||||
* Worker-pool fields are protected by a mutex; DB-writer fields are
|
||||
* single-threaded and use plain addition.
|
||||
*/
|
||||
export interface ScanMetrics {
|
||||
/**
|
||||
* Top-level phases (wall-clock).
|
||||
*/
|
||||
"total": time$0.Duration;
|
||||
"loadExisting": time$0.Duration;
|
||||
"walkDuration": time$0.Duration;
|
||||
"extractionWallClock": time$0.Duration;
|
||||
"dbWritesWallClock": time$0.Duration;
|
||||
"orphanCleanup": time$0.Duration;
|
||||
"postScanVariants": time$0.Duration;
|
||||
|
||||
/**
|
||||
* Per-format extraction (cumulative across workers).
|
||||
*/
|
||||
"formatExtraction": { [_ in string]?: number } | null;
|
||||
"formatCount": { [_ in string]?: number } | null;
|
||||
|
||||
/**
|
||||
* Sub-operation cumulative times (across workers).
|
||||
*/
|
||||
"tagExtraction": time$0.Duration;
|
||||
"durationExtraction": time$0.Duration;
|
||||
|
||||
/**
|
||||
* DB sub-operations (cumulative, single-threaded DB writer).
|
||||
*/
|
||||
"batchCommits": time$0.Duration;
|
||||
"coverArtSave": time$0.Duration;
|
||||
|
||||
/**
|
||||
* Thumbnail generation (async worker pool).
|
||||
*/
|
||||
"thumbnailWallClock": time$0.Duration;
|
||||
"thumbnailGeneration": time$0.Duration;
|
||||
"thumbnailSmall": time$0.Duration;
|
||||
"thumbnailMedium": time$0.Duration;
|
||||
"thumbnailLarge": time$0.Duration;
|
||||
|
||||
/**
|
||||
* Full-rescan-specific phases.
|
||||
*/
|
||||
"clearQueue": time$0.Duration;
|
||||
"clearDatabase": time$0.Duration;
|
||||
"clearCoverFiles": time$0.Duration;
|
||||
|
||||
/**
|
||||
* File counts.
|
||||
*/
|
||||
"added": number;
|
||||
"updated": number;
|
||||
"skipped": number;
|
||||
"removed": number;
|
||||
|
||||
/**
|
||||
* Cancelled is true when the scan was stopped via CancelScan.
|
||||
*/
|
||||
"cancelled": boolean;
|
||||
|
||||
/**
|
||||
* Library identification.
|
||||
* library that was scanned
|
||||
*/
|
||||
"libraryId": number;
|
||||
|
||||
/**
|
||||
* display name of scanned library
|
||||
*/
|
||||
"libraryName": string;
|
||||
|
||||
/**
|
||||
* Non-fatal issues encountered during scanning.
|
||||
*/
|
||||
"warnings": ScanWarning[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ScanWarning represents a non-fatal issue encountered during scanning.
|
||||
*/
|
||||
export interface ScanWarning {
|
||||
"filePath": string;
|
||||
"phase": string;
|
||||
"err": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track represents a playable audio file in the library.
|
||||
*/
|
||||
export interface Track {
|
||||
"TrackName": string;
|
||||
"ArtistName": string;
|
||||
"TrackLength": string;
|
||||
"FilePath": string;
|
||||
"TrackNumber": number;
|
||||
"DiscNumber": number;
|
||||
"Album": string;
|
||||
"Genre": string[] | null;
|
||||
"Year": number;
|
||||
"Composer": string;
|
||||
"FileType": string;
|
||||
"SampleRate": number;
|
||||
"BitDepth": number;
|
||||
"Channels": number;
|
||||
"Bitrate": number;
|
||||
"FileSize": number;
|
||||
"PlayCount": number;
|
||||
"LastPlayed": string;
|
||||
"RecordingMBID": string;
|
||||
"ArtistMBID": string;
|
||||
"ReleaseGroupMBID": string;
|
||||
"CoverArtPath": string;
|
||||
"CoverArtSmall": string;
|
||||
"CoverArtMedium": string;
|
||||
"CoverArtLarge": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TrackMBIDs holds MusicBrainz identifiers for a track, resolved
|
||||
* from the recording, release group, and artist tables.
|
||||
*/
|
||||
export interface TrackMBIDs {
|
||||
"recordingMbid": string;
|
||||
"releaseGroupMbid": string;
|
||||
"artistMbid": string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export type {
|
||||
Handler
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,7 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Handler manages the OS media control integration.
|
||||
*/
|
||||
export type Handler = any;
|
||||
@@ -0,0 +1,16 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Player from "./player.js";
|
||||
export {
|
||||
Player
|
||||
};
|
||||
|
||||
export {
|
||||
State,
|
||||
UserVolume
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
TrackInfo
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,61 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* State represents the current playback state.
|
||||
*/
|
||||
export enum State {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Playback state values.
|
||||
*/
|
||||
Playing = "playing",
|
||||
Paused = "paused",
|
||||
Stopped = "stopped",
|
||||
};
|
||||
|
||||
/**
|
||||
* TrackInfo contains metadata and playback state for the currently
|
||||
* loaded track. It is emitted as the payload of the TrackChanged
|
||||
* event and serialized as camelCase JSON to match the frontend
|
||||
* TrackInfo interface in player-store.ts.
|
||||
*/
|
||||
export interface TrackInfo {
|
||||
"fileName": string;
|
||||
"filePath": string;
|
||||
"state": State;
|
||||
"title": string;
|
||||
"artist": string;
|
||||
"album": string;
|
||||
"coverArt": string;
|
||||
"coverArtSmall": string;
|
||||
"coverArtMedium": string;
|
||||
"coverArtLarge": string;
|
||||
"trackLength": number;
|
||||
"seekPosition": number;
|
||||
"trackChangeId": number;
|
||||
"artistMbid": string;
|
||||
"releaseGroupMbid": string;
|
||||
"recordingMbid": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UserVolume represents volume on a user-facing scale (0-100).
|
||||
*/
|
||||
export enum UserVolume {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = 0,
|
||||
|
||||
/**
|
||||
* User volume range bounds.
|
||||
*/
|
||||
MinUserVol = 0,
|
||||
MaxUserVol = 100,
|
||||
DefaultUserVol = 50,
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Player handles audio playback and state management.
|
||||
*
|
||||
* Lock ordering: always acquire p.mu BEFORE speaker.Lock().
|
||||
* The beep playback-finished callback dispatches to a new goroutine
|
||||
* so it never holds p.mu while the speaker lock is held.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as mediacontrols$0 from "../mediacontrols/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* ChangeVolume adjusts the volume by a relative amount.
|
||||
*/
|
||||
export function ChangeVolume(deltaVolume: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(742861629, deltaVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* CurrentPosition returns the playback position as a percentage
|
||||
* (0-100).
|
||||
*/
|
||||
export function CurrentPosition(): $CancellablePromise<number> {
|
||||
return $Call.ByID(2723620683);
|
||||
}
|
||||
|
||||
/**
|
||||
* CurrentPositionSeconds returns the current playback position in
|
||||
* display seconds.
|
||||
*/
|
||||
export function CurrentPositionSeconds(): $CancellablePromise<number> {
|
||||
return $Call.ByID(1423359008);
|
||||
}
|
||||
|
||||
/**
|
||||
* EmitCurrentState pushes the current player state to the frontend.
|
||||
* This is intended to be called after the frontend is ready to
|
||||
* receive events, separately from RestoreState which does the heavy
|
||||
* lifting during OnStartup.
|
||||
*/
|
||||
export function EmitCurrentState(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2143346946);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetCurrentTrackInfo returns information about the currently
|
||||
* loaded track.
|
||||
*/
|
||||
export function GetCurrentTrackInfo(): $CancellablePromise<$models.TrackInfo> {
|
||||
return $Call.ByID(2615351053);
|
||||
}
|
||||
|
||||
/**
|
||||
* InitSpeaker initializes the audio output device. This is
|
||||
* separated from NewPlayer so the player struct can be created
|
||||
* before wails.Run (for binding registration) while deferring
|
||||
* hardware initialization to OnStartup.
|
||||
*/
|
||||
export function InitSpeaker(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3317123670);
|
||||
}
|
||||
|
||||
/**
|
||||
* IsPlaying reports whether the player is currently playing audio.
|
||||
*/
|
||||
export function IsPlaying(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(1219303535);
|
||||
}
|
||||
|
||||
/**
|
||||
* LoadFile opens and decodes an audio file for playback.
|
||||
*/
|
||||
export function LoadFile(filePath: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2214941041, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* MuteToggle toggles the mute state.
|
||||
*/
|
||||
export function MuteToggle(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2840614678);
|
||||
}
|
||||
|
||||
/**
|
||||
* Muted reports whether playback is currently silenced.
|
||||
*/
|
||||
export function Muted(): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(2954670878);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause pauses the current playback.
|
||||
*/
|
||||
export function Pause(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1402758839);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play starts or resumes audio playback.
|
||||
*/
|
||||
export function Play(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3327176373);
|
||||
}
|
||||
|
||||
/**
|
||||
* RestoreState loads the persisted player state from the database.
|
||||
*/
|
||||
export function RestoreState(): $CancellablePromise<void> {
|
||||
return $Call.ByID(318202560);
|
||||
}
|
||||
|
||||
/**
|
||||
* SaveState persists the current player state to the database and
|
||||
* waits for the write. This is called during shutdown to capture the
|
||||
* final state, which is the one case that cannot be deferred.
|
||||
*/
|
||||
export function SaveState(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3845072215);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek jumps to a specific position in seconds.
|
||||
*/
|
||||
export function Seek(targetSeconds: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(829056707, targetSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetMediaControls provides an OS media controls handler. When set,
|
||||
* the player pushes metadata, playback state, volume, and seek
|
||||
* notifications to the OS media overlay.
|
||||
*/
|
||||
export function SetMediaControls(h: mediacontrols$0.Handler): $CancellablePromise<void> {
|
||||
return $Call.ByID(1896286877, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetPlaybackFinishedHandler sets a callback invoked when a track
|
||||
* finishes naturally. This allows the queue to drive auto-advance
|
||||
* without circular imports.
|
||||
*/
|
||||
export function SetPlaybackFinishedHandler(handler: any): $CancellablePromise<void> {
|
||||
return $Call.ByID(2023377546, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetVolume sets the playback volume (0-100), emits a
|
||||
* VolumeChanged event, and persists the new level.
|
||||
*/
|
||||
export function SetVolume(desiredVolume: $models.UserVolume): $CancellablePromise<void> {
|
||||
return $Call.ByID(1375836663, desiredVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* TrackLengthInSeconds returns the duration of the current track.
|
||||
*/
|
||||
export function TrackLengthInSeconds(): $CancellablePromise<number> {
|
||||
return $Call.ByID(3749851426);
|
||||
}
|
||||
|
||||
/**
|
||||
* UnloadTrack tears down the current track, releasing the file and
|
||||
* streamer chain. The player returns to the initial "no track
|
||||
* loaded" state and emits events so the frontend clears its
|
||||
* current-track display.
|
||||
*/
|
||||
export function UnloadTrack(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3127425467);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Service from "./service.js";
|
||||
export {
|
||||
Service
|
||||
};
|
||||
|
||||
export type {
|
||||
CandidateTrack,
|
||||
DuplicateCheckResult,
|
||||
DuplicateTrackInfo,
|
||||
FavoritesConfigProvider,
|
||||
PhantomMatch,
|
||||
PhantomSearchResult,
|
||||
Summary,
|
||||
Track,
|
||||
WithTracks
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,103 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* CandidateTrack represents a potential library match for a
|
||||
* phantom track.
|
||||
*/
|
||||
export interface CandidateTrack {
|
||||
"FilePath": string;
|
||||
"Title": string;
|
||||
"Artist": string;
|
||||
"Album": string;
|
||||
"Duration": string;
|
||||
"Score": number;
|
||||
}
|
||||
|
||||
/**
|
||||
* DuplicateCheckResult contains the outcome of checking for
|
||||
* duplicate tracks in a playlist.
|
||||
*/
|
||||
export interface DuplicateCheckResult {
|
||||
"Duplicates": DuplicateTrackInfo[] | null;
|
||||
"Unique": string[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* DuplicateTrackInfo holds metadata for a track that already
|
||||
* exists in a playlist.
|
||||
*/
|
||||
export interface DuplicateTrackInfo {
|
||||
"FilePath": string;
|
||||
"Title": string;
|
||||
"Artist": string;
|
||||
"Album": string;
|
||||
"Duration": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* FavoritesConfigProvider is a narrow interface for reading and
|
||||
* writing the default-playlist configuration.
|
||||
*/
|
||||
export type FavoritesConfigProvider = any;
|
||||
|
||||
/**
|
||||
* PhantomMatch represents a high-confidence pairing of a phantom
|
||||
* track to a library track.
|
||||
*/
|
||||
export interface PhantomMatch {
|
||||
"PhantomPath": string;
|
||||
"PhantomTitle": string;
|
||||
"Candidate": CandidateTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* PhantomSearchResult contains auto-matched pairs and remaining
|
||||
* unmatched phantom paths for a batch search operation.
|
||||
*/
|
||||
export interface PhantomSearchResult {
|
||||
"AutoMatched": PhantomMatch[] | null;
|
||||
"Unmatched": string[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary is a lightweight representation of a playlist for the
|
||||
* picker UI.
|
||||
*/
|
||||
export interface Summary {
|
||||
"ID": number;
|
||||
"Name": string;
|
||||
"CreatedAt": string;
|
||||
"UpdatedAt": string;
|
||||
"IsSmart": boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track represents a track within a playlist, including its
|
||||
* metadata.
|
||||
*/
|
||||
export interface Track {
|
||||
"ID": number;
|
||||
"Position": number;
|
||||
"FilePath": string;
|
||||
"Title": string;
|
||||
"Artist": string;
|
||||
"Album": string;
|
||||
"CoverArtPath": string;
|
||||
"CoverArtSmall": string;
|
||||
"CoverArtMedium": string;
|
||||
"CoverArtLarge": string;
|
||||
"Duration": string;
|
||||
"Phantom": boolean;
|
||||
"ArtistMBID": string;
|
||||
"ReleaseGroupMBID": string;
|
||||
"RecordingMBID": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WithTracks contains a playlist summary and all its tracks.
|
||||
*/
|
||||
export interface WithTracks {
|
||||
"Summary": Summary;
|
||||
"Tracks": Track[] | null;
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Service manages playlist operations.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as library$0 from "../library/models.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* AddToDefaultPlaylist adds multiple tracks to the default
|
||||
* playlist, skipping any that are already present.
|
||||
*/
|
||||
export function AddToDefaultPlaylist(filePaths: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(1493850977, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* AddTracksToPlaylist adds one or more tracks to an existing
|
||||
* playlist.
|
||||
*/
|
||||
export function AddTracksToPlaylist(playlistID: number, filePaths: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(2843242868, playlistID, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* CreatePlaylist creates a new empty playlist with the given name.
|
||||
*/
|
||||
export function CreatePlaylist(name: string): $CancellablePromise<$models.Summary> {
|
||||
return $Call.ByID(4080218878, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* CreatePlaylistWithTracks creates a new playlist and populates
|
||||
* it with tracks.
|
||||
*/
|
||||
export function CreatePlaylistWithTracks(name: string, filePaths: string[] | null): $CancellablePromise<$models.Summary> {
|
||||
return $Call.ByID(4079964334, name, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateSmartPlaylist creates a new smart playlist with the given
|
||||
* name and JSON rule set. The rules are validated before storage.
|
||||
*/
|
||||
export function CreateSmartPlaylist(name: string, rulesJSON: string): $CancellablePromise<$models.Summary> {
|
||||
return $Call.ByID(2020825359, name, rulesJSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* DeletePlaylist deletes a playlist and its M3U8 file.
|
||||
* If the deleted playlist was the default, a new default
|
||||
* playlist is automatically created.
|
||||
*/
|
||||
export function DeletePlaylist(playlistID: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(1998239209, playlistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* EnsureDefaultPlaylist verifies the configured default playlist
|
||||
* exists in the database. If the playlist is missing or no ID
|
||||
* has been configured yet, a new playlist named "Favorites" is
|
||||
* created and the config is updated.
|
||||
*/
|
||||
export function EnsureDefaultPlaylist(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3280319649);
|
||||
}
|
||||
|
||||
/**
|
||||
* EvaluateSmartPlaylist loads the rule set for a smart playlist
|
||||
* from the database and evaluates it against the track library,
|
||||
* returning the matching tracks.
|
||||
*/
|
||||
export function EvaluateSmartPlaylist(playlistID: number): $CancellablePromise<library$0.Track[] | null> {
|
||||
return $Call.ByID(2192251014, playlistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* FindDuplicateTracksInPlaylist checks which of the given file
|
||||
* paths already exist in the specified playlist. Returns metadata
|
||||
* for each duplicate and a list of non-duplicate file paths.
|
||||
*/
|
||||
export function FindDuplicateTracksInPlaylist(playlistID: number, filePaths: string[] | null): $CancellablePromise<$models.DuplicateCheckResult> {
|
||||
return $Call.ByID(57159613, playlistID, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* FindPhantomMatches searches the library for matches for the
|
||||
* given phantom file paths. High-confidence matches are returned
|
||||
* as auto-matched pairs; the rest remain in the unmatched list.
|
||||
*/
|
||||
export function FindPhantomMatches(playlistID: number, phantomPaths: string[] | null): $CancellablePromise<$models.PhantomSearchResult> {
|
||||
return $Call.ByID(2112903901, playlistID, phantomPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllPlaylists returns all playlists ordered by most recently
|
||||
* updated.
|
||||
*/
|
||||
export function GetAllPlaylists(): $CancellablePromise<$models.Summary[] | null> {
|
||||
return $Call.ByID(1751004770);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAllPlaylistsWithTracks returns all playlists with their
|
||||
* tracks in a single call, merging phantom tracks from M3U8 files.
|
||||
*/
|
||||
export function GetAllPlaylistsWithTracks(): $CancellablePromise<$models.WithTracks[] | null> {
|
||||
return $Call.ByID(1602859466);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetDefaultPlaylistInfo returns the ID and name of the default
|
||||
* playlist for display in the frontend.
|
||||
*/
|
||||
export function GetDefaultPlaylistInfo(): $CancellablePromise<$models.Summary> {
|
||||
return $Call.ByID(845855111);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetDefaultPlaylistTrackPaths returns the file paths of all
|
||||
* tracks in the default playlist.
|
||||
*/
|
||||
export function GetDefaultPlaylistTrackPaths(): $CancellablePromise<string[] | null> {
|
||||
return $Call.ByID(135237414);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetPhantomCandidates returns scored candidate matches for a
|
||||
* single phantom track.
|
||||
*/
|
||||
export function GetPhantomCandidates(playlistID: number, phantomPath: string): $CancellablePromise<$models.CandidateTrack[] | null> {
|
||||
return $Call.ByID(904105323, playlistID, phantomPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetPlaylistTracks returns all tracks in a playlist with full
|
||||
* metadata, merging phantom tracks from the M3U8 file.
|
||||
*/
|
||||
export function GetPlaylistTracks(playlistID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(3997481994, playlistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetSmartPlaylistRules returns the raw JSON rule string for an
|
||||
* existing smart playlist. This is used when the user opens the
|
||||
* rule editor for an existing smart playlist.
|
||||
*/
|
||||
export function GetSmartPlaylistRules(playlistID: number): $CancellablePromise<string> {
|
||||
return $Call.ByID(997403286, playlistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetSmartPlaylistTracks returns the persisted snapshot of a smart
|
||||
* playlist as regular playlist tracks (with resolved cover art and
|
||||
* phantom entries), identical to a normal playlist. If the playlist
|
||||
* has never been materialized — e.g. it predates snapshot support —
|
||||
* it is evaluated and stored on first access.
|
||||
*/
|
||||
export function GetSmartPlaylistTracks(playlistID: number): $CancellablePromise<$models.Track[] | null> {
|
||||
return $Call.ByID(2517005223, playlistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* ImportPlaylist imports a playlist from an external M3U/M3U8
|
||||
* file. It creates a new playlist in the DB, resolves tracks
|
||||
* against the library, and saves an M3U8 file.
|
||||
*/
|
||||
export function ImportPlaylist(filePath: string): $CancellablePromise<$models.Summary> {
|
||||
return $Call.ByID(3295216919, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* ImportPlaylists imports multiple playlists from external M3U/M3U8
|
||||
* files. Each file is imported sequentially using ImportPlaylist.
|
||||
* Errors from individual imports are collected; partial success is
|
||||
* possible. Returns the summaries of successfully imported playlists
|
||||
* and the first error encountered (if any).
|
||||
*/
|
||||
export function ImportPlaylists(filePaths: string[] | null): $CancellablePromise<$models.Summary[] | null> {
|
||||
return $Call.ByID(2505276524, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* MaterializeUnmaterializedSmartPlaylists evaluates and snapshots any
|
||||
* smart playlist that has never been materialized (smart_snapshot_at
|
||||
* IS NULL) — e.g. playlists created before creation-time
|
||||
* materialization existed. It runs once at startup and is idempotent:
|
||||
* once every smart playlist has a snapshot it becomes a no-op. Errors
|
||||
* on individual playlists are logged and skipped so one bad rule set
|
||||
* doesn't block the rest.
|
||||
*/
|
||||
export function MaterializeUnmaterializedSmartPlaylists(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3018316369);
|
||||
}
|
||||
|
||||
/**
|
||||
* PreviewSmartPlaylist evaluates a rule set from raw JSON without
|
||||
* requiring a saved playlist. This powers live preview in the rule
|
||||
* editor — the frontend sends rules as they are being edited and
|
||||
* receives matching tracks immediately.
|
||||
*/
|
||||
export function PreviewSmartPlaylist(rulesJSON: string): $CancellablePromise<library$0.Track[] | null> {
|
||||
return $Call.ByID(3006325439, rulesJSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* RefreshSmartPlaylist re-evaluates a smart playlist's rules against
|
||||
* the current library and replaces its persisted membership in
|
||||
* playlist_tracks with the result. This is the only path that
|
||||
* re-evaluates a smart playlist — opening one otherwise reads the
|
||||
* stored snapshot. Triggered on rule save and by the manual Refresh
|
||||
* button.
|
||||
*/
|
||||
export function RefreshSmartPlaylist(playlistID: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2225480718, playlistID);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveFromDefaultPlaylist removes multiple tracks from the
|
||||
* default playlist.
|
||||
*/
|
||||
export function RemoveFromDefaultPlaylist(filePaths: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(619411935, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemovePhantomTracks removes phantom entries from a playlist's
|
||||
* M3U8 file. Since phantom tracks have no DB rows, only the
|
||||
* M3U8 file is modified.
|
||||
*/
|
||||
export function RemovePhantomTracks(playlistID: number, phantomPaths: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(823798913, playlistID, phantomPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveTracksFromPlaylist removes multiple tracks from a playlist
|
||||
* by their playlist_track IDs.
|
||||
*/
|
||||
export function RemoveTracksFromPlaylist(playlistID: number, trackIDs: number[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(2519855654, playlistID, trackIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* RenamePlaylist renames a playlist and updates its M3U8 file.
|
||||
*/
|
||||
export function RenamePlaylist(playlistID: number, newName: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(1835045264, playlistID, newName);
|
||||
}
|
||||
|
||||
/**
|
||||
* RepopulateFromM3U re-imports tracks for playlists that have zero
|
||||
* playlist_tracks rows but still have a corresponding M3U8 file.
|
||||
* This recovers from a FullRescan that deleted playlist tracks
|
||||
* before the ON DELETE SET NULL fix was in place. Each M3U8 entry
|
||||
* is resolved against the audio_files table; unresolved entries
|
||||
* become phantom tracks with metadata preserved from the M3U8.
|
||||
*/
|
||||
export function RepopulateFromM3U(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3399290718);
|
||||
}
|
||||
|
||||
/**
|
||||
* ResolvePhantomTracks replaces phantom entries in a playlist
|
||||
* with real library tracks. The matches map keys are phantom
|
||||
* absolute paths and values are resolved absolute paths.
|
||||
*/
|
||||
export function ResolvePhantomTracks(playlistID: number, matches: { [_ in string]?: string } | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(1239473037, playlistID, matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* ResolvePhantomTracksAfterScan re-links phantom playlist tracks
|
||||
* whose files now exist in the library. It iterates each playlist
|
||||
* that has phantoms, reads its M3U8 file, resolves each entry
|
||||
* against the current audio_files table using multi-root path
|
||||
* resolution, and updates matching phantom playlist_tracks. This
|
||||
* handles both pre-existing phantoms (created before migration 7,
|
||||
* with NULL phantom_file_path) and new ones.
|
||||
*/
|
||||
export function ResolvePhantomTracksAfterScan(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3836161100);
|
||||
}
|
||||
|
||||
/**
|
||||
* RestoreAllPlaylists restores playlist tracks from M3U8 files.
|
||||
* This is called after a full library rescan to repopulate
|
||||
* playlist_tracks from the surviving M3U8 files.
|
||||
*/
|
||||
export function RestoreAllPlaylists(): $CancellablePromise<void> {
|
||||
return $Call.ByID(453794624);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchLibrary searches the entire library by a free-text query
|
||||
* for manual phantom resolution.
|
||||
*/
|
||||
export function SearchLibrary(query: string): $CancellablePromise<$models.CandidateTrack[] | null> {
|
||||
return $Call.ByID(3912116995, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetFavoritesConfig sets the provider used to read and write
|
||||
* the default-playlist configuration.
|
||||
*/
|
||||
export function SetFavoritesConfig(provider: $models.FavoritesConfigProvider): $CancellablePromise<void> {
|
||||
return $Call.ByID(2117418507, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* ToggleDefaultPlaylistTrack adds or removes a single track
|
||||
* from the default playlist. Returns true if the track is now
|
||||
* in the playlist (was added), false if it was removed.
|
||||
*/
|
||||
export function ToggleDefaultPlaylistTrack(filePath: string): $CancellablePromise<boolean> {
|
||||
return $Call.ByID(450278306, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateSmartPlaylistRules updates the rule set for an existing
|
||||
* smart playlist. Returns an error if the playlist does not exist
|
||||
* or is not a smart playlist.
|
||||
*/
|
||||
export function UpdateSmartPlaylistRules(playlistID: number, rulesJSON: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(2746792411, playlistID, rulesJSON);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as Queue from "./queue.js";
|
||||
export {
|
||||
Queue
|
||||
};
|
||||
|
||||
export {
|
||||
RepeatMode
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
FallbackSource,
|
||||
Source,
|
||||
State,
|
||||
Track,
|
||||
TrackLoader
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,72 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* FallbackSource resolves what should auto-play, if anything, once the
|
||||
* queue is exhausted. Implemented outside this package (see app.go) so
|
||||
* the queue does not need to know about config, playlists or
|
||||
* similarity data.
|
||||
*/
|
||||
export type FallbackSource = any;
|
||||
|
||||
/**
|
||||
* RepeatMode represents the queue repeat behavior.
|
||||
*/
|
||||
export enum RepeatMode {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Repeat mode values.
|
||||
*/
|
||||
RepeatOff = "off",
|
||||
RepeatAll = "all",
|
||||
RepeatOne = "one",
|
||||
};
|
||||
|
||||
/**
|
||||
* Source describes the collection a queue was built from — an album, a
|
||||
* playlist, a genre, an artist — so the frontend can offer to navigate
|
||||
* back to it. An empty Type means the queue has no single source (the
|
||||
* whole library, or one ad-hoc track).
|
||||
*/
|
||||
export interface Source {
|
||||
"type": string;
|
||||
"id": number;
|
||||
"label": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* State is the full state emitted to the frontend.
|
||||
*/
|
||||
export interface State {
|
||||
"tracks": Track[] | null;
|
||||
"currentIndex": number;
|
||||
"shuffleMode": boolean;
|
||||
"repeatMode": RepeatMode;
|
||||
"source": Source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track represents a track in the queue with its metadata.
|
||||
*/
|
||||
export interface Track {
|
||||
"id": number;
|
||||
"audioFileId": number;
|
||||
"filePath": string;
|
||||
"position": number;
|
||||
"title": string;
|
||||
"artist": string;
|
||||
"album": string;
|
||||
"coverArtPath": string;
|
||||
"artistMbid": string;
|
||||
"releaseGroupMbid": string;
|
||||
"recordingMbid": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TrackLoader is the interface the queue uses to tell the player to load a file.
|
||||
*/
|
||||
export type TrackLoader = any;
|
||||
@@ -0,0 +1,219 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Queue manages an ordered list of tracks for playback.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* AddTrack appends a track to the end of the queue.
|
||||
* If the queue was empty, it loads the added track in a paused state.
|
||||
*/
|
||||
export function AddTrack(filePath: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(3566864127, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* AddTracks appends multiple tracks to the end of the queue.
|
||||
* If the queue was empty, it loads the first added track in a paused state.
|
||||
*/
|
||||
export function AddTracks(filePaths: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(980962916, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear removes all tracks from the queue, stops playback, and
|
||||
* resets the queue state. It persists the cleared state and
|
||||
* notifies the frontend.
|
||||
*/
|
||||
export function Clear(): $CancellablePromise<void> {
|
||||
return $Call.ByID(4166243766);
|
||||
}
|
||||
|
||||
/**
|
||||
* CompactAfterLibraryRemoval reloads queue state from the database
|
||||
* after a library removal has cascade-deleted queue_tracks rows.
|
||||
* It resets currentIndex to 0 (or -1 if empty), clears shuffleOrder,
|
||||
* unloads the current track if it was removed, and emits QueueChanged.
|
||||
*/
|
||||
export function CompactAfterLibraryRemoval(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3620379645);
|
||||
}
|
||||
|
||||
/**
|
||||
* CycleRepeat cycles through repeat modes: off -> all -> one -> off.
|
||||
*/
|
||||
export function CycleRepeat(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3510519482);
|
||||
}
|
||||
|
||||
/**
|
||||
* EmitCurrentState emits the current queue state to the frontend.
|
||||
* This is called after the frontend DOM is ready.
|
||||
*/
|
||||
export function EmitCurrentState(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1245022280);
|
||||
}
|
||||
|
||||
/**
|
||||
* GetState returns the current queue state for the frontend.
|
||||
*/
|
||||
export function GetState(): $CancellablePromise<$models.State> {
|
||||
return $Call.ByID(3472178852);
|
||||
}
|
||||
|
||||
/**
|
||||
* InsertNext inserts a track right after the currently playing track.
|
||||
* If the queue was empty, it loads the inserted track in a paused state.
|
||||
*/
|
||||
export function InsertNext(filePath: string): $CancellablePromise<void> {
|
||||
return $Call.ByID(1498463573, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* InsertNextTracks inserts multiple tracks as a contiguous block after the current track.
|
||||
* If the queue was empty, it loads the first inserted track in a paused state.
|
||||
*/
|
||||
export function InsertNextTracks(filePaths: string[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(2824705517, filePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* InsertTracksAt inserts multiple tracks at the given index.
|
||||
* If the queue was empty, it loads the first inserted track in a paused state.
|
||||
*/
|
||||
export function InsertTracksAt(filePaths: string[] | null, index: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2550945669, filePaths, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* MoveQueueTracks moves tracks at the given indices to a new position
|
||||
* as a contiguous block. The toIndex is the target position in the
|
||||
* original (pre-move) array.
|
||||
*/
|
||||
export function MoveQueueTracks(fromIndices: number[] | null, toIndex: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(2149753655, fromIndices, toIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Next advances to the next track. If the player was paused, the next
|
||||
* track is loaded but not played. In RepeatOne mode, the current track
|
||||
* is replayed instead of advancing.
|
||||
*/
|
||||
export function Next(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1968784044);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnPlaybackFinished is called when a track finishes playing naturally.
|
||||
* This drives the auto-advance behavior and records the play.
|
||||
*/
|
||||
export function OnPlaybackFinished(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2184869763);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play handles a play request by either resuming the current track or
|
||||
* starting playback from the beginning of the queue. When a track is
|
||||
* already active (currentIndex != -1) the player is told to resume;
|
||||
* otherwise playback starts from the first track (or a random one when
|
||||
* shuffle is enabled).
|
||||
*/
|
||||
export function Play(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3197379127);
|
||||
}
|
||||
|
||||
/**
|
||||
* PlayIndex jumps to and plays the track at the given index.
|
||||
*/
|
||||
export function PlayIndex(index: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(200440797, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous goes to the previous track (or restarts current if >3s in).
|
||||
* If the player was paused, the track is loaded but not played.
|
||||
* In RepeatOne mode, the current track is replayed instead of navigating.
|
||||
*/
|
||||
export function Previous(): $CancellablePromise<void> {
|
||||
return $Call.ByID(708701476);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveTrack removes a track at the given position from the queue.
|
||||
*/
|
||||
export function RemoveTrack(position: number): $CancellablePromise<void> {
|
||||
return $Call.ByID(6811080, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveTracks removes multiple tracks at the given positions from the queue.
|
||||
* Positions are deduplicated, validated, and removed in descending order so
|
||||
* that indices remain stable during removal.
|
||||
*/
|
||||
export function RemoveTracks(positions: number[] | null): $CancellablePromise<void> {
|
||||
return $Call.ByID(1587232097, positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* RestoreState loads the queue state from the database.
|
||||
*/
|
||||
export function RestoreState(): $CancellablePromise<void> {
|
||||
return $Call.ByID(1652105446);
|
||||
}
|
||||
|
||||
/**
|
||||
* SaveState persists the queue state to the database. Unlike every
|
||||
* other write here it waits for the writer: its callers are shutdown
|
||||
* and the tests, both of which need the row to exist on return.
|
||||
*/
|
||||
export function SaveState(): $CancellablePromise<void> {
|
||||
return $Call.ByID(2913531465);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetFallbackSource provides the queue with what to auto-play, if
|
||||
* anything, once it runs out. A nil source (the default) leaves
|
||||
* today's behavior: the queue just goes idle.
|
||||
*/
|
||||
export function SetFallbackSource(fs: $models.FallbackSource): $CancellablePromise<void> {
|
||||
return $Call.ByID(1344903240, fs);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetPlayer provides the queue with a reference to the player for auto-advance.
|
||||
*/
|
||||
export function SetPlayer(player: $models.TrackLoader): $CancellablePromise<void> {
|
||||
return $Call.ByID(2806250342, player);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetQueue replaces the entire queue with new tracks and starts playing.
|
||||
* When shuffleStart is true and shuffle mode is active, a random first
|
||||
* track is chosen instead of the one at startIndex. This is intended for
|
||||
* "Play All" type actions where no specific track was selected.
|
||||
* It uses a two-phase approach: the first batch of tracks (up to
|
||||
* initialBatchSize) is resolved immediately so playback begins and the
|
||||
* queue panel is populated without delay. The remaining tracks are then
|
||||
* resolved in the background. A generation counter ensures stale
|
||||
* background work is discarded if SetQueue is called again.
|
||||
*/
|
||||
export function SetQueue(filePaths: string[] | null, startIndex: number, shuffleStart: boolean, source: $models.Source): $CancellablePromise<void> {
|
||||
return $Call.ByID(1990450094, filePaths, startIndex, shuffleStart, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* ToggleShuffle toggles shuffle mode on/off.
|
||||
*/
|
||||
export function ToggleShuffle(): $CancellablePromise<void> {
|
||||
return $Call.ByID(612647562);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as TagWriter from "./tagwriter.js";
|
||||
export {
|
||||
TagWriter
|
||||
};
|
||||
|
||||
export type {
|
||||
BatchFailure,
|
||||
BatchResult,
|
||||
TagChanges
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,28 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* BatchFailure records a single track that failed during a batch write.
|
||||
*/
|
||||
export interface BatchFailure {
|
||||
"filePath": string;
|
||||
"error": string;
|
||||
}
|
||||
|
||||
/**
|
||||
* BatchResult summarises the outcome of a batch tag write.
|
||||
*/
|
||||
export interface BatchResult {
|
||||
"total": number;
|
||||
"succeeded": number;
|
||||
"failed": number;
|
||||
"cancelled": boolean;
|
||||
"failures": BatchFailure[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TagChanges is a diff map of field name → new value. Only changed
|
||||
* fields are present. Callers specify changed fields; unchanged
|
||||
* fields are left as-is in the file.
|
||||
*/
|
||||
export type TagChanges = { [_ in string]?: any } | null;
|
||||
@@ -0,0 +1,80 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* TagWriter orchestrates the complete tag writing pipeline:
|
||||
* file write → DB sync → event emission.
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* BatchWriteTrackTags applies the same TagChanges to every file in
|
||||
* filePaths. It processes tracks sequentially, emits a
|
||||
* BatchWriteProgress event after each track, and continues past
|
||||
* individual failures. Returns a BatchResult summarising outcomes.
|
||||
*/
|
||||
export function BatchWriteTrackTags(filePaths: string[] | null, changes: $models.TagChanges): $CancellablePromise<$models.BatchResult> {
|
||||
return $Call.ByID(2772510056, filePaths, changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelBatchWrite signals the in-progress batch write to stop after
|
||||
* the current track completes.
|
||||
*/
|
||||
export function CancelBatchWrite(): $CancellablePromise<void> {
|
||||
return $Call.ByID(3075727604);
|
||||
}
|
||||
|
||||
/**
|
||||
* WriteTrackTags is the single entry point for writing metadata to
|
||||
* a track's audio file and synchronising all changes to the
|
||||
* database. It accepts a track ID (audio_file.id) and a diff map
|
||||
* of changed fields.
|
||||
*
|
||||
* The pipeline:
|
||||
* 1. Look up track from DB.
|
||||
* 2. Detect audio format.
|
||||
* 3. Acquire pipeline lock (mutual exclusion with scan).
|
||||
* 4. Stop player if this file is currently playing.
|
||||
* 5. Write tags to file (format-specific writer).
|
||||
* 6. Sync database (entity relink, FTS5, orphan cleanup).
|
||||
* 7. Emit TrackMetadataChanged event.
|
||||
*/
|
||||
export function WriteTrackTags(trackID: number, changes: $models.TagChanges): $CancellablePromise<void> {
|
||||
return $Call.ByID(1881385878, trackID, changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* WriteTrackTagsByPath resolves a file path to its audio_file.id and
|
||||
* delegates to WriteTrackTags. This is the frontend-facing entry
|
||||
* point since the frontend identifies tracks by FilePath.
|
||||
*/
|
||||
export function WriteTrackTagsByPath(filePath: string, changes: $models.TagChanges): $CancellablePromise<void> {
|
||||
return $Call.ByID(3526432184, filePath, changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* WriteUntrackedFileTags writes tags to a file that is not in the
|
||||
* library, skipping every step of the full pipeline that assumes it is:
|
||||
* no audio_file lookup, no database sync, no event.
|
||||
*
|
||||
* This exists for the download import path, which tags files while they
|
||||
* are still in the staging directory. Tagging before the move is what
|
||||
* makes the import atomic from the library's point of view — the
|
||||
* scanner only ever sees a finished, correctly tagged file, instead of
|
||||
* ingesting a mislabelled one and being corrected afterwards.
|
||||
*
|
||||
* Callers are responsible for ensuring the file is not in a library
|
||||
* path; using this on a tracked file would leave the database stale.
|
||||
*/
|
||||
export function WriteUntrackedFileTags(filePath: string, changes: $models.TagChanges): $CancellablePromise<void> {
|
||||
return $Call.ByID(3625602876, filePath, changes);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export {
|
||||
ColumnID
|
||||
} from "./models.js";
|
||||
|
||||
export type {
|
||||
Column
|
||||
} from "./models.js";
|
||||
@@ -0,0 +1,41 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* Column represents a visible column in the track list.
|
||||
*/
|
||||
export interface Column {
|
||||
"id": ColumnID;
|
||||
}
|
||||
|
||||
/**
|
||||
* ColumnID identifies a displayable column in the track list.
|
||||
*/
|
||||
export enum ColumnID {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Valid column identifiers.
|
||||
*/
|
||||
ColTrackName = "trackName",
|
||||
ColArtistName = "artistName",
|
||||
ColTrackLength = "trackLength",
|
||||
ColAlbum = "album",
|
||||
ColGenre = "genre",
|
||||
ColYear = "year",
|
||||
ColComposer = "composer",
|
||||
ColTrackNumber = "trackNumber",
|
||||
ColDiscNumber = "discNumber",
|
||||
ColFilePath = "filePath",
|
||||
ColFileType = "fileType",
|
||||
ColSampleRate = "sampleRate",
|
||||
ColBitDepth = "bitDepth",
|
||||
ColChannels = "channels",
|
||||
ColBitrate = "bitrate",
|
||||
ColFileSize = "fileSize",
|
||||
ColPlayCount = "playCount",
|
||||
ColAlbumArt = "albumArt",
|
||||
};
|
||||
+3
-3
@@ -39,9 +39,9 @@ import { setBasePath } from '@awesome.me/webawesome/dist/webawesome.js';
|
||||
import { registerBundledIcons } from './src/icons';
|
||||
import { queueStore } from '@store/queue-store';
|
||||
import { searchStore } from '@store/search-store';
|
||||
import * as Player from '@go/player/Player';
|
||||
import * as Queue from '@go/queue/Queue';
|
||||
import { GetDefaultPage } from '@go/config/Config';
|
||||
import * as Player from '@go/player/player.js';
|
||||
import * as Queue from '@go/queue/queue.js';
|
||||
import { GetDefaultPage } from '@go/config/config.js';
|
||||
// Importing the theme store triggers initialization: it fetches the saved
|
||||
// theme from the backend and applies CSS custom properties to :root.
|
||||
import '@store/theme-store';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"@awesome.me/webawesome": "^3.2.1",
|
||||
"@lit-labs/signals": "^0.2.0",
|
||||
"@lit-labs/virtualizer": "^2.1.1",
|
||||
"@wailsio/runtime": "3.0.0-beta.8",
|
||||
"lit": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+8
@@ -17,6 +17,9 @@ importers:
|
||||
'@lit-labs/virtualizer':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
'@wailsio/runtime':
|
||||
specifier: 3.0.0-beta.8
|
||||
version: 3.0.0-beta.8
|
||||
lit:
|
||||
specifier: ^3.2.1
|
||||
version: 3.3.2
|
||||
@@ -543,6 +546,9 @@ packages:
|
||||
'@vscode/web-custom-data@0.4.13':
|
||||
resolution: {integrity: sha512-2ZUIRfhofZ/npLlf872EBnPmn27Kt4M2UssmQIfnJvgGgMYZJ5fvtHEDnttBBf2hnVtBgNCqZMVHJA+wsFVqTA==}
|
||||
|
||||
'@wailsio/runtime@3.0.0-beta.8':
|
||||
resolution: {integrity: sha512-c9PZJcOR9z1a6cxtBS2q5cygNajlxDE8Oxv/vvcTFYRbZSoOGBszq4uzlkPTOd0Bot1xkM81jnpaBgKWRpBh2g==}
|
||||
|
||||
ajv@8.18.0:
|
||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||
|
||||
@@ -1734,6 +1740,8 @@ snapshots:
|
||||
|
||||
'@vscode/web-custom-data@0.4.13': {}
|
||||
|
||||
'@wailsio/runtime@3.0.0-beta.8': {}
|
||||
|
||||
ajv@8.18.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
|
||||
@@ -4,13 +4,13 @@ import {
|
||||
property,
|
||||
state,
|
||||
} from 'lit/decorators.js';
|
||||
import { library } from '@go/models';
|
||||
import * as library from '@go/library/models.js';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import {
|
||||
GetArtistImageURL,
|
||||
GetArtistImageCachedPath,
|
||||
GetArtistMBID,
|
||||
} from '@go/explore/Service';
|
||||
} from '@go/explore/service.js';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '@components/cover-grid/cover-grid.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
GetAlbumsByArtist,
|
||||
GetAlbumsByArtistByLibrary,
|
||||
GetFilePathsByAlbums,
|
||||
} from '@go/library/Library';
|
||||
import { library } from '@go/models';
|
||||
} from '@go/library/library.js';
|
||||
import * as library from '@go/library/models.js';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import { SearchController } from '@store/controllers/search-controller';
|
||||
@@ -36,6 +36,7 @@ import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
import '@components/playlist-picker/playlist-picker.js';
|
||||
import { dict, list } from '@utils/binding';
|
||||
|
||||
/** Pixels to change card width per scroll tick. */
|
||||
const ZOOM_STEP = 16;
|
||||
@@ -1055,18 +1056,21 @@ export class ArtistsView
|
||||
const libId =
|
||||
this.libraryCtrl.selectedLibraryId;
|
||||
|
||||
const albums = libId !== null
|
||||
? await GetAlbumsByArtistByLibrary(
|
||||
artist.ID,
|
||||
libId,
|
||||
)
|
||||
: await GetAlbumsByArtist(artist.ID);
|
||||
const albums = await list(
|
||||
libId !== null
|
||||
? GetAlbumsByArtistByLibrary(
|
||||
artist.ID,
|
||||
libId,
|
||||
)
|
||||
: GetAlbumsByArtist(artist.ID),
|
||||
);
|
||||
|
||||
const byAlbum =
|
||||
await GetFilePathsByAlbums(
|
||||
const byAlbum = await dict(
|
||||
GetFilePathsByAlbums(
|
||||
albums.map((a) => a.ID),
|
||||
libId ?? 0,
|
||||
);
|
||||
),
|
||||
);
|
||||
|
||||
const allPaths: string[] = [];
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
ClearCompletedEntries,
|
||||
SearchCandidates,
|
||||
SelectSearchCandidate,
|
||||
} from '@go/autotagservice/Service';
|
||||
import type { autotagservice } from '@go/models';
|
||||
} from '@go/autotagservice/service.js';
|
||||
import type * as autotagservice from '@go/autotagservice/models.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { inlineDiff, normalizeStrict, isCosmeticDiff } from '../../utils/text-diff';
|
||||
@@ -29,6 +29,7 @@ import { nameDialogsIn } from '../../utils/name-dialog';
|
||||
import { ViewLifecycleMixin } from '../../utils/view-lifecycle';
|
||||
import { confirmAction } from '../confirm-dialog/confirm-dialog';
|
||||
import '@awesome.me/webawesome/dist/components/dialog/dialog.js';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
type PendingItem = autotagservice.PendingItem;
|
||||
type ScoreView = autotagservice.ScoreView;
|
||||
@@ -1483,8 +1484,10 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
this.searchError = '';
|
||||
this.searchRan = true;
|
||||
try {
|
||||
this.searchResults = await SearchCandidates(
|
||||
this.searchKind, query, this.searchArtist.trim(),
|
||||
this.searchResults = await list(
|
||||
SearchCandidates(
|
||||
this.searchKind, query, this.searchArtist.trim(),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('autotag: candidate search failed', err);
|
||||
@@ -1755,12 +1758,12 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
}
|
||||
|
||||
private topScore(): number {
|
||||
if (!this.score || this.score.candidates.length === 0) return 0;
|
||||
return this.score.candidates[0]?.score ?? 0;
|
||||
if (!this.score || (this.score.candidates ?? []).length === 0) return 0;
|
||||
return (this.score.candidates ?? [])[0]?.score ?? 0;
|
||||
}
|
||||
|
||||
private async onApply(): Promise<void> {
|
||||
if (!this.current || !this.score || this.score.candidates.length === 0) return;
|
||||
if (!this.current || !this.score || (this.score.candidates ?? []).length === 0) return;
|
||||
if (!this.hasLibraryWarningBeenAcked(this.current.libraryId)) {
|
||||
await this.confirmWarningThenApply();
|
||||
|
||||
@@ -1773,7 +1776,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
private async executeApply(): Promise<void> {
|
||||
if (!this.current || !this.score) return;
|
||||
|
||||
const cand = this.score.candidates[this.selectedCandidateIdx];
|
||||
const cand = (this.score.candidates ?? [])[this.selectedCandidateIdx];
|
||||
if (!cand) {
|
||||
this.errorMessage = 'No candidate selected.';
|
||||
return;
|
||||
@@ -1781,7 +1784,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
|
||||
const groupKey = this.current.groupKey;
|
||||
const mbid = cand.releaseMbid || cand.releaseGroupMbid;
|
||||
const total = this.score.localTracks.length;
|
||||
const total = (this.score.localTracks ?? []).length;
|
||||
|
||||
// Pre-mark the folder as running so the sidebar icon flips
|
||||
// to the progress ring immediately — the Started event will
|
||||
@@ -1955,16 +1958,16 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
*/
|
||||
private currentCandidate(): CandidateView | null {
|
||||
if (!this.score) return null;
|
||||
return this.score.candidates[this.selectedCandidateIdx] ?? null;
|
||||
return (this.score.candidates ?? [])[this.selectedCandidateIdx] ?? null;
|
||||
}
|
||||
|
||||
private async selectCandidateByIdx(idx: number): Promise<void> {
|
||||
if (!this.score || idx < 0 || idx >= this.score.candidates.length) return;
|
||||
if (!this.score || idx < 0 || idx >= (this.score.candidates ?? []).length) return;
|
||||
this.selectedCandidateIdx = idx;
|
||||
// Lazy-load cover art for non-top candidates. The backend
|
||||
// populates art for the top one eagerly; everything else
|
||||
// arrives empty until the user picks it.
|
||||
const cand = this.score.candidates[idx]!;
|
||||
const cand = (this.score.candidates ?? [])[idx]!;
|
||||
if (!cand.coverArtUrl) {
|
||||
try {
|
||||
const url = await GetCandidateCoverArt(
|
||||
@@ -2252,7 +2255,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
if (!cluster || cluster.candidates.length <= 1) return nothing;
|
||||
|
||||
const editions = cluster.candidates
|
||||
.map((c) => ({ cand: c, idx: this.score?.candidates.indexOf(c) ?? -1 }))
|
||||
.map((c) => ({ cand: c, idx: (this.score?.candidates ?? []).indexOf(c) ?? -1 }))
|
||||
.filter((e) => e.idx >= 0);
|
||||
|
||||
return html`
|
||||
@@ -2316,11 +2319,11 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
// the score forgives, the UI mutes.
|
||||
const SUBTLE_LENGTH_MAX_MS = 5000;
|
||||
|
||||
for (const a of cand.alignments) {
|
||||
for (const a of (cand.alignments ?? [])) {
|
||||
if (a.status === 'matched' || a.status === 'mismatched') {
|
||||
paired++;
|
||||
const local = a.localIndex >= 0
|
||||
? this.score?.localTracks[a.localIndex] ?? null
|
||||
? (this.score?.localTracks ?? [])[a.localIndex] ?? null
|
||||
: null;
|
||||
if (local) {
|
||||
if (local.title !== a.candidateTitle) {
|
||||
@@ -2374,7 +2377,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
missingTitles.push(a.candidateTitle || '(untitled)');
|
||||
} else if (a.status === 'unmatched') {
|
||||
const local = a.localIndex >= 0
|
||||
? this.score?.localTracks[a.localIndex] ?? null
|
||||
? (this.score?.localTracks ?? [])[a.localIndex] ?? null
|
||||
: null;
|
||||
extraTitles.push(a.localTitle || local?.title || '(untitled)');
|
||||
}
|
||||
@@ -2659,7 +2662,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
}
|
||||
|
||||
private renderLowConfidenceBanner(clusters: VersionCluster[]) {
|
||||
const top = this.score?.candidates[0];
|
||||
const top = (this.score?.candidates ?? [])[0];
|
||||
if (!top) return nothing;
|
||||
if (top.score >= LOW_CONFIDENCE_THRESHOLD) return nothing;
|
||||
if (clusters.length < 2) return nothing;
|
||||
@@ -2695,18 +2698,18 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
* renderMatchDetails, so each column shows its own values plainly.
|
||||
*/
|
||||
private renderComparison(cand: CandidateView, clusters: VersionCluster[] = []) {
|
||||
const locals = this.score?.localTracks ?? [];
|
||||
const locals = (this.score?.localTracks ?? []) ?? [];
|
||||
|
||||
// localIndex -> its alignment, so a folder row knows whether it
|
||||
// paired and (if so) how confidently.
|
||||
const alignByLocal = new Map<number, AlignmentView>();
|
||||
for (const a of cand.alignments) {
|
||||
for (const a of (cand.alignments ?? [])) {
|
||||
if (a.localIndex >= 0) alignByLocal.set(a.localIndex, a);
|
||||
}
|
||||
|
||||
// Candidate side: every alignment that has a candidate track
|
||||
// (paired or missing-from-folder), in candidate order.
|
||||
const candRows = cand.alignments
|
||||
const candRows = (cand.alignments ?? [])
|
||||
.filter((a) => a.status !== 'unmatched' && a.candidatePosition > 0)
|
||||
.slice()
|
||||
.sort((x, y) =>
|
||||
@@ -2856,7 +2859,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
${clusters.map((cluster) => {
|
||||
const best = cluster.candidates[cluster.bestIdx]!;
|
||||
const active = cluster.candidates.includes(activeCand);
|
||||
const idx = this.score?.candidates.indexOf(best) ?? -1;
|
||||
const idx = (this.score?.candidates ?? []).indexOf(best) ?? -1;
|
||||
return html`
|
||||
<div class="cand-chip ${active ? 'selected' : ''}"
|
||||
title=${cluster.label}
|
||||
@@ -2949,7 +2952,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this.score || this.score.candidates.length === 0) {
|
||||
if (!this.score || (this.score.candidates ?? []).length === 0) {
|
||||
return html`
|
||||
<div class="main">
|
||||
<div class="empty">
|
||||
@@ -2965,7 +2968,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) {
|
||||
return html`<div class="main"><div class="empty">Candidate index out of range.</div></div>`;
|
||||
}
|
||||
|
||||
const clusters = this.clusterVersions(this.score.candidates);
|
||||
const clusters = this.clusterVersions((this.score.candidates ?? []));
|
||||
|
||||
return html`
|
||||
<div class="main">
|
||||
|
||||
@@ -2,14 +2,14 @@ import { LitElement, html, css, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import type { explore } from '@go/models';
|
||||
import type * as explore from '@go/explore/models.js';
|
||||
import {
|
||||
AddLibrary,
|
||||
RenameLibrary,
|
||||
RemoveLibrary,
|
||||
GetRemovalImpact,
|
||||
GetAllLibrariesWithTrackCounts,
|
||||
} from '@go/library/Library';
|
||||
} from '@go/library/library.js';
|
||||
import {
|
||||
GetScanConcurrency,
|
||||
SetScanConcurrency,
|
||||
@@ -17,17 +17,17 @@ import {
|
||||
SetDefaultPage,
|
||||
GetQueueFallback,
|
||||
SetQueueFallback,
|
||||
} from '@go/config/Config';
|
||||
import { GetIndexStatus } from '@go/explore/Service';
|
||||
import { DirectoryPicker } from '@go/frontendutil/FrontendUtil';
|
||||
} from '@go/config/config.js';
|
||||
import { GetIndexStatus } from '@go/explore/service.js';
|
||||
import { DirectoryPicker } from '@go/frontendutil/frontendutil.js';
|
||||
import { notificationStore } from '@store/notification-store';
|
||||
import { describeError, explainError } from '@utils/describe-error';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { ThemeController } from '@store/controllers/theme-controller';
|
||||
import { TrackListController } from '@store/controllers/tracklist-controller';
|
||||
import { FavoritesController } from '@store/controllers/favorites-controller';
|
||||
import { GetAllPlaylists } from '@go/playlist/Service';
|
||||
import type { playlist } from '@go/models';
|
||||
import { GetAllPlaylists } from '@go/playlist/service.js';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import { Events } from '../../events';
|
||||
import {
|
||||
SHORTCUT_CATEGORIES,
|
||||
@@ -49,6 +49,7 @@ import './shortcut-capture';
|
||||
import { confirmAction } from '../confirm-dialog/confirm-dialog';
|
||||
import { shortcutsStore } from '../../store/shortcuts-store';
|
||||
import { ShortcutsController } from '../../store/controllers/shortcuts-controller';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
const SCROLL_STORAGE_KEY = 'yj-now-playing-scroll-mode';
|
||||
const SCROLL_CHANGE_EVENT = 'yj-scroll-mode-changed';
|
||||
@@ -1012,9 +1013,9 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
|
||||
const ok = await confirmAction({
|
||||
title: 'Remove library',
|
||||
message: `Remove “${libName}”?`,
|
||||
impact: `This deletes ${impact.trackCount} tracks, affects `
|
||||
+ `${impact.playlistsAffected} playlists and removes `
|
||||
+ `${impact.queueItemCount} queue items.`,
|
||||
impact: `This deletes ${impact?.trackCount ?? 0} tracks, affects `
|
||||
+ `${impact?.playlistsAffected ?? 0} playlists and removes `
|
||||
+ `${impact?.queueItemCount ?? 0} queue items.`,
|
||||
confirmLabel: 'Remove',
|
||||
danger: true,
|
||||
});
|
||||
@@ -1187,8 +1188,7 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
|
||||
|
||||
private async loadPlaylists(): Promise<void> {
|
||||
try {
|
||||
this.playlists =
|
||||
await GetAllPlaylists();
|
||||
this.playlists = await list(GetAllPlaylists());
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Failed to load playlists:',
|
||||
@@ -1456,10 +1456,10 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
|
||||
<span class="index-stat">updated ${this.timeAgo(s.lastBuilt)}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
${s.tiers?.length > 0 && s.tiers.some((t) => t.state === 'running' || t.state === 'pending' || t.state === 'error')
|
||||
${(s.tiers?.length ?? 0) > 0 && (s.tiers ?? []).some((t) => t.state === 'running' || t.state === 'pending' || t.state === 'error')
|
||||
? html`
|
||||
<div class="index-tiers">
|
||||
${s.tiers.map(
|
||||
${(s.tiers ?? []).map(
|
||||
(t) => html`
|
||||
<div class="index-tier">
|
||||
<span class="tier-icon">${this.tierIcon(t.state)}</span>
|
||||
|
||||
@@ -14,10 +14,12 @@ import type {
|
||||
ProviderField,
|
||||
} from '@store/download-store';
|
||||
import { downloadStore } from '@store/download-store';
|
||||
import { DirectoryPicker } from '@go/frontendutil/FrontendUtil';
|
||||
import { GetDownloadPreferences, SetDownloadPreferences } from '@go/config/Config';
|
||||
import { SetPreferences } from '@go/download/Service';
|
||||
import type { download } from '@go/models';
|
||||
import { DirectoryPicker } from '@go/frontendutil/frontendutil.js';
|
||||
import { GetDownloadPreferences, SetDownloadPreferences } from '@go/config/config.js';
|
||||
import { SetPreferences } from '@go/download/service.js';
|
||||
import type * as download from '@go/download/models.js';
|
||||
import { Format } from '@go/download/models.js';
|
||||
import { compact } from '@utils/binding';
|
||||
import { describeError, explainError } from '@utils/describe-error';
|
||||
import { confirmAction } from '@components/confirm-dialog/confirm-dialog';
|
||||
import './config-section';
|
||||
@@ -28,15 +30,15 @@ import './config-section';
|
||||
* deliberately excluded — it names "no format detected", not a format a
|
||||
* user could opt into.
|
||||
*/
|
||||
const AUTO_DOWNLOAD_FORMATS: { value: string; label: string }[] = [
|
||||
{ value: 'flac', label: 'FLAC' },
|
||||
{ value: 'alac', label: 'ALAC' },
|
||||
{ value: 'wav', label: 'WAV' },
|
||||
{ value: 'mp3', label: 'MP3' },
|
||||
{ value: 'aac', label: 'AAC' },
|
||||
{ value: 'ogg', label: 'OGG' },
|
||||
{ value: 'opus', label: 'Opus' },
|
||||
{ value: 'wma', label: 'WMA' },
|
||||
const AUTO_DOWNLOAD_FORMATS: { value: Format; label: string }[] = [
|
||||
{ value: Format.FormatFLAC, label: 'FLAC' },
|
||||
{ value: Format.FormatALAC, label: 'ALAC' },
|
||||
{ value: Format.FormatWAV, label: 'WAV' },
|
||||
{ value: Format.FormatMP3, label: 'MP3' },
|
||||
{ value: Format.FormatAAC, label: 'AAC' },
|
||||
{ value: Format.FormatOGG, label: 'OGG' },
|
||||
{ value: Format.FormatOpus, label: 'Opus' },
|
||||
{ value: Format.FormatWMA, label: 'WMA' },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -607,7 +609,7 @@ export class DownloadClients extends LitElement {
|
||||
// Secrets are never sent back to the frontend, so their fields
|
||||
// start blank; a blank secret on save means "leave it alone"
|
||||
// rather than "clear it".
|
||||
this.draft = { ...(provider.settings ?? {}) };
|
||||
this.draft = compact(provider.settings);
|
||||
}
|
||||
|
||||
private cancelEdit = () => {
|
||||
@@ -748,7 +750,7 @@ export class DownloadClients extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private toggleFormat(format: string, checked: boolean): void {
|
||||
private toggleFormat(format: Format, checked: boolean): void {
|
||||
const current = this.prefs.allowedFormats ?? [];
|
||||
const allowedFormats = checked
|
||||
? [...current, format]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LitElement, html, svg, css } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { PlayerController } from '@store/controllers/player-controller';
|
||||
import { FavoritesController } from '@store/controllers/favorites-controller';
|
||||
import { formatMilliseconds } from '@utils/time';
|
||||
|
||||
@@ -2,9 +2,10 @@ import {
|
||||
GetAlbumTracks,
|
||||
GetAlbumTracksByLibrary,
|
||||
GetFilePathsByAlbums,
|
||||
} from '@go/library/Library';
|
||||
} from '@go/library/library.js';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import type { library } from '@go/models';
|
||||
import { dict, list } from '@utils/binding';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import type { CoverArtUrls } from '@components/track-details/track-details.js';
|
||||
|
||||
/**
|
||||
@@ -58,9 +59,11 @@ export class AlbumSelectionManager {
|
||||
const libId =
|
||||
libraryStore.getSelectedLibraryId();
|
||||
|
||||
return libId !== null
|
||||
? GetAlbumTracksByLibrary(albumId, libId)
|
||||
: GetAlbumTracks(albumId);
|
||||
return list(
|
||||
libId !== null
|
||||
? GetAlbumTracksByLibrary(albumId, libId)
|
||||
: GetAlbumTracks(albumId),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +88,7 @@ export class AlbumSelectionManager {
|
||||
const libId =
|
||||
libraryStore.getSelectedLibraryId();
|
||||
|
||||
return GetFilePathsByAlbums(ids, libId ?? 0);
|
||||
return dict(GetFilePathsByAlbums(ids, libId ?? 0));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
|
||||
/**
|
||||
* Discriminated context menu target so we know whether the
|
||||
|
||||
@@ -14,8 +14,8 @@ import { grid } from '@lit-labs/virtualizer/layouts/grid.js';
|
||||
import {
|
||||
GetAlbumTracks,
|
||||
GetAlbumTracksByLibrary,
|
||||
} from '@go/library/Library';
|
||||
import { library } from '@go/models';
|
||||
} from '@go/library/library.js';
|
||||
import * as library from '@go/library/models.js';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import { SearchController } from '@store/controllers/search-controller';
|
||||
import { ViewLifecycleMixin } from '@utils/view-lifecycle';
|
||||
@@ -74,6 +74,7 @@ import type {
|
||||
GridEntry,
|
||||
SortDirection,
|
||||
} from './cover-grid-types.js';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
@customElement('cover-grid')
|
||||
export class CoverGrid
|
||||
@@ -933,12 +934,14 @@ export class CoverGrid
|
||||
const libId =
|
||||
this.libraryCtrl.selectedLibraryId;
|
||||
|
||||
const tracks = libId !== null
|
||||
? await GetAlbumTracksByLibrary(
|
||||
album.ID,
|
||||
libId,
|
||||
)
|
||||
: await GetAlbumTracks(album.ID);
|
||||
const tracks = await list(
|
||||
libId !== null
|
||||
? GetAlbumTracksByLibrary(
|
||||
album.ID,
|
||||
libId,
|
||||
)
|
||||
: GetAlbumTracks(album.ID),
|
||||
);
|
||||
|
||||
if (this.expandedAlbumId === album.ID) {
|
||||
this.expandedTracks = tracks;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LitElement } from 'lit';
|
||||
import type { LitVirtualizer } from '@lit-labs/virtualizer';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import type { LibraryController } from '@store/controllers/library-controller';
|
||||
|
||||
import type { GridEntry } from './cover-grid-types.js';
|
||||
|
||||
@@ -7,7 +7,7 @@ import '@awesome.me/webawesome/dist/components/callout/callout.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import type { DownloadCandidate } from '@store/download-store';
|
||||
import { downloadStore } from '@store/download-store';
|
||||
import type { download } from '@go/models';
|
||||
import type * as download from '@go/download/models.js';
|
||||
import './candidate-row';
|
||||
import { explainError } from '@utils/describe-error';
|
||||
import { nameDialogsIn } from '@utils/name-dialog';
|
||||
|
||||
@@ -686,7 +686,7 @@ export class DownloadsView extends ViewLifecycleMixin(LitElement) {
|
||||
/** Provider/progress summary for a download's second line. */
|
||||
private downloadDetail(view: DownloadRecord): string {
|
||||
const providers = [
|
||||
...new Set(view.items.map((item) => item.candidate?.origin).filter(Boolean)),
|
||||
...new Set((view.items ?? []).map((item) => item.candidate?.origin).filter(Boolean)),
|
||||
];
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
@@ -3,7 +3,7 @@ import { customElement, state, query } from 'lit/decorators.js';
|
||||
import '@awesome.me/webawesome/dist/components/dialog/dialog.js';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '@awesome.me/webawesome/dist/components/switch/switch.js';
|
||||
import { AddTracksToPlaylist } from '@go/playlist/Service';
|
||||
import { AddTracksToPlaylist } from '@go/playlist/service.js';
|
||||
import { formatMilliseconds } from '@utils/time';
|
||||
import { nameDialogsIn } from '@utils/name-dialog';
|
||||
|
||||
|
||||
@@ -6,15 +6,16 @@ import {
|
||||
LookupReleaseGroup,
|
||||
BrowseReleases,
|
||||
GetThumbnail,
|
||||
} from '@go/explore/Service';
|
||||
} from '@go/explore/service.js';
|
||||
import {
|
||||
GetAlbumTracks,
|
||||
GetAlbumCompleteness,
|
||||
GetFilePathsByAlbums,
|
||||
GetFilePathsByRecordingMBIDs,
|
||||
} from '@go/library/Library';
|
||||
import { library } from '@go/models';
|
||||
import type { download, explore } from '@go/models';
|
||||
} from '@go/library/library.js';
|
||||
import * as library from '@go/library/models.js';
|
||||
import type * as download from '@go/download/models.js';
|
||||
import type * as explore from '@go/explore/models.js';
|
||||
type MBReleaseGroup = explore.MBReleaseGroup;
|
||||
type MBRelease = explore.MBRelease;
|
||||
type MBTrack = explore.MBTrack;
|
||||
@@ -46,6 +47,7 @@ import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
import { dict, dictByName } from '@utils/binding';
|
||||
|
||||
/**
|
||||
* The region the album header's own failures are rendered in.
|
||||
@@ -2248,9 +2250,8 @@ export class ExploreAlbumDetails extends LitElement implements ContextMenuHost {
|
||||
// to an empty set and the Play button silently does nothing.
|
||||
// That is exactly what the first version of this did.
|
||||
if (this.localAlbumId > 0) {
|
||||
const byAlbum = await GetFilePathsByAlbums(
|
||||
[this.localAlbumId],
|
||||
libraryID,
|
||||
const byAlbum = await dict(
|
||||
GetFilePathsByAlbums([this.localAlbumId], libraryID),
|
||||
);
|
||||
|
||||
return byAlbum[this.localAlbumId] ?? [];
|
||||
@@ -2266,7 +2267,9 @@ export class ExploreAlbumDetails extends LitElement implements ContextMenuHost {
|
||||
|
||||
if (mbids.length === 0) return [];
|
||||
|
||||
const byMBID = await GetFilePathsByRecordingMBIDs(mbids, libraryID);
|
||||
const byMBID = await dictByName(
|
||||
GetFilePathsByRecordingMBIDs(mbids, libraryID),
|
||||
);
|
||||
|
||||
// Walked in tracklist order rather than flattened, because the
|
||||
// grouping is what lets the caller keep its own order. A
|
||||
@@ -2349,7 +2352,9 @@ export class ExploreAlbumDetails extends LitElement implements ContextMenuHost {
|
||||
if (!track.inLibrary || !track.mbid) return null;
|
||||
|
||||
const libraryID = libraryStore.getSelectedLibraryId() ?? 0;
|
||||
const byMBID = await GetFilePathsByRecordingMBIDs([track.mbid], libraryID);
|
||||
const byMBID = await dictByName(
|
||||
GetFilePathsByRecordingMBIDs([track.mbid], libraryID),
|
||||
);
|
||||
|
||||
return byMBID[track.mbid]?.[0] ?? null;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
GetTrackThumbnails,
|
||||
ResolveReleaseGroupMBIDs,
|
||||
PrefetchReleases,
|
||||
} from '@go/explore/Service';
|
||||
import type { explore } from '@go/models';
|
||||
} from '@go/explore/service.js';
|
||||
import type * as explore from '@go/explore/models.js';
|
||||
type MBArtist = explore.MBArtist;
|
||||
type MBReleaseGroup = explore.MBReleaseGroup;
|
||||
type LBTopRecording = explore.LBTopRecording;
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
GetAlbumsByArtist,
|
||||
GetFilePathsByAlbums,
|
||||
GetFilePathsByRecordingMBIDs,
|
||||
} from '@go/library/Library';
|
||||
} from '@go/library/library.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
@@ -55,6 +55,7 @@ import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
import { dict, dictByName } from '@utils/binding';
|
||||
|
||||
/* ── Constants ── */
|
||||
|
||||
@@ -1423,7 +1424,7 @@ export class ExploreArtistDetails extends LitElement implements ContextMenuHost
|
||||
let mapping: Record<string, string> = {};
|
||||
if (caaMbids.length > 0) {
|
||||
try {
|
||||
mapping = (await ResolveReleaseGroupMBIDs(caaMbids)) || {};
|
||||
mapping = await dictByName(ResolveReleaseGroupMBIDs(caaMbids));
|
||||
} catch (err) {
|
||||
console.warn('[explore-artist] failed to resolve release groups for tracks:', err);
|
||||
}
|
||||
@@ -1475,7 +1476,7 @@ export class ExploreArtistDetails extends LitElement implements ContextMenuHost
|
||||
// Phase 1: batched cached lookup.
|
||||
let cached: Record<string, string> = {};
|
||||
try {
|
||||
cached = (await GetTrackThumbnails(requests)) || {};
|
||||
cached = await dictByName(GetTrackThumbnails(requests));
|
||||
if (Object.keys(cached).length > 0) {
|
||||
const updated = new Map(this.trackThumbnails);
|
||||
for (const [key, url] of Object.entries(cached)) {
|
||||
@@ -1684,7 +1685,7 @@ export class ExploreArtistDetails extends LitElement implements ContextMenuHost
|
||||
artistName: i.artistName,
|
||||
}));
|
||||
|
||||
cached = (await GetThumbnails(requests)) || {};
|
||||
cached = await dictByName(GetThumbnails(requests));
|
||||
if (Object.keys(cached).length > 0) {
|
||||
const updated = new Map(this.thumbnailURLs);
|
||||
for (const [mbid, url] of Object.entries(cached)) {
|
||||
@@ -1927,7 +1928,7 @@ export class ExploreArtistDetails extends LitElement implements ContextMenuHost
|
||||
if (albumIds.length === 0) return [];
|
||||
|
||||
const libraryID = libraryStore.getSelectedLibraryId() ?? 0;
|
||||
const byAlbum = await GetFilePathsByAlbums(albumIds, libraryID);
|
||||
const byAlbum = await dict(GetFilePathsByAlbums(albumIds, libraryID));
|
||||
const paths: string[] = [];
|
||||
|
||||
for (const id of albumIds) paths.push(...(byAlbum[id] ?? []));
|
||||
@@ -1996,7 +1997,9 @@ export class ExploreArtistDetails extends LitElement implements ContextMenuHost
|
||||
if (!(track.inLibrary || track.localId) || !track.recordingMbid) return null;
|
||||
|
||||
const libraryID = libraryStore.getSelectedLibraryId() ?? 0;
|
||||
const byMBID = await GetFilePathsByRecordingMBIDs([track.recordingMbid], libraryID);
|
||||
const byMBID = await dictByName(
|
||||
GetFilePathsByRecordingMBIDs([track.recordingMbid], libraryID),
|
||||
);
|
||||
|
||||
return byMBID[track.recordingMbid]?.[0] ?? null;
|
||||
}
|
||||
@@ -2145,7 +2148,9 @@ export class ExploreArtistDetails extends LitElement implements ContextMenuHost
|
||||
if (release.localId <= 0) return [];
|
||||
|
||||
const libraryID = libraryStore.getSelectedLibraryId() ?? 0;
|
||||
const byAlbum = await GetFilePathsByAlbums([release.localId], libraryID);
|
||||
const byAlbum = await dict(
|
||||
GetFilePathsByAlbums([release.localId], libraryID),
|
||||
);
|
||||
|
||||
return byAlbum[release.localId] ?? [];
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { classMap } from 'lit/directives/class-map.js';
|
||||
import '@components/page-header/page-header';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import { srOnly } from '../../styles/sr-only.css';
|
||||
import { SearchLocal, SearchLyrics, GetThumbnail, GetThumbnails, GetArtistImageURL, GetArtistImagesCachedPaths, GetExploreShelves, RecordSearchClick } from '@go/explore/Service';
|
||||
import { GetFilePathsByAlbums, GetFilePathsByRecordingMBIDs } from '@go/library/Library';
|
||||
import { SearchLocal, SearchLyrics, GetThumbnail, GetThumbnails, GetArtistImageURL, GetArtistImagesCachedPaths, GetExploreShelves, RecordSearchClick } from '@go/explore/service.js';
|
||||
import { GetFilePathsByAlbums, GetFilePathsByRecordingMBIDs } from '@go/library/library.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { libraryStore } from '../../store/library-store';
|
||||
@@ -21,7 +21,7 @@ import { describeError } from '../../utils/describe-error';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '../library-status-indicator/library-status-indicator.js';
|
||||
import '../top-results-row/top-results-row.js';
|
||||
import { explore } from '@go/models';
|
||||
import * as explore from '@go/explore/models.js';
|
||||
import { ViewLifecycleMixin } from '../../utils/view-lifecycle';
|
||||
import { registerCacheProbe } from '../../utils/cache-stats';
|
||||
import { LRUMap } from '../../utils/lru-map';
|
||||
@@ -34,6 +34,7 @@ import type { ContextMenuHost } from '@utils/context-menu-controller.js';
|
||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
import { dict, dictByName } from '@utils/binding';
|
||||
|
||||
/** The region explore's own action failures (play/queue) are rendered in. */
|
||||
export const ExploreRegion = 'explore';
|
||||
@@ -833,10 +834,10 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) implements Conte
|
||||
// a state this view already renders honestly. A *rejected*
|
||||
// call still reaches the catch below.
|
||||
if (!page || !Array.isArray(page.shelves)) {
|
||||
this.shelves = explore.ShelfPage.createFrom({
|
||||
this.shelves = {
|
||||
shelves: [],
|
||||
state: 'no-index',
|
||||
});
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -853,10 +854,10 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) implements Conte
|
||||
// Inline, and quietly: the search box above still works, so
|
||||
// this is a panel that could not fill itself rather than
|
||||
// something the user asked for and did not get.
|
||||
this.shelves = explore.ShelfPage.createFrom({
|
||||
this.shelves = {
|
||||
shelves: [],
|
||||
state: 'no-index',
|
||||
});
|
||||
};
|
||||
} finally {
|
||||
this.shelvesPending = false;
|
||||
}
|
||||
@@ -1043,13 +1044,12 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) implements Conte
|
||||
// index has no hits, in which case we keep the owned-library
|
||||
// matches already displayed.
|
||||
const result =
|
||||
(await SearchLocal(query)) ??
|
||||
explore.MBSearchResult.createFrom({
|
||||
(await SearchLocal(query)) ?? {
|
||||
artists: [],
|
||||
releaseGroups: [],
|
||||
recordings: [],
|
||||
topResults: [],
|
||||
});
|
||||
};
|
||||
|
||||
// Discard stale response
|
||||
if (version !== this.searchVersion) {
|
||||
@@ -1131,14 +1131,16 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) implements Conte
|
||||
*/
|
||||
private async albumFilePaths(localId: number): Promise<string[]> {
|
||||
const libraryID = libraryStore.getSelectedLibraryId() ?? 0;
|
||||
const byAlbum = await GetFilePathsByAlbums([localId], libraryID);
|
||||
const byAlbum = await dict(GetFilePathsByAlbums([localId], libraryID));
|
||||
|
||||
return byAlbum[localId] ?? [];
|
||||
}
|
||||
|
||||
private async recordingFilePath(mbid: string): Promise<string | null> {
|
||||
const libraryID = libraryStore.getSelectedLibraryId() ?? 0;
|
||||
const byMBID = await GetFilePathsByRecordingMBIDs([mbid], libraryID);
|
||||
const byMBID = await dictByName(
|
||||
GetFilePathsByRecordingMBIDs([mbid], libraryID),
|
||||
);
|
||||
|
||||
return byMBID[mbid]?.[0] ?? null;
|
||||
}
|
||||
@@ -2018,7 +2020,7 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) implements Conte
|
||||
// of nothing.
|
||||
if (!page) return nothing;
|
||||
|
||||
if (page.shelves.length === 0) {
|
||||
if (!page.shelves?.length) {
|
||||
return html`
|
||||
<div class="shelves-empty">
|
||||
<wa-icon
|
||||
@@ -2048,7 +2050,7 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) implements Conte
|
||||
to come.
|
||||
</p>`
|
||||
: nothing}
|
||||
${page.shelves.map((shelf) =>
|
||||
${(page.shelves ?? []).map((shelf) =>
|
||||
shelf.artists?.length
|
||||
? this.renderArtistsSection(
|
||||
shelf.artists,
|
||||
|
||||
@@ -5,8 +5,8 @@ import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import {
|
||||
AddLibrary,
|
||||
GetAllLibrariesWithTrackCounts,
|
||||
} from '@go/library/Library';
|
||||
import { DirectoryPicker } from '@go/frontendutil/FrontendUtil';
|
||||
} from '@go/library/library.js';
|
||||
import { DirectoryPicker } from '@go/frontendutil/frontendutil.js';
|
||||
import { describeError, explainError } from '@utils/describe-error';
|
||||
import { nameDialogsIn } from '@utils/name-dialog';
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
property,
|
||||
state,
|
||||
} from 'lit/decorators.js';
|
||||
import { library } from '@go/models';
|
||||
import * as library from '@go/library/models.js';
|
||||
import {
|
||||
GetTracksByGenre,
|
||||
GetTracksByGenreByLibrary,
|
||||
} from '@go/library/Library';
|
||||
} from '@go/library/library.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
@@ -16,6 +16,7 @@ import { describeError } from '@utils/describe-error';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '@components/track-list/track-list.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
@customElement('genre-details')
|
||||
export class GenreDetails extends LitElement {
|
||||
@@ -207,14 +208,14 @@ export class GenreDetails extends LitElement {
|
||||
const libId =
|
||||
libraryStore.getSelectedLibraryId();
|
||||
|
||||
this.tracks = libId !== null
|
||||
? await GetTracksByGenreByLibrary(
|
||||
this.genreName,
|
||||
libId,
|
||||
)
|
||||
: await GetTracksByGenre(
|
||||
this.genreName,
|
||||
);
|
||||
this.tracks = await list(
|
||||
libId !== null
|
||||
? GetTracksByGenreByLibrary(
|
||||
this.genreName,
|
||||
libId,
|
||||
)
|
||||
: GetTracksByGenre(this.genreName),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error loading genre tracks:', error);
|
||||
this.tracks = [];
|
||||
|
||||
@@ -12,8 +12,8 @@ import type {
|
||||
import { grid } from '@lit-labs/virtualizer/layouts/grid.js';
|
||||
import {
|
||||
GetFilePathsByGenres,
|
||||
} from '@go/library/Library';
|
||||
import type { library } from '@go/models';
|
||||
} from '@go/library/library.js';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import { SearchController } from '@store/controllers/search-controller';
|
||||
import '@components/page-header/page-header';
|
||||
@@ -33,6 +33,7 @@ import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import type WaPopup from '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
import '@components/playlist-picker/playlist-picker.js';
|
||||
import { dictByName } from '@utils/binding';
|
||||
|
||||
/** Pixels to change card width per scroll tick. */
|
||||
const ZOOM_STEP = 16;
|
||||
@@ -831,9 +832,8 @@ export class GenresView
|
||||
// read off them — 6 MB over the IPC for five
|
||||
// genres of a 50 000-track library.
|
||||
const names = Array.from(genreNames);
|
||||
const byGenre = await GetFilePathsByGenres(
|
||||
names,
|
||||
libId ?? 0,
|
||||
const byGenre = await dictByName(
|
||||
GetFilePathsByGenres(names, libId ?? 0),
|
||||
);
|
||||
|
||||
// Still de-duplicated here: a track with two of
|
||||
|
||||
@@ -2,9 +2,10 @@ import { LitElement, html, css, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '@awesome.me/webawesome/dist/components/button/button.js';
|
||||
import { GetShelves } from '@go/home/Service';
|
||||
import { GetAlbumTracks } from '@go/library/Library';
|
||||
import type { home, library } from '@go/models';
|
||||
import { GetShelves } from '@go/home/service.js';
|
||||
import { GetAlbumTracks } from '@go/library/library.js';
|
||||
import type * as home from '@go/home/models.js';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { queueStore } from '@store/queue-store';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
@@ -311,7 +312,7 @@ export class HomeView extends ViewLifecycleMixin(LitElement) {
|
||||
</div>
|
||||
<p class="shelf-sub">${shelf.subtitle}</p>
|
||||
<div class="row">
|
||||
${shelf.albums.map((album) => this.renderCard(album))}
|
||||
${(shelf.albums ?? []).map((album) => this.renderCard(album))}
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
ScanLibrary,
|
||||
ScanAllLibraries,
|
||||
FullRescan,
|
||||
} from '@go/library/Library';
|
||||
import type { library } from '@go/models';
|
||||
} from '@go/library/library.js';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { jobStore } from '@store/job-store';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { LitElement, html, css } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
SearchLibrary,
|
||||
ResolvePhantomTracks,
|
||||
RemovePhantomTracks,
|
||||
} from '@go/playlist/Service';
|
||||
import type { playlist } from '@go/models';
|
||||
} from '@go/playlist/service.js';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import { formatMilliseconds } from '@utils/time';
|
||||
import { nameDialogsIn } from '@utils/name-dialog';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
const SEARCH_DEBOUNCE_MS = 400;
|
||||
|
||||
@@ -143,11 +144,12 @@ export class PhantomResolver extends LitElement {
|
||||
this.candidatesLoading = true;
|
||||
|
||||
try {
|
||||
this.candidates =
|
||||
await GetPhantomCandidates(
|
||||
this.candidates = await list(
|
||||
GetPhantomCandidates(
|
||||
this.playlistId,
|
||||
this.selectedPhantom,
|
||||
);
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Failed to load candidates:',
|
||||
@@ -171,8 +173,7 @@ export class PhantomResolver extends LitElement {
|
||||
this.searching = true;
|
||||
|
||||
try {
|
||||
this.searchResults =
|
||||
await SearchLibrary(query);
|
||||
this.searchResults = await list(SearchLibrary(query));
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Library search failed:',
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
RemoveTracksFromPlaylist,
|
||||
RemovePhantomTracks,
|
||||
FindDuplicateTracksInPlaylist,
|
||||
} from '@go/playlist/Service';
|
||||
import type { playlist } from '@go/models';
|
||||
} from '@go/playlist/service.js';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { queueStore } from '@store/queue-store';
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
exploreLinkStyles,
|
||||
} from '@utils/explore-link';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
/** One playlist row: the track and its position in the *playlist*,
|
||||
* which is not its position in the filtered view. */
|
||||
@@ -276,8 +277,8 @@ export class PlaylistDetails
|
||||
if (!this.playlistId) return;
|
||||
|
||||
try {
|
||||
this.tracks = await GetPlaylistTracks(
|
||||
this.playlistId,
|
||||
this.tracks = await list(
|
||||
GetPlaylistTracks(this.playlistId),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -294,8 +295,8 @@ export class PlaylistDetails
|
||||
if (!this.playlistId) return;
|
||||
|
||||
try {
|
||||
this.tracks = await GetPlaylistTracks(
|
||||
this.playlistId,
|
||||
this.tracks = await list(
|
||||
GetPlaylistTracks(this.playlistId),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
AddTracksToPlaylist,
|
||||
CreatePlaylistWithTracks,
|
||||
FindDuplicateTracksInPlaylist,
|
||||
} from '@go/playlist/Service';
|
||||
} from '@go/playlist/service.js';
|
||||
import { Events } from '../../events';
|
||||
import type { playlist } from '@go/models';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import '@components/duplicate-tracks-dialog/duplicate-tracks-dialog.js';
|
||||
import { notificationStore } from '@store/notification-store';
|
||||
import { describeError } from '@utils/describe-error';
|
||||
import type { DuplicateTracksDialog } from '@components/duplicate-tracks-dialog/duplicate-tracks-dialog.js';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
/**
|
||||
* A reusable playlist picker that displays existing playlists
|
||||
@@ -156,7 +157,7 @@ export class PlaylistPicker extends LitElement {
|
||||
|
||||
private async loadPlaylists() {
|
||||
try {
|
||||
this.playlists = await GetAllPlaylists();
|
||||
this.playlists = await list(GetAllPlaylists());
|
||||
} catch (err) {
|
||||
console.error('Failed to load playlists:', err);
|
||||
this.playlists = [];
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
RenamePlaylist,
|
||||
ImportPlaylists,
|
||||
FindDuplicateTracksInPlaylist,
|
||||
} from '@go/playlist/Service';
|
||||
import { PlaylistFilePicker } from '@go/frontendutil/FrontendUtil';
|
||||
import type { playlist } from '@go/models';
|
||||
} from '@go/playlist/service.js';
|
||||
import { PlaylistFilePicker } from '@go/frontendutil/frontendutil.js';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import { PlaylistController } from '@store/controllers/playlist-controller';
|
||||
import { SearchController } from '@store/controllers/search-controller';
|
||||
import {
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
removeDragImage,
|
||||
} from '@utils/drag-image';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { loadTrackDetails } from '@utils/lazy-track-details.js';
|
||||
import { tracksByFilePath } from '@utils/track-index.js';
|
||||
import type { TrackDetails } from '@components/track-details/track-details.js';
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
state,
|
||||
query,
|
||||
} from 'lit/decorators.js';
|
||||
import type { playlist } from '@go/models';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import {
|
||||
GetSmartPlaylistTracks,
|
||||
RefreshSmartPlaylist,
|
||||
GetSmartPlaylistRules,
|
||||
UpdateSmartPlaylistRules,
|
||||
} from '@go/playlist/Service';
|
||||
} from '@go/playlist/service.js';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
import { queueStore } from '@store/queue-store';
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
} from '@utils/explore-link';
|
||||
import '@components/smart-playlist-editor/smart-playlist-editor.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
|
||||
/**
|
||||
@@ -700,8 +701,8 @@ export class SmartPlaylistDetails
|
||||
if (!this.playlistId) return;
|
||||
|
||||
try {
|
||||
this.tracks = await GetSmartPlaylistTracks(
|
||||
this.playlistId,
|
||||
this.tracks = await list(
|
||||
GetSmartPlaylistTracks(this.playlistId),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LitElement, html, css, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { library } from '@go/models';
|
||||
import { PreviewSmartPlaylist } from '@go/playlist/Service';
|
||||
import * as library from '@go/library/models.js';
|
||||
import { PreviewSmartPlaylist } from '@go/playlist/service.js';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import { describeError } from '@utils/describe-error';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { LitElement, html, css, nothing } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import type { explore } from '@go/models';
|
||||
import type * as explore from '@go/explore/models.js';
|
||||
import {
|
||||
GetArtistImageURL,
|
||||
GetThumbnail,
|
||||
RecordSearchClick,
|
||||
} from '@go/explore/Service';
|
||||
} from '@go/explore/service.js';
|
||||
import '../library-status-indicator/library-status-indicator.js';
|
||||
import type { LibraryStatus } from '../library-status-indicator/library-status-indicator.js';
|
||||
import { artistLink, exploreLinkStyles } from '../../utils/explore-link';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
state,
|
||||
query,
|
||||
} from 'lit/decorators.js';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import {
|
||||
formatSampleRate,
|
||||
formatBitDepth,
|
||||
@@ -14,14 +14,14 @@ import {
|
||||
} from '@utils/format';
|
||||
import { formatMilliseconds } from '@utils/time';
|
||||
import { nameDialogsIn } from '@utils/name-dialog';
|
||||
import { WriteTrackTagsByPath } from '@go/tagwriter/TagWriter';
|
||||
import { WriteTrackTagsByPath } from '@go/tagwriter/tagwriter.js';
|
||||
import {
|
||||
BatchWriteTrackTags,
|
||||
CancelBatchWrite,
|
||||
} from '@go/tagwriter/TagWriter';
|
||||
import { GetTrackMBIDs } from '@go/library/Library';
|
||||
} from '@go/tagwriter/tagwriter.js';
|
||||
import { GetTrackMBIDs } from '@go/library/library.js';
|
||||
type TrackMBIDs = library.TrackMBIDs;
|
||||
import { ImageFilePicker, ReadFile } from '@go/frontendutil/FrontendUtil';
|
||||
import { ImageFilePicker, ReadFile } from '@go/frontendutil/frontendutil.js';
|
||||
import { libraryStore } from '../../store/library-store';
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../../events';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import {
|
||||
formatSampleRate,
|
||||
formatBitDepth,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { html } from 'lit';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { library } from '@go/models';
|
||||
import * as library from '@go/library/models.js';
|
||||
import { LitElement, html, svg, css, nothing } from 'lit';
|
||||
import { designTokens } from '../../styles/tokens.css';
|
||||
import { srOnly } from '../../styles/sr-only.css';
|
||||
@@ -65,7 +65,7 @@ import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import { describeError } from '@utils/describe-error';
|
||||
import { notificationStore } from '@store/notification-store';
|
||||
import { confirmAction } from '@components/confirm-dialog/confirm-dialog';
|
||||
import { RemoveFromLibrary } from '@go/library/Library';
|
||||
import { RemoveFromLibrary } from '@go/library/library.js';
|
||||
import { loadTrackDetails } from '@utils/lazy-track-details.js';
|
||||
import { tracksByFilePath, tracksForPaths } from '@utils/track-index.js';
|
||||
import '@components/playlist-picker/playlist-picker.js';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { shortcutsStore } from '@store/shortcuts-store';
|
||||
import { ambientShortcutScope } from './shortcut-scope';
|
||||
import { playerStore } from '@store/player-store';
|
||||
import { queueStore } from '@store/queue-store';
|
||||
import * as Player from '@go/player/Player';
|
||||
import * as Player from '@go/player/player.js';
|
||||
import type { SearchBar } from '@components/search-bar/search-bar';
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { libraryStore } from '../library-store';
|
||||
|
||||
type ViewName = 'tracks' | 'albums' | 'artists' | 'genres';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||
import type { playlist } from '@go/models';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import { playlistStore } from '../playlist-store';
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
StartDownload,
|
||||
TestProvider,
|
||||
UpdateProvider,
|
||||
} from '@go/download/Service';
|
||||
import type { download } from '@go/models';
|
||||
} from '@go/download/service.js';
|
||||
import type * as download from '@go/download/models.js';
|
||||
import { Events } from '../events';
|
||||
|
||||
export type DownloadCandidate = download.Candidate;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ToggleDefaultPlaylistTrack,
|
||||
AddToDefaultPlaylist,
|
||||
RemoveFromDefaultPlaylist,
|
||||
} from '@go/playlist/Service';
|
||||
} from '@go/playlist/service.js';
|
||||
import {
|
||||
GetFavoritesIconStyle,
|
||||
GetFavoritesPlaylistID,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
SetFavoritesIconStyle,
|
||||
SetFavoritesPlaylistID,
|
||||
SetPinDefaultPlaylist,
|
||||
} from '@go/config/Config';
|
||||
} from '@go/config/config.js';
|
||||
import { Events } from '../events';
|
||||
import { describeError } from '@utils/describe-error';
|
||||
import { notificationStore } from './notification-store';
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
CancelJob,
|
||||
DismissJob,
|
||||
ClearFinishedJobs,
|
||||
} from '@go/jobs/Service';
|
||||
import type { jobs } from '@go/models';
|
||||
} from '@go/jobs/service.js';
|
||||
import type * as jobs from '@go/jobs/models.js';
|
||||
import { Events } from '../events';
|
||||
|
||||
export type Job = jobs.Job;
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
GetAllGenresWithCountsByLibrary,
|
||||
GetAlbumsByArtistByLibrary,
|
||||
GetAllLibrariesWithTrackCounts,
|
||||
} from '@go/library/Library';
|
||||
import type { library } from '@go/models';
|
||||
} from '@go/library/library.js';
|
||||
import type * as library from '@go/library/models.js';
|
||||
import { list } from '@utils/binding';
|
||||
import { Events } from '../events';
|
||||
|
||||
type ViewName = 'tracks' | 'albums' | 'artists' | 'genres';
|
||||
@@ -231,7 +232,7 @@ class LibraryStore {
|
||||
|
||||
return this.track(
|
||||
'tracks',
|
||||
id !== null ? GetAllTracksByLibrary(id) : GetAllTracks(),
|
||||
list(id !== null ? GetAllTracksByLibrary(id) : GetAllTracks()),
|
||||
(tracks) => {
|
||||
this.tracks = tracks;
|
||||
},
|
||||
@@ -252,7 +253,7 @@ class LibraryStore {
|
||||
|
||||
return this.track(
|
||||
'albums',
|
||||
id !== null ? GetAllAlbumsByLibrary(id) : GetAllAlbums(),
|
||||
list(id !== null ? GetAllAlbumsByLibrary(id) : GetAllAlbums()),
|
||||
(albums) => {
|
||||
this.albums = albums;
|
||||
},
|
||||
@@ -273,7 +274,7 @@ class LibraryStore {
|
||||
|
||||
return this.track(
|
||||
'artists',
|
||||
id !== null ? GetAllArtistsByLibrary(id) : GetAllArtists(),
|
||||
list(id !== null ? GetAllArtistsByLibrary(id) : GetAllArtists()),
|
||||
(artists) => {
|
||||
this.artists = artists;
|
||||
},
|
||||
@@ -294,9 +295,11 @@ class LibraryStore {
|
||||
|
||||
return this.track(
|
||||
'genres',
|
||||
id !== null
|
||||
? GetAllGenresWithCountsByLibrary(id)
|
||||
: GetAllGenresWithCounts(),
|
||||
list(
|
||||
id !== null
|
||||
? GetAllGenresWithCountsByLibrary(id)
|
||||
: GetAllGenresWithCounts(),
|
||||
),
|
||||
(genres) => {
|
||||
this.genres = genres;
|
||||
},
|
||||
@@ -309,9 +312,11 @@ class LibraryStore {
|
||||
): Promise<library.Album[]> {
|
||||
const id = this.selectedLibraryIdValue;
|
||||
|
||||
return id !== null
|
||||
? GetAlbumsByArtistByLibrary(artistID, id)
|
||||
: GetAlbumsByArtist(artistID);
|
||||
return list(
|
||||
id !== null
|
||||
? GetAlbumsByArtistByLibrary(artistID, id)
|
||||
: GetAlbumsByArtist(artistID),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,8 +415,7 @@ class LibraryStore {
|
||||
return this.libraries;
|
||||
}
|
||||
|
||||
const libs =
|
||||
await GetAllLibrariesWithTrackCounts();
|
||||
const libs = await list(GetAllLibrariesWithTrackCounts());
|
||||
|
||||
this.libraries = libs;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../events';
|
||||
import * as Player from '@go/player/Player';
|
||||
import * as Player from '@go/player/player.js';
|
||||
import { notificationStore } from './notification-store';
|
||||
|
||||
/** The region `<inline-notice>` renders these in: the player bar. */
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
GetAllPlaylists,
|
||||
GetAllPlaylistsWithTracks,
|
||||
GetPlaylistTracks,
|
||||
} from '@go/playlist/Service';
|
||||
import type { playlist } from '@go/models';
|
||||
} from '@go/playlist/service.js';
|
||||
import type * as playlist from '@go/playlist/models.js';
|
||||
import { Events } from '../events';
|
||||
|
||||
type Subscriber = () => void;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EventsOn } from '@runtime/runtime';
|
||||
import { Events } from '../events';
|
||||
import * as Queue from '@go/queue/Queue';
|
||||
import * as Queue from '@go/queue/queue.js';
|
||||
|
||||
// Types
|
||||
export interface QueueTrack {
|
||||
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
SetShortcut,
|
||||
SetShortcuts,
|
||||
ResetShortcuts,
|
||||
} from '@go/config/Config';
|
||||
} from '@go/config/config.js';
|
||||
import { Events } from '../events';
|
||||
import { dictByName } from '@utils/binding';
|
||||
|
||||
export interface ShortcutsState {
|
||||
bindings: Map<string, string>; // action → key combo
|
||||
@@ -56,7 +57,7 @@ class ShortcutsStore {
|
||||
|
||||
private async loadFromBackend(): Promise<void> {
|
||||
try {
|
||||
const raw = await GetShortcuts();
|
||||
const raw = await dictByName(GetShortcuts());
|
||||
this.state = {
|
||||
bindings: new Map(Object.entries(raw)),
|
||||
loaded: true,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
GetThemeBackgroundShade,
|
||||
SetThemeAccentColor,
|
||||
SetThemeBackgroundShade,
|
||||
} from '@go/config/Config';
|
||||
} from '@go/config/config.js';
|
||||
import { Events } from '../events';
|
||||
|
||||
export type BackgroundShade = 'darker' | 'dark' | 'light';
|
||||
|
||||
@@ -2,10 +2,11 @@ import { EventsOn } from '@runtime/runtime';
|
||||
import {
|
||||
GetTrackListColumns,
|
||||
SetTrackListColumns,
|
||||
} from '@go/config/Config';
|
||||
import { tracklist } from '@go/models';
|
||||
} from '@go/config/config.js';
|
||||
import * as tracklist from '@go/tracklist/models.js';
|
||||
import { Events } from '../events';
|
||||
import { DEFAULT_COLUMN_IDS } from '@components/track-list/columns';
|
||||
import { list } from '@utils/binding';
|
||||
|
||||
export interface TrackListState {
|
||||
/** Ordered list of visible column IDs. */
|
||||
@@ -47,12 +48,10 @@ class TrackListStore {
|
||||
|
||||
private async loadFromBackend(): Promise<void> {
|
||||
try {
|
||||
const columns = await GetTrackListColumns();
|
||||
const columns = await list(GetTrackListColumns());
|
||||
|
||||
this.update({
|
||||
columnIds: columns.map(
|
||||
(c: tracklist.Column) => c.id,
|
||||
),
|
||||
columnIds: columns.map((c) => c.id),
|
||||
});
|
||||
} catch {
|
||||
// Use defaults on failure.
|
||||
@@ -72,12 +71,9 @@ class TrackListStore {
|
||||
// ===============================================================
|
||||
|
||||
async setColumns(columnIds: string[]): Promise<void> {
|
||||
const columns = columnIds.map((id) => {
|
||||
const col = new tracklist.Column();
|
||||
col.id = id;
|
||||
|
||||
return col;
|
||||
});
|
||||
const columns: tracklist.Column[] = columnIds.map((id) => ({
|
||||
id: id as tracklist.ColumnID,
|
||||
}));
|
||||
|
||||
await SetTrackListColumns(columns);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* The boundary between a Go return value and the app's own types.
|
||||
*
|
||||
* v2's binding generator typed a `[]T` return as `T[]`, which was a
|
||||
* lie: a nil slice marshals to JSON `null`, and every list in this app
|
||||
* has always been able to arrive that way. v3 types it honestly as
|
||||
* `T[] | null`, which surfaced ~50 sites that were relying on the lie.
|
||||
*
|
||||
* The app's contract is the one it has always behaved as if it had —
|
||||
* *an absent list is an empty list* — so it is stated once here rather
|
||||
* than as `?? []` at every call site, and stated at the only place it
|
||||
* is true: the moment a value crosses from Go.
|
||||
*
|
||||
* These helpers also return a plain `Promise`. v3 bindings return a
|
||||
* `CancellablePromise`, and nothing in this app cancels one; letting
|
||||
* that type leak inward would put a Wails type in the signature of
|
||||
* every store method for a capability none of them use.
|
||||
*/
|
||||
|
||||
/**
|
||||
* list awaits a binding returning a Go slice and yields `[]` for nil.
|
||||
*/
|
||||
export async function list<T>(
|
||||
request: PromiseLike<T[] | null>,
|
||||
): Promise<T[]> {
|
||||
return (await request) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* dict awaits a binding returning a Go map and yields `{}` for nil.
|
||||
*
|
||||
* Two shapes of the same lie are undone here. The generator types a
|
||||
* `map[int64]T` with a template-literal key (`` `${number}` ``), which
|
||||
* cannot be indexed by a `number` even though every such key is one;
|
||||
* and it types each value as nullable, because a map of slices can
|
||||
* hold a nil one. A null-valued key is dropped rather than kept,
|
||||
* which loses nothing: `noUncheckedIndexedAccess` already makes every
|
||||
* read `V | undefined`, so an absent key and a nil value are
|
||||
* indistinguishable to every consumer.
|
||||
*/
|
||||
export async function dict<V>(
|
||||
request: PromiseLike<Record<string, V | null | undefined> | null>,
|
||||
): Promise<Record<number, V>> {
|
||||
const raw = (await request) ?? {};
|
||||
const out: Record<number, V> = {};
|
||||
|
||||
for (const [key, val] of Object.entries(raw)) {
|
||||
if (val != null) out[Number(key)] = val;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* dictByName is dict for a Go map keyed by something that is already a
|
||||
* string — an MBID, a file path, a genre name.
|
||||
*/
|
||||
export async function dictByName<V>(
|
||||
request: PromiseLike<Record<string, V | null | undefined> | null>,
|
||||
): Promise<Record<string, V>> {
|
||||
return compact(await request);
|
||||
}
|
||||
|
||||
/**
|
||||
* compact is dictByName for a map that arrived as a *field* rather
|
||||
* than as a return value — a nested `map[string]string`, which the
|
||||
* generator types with optional values because a JSON object need not
|
||||
* carry every key.
|
||||
*/
|
||||
export function compact<V>(
|
||||
map: Record<string, V | null | undefined> | null | undefined,
|
||||
): Record<string, V> {
|
||||
const out: Record<string, V> = {};
|
||||
|
||||
for (const [key, val] of Object.entries(map ?? {})) {
|
||||
if (val != null) out[key] = val;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* value awaits a binding whose result is used as-is, dropping only the
|
||||
* cancellation the app never asks for.
|
||||
*/
|
||||
export async function value<T>(request: PromiseLike<T>): Promise<T> {
|
||||
return await request;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { downloadStore } from '@store/download-store';
|
||||
import { libraryStore } from '@store/library-store';
|
||||
import type { download } from '@go/models';
|
||||
import type * as download from '@go/download/models.js';
|
||||
import type { LibraryStatus } from '../components/library-status-indicator/library-status-indicator';
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* given array and never again.
|
||||
*/
|
||||
|
||||
import type { library } from '@go/models';
|
||||
import type * as library from '@go/library/models.js';
|
||||
|
||||
const byArray = new WeakMap<
|
||||
readonly library.Track[],
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* The `@runtime/runtime` seam.
|
||||
*
|
||||
* 22 files import `EventsOn` from here and nothing else, so rather than
|
||||
* rewriting 22 imports to v3's `Events.On` the alias points at this
|
||||
* module — which is also what gives the Vitest fake exactly one seam to
|
||||
* intercept instead of a `window.runtime` global that v3 does not have.
|
||||
* (Plan 009, D4.)
|
||||
*
|
||||
* The one real difference between the two runtimes is the callback
|
||||
* shape: v2 spread an event's data across the callback's arguments,
|
||||
* v3 hands over a single `WailsEvent` object. Unwrapping `.data` here
|
||||
* reproduces v2's shape for the single-value case, which is every emit
|
||||
* in this tree — `backend/events` has no call site passing more than
|
||||
* one data argument, and v3's `EventManager.Emit` only packs arguments
|
||||
* into a slice when there is more than one, so there is nothing to
|
||||
* un-spread.
|
||||
*/
|
||||
|
||||
import { Events } from '@wailsio/runtime';
|
||||
|
||||
/**
|
||||
* EventsOn registers a listener for a backend event and returns the
|
||||
* function that unregisters it.
|
||||
*/
|
||||
export function EventsOn(
|
||||
eventName: string,
|
||||
callback: (...data: any[]) => void,
|
||||
): () => void {
|
||||
return Events.On(eventName, (ev) => {
|
||||
callback(ev.data);
|
||||
});
|
||||
}
|
||||
@@ -34,7 +34,14 @@ import { fixture, shadow, shadowAll, update } from '@test/support/render';
|
||||
|
||||
const SEARCH = 'explore.Service.SearchLocal';
|
||||
|
||||
function request(overrides: Partial<Request>): Request {
|
||||
// v3's bindings type `state` and `entity` as real enums where v2 typed
|
||||
// them as strings; the fixture widens both back to their value unions.
|
||||
type RequestOverrides = Partial<Omit<Request, 'state' | 'entity'>> & {
|
||||
state?: `${Request['state']}`;
|
||||
entity?: `${Request['entity']}`;
|
||||
};
|
||||
|
||||
function request(overrides: RequestOverrides): Request {
|
||||
return {
|
||||
id: 1,
|
||||
mbid: 'rg-wanted',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user