diff --git a/Makefile b/Makefile index bd0063e..5e9f8e2 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.ts b/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.ts new file mode 100644 index 0000000..1ea1058 --- /dev/null +++ b/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.ts @@ -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); diff --git a/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts b/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts new file mode 100644 index 0000000..3dd1807 --- /dev/null +++ b/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts @@ -0,0 +1,2 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT diff --git a/frontend/wailsjs/go/home/Service.d.ts b/frontend/bindings/time/index.ts old mode 100755 new mode 100644 similarity index 56% rename from frontend/wailsjs/go/home/Service.d.ts rename to frontend/bindings/time/index.ts index 43c908e..ffd69de --- a/frontend/wailsjs/go/home/Service.d.ts +++ b/frontend/bindings/time/index.ts @@ -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>; +export { + Duration +} from "./models.js"; diff --git a/frontend/bindings/time/models.ts b/frontend/bindings/time/models.ts new file mode 100644 index 0000000..6e14050 --- /dev/null +++ b/frontend/bindings/time/models.ts @@ -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, +}; diff --git a/frontend/bindings/yellowjacket/backend/autotagservice/index.ts b/frontend/bindings/yellowjacket/backend/autotagservice/index.ts new file mode 100644 index 0000000..2d8d648 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/autotagservice/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/autotagservice/models.ts b/frontend/bindings/yellowjacket/backend/autotagservice/models.ts new file mode 100644 index 0000000..2c4ae9d --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/autotagservice/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/autotagservice/service.ts b/frontend/bindings/yellowjacket/backend/autotagservice/service.ts new file mode 100644 index 0000000..8b0159a --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/autotagservice/service.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(1561223347); +} diff --git a/frontend/bindings/yellowjacket/backend/config/config.ts b/frontend/bindings/yellowjacket/backend/config/config.ts new file mode 100644 index 0000000..75f97fa --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/config/config.ts @@ -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 { + return $Call.ByID(3209119019); +} + +/** + * GetDownloadPreferences returns the configured auto-download + * guardrails. + */ +export function GetDownloadPreferences(): $CancellablePromise { + return $Call.ByID(4104118319); +} + +/** + * GetFavoritesIconStyle returns the configured icon style. + */ +export function GetFavoritesIconStyle(): $CancellablePromise { + return $Call.ByID(3222927230); +} + +/** + * GetFavoritesPlaylistID returns the configured default playlist ID. + */ +export function GetFavoritesPlaylistID(): $CancellablePromise { + return $Call.ByID(4116868495); +} + +/** + * GetLibraryDirectory returns the currently configured library directory path. + */ +export function GetLibraryDirectory(): $CancellablePromise { + return $Call.ByID(2123565657); +} + +/** + * GetPinDefaultPlaylist returns whether the default playlist + * is pinned to the top of the playlist view. + */ +export function GetPinDefaultPlaylist(): $CancellablePromise { + return $Call.ByID(3818283301); +} + +/** + * GetQueueFallback returns what plays, if anything, once the queue + * runs out. + */ +export function GetQueueFallback(): $CancellablePromise { + return $Call.ByID(1504773860); +} + +/** + * GetScanConcurrency returns the configured scan concurrency mode. + */ +export function GetScanConcurrency(): $CancellablePromise { + 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 { + return $Call.ByID(4264374057); +} + +/** + * GetThemeBackgroundShade returns the configured background shade. + */ +export function GetThemeBackgroundShade(): $CancellablePromise { + return $Call.ByID(578772653); +} + +/** + * GetTrackListColumns returns the configured track-list columns. + */ +export function GetTrackListColumns(): $CancellablePromise { + return $Call.ByID(3426289065); +} + +/** + * Load reads and parses the config file from disk. + */ +export function Load(): $CancellablePromise { + return $Call.ByID(2408891113); +} + +/** + * ResetShortcuts resets all shortcuts to defaults. + */ +export function ResetShortcuts(): $CancellablePromise { + 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 { + return $Call.ByID(1988945736); +} + +/** + * SetDefaultPage validates and saves a new launch page. + */ +export function SetDefaultPage(page: string): $CancellablePromise { + 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 { + return $Call.ByID(2655203755, prefs); +} + +/** + * SetFavoritesIconStyle validates and saves a new icon style. + */ +export function SetFavoritesIconStyle(style: string): $CancellablePromise { + return $Call.ByID(812590378, style); +} + +/** + * SetFavoritesPlaylistID saves a new default playlist ID. + */ +export function SetFavoritesPlaylistID(id: number): $CancellablePromise { + 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 { + 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 { + return $Call.ByID(372446849, pin); +} + +/** + * SetQueueFallback validates and saves a new queue-fallback mode. + */ +export function SetQueueFallback(mode: string): $CancellablePromise { + 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 { + return $Call.ByID(742893445, mode); +} + +/** + * SetShortcut saves a single shortcut binding. + */ +export function SetShortcut(action: string, key: string): $CancellablePromise { + return $Call.ByID(275130705, action, key); +} + +/** + * SetShortcuts saves the entire shortcut bindings map. + */ +export function SetShortcuts(bindings: { [_ in string]?: string } | null): $CancellablePromise { + return $Call.ByID(4073898118, bindings); +} + +/** + * SetThemeAccentColor validates and saves a new accent colour. + */ +export function SetThemeAccentColor(color: string): $CancellablePromise { + return $Call.ByID(3536665861, color); +} + +/** + * SetThemeBackgroundShade validates and saves a new background shade. + */ +export function SetThemeBackgroundShade(shade: string): $CancellablePromise { + return $Call.ByID(2709290065, shade); +} + +/** + * SetTrackListColumns validates and saves a new column layout. + */ +export function SetTrackListColumns(columns: tracklist$0.Column[] | null): $CancellablePromise { + return $Call.ByID(4226159685, columns); +} + +/** + * Validate returns errors if there is a breaking issue with the config. + */ +export function Validate(): $CancellablePromise { + return $Call.ByID(3087982155); +} diff --git a/frontend/wailsjs/go/home/Service.js b/frontend/bindings/yellowjacket/backend/config/index.ts old mode 100755 new mode 100644 similarity index 52% rename from frontend/wailsjs/go/home/Service.js rename to frontend/bindings/yellowjacket/backend/config/index.ts index 0c41620..ec844f7 --- a/frontend/wailsjs/go/home/Service.js +++ b/frontend/bindings/yellowjacket/backend/config/index.ts @@ -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 +}; diff --git a/frontend/bindings/yellowjacket/backend/database/sql/sqlcgen/index.ts b/frontend/bindings/yellowjacket/backend/database/sql/sqlcgen/index.ts new file mode 100644 index 0000000..ce842bb --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/database/sql/sqlcgen/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/database/sql/sqlcgen/models.ts b/frontend/bindings/yellowjacket/backend/database/sql/sqlcgen/models.ts new file mode 100644 index 0000000..eee58ac --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/database/sql/sqlcgen/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/download/index.ts b/frontend/bindings/yellowjacket/backend/download/index.ts new file mode 100644 index 0000000..9cbc1ec --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/download/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/download/models.ts b/frontend/bindings/yellowjacket/backend/download/models.ts new file mode 100644 index 0000000..7092151 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/download/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/download/service.ts b/frontend/bindings/yellowjacket/backend/download/service.ts new file mode 100644 index 0000000..41aa704 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/download/service.ts @@ -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 { + 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 { + return $Call.ByID(2621441882, req); +} + +/** + * Cancel aborts a live download. + */ +export function Cancel(downloadID: string): $CancellablePromise { + 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 { + return $Call.ByID(3757713449); +} + +/** + * ClearSatisfiedRequests drops everything already owned. + */ +export function ClearSatisfiedRequests(): $CancellablePromise { + return $Call.ByID(2681765363); +} + +/** + * DeleteProvider removes a provider and its credentials. + */ +export function DeleteProvider(id: number): $CancellablePromise { + 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 { + 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 { + 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 { + return $Call.ByID(2652842089, id, paused); +} + +/** + * Pick starts the transfer for the candidate the user chose. + */ +export function Pick(downloadID: string, candidateID: string): $CancellablePromise { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(42359606, id, name, enabled, priority, settings); +} diff --git a/frontend/bindings/yellowjacket/backend/explore/index.ts b/frontend/bindings/yellowjacket/backend/explore/index.ts new file mode 100644 index 0000000..ebbacfc --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/explore/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/explore/models.ts b/frontend/bindings/yellowjacket/backend/explore/models.ts new file mode 100644 index 0000000..74d22ef --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/explore/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/explore/service.ts b/frontend/bindings/yellowjacket/backend/explore/service.ts new file mode 100644 index 0000000..9dd4aab --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/explore/service.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(3872948181); +} + +/** + * IndexLastImported returns when the dump import last completed, or the + * zero time if it never has. + */ +export function IndexLastImported(): $CancellablePromise { + 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 { + 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 { + return $Call.ByID(2446309672); +} + +/** + * IsIndexReady returns true once the index has been populated. + */ +export function IsIndexReady(): $CancellablePromise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(3200929511); +} diff --git a/frontend/bindings/yellowjacket/backend/frontendutil/frontendutil.ts b/frontend/bindings/yellowjacket/backend/frontendutil/frontendutil.ts new file mode 100644 index 0000000..3915b07 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/frontendutil/frontendutil.ts @@ -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 { + 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 { + 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 { + 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 { + return $Call.ByID(3350710867, path); +} diff --git a/frontend/bindings/yellowjacket/backend/frontendutil/index.ts b/frontend/bindings/yellowjacket/backend/frontendutil/index.ts new file mode 100644 index 0000000..5c57627 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/frontendutil/index.ts @@ -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 +}; diff --git a/frontend/bindings/yellowjacket/backend/home/index.ts b/frontend/bindings/yellowjacket/backend/home/index.ts new file mode 100644 index 0000000..c35651f --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/home/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/home/models.ts b/frontend/bindings/yellowjacket/backend/home/models.ts new file mode 100644 index 0000000..f6a5059 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/home/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/home/service.ts b/frontend/bindings/yellowjacket/backend/home/service.ts new file mode 100644 index 0000000..6176bec --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/home/service.ts @@ -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); +} diff --git a/frontend/bindings/yellowjacket/backend/jobs/index.ts b/frontend/bindings/yellowjacket/backend/jobs/index.ts new file mode 100644 index 0000000..667a2e0 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/jobs/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/jobs/models.ts b/frontend/bindings/yellowjacket/backend/jobs/models.ts new file mode 100644 index 0000000..6bbbb7f --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/jobs/models.ts @@ -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", +}; diff --git a/frontend/bindings/yellowjacket/backend/jobs/service.ts b/frontend/bindings/yellowjacket/backend/jobs/service.ts new file mode 100644 index 0000000..f1295bb --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/jobs/service.ts @@ -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 { + return $Call.ByID(680141523, id); +} + +/** + * ClearFinishedJobs removes every terminal job from the list. + */ +export function ClearFinishedJobs(): $CancellablePromise { + return $Call.ByID(4219744941); +} + +/** + * DismissJob removes a single finished job from the list. + */ +export function DismissJob(id: string): $CancellablePromise { + 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 { + return $Call.ByID(4094353111, id); +} + +/** + * ResumeJob continues a paused job. + */ +export function ResumeJob(id: string): $CancellablePromise { + return $Call.ByID(3937701508, id); +} diff --git a/frontend/bindings/yellowjacket/backend/library/index.ts b/frontend/bindings/yellowjacket/backend/library/index.ts new file mode 100644 index 0000000..04d14f2 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/library/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/library/library.ts b/frontend/bindings/yellowjacket/backend/library/library.ts new file mode 100644 index 0000000..f5157ca --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/library/library.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(1365062070); +} + +/** + * IsScanPaused returns whether the scan is currently paused. + */ +export function IsScanPaused(): $CancellablePromise { + return $Call.ByID(2930130958); +} + +/** + * LibraryPath resolves a library's root directory by id. + */ +export function LibraryPath(id: number): $CancellablePromise { + 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 { + return $Call.ByID(3522392788); +} + +/** + * QueuedLibraryNames returns the display names of libraries waiting + * in the scan queue, in FIFO order. + */ +export function QueuedLibraryNames(): $CancellablePromise { + return $Call.ByID(2036951097); +} + +/** + * ReleasePipelineLock releases the pipeline mutex after a tag write. + */ +export function ReleasePipelineLock(): $CancellablePromise { + 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 { + 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 { + return $Call.ByID(4243712755); +} + +/** + * ResumeScan unblocks a paused scan. + */ +export function ResumeScan(): $CancellablePromise { + 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 { + 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 { + 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 { + return $Call.ByID(4271773525, reg); +} + +/** + * SetRemovalHooks provides optional hooks for cross-cutting + * orchestration during RemoveLibrary. + */ +export function SetRemovalHooks(h: $models.RemovalHooks): $CancellablePromise { + return $Call.ByID(3190933207, h); +} + +/** + * SetRescanHooks provides optional hooks for cross-cutting + * orchestration during FullRescan. + */ +export function SetRescanHooks(h: $models.RescanHooks): $CancellablePromise { + return $Call.ByID(1944064333, h); +} + +/** + * SetScanHooks provides optional hooks for cross-cutting + * orchestration after each library scan. + */ +export function SetScanHooks(h: $models.ScanHooks): $CancellablePromise { + 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 { + return $Call.ByID(1401473402); +} diff --git a/frontend/bindings/yellowjacket/backend/library/models.ts b/frontend/bindings/yellowjacket/backend/library/models.ts new file mode 100644 index 0000000..3f11eca --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/library/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/mediacontrols/index.ts b/frontend/bindings/yellowjacket/backend/mediacontrols/index.ts new file mode 100644 index 0000000..dd70b92 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/mediacontrols/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/mediacontrols/models.ts b/frontend/bindings/yellowjacket/backend/mediacontrols/models.ts new file mode 100644 index 0000000..935ac3c --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/mediacontrols/models.ts @@ -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; diff --git a/frontend/bindings/yellowjacket/backend/player/index.ts b/frontend/bindings/yellowjacket/backend/player/index.ts new file mode 100644 index 0000000..ed0898f --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/player/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/player/models.ts b/frontend/bindings/yellowjacket/backend/player/models.ts new file mode 100644 index 0000000..e4ed6f2 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/player/models.ts @@ -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, +}; diff --git a/frontend/bindings/yellowjacket/backend/player/player.ts b/frontend/bindings/yellowjacket/backend/player/player.ts new file mode 100644 index 0000000..4b96a0e --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/player/player.ts @@ -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 { + return $Call.ByID(742861629, deltaVolume); +} + +/** + * CurrentPosition returns the playback position as a percentage + * (0-100). + */ +export function CurrentPosition(): $CancellablePromise { + return $Call.ByID(2723620683); +} + +/** + * CurrentPositionSeconds returns the current playback position in + * display seconds. + */ +export function CurrentPositionSeconds(): $CancellablePromise { + 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 { + 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 { + return $Call.ByID(3317123670); +} + +/** + * IsPlaying reports whether the player is currently playing audio. + */ +export function IsPlaying(): $CancellablePromise { + return $Call.ByID(1219303535); +} + +/** + * LoadFile opens and decodes an audio file for playback. + */ +export function LoadFile(filePath: string): $CancellablePromise { + return $Call.ByID(2214941041, filePath); +} + +/** + * MuteToggle toggles the mute state. + */ +export function MuteToggle(): $CancellablePromise { + return $Call.ByID(2840614678); +} + +/** + * Muted reports whether playback is currently silenced. + */ +export function Muted(): $CancellablePromise { + return $Call.ByID(2954670878); +} + +/** + * Pause pauses the current playback. + */ +export function Pause(): $CancellablePromise { + return $Call.ByID(1402758839); +} + +/** + * Play starts or resumes audio playback. + */ +export function Play(): $CancellablePromise { + return $Call.ByID(3327176373); +} + +/** + * RestoreState loads the persisted player state from the database. + */ +export function RestoreState(): $CancellablePromise { + 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 { + return $Call.ByID(3845072215); +} + +/** + * Seek jumps to a specific position in seconds. + */ +export function Seek(targetSeconds: number): $CancellablePromise { + 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 { + 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 { + 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 { + return $Call.ByID(1375836663, desiredVolume); +} + +/** + * TrackLengthInSeconds returns the duration of the current track. + */ +export function TrackLengthInSeconds(): $CancellablePromise { + 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 { + return $Call.ByID(3127425467); +} diff --git a/frontend/bindings/yellowjacket/backend/playlist/index.ts b/frontend/bindings/yellowjacket/backend/playlist/index.ts new file mode 100644 index 0000000..a91e6ce --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/playlist/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/playlist/models.ts b/frontend/bindings/yellowjacket/backend/playlist/models.ts new file mode 100644 index 0000000..b42bd03 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/playlist/models.ts @@ -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; +} diff --git a/frontend/bindings/yellowjacket/backend/playlist/service.ts b/frontend/bindings/yellowjacket/backend/playlist/service.ts new file mode 100644 index 0000000..cac9f7d --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/playlist/service.ts @@ -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 { + return $Call.ByID(1493850977, filePaths); +} + +/** + * AddTracksToPlaylist adds one or more tracks to an existing + * playlist. + */ +export function AddTracksToPlaylist(playlistID: number, filePaths: string[] | null): $CancellablePromise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(2225480718, playlistID); +} + +/** + * RemoveFromDefaultPlaylist removes multiple tracks from the + * default playlist. + */ +export function RemoveFromDefaultPlaylist(filePaths: string[] | null): $CancellablePromise { + 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 { + 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 { + return $Call.ByID(2519855654, playlistID, trackIDs); +} + +/** + * RenamePlaylist renames a playlist and updates its M3U8 file. + */ +export function RenamePlaylist(playlistID: number, newName: string): $CancellablePromise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(2746792411, playlistID, rulesJSON); +} diff --git a/frontend/bindings/yellowjacket/backend/queue/index.ts b/frontend/bindings/yellowjacket/backend/queue/index.ts new file mode 100644 index 0000000..308c4e6 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/queue/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/queue/models.ts b/frontend/bindings/yellowjacket/backend/queue/models.ts new file mode 100644 index 0000000..8c45745 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/queue/models.ts @@ -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; diff --git a/frontend/bindings/yellowjacket/backend/queue/queue.ts b/frontend/bindings/yellowjacket/backend/queue/queue.ts new file mode 100644 index 0000000..873ec28 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/queue/queue.ts @@ -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 { + 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 { + 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 { + 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 { + return $Call.ByID(3620379645); +} + +/** + * CycleRepeat cycles through repeat modes: off -> all -> one -> off. + */ +export function CycleRepeat(): $CancellablePromise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(3197379127); +} + +/** + * PlayIndex jumps to and plays the track at the given index. + */ +export function PlayIndex(index: number): $CancellablePromise { + 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 { + return $Call.ByID(708701476); +} + +/** + * RemoveTrack removes a track at the given position from the queue. + */ +export function RemoveTrack(position: number): $CancellablePromise { + 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 { + return $Call.ByID(1587232097, positions); +} + +/** + * RestoreState loads the queue state from the database. + */ +export function RestoreState(): $CancellablePromise { + 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 { + 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 { + 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 { + 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 { + return $Call.ByID(1990450094, filePaths, startIndex, shuffleStart, source); +} + +/** + * ToggleShuffle toggles shuffle mode on/off. + */ +export function ToggleShuffle(): $CancellablePromise { + return $Call.ByID(612647562); +} diff --git a/frontend/bindings/yellowjacket/backend/tagwriter/index.ts b/frontend/bindings/yellowjacket/backend/tagwriter/index.ts new file mode 100644 index 0000000..8e12c8e --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/tagwriter/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/tagwriter/models.ts b/frontend/bindings/yellowjacket/backend/tagwriter/models.ts new file mode 100644 index 0000000..099bbd2 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/tagwriter/models.ts @@ -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; diff --git a/frontend/bindings/yellowjacket/backend/tagwriter/tagwriter.ts b/frontend/bindings/yellowjacket/backend/tagwriter/tagwriter.ts new file mode 100644 index 0000000..546d4e4 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/tagwriter/tagwriter.ts @@ -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 { + 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 { + 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 { + 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 { + return $Call.ByID(3625602876, filePath, changes); +} diff --git a/frontend/bindings/yellowjacket/backend/tracklist/index.ts b/frontend/bindings/yellowjacket/backend/tracklist/index.ts new file mode 100644 index 0000000..b7307f1 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/tracklist/index.ts @@ -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"; diff --git a/frontend/bindings/yellowjacket/backend/tracklist/models.ts b/frontend/bindings/yellowjacket/backend/tracklist/models.ts new file mode 100644 index 0000000..2d6b0a8 --- /dev/null +++ b/frontend/bindings/yellowjacket/backend/tracklist/models.ts @@ -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", +}; diff --git a/frontend/index.ts b/frontend/index.ts index bb4ea94..095ca2b 100644 --- a/frontend/index.ts +++ b/frontend/index.ts @@ -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'; diff --git a/frontend/package.json b/frontend/package.json index 82eacfa..69fadc0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index d22e22d..a45f412 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -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 diff --git a/frontend/src/components/artist-details/artist-details.ts b/frontend/src/components/artist-details/artist-details.ts index e829125..0a8039f 100644 --- a/frontend/src/components/artist-details/artist-details.ts +++ b/frontend/src/components/artist-details/artist-details.ts @@ -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'; diff --git a/frontend/src/components/artists-view/artists-view.ts b/frontend/src/components/artists-view/artists-view.ts index d67f918..23bc1b9 100644 --- a/frontend/src/components/artists-view/artists-view.ts +++ b/frontend/src/components/artists-view/artists-view.ts @@ -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[] = []; diff --git a/frontend/src/components/autotag-view/autotag-view.ts b/frontend/src/components/autotag-view/autotag-view.ts index 86cd4a2..2d4db90 100644 --- a/frontend/src/components/autotag-view/autotag-view.ts +++ b/frontend/src/components/autotag-view/autotag-view.ts @@ -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 { - 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 { 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 { - 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(); - 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`
@@ -2965,7 +2968,7 @@ export class AutotagView extends ViewLifecycleMixin(LitElement) { return html`
Candidate index out of range.
`; } - const clusters = this.clusterVersions(this.score.candidates); + const clusters = this.clusterVersions((this.score.candidates ?? [])); return html`
diff --git a/frontend/src/components/config-page/config-page.ts b/frontend/src/components/config-page/config-page.ts index 04427f5..986ac62 100644 --- a/frontend/src/components/config-page/config-page.ts +++ b/frontend/src/components/config-page/config-page.ts @@ -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 { 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) { updated ${this.timeAgo(s.lastBuilt)}` : nothing}
- ${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`
- ${s.tiers.map( + ${(s.tiers ?? []).map( (t) => html`
${this.tierIcon(t.state)} diff --git a/frontend/src/components/config-page/download-clients.ts b/frontend/src/components/config-page/download-clients.ts index 3581e60..cdeb150 100644 --- a/frontend/src/components/config-page/download-clients.ts +++ b/frontend/src/components/config-page/download-clients.ts @@ -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] diff --git a/frontend/src/components/cover-grid/album-dropdown.ts b/frontend/src/components/cover-grid/album-dropdown.ts index f81df2e..274d33e 100644 --- a/frontend/src/components/cover-grid/album-dropdown.ts +++ b/frontend/src/components/cover-grid/album-dropdown.ts @@ -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'; diff --git a/frontend/src/components/cover-grid/album-selection.ts b/frontend/src/components/cover-grid/album-selection.ts index c27f233..6e6175c 100644 --- a/frontend/src/components/cover-grid/album-selection.ts +++ b/frontend/src/components/cover-grid/album-selection.ts @@ -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)); } // ================================================================ diff --git a/frontend/src/components/cover-grid/cover-grid-types.ts b/frontend/src/components/cover-grid/cover-grid-types.ts index 068caa9..993e133 100644 --- a/frontend/src/components/cover-grid/cover-grid-types.ts +++ b/frontend/src/components/cover-grid/cover-grid-types.ts @@ -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 diff --git a/frontend/src/components/cover-grid/cover-grid.ts b/frontend/src/components/cover-grid/cover-grid.ts index f1696be..4dbf4b9 100644 --- a/frontend/src/components/cover-grid/cover-grid.ts +++ b/frontend/src/components/cover-grid/cover-grid.ts @@ -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; diff --git a/frontend/src/components/cover-grid/scroll-manager.ts b/frontend/src/components/cover-grid/scroll-manager.ts index ae47b0d..03bc000 100644 --- a/frontend/src/components/cover-grid/scroll-manager.ts +++ b/frontend/src/components/cover-grid/scroll-manager.ts @@ -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'; diff --git a/frontend/src/components/download-picker/download-picker.ts b/frontend/src/components/download-picker/download-picker.ts index 26de54c..917add5 100644 --- a/frontend/src/components/download-picker/download-picker.ts +++ b/frontend/src/components/download-picker/download-picker.ts @@ -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'; diff --git a/frontend/src/components/downloads-view/downloads-view.ts b/frontend/src/components/downloads-view/downloads-view.ts index a4ae703..d774ab0 100644 --- a/frontend/src/components/downloads-view/downloads-view.ts +++ b/frontend/src/components/downloads-view/downloads-view.ts @@ -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[] = []; diff --git a/frontend/src/components/duplicate-tracks-dialog/duplicate-tracks-dialog.ts b/frontend/src/components/duplicate-tracks-dialog/duplicate-tracks-dialog.ts index 8a795ba..57c3a7f 100644 --- a/frontend/src/components/duplicate-tracks-dialog/duplicate-tracks-dialog.ts +++ b/frontend/src/components/duplicate-tracks-dialog/duplicate-tracks-dialog.ts @@ -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'; diff --git a/frontend/src/components/explore-album-details/explore-album-details.ts b/frontend/src/components/explore-album-details/explore-album-details.ts index 9b1e078..e5da042 100644 --- a/frontend/src/components/explore-album-details/explore-album-details.ts +++ b/frontend/src/components/explore-album-details/explore-album-details.ts @@ -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; } diff --git a/frontend/src/components/explore-artist-details/explore-artist-details.ts b/frontend/src/components/explore-artist-details/explore-artist-details.ts index 91af887..e45522c 100644 --- a/frontend/src/components/explore-artist-details/explore-artist-details.ts +++ b/frontend/src/components/explore-artist-details/explore-artist-details.ts @@ -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 = {}; 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 = {}; 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] ?? []; } diff --git a/frontend/src/components/explore-view/explore-view.ts b/frontend/src/components/explore-view/explore-view.ts index c6554a0..fc4e47a 100644 --- a/frontend/src/components/explore-view/explore-view.ts +++ b/frontend/src/components/explore-view/explore-view.ts @@ -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 { 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 { 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`
` : nothing} - ${page.shelves.map((shelf) => + ${(page.shelves ?? []).map((shelf) => shelf.artists?.length ? this.renderArtistsSection( shelf.artists, diff --git a/frontend/src/components/first-run-wizard/first-run-wizard.ts b/frontend/src/components/first-run-wizard/first-run-wizard.ts index ef7dfe4..96316ff 100644 --- a/frontend/src/components/first-run-wizard/first-run-wizard.ts +++ b/frontend/src/components/first-run-wizard/first-run-wizard.ts @@ -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'; diff --git a/frontend/src/components/genre-details/genre-details.ts b/frontend/src/components/genre-details/genre-details.ts index 808ace1..beab7c3 100644 --- a/frontend/src/components/genre-details/genre-details.ts +++ b/frontend/src/components/genre-details/genre-details.ts @@ -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 = []; diff --git a/frontend/src/components/genres-view/genres-view.ts b/frontend/src/components/genres-view/genres-view.ts index 139a547..fb599b2 100644 --- a/frontend/src/components/genres-view/genres-view.ts +++ b/frontend/src/components/genres-view/genres-view.ts @@ -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 diff --git a/frontend/src/components/home-view/home-view.ts b/frontend/src/components/home-view/home-view.ts index 38badab..cff79ad 100644 --- a/frontend/src/components/home-view/home-view.ts +++ b/frontend/src/components/home-view/home-view.ts @@ -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) {

${shelf.subtitle}

- ${shelf.albums.map((album) => this.renderCard(album))} + ${(shelf.albums ?? []).map((album) => this.renderCard(album))}
`; diff --git a/frontend/src/components/jobs/jobs-view.ts b/frontend/src/components/jobs/jobs-view.ts index a84f6d9..cdd1261 100644 --- a/frontend/src/components/jobs/jobs-view.ts +++ b/frontend/src/components/jobs/jobs-view.ts @@ -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'; diff --git a/frontend/src/components/library-filter/library-filter.ts b/frontend/src/components/library-filter/library-filter.ts index dcd8883..f62085c 100644 --- a/frontend/src/components/library-filter/library-filter.ts +++ b/frontend/src/components/library-filter/library-filter.ts @@ -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'; /** diff --git a/frontend/src/components/phantom-resolver/phantom-resolver.ts b/frontend/src/components/phantom-resolver/phantom-resolver.ts index c98be2c..9f48396 100644 --- a/frontend/src/components/phantom-resolver/phantom-resolver.ts +++ b/frontend/src/components/phantom-resolver/phantom-resolver.ts @@ -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:', diff --git a/frontend/src/components/playlist-details/playlist-details.ts b/frontend/src/components/playlist-details/playlist-details.ts index 6bb41e1..c3ac7b8 100644 --- a/frontend/src/components/playlist-details/playlist-details.ts +++ b/frontend/src/components/playlist-details/playlist-details.ts @@ -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( diff --git a/frontend/src/components/playlist-picker/playlist-picker.ts b/frontend/src/components/playlist-picker/playlist-picker.ts index 777074d..8116f30 100644 --- a/frontend/src/components/playlist-picker/playlist-picker.ts +++ b/frontend/src/components/playlist-picker/playlist-picker.ts @@ -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 = []; diff --git a/frontend/src/components/playlist-view/playlist-view.ts b/frontend/src/components/playlist-view/playlist-view.ts index 775d94f..0f69572 100644 --- a/frontend/src/components/playlist-view/playlist-view.ts +++ b/frontend/src/components/playlist-view/playlist-view.ts @@ -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 { diff --git a/frontend/src/components/queue-panel/queue-panel.ts b/frontend/src/components/queue-panel/queue-panel.ts index 21cf1c9..ab6da06 100644 --- a/frontend/src/components/queue-panel/queue-panel.ts +++ b/frontend/src/components/queue-panel/queue-panel.ts @@ -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'; diff --git a/frontend/src/components/smart-playlist-details/smart-playlist-details.ts b/frontend/src/components/smart-playlist-details/smart-playlist-details.ts index 61a5839..d3f4e48 100644 --- a/frontend/src/components/smart-playlist-details/smart-playlist-details.ts +++ b/frontend/src/components/smart-playlist-details/smart-playlist-details.ts @@ -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( diff --git a/frontend/src/components/smart-playlist-editor/smart-playlist-editor.ts b/frontend/src/components/smart-playlist-editor/smart-playlist-editor.ts index 5f7a7da..5e723ec 100644 --- a/frontend/src/components/smart-playlist-editor/smart-playlist-editor.ts +++ b/frontend/src/components/smart-playlist-editor/smart-playlist-editor.ts @@ -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'; diff --git a/frontend/src/components/top-results-row/top-results-row.ts b/frontend/src/components/top-results-row/top-results-row.ts index 7259d5c..af99e99 100644 --- a/frontend/src/components/top-results-row/top-results-row.ts +++ b/frontend/src/components/top-results-row/top-results-row.ts @@ -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'; diff --git a/frontend/src/components/track-details/track-details.ts b/frontend/src/components/track-details/track-details.ts index e617c91..23cf502 100644 --- a/frontend/src/components/track-details/track-details.ts +++ b/frontend/src/components/track-details/track-details.ts @@ -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'; diff --git a/frontend/src/components/track-list/columns.ts b/frontend/src/components/track-list/columns.ts index 7880f96..8504b60 100644 --- a/frontend/src/components/track-list/columns.ts +++ b/frontend/src/components/track-list/columns.ts @@ -1,4 +1,4 @@ -import type { library } from '@go/models'; +import type * as library from '@go/library/models.js'; import { formatSampleRate, formatBitDepth, diff --git a/frontend/src/components/track-list/search-ranking.ts b/frontend/src/components/track-list/search-ranking.ts index 29cf5aa..e295e33 100644 --- a/frontend/src/components/track-list/search-ranking.ts +++ b/frontend/src/components/track-list/search-ranking.ts @@ -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'; diff --git a/frontend/src/components/track-list/track-list.ts b/frontend/src/components/track-list/track-list.ts index 594d44d..08c8a2f 100644 --- a/frontend/src/components/track-list/track-list.ts +++ b/frontend/src/components/track-list/track-list.ts @@ -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'; diff --git a/frontend/src/services/keyboard-shortcut-service.ts b/frontend/src/services/keyboard-shortcut-service.ts index 9e0053d..ed8d049 100644 --- a/frontend/src/services/keyboard-shortcut-service.ts +++ b/frontend/src/services/keyboard-shortcut-service.ts @@ -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'; // =================================================================== diff --git a/frontend/src/store/controllers/library-controller.ts b/frontend/src/store/controllers/library-controller.ts index 8e60d7f..7d48f64 100644 --- a/frontend/src/store/controllers/library-controller.ts +++ b/frontend/src/store/controllers/library-controller.ts @@ -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'; diff --git a/frontend/src/store/controllers/playlist-controller.ts b/frontend/src/store/controllers/playlist-controller.ts index 8744aa2..15e4e09 100644 --- a/frontend/src/store/controllers/playlist-controller.ts +++ b/frontend/src/store/controllers/playlist-controller.ts @@ -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'; /** diff --git a/frontend/src/store/download-store.ts b/frontend/src/store/download-store.ts index ef8806d..343a95c 100644 --- a/frontend/src/store/download-store.ts +++ b/frontend/src/store/download-store.ts @@ -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; diff --git a/frontend/src/store/favorites-store.ts b/frontend/src/store/favorites-store.ts index fc470e9..4f2ba61 100644 --- a/frontend/src/store/favorites-store.ts +++ b/frontend/src/store/favorites-store.ts @@ -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'; diff --git a/frontend/src/store/job-store.ts b/frontend/src/store/job-store.ts index 812daba..29e83f8 100644 --- a/frontend/src/store/job-store.ts +++ b/frontend/src/store/job-store.ts @@ -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; diff --git a/frontend/src/store/library-store.ts b/frontend/src/store/library-store.ts index 0f79282..4ab7d88 100644 --- a/frontend/src/store/library-store.ts +++ b/frontend/src/store/library-store.ts @@ -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 { 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; diff --git a/frontend/src/store/player-store.ts b/frontend/src/store/player-store.ts index b462ca2..9ed1ea5 100644 --- a/frontend/src/store/player-store.ts +++ b/frontend/src/store/player-store.ts @@ -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 `` renders these in: the player bar. */ diff --git a/frontend/src/store/playlist-store.ts b/frontend/src/store/playlist-store.ts index fbe4c69..621b821 100644 --- a/frontend/src/store/playlist-store.ts +++ b/frontend/src/store/playlist-store.ts @@ -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; diff --git a/frontend/src/store/queue-store.ts b/frontend/src/store/queue-store.ts index fb4b1ef..ed62dbb 100644 --- a/frontend/src/store/queue-store.ts +++ b/frontend/src/store/queue-store.ts @@ -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 { diff --git a/frontend/src/store/shortcuts-store.ts b/frontend/src/store/shortcuts-store.ts index be59c05..79d0880 100644 --- a/frontend/src/store/shortcuts-store.ts +++ b/frontend/src/store/shortcuts-store.ts @@ -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; // action → key combo @@ -56,7 +57,7 @@ class ShortcutsStore { private async loadFromBackend(): Promise { try { - const raw = await GetShortcuts(); + const raw = await dictByName(GetShortcuts()); this.state = { bindings: new Map(Object.entries(raw)), loaded: true, diff --git a/frontend/src/store/theme-store.ts b/frontend/src/store/theme-store.ts index 8b33599..770f728 100644 --- a/frontend/src/store/theme-store.ts +++ b/frontend/src/store/theme-store.ts @@ -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'; diff --git a/frontend/src/store/tracklist-store.ts b/frontend/src/store/tracklist-store.ts index 8d2773a..e550912 100644 --- a/frontend/src/store/tracklist-store.ts +++ b/frontend/src/store/tracklist-store.ts @@ -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 { 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 { - 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); } diff --git a/frontend/src/utils/binding.ts b/frontend/src/utils/binding.ts new file mode 100644 index 0000000..1988e8d --- /dev/null +++ b/frontend/src/utils/binding.ts @@ -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( + request: PromiseLike, +): Promise { + 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( + request: PromiseLike | null>, +): Promise> { + const raw = (await request) ?? {}; + const out: Record = {}; + + 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( + request: PromiseLike | null>, +): Promise> { + 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( + map: Record | null | undefined, +): Record { + const out: Record = {}; + + 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(request: PromiseLike): Promise { + return await request; +} diff --git a/frontend/src/utils/library-status.ts b/frontend/src/utils/library-status.ts index 50be773..d943d29 100644 --- a/frontend/src/utils/library-status.ts +++ b/frontend/src/utils/library-status.ts @@ -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'; /** diff --git a/frontend/src/utils/track-index.ts b/frontend/src/utils/track-index.ts index a5a1618..3685586 100644 --- a/frontend/src/utils/track-index.ts +++ b/frontend/src/utils/track-index.ts @@ -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[], diff --git a/frontend/src/wails/runtime.ts b/frontend/src/wails/runtime.ts new file mode 100644 index 0000000..b08d29c --- /dev/null +++ b/frontend/src/wails/runtime.ts @@ -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); + }); +} diff --git a/frontend/test/components/library-status.test.ts b/frontend/test/components/library-status.test.ts index af069b9..d5483d4 100644 --- a/frontend/test/components/library-status.test.ts +++ b/frontend/test/components/library-status.test.ts @@ -34,7 +34,14 @@ import { fixture, shadow, shadowAll, update } from '@test/support/render'; const SEARCH = 'explore.Service.SearchLocal'; -function request(overrides: Partial): 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> & { + state?: `${Request['state']}`; + entity?: `${Request['entity']}`; +}; + +function request(overrides: RequestOverrides): Request { return { id: 1, mbid: 'rg-wanted', diff --git a/frontend/test/harness.test.ts b/frontend/test/harness.test.ts index 64753a3..e9d0daa 100644 --- a/frontend/test/harness.test.ts +++ b/frontend/test/harness.test.ts @@ -22,7 +22,7 @@ describe('component-tier harness', () => { it('routes generated bindings through the fake, not a module mock', async () => { // The import path under test is the real generated stub, which does // window['go']['queue']['Queue']['GetState'](). - const Queue = await import('@go/queue/Queue'); + const Queue = await import('@go/queue/queue.js'); wails.stub('queue.Queue.GetState', { currentIndex: 4 }); @@ -31,7 +31,7 @@ describe('component-tier harness', () => { }); it('resolves an unstubbed binding instead of hanging', async () => { - const Queue = await import('@go/queue/Queue'); + const Queue = await import('@go/queue/queue.js'); // The real trap this pays for is the reverse: a *real* binding // called with wrong argument types never settles. Here, silence is diff --git a/frontend/test/stores/download-store.test.ts b/frontend/test/stores/download-store.test.ts index 89139ad..9e502d2 100644 --- a/frontend/test/stores/download-store.test.ts +++ b/frontend/test/stores/download-store.test.ts @@ -33,10 +33,22 @@ function view(id: string, state: string): DownloadView { } function provider(id: number, enabled: boolean): DownloadProvider { - return { id, name: `p${id}`, enabled, kind: 'test' } as DownloadProvider; + return { + id, + name: `p${id}`, + enabled, + kind: 'test', + } as unknown as DownloadProvider; } -function request(overrides: Partial): 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> & { + state?: `${Request['state']}`; + entity?: `${Request['entity']}`; +}; + +function request(overrides: RequestOverrides): Request { return { id: 1, mbid: 'abc', diff --git a/frontend/test/stores/job-store.test.ts b/frontend/test/stores/job-store.test.ts index 248f8cf..9e352c5 100644 --- a/frontend/test/stores/job-store.test.ts +++ b/frontend/test/stores/job-store.test.ts @@ -17,7 +17,16 @@ import { import { Events } from '../../src/events'; import { emit, calls, stub, flush } from '@test/support/harness'; -function job(overrides: Partial & { id: string }): Job { +// v3's bindings type `state` and `kind` as real enums where v2 typed +// them as strings, so the fixture widens both back to their value +// unions — which is still literal-checked, just not enum-checked. +type JobOverrides = Partial> & { + id: string; + state?: `${Job['state']}`; + kind?: `${Job['kind']}`; +}; + +function job(overrides: JobOverrides): Job { return { kind: 'library-scan', state: 'running', @@ -43,7 +52,7 @@ describe('job predicates', () => { }); it('treats every in-flight state, including paused, as active', () => { - const states = ['queued', 'running', 'pausing', 'paused', 'cancelling']; + const states = ['queued', 'running', 'pausing', 'paused', 'cancelling'] as const; expect(states.map((state) => isActive(job({ id: state, state })))).toEqual([ true, diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 0a826f8..a668663 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -25,11 +25,11 @@ // module resolution "moduleResolution": "bundler", "paths": { // path aliases to find modules - "@go/*": ["./wailsjs/go/*"], + "@go/*": ["./bindings/yellowjacket/backend/*"], "@components/*": ["./src/components/*"], "@assets/*": ["./src/assets/*"], "@pages/*": ["./src/pages/*"], - "@runtime/*": ["./wailsjs/runtime/*"], + "@runtime/*": ["./src/wails/*"], "@utils/*": ["./src/utils/*"], "@store/*": ["./src/store/*"], "@test/*": ["./test/*"] @@ -44,8 +44,5 @@ {"name": "typescript-lit-html-plugin"} ] }, - "include": ["src", "wailsjs", "test"], - // Wails-generated JS stubs use @ts-check with untyped patterns that - // violate strict mode. The companion .d.ts files provide the types. - "exclude": ["wailsjs/go/**/*.js"] + "include": ["src", "bindings", "test"] } diff --git a/frontend/vite.config.mts b/frontend/vite.config.mts index f2b21e0..2143dd9 100644 --- a/frontend/vite.config.mts +++ b/frontend/vite.config.mts @@ -4,11 +4,15 @@ import { defineConfig } from "vite"; export default defineConfig({ resolve: { alias: { - "@go": path.resolve(__dirname, "wailsjs/go"), + // v3 generates bindings nested by Go import path. The + // alias absorbs the constant prefix, so a site imports + // "@go/library/library" rather than the full + // "bindings/yellowjacket/backend/library/library". + "@go": path.resolve(__dirname, "bindings/yellowjacket/backend"), "@components": path.resolve(__dirname, "src/components"), "@assets": path.resolve(__dirname, "src/assets"), "@pages": path.resolve(__dirname, "src/pages"), - "@runtime": path.resolve(__dirname, "wailsjs/runtime"), + "@runtime": path.resolve(__dirname, "src/wails"), "@utils": path.resolve(__dirname, "src/utils"), "@store": path.resolve(__dirname, "src/store"), }, diff --git a/frontend/wailsjs/go/autotagservice/Service.d.ts b/frontend/wailsjs/go/autotagservice/Service.d.ts deleted file mode 100755 index 76fd614..0000000 --- a/frontend/wailsjs/go/autotagservice/Service.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {autotagservice} from '../models'; -import {context} from '../models'; -import {jobs} from '../models'; - -export function AckLibraryWarning(arg1:number):Promise; - -export function Apply(arg1:string,arg2:string):Promise; - -export function ApplyAsync(arg1:string,arg2:string):Promise; - -export function ClearCompletedEntries(arg1:number):Promise; - -export function GetCandidateCoverArt(arg1:string,arg2:string):Promise; - -export function GetCandidates(arg1:string):Promise; - -export function GetCandidatesForPasteURL(arg1:string,arg2:string):Promise; - -export function GetLocalCoverArt(arg1:string):Promise; - -export function GetNextPending():Promise; - -export function GetPendingFolder(arg1:string):Promise; - -export function LeaveAsIs(arg1:string):Promise; - -export function ListPendingFolders(arg1:number):Promise>; - -export function RetagGroup(arg1:string):Promise; - -export function SearchCandidates(arg1:string,arg2:string,arg3:string):Promise>; - -export function SelectSearchCandidate(arg1:string,arg2:string,arg3:string):Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetJobRegistry(arg1:jobs.Registry):Promise; - -export function Skip(arg1:string):Promise; - -export function SplitMixedFolder(arg1:string):Promise>; - -export function StartAutotagQueue(arg1:number):Promise; - -export function StartBackgroundPrefetch():Promise; - -export function WritesInFlight():Promise; diff --git a/frontend/wailsjs/go/autotagservice/Service.js b/frontend/wailsjs/go/autotagservice/Service.js deleted file mode 100755 index ff9fe74..0000000 --- a/frontend/wailsjs/go/autotagservice/Service.js +++ /dev/null @@ -1,91 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AckLibraryWarning(arg1) { - return window['go']['autotagservice']['Service']['AckLibraryWarning'](arg1); -} - -export function Apply(arg1, arg2) { - return window['go']['autotagservice']['Service']['Apply'](arg1, arg2); -} - -export function ApplyAsync(arg1, arg2) { - return window['go']['autotagservice']['Service']['ApplyAsync'](arg1, arg2); -} - -export function ClearCompletedEntries(arg1) { - return window['go']['autotagservice']['Service']['ClearCompletedEntries'](arg1); -} - -export function GetCandidateCoverArt(arg1, arg2) { - return window['go']['autotagservice']['Service']['GetCandidateCoverArt'](arg1, arg2); -} - -export function GetCandidates(arg1) { - return window['go']['autotagservice']['Service']['GetCandidates'](arg1); -} - -export function GetCandidatesForPasteURL(arg1, arg2) { - return window['go']['autotagservice']['Service']['GetCandidatesForPasteURL'](arg1, arg2); -} - -export function GetLocalCoverArt(arg1) { - return window['go']['autotagservice']['Service']['GetLocalCoverArt'](arg1); -} - -export function GetNextPending() { - return window['go']['autotagservice']['Service']['GetNextPending'](); -} - -export function GetPendingFolder(arg1) { - return window['go']['autotagservice']['Service']['GetPendingFolder'](arg1); -} - -export function LeaveAsIs(arg1) { - return window['go']['autotagservice']['Service']['LeaveAsIs'](arg1); -} - -export function ListPendingFolders(arg1) { - return window['go']['autotagservice']['Service']['ListPendingFolders'](arg1); -} - -export function RetagGroup(arg1) { - return window['go']['autotagservice']['Service']['RetagGroup'](arg1); -} - -export function SearchCandidates(arg1, arg2, arg3) { - return window['go']['autotagservice']['Service']['SearchCandidates'](arg1, arg2, arg3); -} - -export function SelectSearchCandidate(arg1, arg2, arg3) { - return window['go']['autotagservice']['Service']['SelectSearchCandidate'](arg1, arg2, arg3); -} - -export function SetContext(arg1) { - return window['go']['autotagservice']['Service']['SetContext'](arg1); -} - -export function SetJobRegistry(arg1) { - return window['go']['autotagservice']['Service']['SetJobRegistry'](arg1); -} - -export function Skip(arg1) { - return window['go']['autotagservice']['Service']['Skip'](arg1); -} - -export function SplitMixedFolder(arg1) { - return window['go']['autotagservice']['Service']['SplitMixedFolder'](arg1); -} - -export function StartAutotagQueue(arg1) { - return window['go']['autotagservice']['Service']['StartAutotagQueue'](arg1); -} - -export function StartBackgroundPrefetch() { - return window['go']['autotagservice']['Service']['StartBackgroundPrefetch'](); -} - -export function WritesInFlight() { - return window['go']['autotagservice']['Service']['WritesInFlight'](); -} diff --git a/frontend/wailsjs/go/config/Config.d.ts b/frontend/wailsjs/go/config/Config.d.ts deleted file mode 100755 index 33d332f..0000000 --- a/frontend/wailsjs/go/config/Config.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {download} from '../models'; -import {tracklist} from '../models'; -import {context} from '../models'; - -export function GetDefaultPage():Promise; - -export function GetDownloadPreferences():Promise; - -export function GetFavoritesIconStyle():Promise; - -export function GetFavoritesPlaylistID():Promise; - -export function GetLibraryDirectory():Promise; - -export function GetPinDefaultPlaylist():Promise; - -export function GetQueueFallback():Promise; - -export function GetScanConcurrency():Promise; - -export function GetShortcuts():Promise>; - -export function GetThemeAccentColor():Promise; - -export function GetThemeBackgroundShade():Promise; - -export function GetTrackListColumns():Promise>; - -export function Load():Promise; - -export function ResetShortcuts():Promise; - -export function Save():Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetDefaultPage(arg1:string):Promise; - -export function SetDownloadPreferences(arg1:download.AutoDownloadPrefs):Promise; - -export function SetFavoritesIconStyle(arg1:string):Promise; - -export function SetFavoritesPlaylistID(arg1:number):Promise; - -export function SetLibraryDirectory(arg1:string):Promise; - -export function SetPinDefaultPlaylist(arg1:boolean):Promise; - -export function SetQueueFallback(arg1:string):Promise; - -export function SetScanConcurrency(arg1:string):Promise; - -export function SetShortcut(arg1:string,arg2:string):Promise; - -export function SetShortcuts(arg1:Record):Promise; - -export function SetThemeAccentColor(arg1:string):Promise; - -export function SetThemeBackgroundShade(arg1:string):Promise; - -export function SetTrackListColumns(arg1:Array):Promise; - -export function Validate():Promise; diff --git a/frontend/wailsjs/go/config/Config.js b/frontend/wailsjs/go/config/Config.js deleted file mode 100755 index f28c2b0..0000000 --- a/frontend/wailsjs/go/config/Config.js +++ /dev/null @@ -1,123 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function GetDefaultPage() { - return window['go']['config']['Config']['GetDefaultPage'](); -} - -export function GetDownloadPreferences() { - return window['go']['config']['Config']['GetDownloadPreferences'](); -} - -export function GetFavoritesIconStyle() { - return window['go']['config']['Config']['GetFavoritesIconStyle'](); -} - -export function GetFavoritesPlaylistID() { - return window['go']['config']['Config']['GetFavoritesPlaylistID'](); -} - -export function GetLibraryDirectory() { - return window['go']['config']['Config']['GetLibraryDirectory'](); -} - -export function GetPinDefaultPlaylist() { - return window['go']['config']['Config']['GetPinDefaultPlaylist'](); -} - -export function GetQueueFallback() { - return window['go']['config']['Config']['GetQueueFallback'](); -} - -export function GetScanConcurrency() { - return window['go']['config']['Config']['GetScanConcurrency'](); -} - -export function GetShortcuts() { - return window['go']['config']['Config']['GetShortcuts'](); -} - -export function GetThemeAccentColor() { - return window['go']['config']['Config']['GetThemeAccentColor'](); -} - -export function GetThemeBackgroundShade() { - return window['go']['config']['Config']['GetThemeBackgroundShade'](); -} - -export function GetTrackListColumns() { - return window['go']['config']['Config']['GetTrackListColumns'](); -} - -export function Load() { - return window['go']['config']['Config']['Load'](); -} - -export function ResetShortcuts() { - return window['go']['config']['Config']['ResetShortcuts'](); -} - -export function Save() { - return window['go']['config']['Config']['Save'](); -} - -export function SetContext(arg1) { - return window['go']['config']['Config']['SetContext'](arg1); -} - -export function SetDefaultPage(arg1) { - return window['go']['config']['Config']['SetDefaultPage'](arg1); -} - -export function SetDownloadPreferences(arg1) { - return window['go']['config']['Config']['SetDownloadPreferences'](arg1); -} - -export function SetFavoritesIconStyle(arg1) { - return window['go']['config']['Config']['SetFavoritesIconStyle'](arg1); -} - -export function SetFavoritesPlaylistID(arg1) { - return window['go']['config']['Config']['SetFavoritesPlaylistID'](arg1); -} - -export function SetLibraryDirectory(arg1) { - return window['go']['config']['Config']['SetLibraryDirectory'](arg1); -} - -export function SetPinDefaultPlaylist(arg1) { - return window['go']['config']['Config']['SetPinDefaultPlaylist'](arg1); -} - -export function SetQueueFallback(arg1) { - return window['go']['config']['Config']['SetQueueFallback'](arg1); -} - -export function SetScanConcurrency(arg1) { - return window['go']['config']['Config']['SetScanConcurrency'](arg1); -} - -export function SetShortcut(arg1, arg2) { - return window['go']['config']['Config']['SetShortcut'](arg1, arg2); -} - -export function SetShortcuts(arg1) { - return window['go']['config']['Config']['SetShortcuts'](arg1); -} - -export function SetThemeAccentColor(arg1) { - return window['go']['config']['Config']['SetThemeAccentColor'](arg1); -} - -export function SetThemeBackgroundShade(arg1) { - return window['go']['config']['Config']['SetThemeBackgroundShade'](arg1); -} - -export function SetTrackListColumns(arg1) { - return window['go']['config']['Config']['SetTrackListColumns'](arg1); -} - -export function Validate() { - return window['go']['config']['Config']['Validate'](); -} diff --git a/frontend/wailsjs/go/download/Service.d.ts b/frontend/wailsjs/go/download/Service.d.ts deleted file mode 100755 index c3875ab..0000000 --- a/frontend/wailsjs/go/download/Service.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {download} from '../models'; -import {context} from '../models'; - -export function AddProvider(arg1:string,arg2:string,arg3:Record):Promise; - -export function AddRequest(arg1:download.RequestInput):Promise; - -export function Cancel(arg1:string):Promise; - -export function Candidates(arg1:string):Promise>; - -export function ClearFinished():Promise; - -export function ClearSatisfiedRequests():Promise; - -export function DeleteProvider(arg1:number):Promise; - -export function ImportExternalRequests(arg1:number,arg2:number):Promise; - -export function IsRequested(arg1:string,arg2:number):Promise; - -export function ListDownloads(arg1:number):Promise>; - -export function ListProviders():Promise>; - -export function ListRequests():Promise>; - -export function PauseRequest(arg1:number,arg2:boolean):Promise; - -export function Pick(arg1:string,arg2:string):Promise; - -export function ProviderKinds():Promise>; - -export function ReconcileRequests():Promise; - -export function RemoveRequest(arg1:number):Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetPreferences(arg1:download.AutoDownloadPrefs):Promise; - -export function SetReconciler(arg1:download.Reconciler):Promise; - -export function StartDownload(arg1:download.SearchRequest):Promise; - -export function TestProvider(arg1:number):Promise; - -export function UpdateProvider(arg1:number,arg2:string,arg3:boolean,arg4:number,arg5:Record):Promise; diff --git a/frontend/wailsjs/go/download/Service.js b/frontend/wailsjs/go/download/Service.js deleted file mode 100755 index 4e41294..0000000 --- a/frontend/wailsjs/go/download/Service.js +++ /dev/null @@ -1,95 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AddProvider(arg1, arg2, arg3) { - return window['go']['download']['Service']['AddProvider'](arg1, arg2, arg3); -} - -export function AddRequest(arg1) { - return window['go']['download']['Service']['AddRequest'](arg1); -} - -export function Cancel(arg1) { - return window['go']['download']['Service']['Cancel'](arg1); -} - -export function Candidates(arg1) { - return window['go']['download']['Service']['Candidates'](arg1); -} - -export function ClearFinished() { - return window['go']['download']['Service']['ClearFinished'](); -} - -export function ClearSatisfiedRequests() { - return window['go']['download']['Service']['ClearSatisfiedRequests'](); -} - -export function DeleteProvider(arg1) { - return window['go']['download']['Service']['DeleteProvider'](arg1); -} - -export function ImportExternalRequests(arg1, arg2) { - return window['go']['download']['Service']['ImportExternalRequests'](arg1, arg2); -} - -export function IsRequested(arg1, arg2) { - return window['go']['download']['Service']['IsRequested'](arg1, arg2); -} - -export function ListDownloads(arg1) { - return window['go']['download']['Service']['ListDownloads'](arg1); -} - -export function ListProviders() { - return window['go']['download']['Service']['ListProviders'](); -} - -export function ListRequests() { - return window['go']['download']['Service']['ListRequests'](); -} - -export function PauseRequest(arg1, arg2) { - return window['go']['download']['Service']['PauseRequest'](arg1, arg2); -} - -export function Pick(arg1, arg2) { - return window['go']['download']['Service']['Pick'](arg1, arg2); -} - -export function ProviderKinds() { - return window['go']['download']['Service']['ProviderKinds'](); -} - -export function ReconcileRequests() { - return window['go']['download']['Service']['ReconcileRequests'](); -} - -export function RemoveRequest(arg1) { - return window['go']['download']['Service']['RemoveRequest'](arg1); -} - -export function SetContext(arg1) { - return window['go']['download']['Service']['SetContext'](arg1); -} - -export function SetPreferences(arg1) { - return window['go']['download']['Service']['SetPreferences'](arg1); -} - -export function SetReconciler(arg1) { - return window['go']['download']['Service']['SetReconciler'](arg1); -} - -export function StartDownload(arg1) { - return window['go']['download']['Service']['StartDownload'](arg1); -} - -export function TestProvider(arg1) { - return window['go']['download']['Service']['TestProvider'](arg1); -} - -export function UpdateProvider(arg1, arg2, arg3, arg4, arg5) { - return window['go']['download']['Service']['UpdateProvider'](arg1, arg2, arg3, arg4, arg5); -} diff --git a/frontend/wailsjs/go/explore/Service.d.ts b/frontend/wailsjs/go/explore/Service.d.ts deleted file mode 100755 index 15092ef..0000000 --- a/frontend/wailsjs/go/explore/Service.d.ts +++ /dev/null @@ -1,120 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {explore} from '../models'; -import {context} from '../models'; -import {time} from '../models'; -import {jobs} from '../models'; - -export function AdoptPausedIndexBuild():Promise; - -export function BackfillLibraryDiscographies():Promise; - -export function BackfillLibraryLyrics():Promise; - -export function BackfillReleaseGroupMBIDs():Promise; - -export function BrowseReleaseGroups(arg1:string):Promise>; - -export function BrowseReleases(arg1:string):Promise>; - -export function CAALimiter():Promise; - -export function CheckLibraryMBIDs(arg1:Array):Promise>; - -export function CoreCatalogImported():Promise; - -export function CoverArtGroupURL(arg1:string):Promise; - -export function CoverArtURL(arg1:string):Promise; - -export function GenerateMix(arg1:context.Context,arg2:Array,arg3:boolean):Promise>; - -export function GetArtistImageCached(arg1:string):Promise; - -export function GetArtistImageCachedPath(arg1:string):Promise; - -export function GetArtistImageURL(arg1:string):Promise; - -export function GetArtistImagesCachedPaths(arg1:Array):Promise>; - -export function GetArtistMBID(arg1:string):Promise; - -export function GetArtistPlayCount(arg1:string):Promise; - -export function GetCandidateThumbnail(arg1:string,arg2:string):Promise; - -export function GetExploreShelves():Promise; - -export function GetIndexStatus():Promise; - -export function GetLibrarySimilarArtists(arg1:string):Promise>; - -export function GetThumbnail(arg1:string,arg2:string,arg3:string):Promise; - -export function GetThumbnails(arg1:Array):Promise>; - -export function GetTrackLyrics(arg1:number):Promise; - -export function GetTrackThumbnail(arg1:string,arg2:string,arg3:string,arg4:string):Promise; - -export function GetTrackThumbnails(arg1:Array):Promise>; - -export function IndexBaselineSeries():Promise; - -export function IndexImportComplete():Promise; - -export function IndexLastImported():Promise; - -export function InvalidateIndexDiscographies():Promise; - -export function InvalidateLibrarySync():Promise; - -export function IsIndexReady():Promise; - -export function LookupArtist(arg1:string):Promise; - -export function LookupReleaseGroup(arg1:string):Promise; - -export function MusicBrainz():Promise; - -export function PopulateLocalCrossReferences():Promise; - -export function PopulateLocalCrossReferencesIfNeeded():Promise; - -export function PrefetchReleases(arg1:Array):Promise; - -export function PrepareIndexRebuild():Promise; - -export function RebuildLyricsIndex():Promise; - -export function RebuildLyricsIndexIfNeeded():Promise; - -export function RecordSearchClick(arg1:string,arg2:string,arg3:string):Promise; - -export function RefreshIndexNow(arg1:time.Duration):Promise; - -export function RefreshListenCounts():Promise; - -export function ResolveReleaseGroupMBIDs(arg1:Array):Promise>; - -export function SearchLocal(arg1:string):Promise; - -export function SearchLyrics(arg1:string):Promise>; - -export function SetAlbumComplete(arg1:explore.AlbumCompleteFunc):Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetJobRegistry(arg1:jobs.Registry):Promise; - -export function SimilarArtists(arg1:string):Promise>; - -export function StartIndexBuild():Promise; - -export function StopIndexBuild():Promise; - -export function TopRecordingsForArtist(arg1:string):Promise>; - -export function TopReleaseGroupsForArtist(arg1:string):Promise>; - -export function WaitForIndexIdle():Promise; diff --git a/frontend/wailsjs/go/explore/Service.js b/frontend/wailsjs/go/explore/Service.js deleted file mode 100755 index c54d63f..0000000 --- a/frontend/wailsjs/go/explore/Service.js +++ /dev/null @@ -1,231 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AdoptPausedIndexBuild() { - return window['go']['explore']['Service']['AdoptPausedIndexBuild'](); -} - -export function BackfillLibraryDiscographies() { - return window['go']['explore']['Service']['BackfillLibraryDiscographies'](); -} - -export function BackfillLibraryLyrics() { - return window['go']['explore']['Service']['BackfillLibraryLyrics'](); -} - -export function BackfillReleaseGroupMBIDs() { - return window['go']['explore']['Service']['BackfillReleaseGroupMBIDs'](); -} - -export function BrowseReleaseGroups(arg1) { - return window['go']['explore']['Service']['BrowseReleaseGroups'](arg1); -} - -export function BrowseReleases(arg1) { - return window['go']['explore']['Service']['BrowseReleases'](arg1); -} - -export function CAALimiter() { - return window['go']['explore']['Service']['CAALimiter'](); -} - -export function CheckLibraryMBIDs(arg1) { - return window['go']['explore']['Service']['CheckLibraryMBIDs'](arg1); -} - -export function CoreCatalogImported() { - return window['go']['explore']['Service']['CoreCatalogImported'](); -} - -export function CoverArtGroupURL(arg1) { - return window['go']['explore']['Service']['CoverArtGroupURL'](arg1); -} - -export function CoverArtURL(arg1) { - return window['go']['explore']['Service']['CoverArtURL'](arg1); -} - -export function GenerateMix(arg1, arg2, arg3) { - return window['go']['explore']['Service']['GenerateMix'](arg1, arg2, arg3); -} - -export function GetArtistImageCached(arg1) { - return window['go']['explore']['Service']['GetArtistImageCached'](arg1); -} - -export function GetArtistImageCachedPath(arg1) { - return window['go']['explore']['Service']['GetArtistImageCachedPath'](arg1); -} - -export function GetArtistImageURL(arg1) { - return window['go']['explore']['Service']['GetArtistImageURL'](arg1); -} - -export function GetArtistImagesCachedPaths(arg1) { - return window['go']['explore']['Service']['GetArtistImagesCachedPaths'](arg1); -} - -export function GetArtistMBID(arg1) { - return window['go']['explore']['Service']['GetArtistMBID'](arg1); -} - -export function GetArtistPlayCount(arg1) { - return window['go']['explore']['Service']['GetArtistPlayCount'](arg1); -} - -export function GetCandidateThumbnail(arg1, arg2) { - return window['go']['explore']['Service']['GetCandidateThumbnail'](arg1, arg2); -} - -export function GetExploreShelves() { - return window['go']['explore']['Service']['GetExploreShelves'](); -} - -export function GetIndexStatus() { - return window['go']['explore']['Service']['GetIndexStatus'](); -} - -export function GetLibrarySimilarArtists(arg1) { - return window['go']['explore']['Service']['GetLibrarySimilarArtists'](arg1); -} - -export function GetThumbnail(arg1, arg2, arg3) { - return window['go']['explore']['Service']['GetThumbnail'](arg1, arg2, arg3); -} - -export function GetThumbnails(arg1) { - return window['go']['explore']['Service']['GetThumbnails'](arg1); -} - -export function GetTrackLyrics(arg1) { - return window['go']['explore']['Service']['GetTrackLyrics'](arg1); -} - -export function GetTrackThumbnail(arg1, arg2, arg3, arg4) { - return window['go']['explore']['Service']['GetTrackThumbnail'](arg1, arg2, arg3, arg4); -} - -export function GetTrackThumbnails(arg1) { - return window['go']['explore']['Service']['GetTrackThumbnails'](arg1); -} - -export function IndexBaselineSeries() { - return window['go']['explore']['Service']['IndexBaselineSeries'](); -} - -export function IndexImportComplete() { - return window['go']['explore']['Service']['IndexImportComplete'](); -} - -export function IndexLastImported() { - return window['go']['explore']['Service']['IndexLastImported'](); -} - -export function InvalidateIndexDiscographies() { - return window['go']['explore']['Service']['InvalidateIndexDiscographies'](); -} - -export function InvalidateLibrarySync() { - return window['go']['explore']['Service']['InvalidateLibrarySync'](); -} - -export function IsIndexReady() { - return window['go']['explore']['Service']['IsIndexReady'](); -} - -export function LookupArtist(arg1) { - return window['go']['explore']['Service']['LookupArtist'](arg1); -} - -export function LookupReleaseGroup(arg1) { - return window['go']['explore']['Service']['LookupReleaseGroup'](arg1); -} - -export function MusicBrainz() { - return window['go']['explore']['Service']['MusicBrainz'](); -} - -export function PopulateLocalCrossReferences() { - return window['go']['explore']['Service']['PopulateLocalCrossReferences'](); -} - -export function PopulateLocalCrossReferencesIfNeeded() { - return window['go']['explore']['Service']['PopulateLocalCrossReferencesIfNeeded'](); -} - -export function PrefetchReleases(arg1) { - return window['go']['explore']['Service']['PrefetchReleases'](arg1); -} - -export function PrepareIndexRebuild() { - return window['go']['explore']['Service']['PrepareIndexRebuild'](); -} - -export function RebuildLyricsIndex() { - return window['go']['explore']['Service']['RebuildLyricsIndex'](); -} - -export function RebuildLyricsIndexIfNeeded() { - return window['go']['explore']['Service']['RebuildLyricsIndexIfNeeded'](); -} - -export function RecordSearchClick(arg1, arg2, arg3) { - return window['go']['explore']['Service']['RecordSearchClick'](arg1, arg2, arg3); -} - -export function RefreshIndexNow(arg1) { - return window['go']['explore']['Service']['RefreshIndexNow'](arg1); -} - -export function RefreshListenCounts() { - return window['go']['explore']['Service']['RefreshListenCounts'](); -} - -export function ResolveReleaseGroupMBIDs(arg1) { - return window['go']['explore']['Service']['ResolveReleaseGroupMBIDs'](arg1); -} - -export function SearchLocal(arg1) { - return window['go']['explore']['Service']['SearchLocal'](arg1); -} - -export function SearchLyrics(arg1) { - return window['go']['explore']['Service']['SearchLyrics'](arg1); -} - -export function SetAlbumComplete(arg1) { - return window['go']['explore']['Service']['SetAlbumComplete'](arg1); -} - -export function SetContext(arg1) { - return window['go']['explore']['Service']['SetContext'](arg1); -} - -export function SetJobRegistry(arg1) { - return window['go']['explore']['Service']['SetJobRegistry'](arg1); -} - -export function SimilarArtists(arg1) { - return window['go']['explore']['Service']['SimilarArtists'](arg1); -} - -export function StartIndexBuild() { - return window['go']['explore']['Service']['StartIndexBuild'](); -} - -export function StopIndexBuild() { - return window['go']['explore']['Service']['StopIndexBuild'](); -} - -export function TopRecordingsForArtist(arg1) { - return window['go']['explore']['Service']['TopRecordingsForArtist'](arg1); -} - -export function TopReleaseGroupsForArtist(arg1) { - return window['go']['explore']['Service']['TopReleaseGroupsForArtist'](arg1); -} - -export function WaitForIndexIdle() { - return window['go']['explore']['Service']['WaitForIndexIdle'](); -} diff --git a/frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts b/frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts deleted file mode 100755 index 3c75085..0000000 --- a/frontend/wailsjs/go/frontendutil/FrontendUtil.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {context} from '../models'; - -export function DirectoryPicker():Promise; - -export function ImageFilePicker():Promise; - -export function PlaylistFilePicker():Promise>; - -export function ReadFile(arg1:string):Promise>; - -export function SetContext(arg1:context.Context):Promise; diff --git a/frontend/wailsjs/go/frontendutil/FrontendUtil.js b/frontend/wailsjs/go/frontendutil/FrontendUtil.js deleted file mode 100755 index b0e52a4..0000000 --- a/frontend/wailsjs/go/frontendutil/FrontendUtil.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function DirectoryPicker() { - return window['go']['frontendutil']['FrontendUtil']['DirectoryPicker'](); -} - -export function ImageFilePicker() { - return window['go']['frontendutil']['FrontendUtil']['ImageFilePicker'](); -} - -export function PlaylistFilePicker() { - return window['go']['frontendutil']['FrontendUtil']['PlaylistFilePicker'](); -} - -export function ReadFile(arg1) { - return window['go']['frontendutil']['FrontendUtil']['ReadFile'](arg1); -} - -export function SetContext(arg1) { - return window['go']['frontendutil']['FrontendUtil']['SetContext'](arg1); -} diff --git a/frontend/wailsjs/go/jobs/Service.d.ts b/frontend/wailsjs/go/jobs/Service.d.ts deleted file mode 100755 index 8beaa9f..0000000 --- a/frontend/wailsjs/go/jobs/Service.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {jobs} from '../models'; - -export function CancelJob(arg1:string):Promise; - -export function ClearFinishedJobs():Promise; - -export function DismissJob(arg1:string):Promise; - -export function GetJobLog(arg1:string):Promise>; - -export function GetJobs():Promise>; - -export function PauseJob(arg1:string):Promise; - -export function ResumeJob(arg1:string):Promise; diff --git a/frontend/wailsjs/go/jobs/Service.js b/frontend/wailsjs/go/jobs/Service.js deleted file mode 100755 index 523f94c..0000000 --- a/frontend/wailsjs/go/jobs/Service.js +++ /dev/null @@ -1,31 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function CancelJob(arg1) { - return window['go']['jobs']['Service']['CancelJob'](arg1); -} - -export function ClearFinishedJobs() { - return window['go']['jobs']['Service']['ClearFinishedJobs'](); -} - -export function DismissJob(arg1) { - return window['go']['jobs']['Service']['DismissJob'](arg1); -} - -export function GetJobLog(arg1) { - return window['go']['jobs']['Service']['GetJobLog'](arg1); -} - -export function GetJobs() { - return window['go']['jobs']['Service']['GetJobs'](); -} - -export function PauseJob(arg1) { - return window['go']['jobs']['Service']['PauseJob'](arg1); -} - -export function ResumeJob(arg1) { - return window['go']['jobs']['Service']['ResumeJob'](arg1); -} diff --git a/frontend/wailsjs/go/library/Library.d.ts b/frontend/wailsjs/go/library/Library.d.ts deleted file mode 100755 index 49440c0..0000000 --- a/frontend/wailsjs/go/library/Library.d.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {sqlcgen} from '../models'; -import {library} from '../models'; -import {context} from '../models'; -import {jobs} from '../models'; - -export function AcquirePipelineLock():Promise; - -export function AddLibrary(arg1:string):Promise; - -export function CancelAllScans():Promise; - -export function CancelCurrentScan():Promise; - -export function CancelScan():Promise; - -export function FullRescan():Promise; - -export function GetAlbumCompleteness(arg1:number):Promise; - -export function GetAlbumTracks(arg1:number):Promise>; - -export function GetAlbumTracksByLibrary(arg1:number,arg2:number):Promise>; - -export function GetAlbumsByArtist(arg1:number):Promise>; - -export function GetAlbumsByArtistByLibrary(arg1:number,arg2:number):Promise>; - -export function GetAllAlbums():Promise>; - -export function GetAllAlbumsByLibrary(arg1:number):Promise>; - -export function GetAllArtists():Promise>; - -export function GetAllArtistsByLibrary(arg1:number):Promise>; - -export function GetAllGenresWithCounts():Promise>; - -export function GetAllGenresWithCountsByLibrary(arg1:number):Promise>; - -export function GetAllLibrariesWithTrackCounts():Promise>; - -export function GetAllTracks():Promise>; - -export function GetAllTracksByLibrary(arg1:number):Promise>; - -export function GetFilePathsByAlbums(arg1:Array,arg2:number):Promise>>; - -export function GetFilePathsByGenres(arg1:Array,arg2:number):Promise>>; - -export function GetFilePathsByRecordingMBIDs(arg1:Array,arg2:number):Promise>>; - -export function GetRemovalImpact(arg1:number):Promise; - -export function GetScanQueueLength():Promise; - -export function GetTrackMBIDs(arg1:string):Promise; - -export function GetTracksByGenre(arg1:string):Promise>; - -export function GetTracksByGenreByLibrary(arg1:string,arg2:number):Promise>; - -export function IsScanActive():Promise; - -export function IsScanPaused():Promise; - -export function LibraryPath(arg1:number):Promise; - -export function PauseScan():Promise; - -export function QueuedLibraryNames():Promise>; - -export function ReleasePipelineLock():Promise; - -export function RemoveFromLibrary(arg1:Array):Promise; - -export function RemoveLibrary(arg1:number):Promise; - -export function RenameLibrary(arg1:number,arg2:string):Promise; - -export function RestorePausedScans():Promise; - -export function ResumeScan():Promise; - -export function ScanAllLibraries():Promise; - -export function ScanLibrary(arg1:number):Promise; - -export function SearchTracks(arg1:string):Promise>; - -export function SearchTracksByLibrary(arg1:string,arg2:number):Promise>; - -export function SetContext(arg1:context.Context):Promise; - -export function SetJobRegistry(arg1:jobs.Registry):Promise; - -export function SetRemovalHooks(arg1:library.RemovalHooks):Promise; - -export function SetRescanHooks(arg1:library.RescanHooks):Promise; - -export function SetScanHooks(arg1:library.ScanHooks):Promise; - -export function SoftScanAllLibraries():Promise; diff --git a/frontend/wailsjs/go/library/Library.js b/frontend/wailsjs/go/library/Library.js deleted file mode 100755 index f8e8e46..0000000 --- a/frontend/wailsjs/go/library/Library.js +++ /dev/null @@ -1,199 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AcquirePipelineLock() { - return window['go']['library']['Library']['AcquirePipelineLock'](); -} - -export function AddLibrary(arg1) { - return window['go']['library']['Library']['AddLibrary'](arg1); -} - -export function CancelAllScans() { - return window['go']['library']['Library']['CancelAllScans'](); -} - -export function CancelCurrentScan() { - return window['go']['library']['Library']['CancelCurrentScan'](); -} - -export function CancelScan() { - return window['go']['library']['Library']['CancelScan'](); -} - -export function FullRescan() { - return window['go']['library']['Library']['FullRescan'](); -} - -export function GetAlbumCompleteness(arg1) { - return window['go']['library']['Library']['GetAlbumCompleteness'](arg1); -} - -export function GetAlbumTracks(arg1) { - return window['go']['library']['Library']['GetAlbumTracks'](arg1); -} - -export function GetAlbumTracksByLibrary(arg1, arg2) { - return window['go']['library']['Library']['GetAlbumTracksByLibrary'](arg1, arg2); -} - -export function GetAlbumsByArtist(arg1) { - return window['go']['library']['Library']['GetAlbumsByArtist'](arg1); -} - -export function GetAlbumsByArtistByLibrary(arg1, arg2) { - return window['go']['library']['Library']['GetAlbumsByArtistByLibrary'](arg1, arg2); -} - -export function GetAllAlbums() { - return window['go']['library']['Library']['GetAllAlbums'](); -} - -export function GetAllAlbumsByLibrary(arg1) { - return window['go']['library']['Library']['GetAllAlbumsByLibrary'](arg1); -} - -export function GetAllArtists() { - return window['go']['library']['Library']['GetAllArtists'](); -} - -export function GetAllArtistsByLibrary(arg1) { - return window['go']['library']['Library']['GetAllArtistsByLibrary'](arg1); -} - -export function GetAllGenresWithCounts() { - return window['go']['library']['Library']['GetAllGenresWithCounts'](); -} - -export function GetAllGenresWithCountsByLibrary(arg1) { - return window['go']['library']['Library']['GetAllGenresWithCountsByLibrary'](arg1); -} - -export function GetAllLibrariesWithTrackCounts() { - return window['go']['library']['Library']['GetAllLibrariesWithTrackCounts'](); -} - -export function GetAllTracks() { - return window['go']['library']['Library']['GetAllTracks'](); -} - -export function GetAllTracksByLibrary(arg1) { - return window['go']['library']['Library']['GetAllTracksByLibrary'](arg1); -} - -export function GetFilePathsByAlbums(arg1, arg2) { - return window['go']['library']['Library']['GetFilePathsByAlbums'](arg1, arg2); -} - -export function GetFilePathsByGenres(arg1, arg2) { - return window['go']['library']['Library']['GetFilePathsByGenres'](arg1, arg2); -} - -export function GetFilePathsByRecordingMBIDs(arg1, arg2) { - return window['go']['library']['Library']['GetFilePathsByRecordingMBIDs'](arg1, arg2); -} - -export function GetRemovalImpact(arg1) { - return window['go']['library']['Library']['GetRemovalImpact'](arg1); -} - -export function GetScanQueueLength() { - return window['go']['library']['Library']['GetScanQueueLength'](); -} - -export function GetTrackMBIDs(arg1) { - return window['go']['library']['Library']['GetTrackMBIDs'](arg1); -} - -export function GetTracksByGenre(arg1) { - return window['go']['library']['Library']['GetTracksByGenre'](arg1); -} - -export function GetTracksByGenreByLibrary(arg1, arg2) { - return window['go']['library']['Library']['GetTracksByGenreByLibrary'](arg1, arg2); -} - -export function IsScanActive() { - return window['go']['library']['Library']['IsScanActive'](); -} - -export function IsScanPaused() { - return window['go']['library']['Library']['IsScanPaused'](); -} - -export function LibraryPath(arg1) { - return window['go']['library']['Library']['LibraryPath'](arg1); -} - -export function PauseScan() { - return window['go']['library']['Library']['PauseScan'](); -} - -export function QueuedLibraryNames() { - return window['go']['library']['Library']['QueuedLibraryNames'](); -} - -export function ReleasePipelineLock() { - return window['go']['library']['Library']['ReleasePipelineLock'](); -} - -export function RemoveFromLibrary(arg1) { - return window['go']['library']['Library']['RemoveFromLibrary'](arg1); -} - -export function RemoveLibrary(arg1) { - return window['go']['library']['Library']['RemoveLibrary'](arg1); -} - -export function RenameLibrary(arg1, arg2) { - return window['go']['library']['Library']['RenameLibrary'](arg1, arg2); -} - -export function RestorePausedScans() { - return window['go']['library']['Library']['RestorePausedScans'](); -} - -export function ResumeScan() { - return window['go']['library']['Library']['ResumeScan'](); -} - -export function ScanAllLibraries() { - return window['go']['library']['Library']['ScanAllLibraries'](); -} - -export function ScanLibrary(arg1) { - return window['go']['library']['Library']['ScanLibrary'](arg1); -} - -export function SearchTracks(arg1) { - return window['go']['library']['Library']['SearchTracks'](arg1); -} - -export function SearchTracksByLibrary(arg1, arg2) { - return window['go']['library']['Library']['SearchTracksByLibrary'](arg1, arg2); -} - -export function SetContext(arg1) { - return window['go']['library']['Library']['SetContext'](arg1); -} - -export function SetJobRegistry(arg1) { - return window['go']['library']['Library']['SetJobRegistry'](arg1); -} - -export function SetRemovalHooks(arg1) { - return window['go']['library']['Library']['SetRemovalHooks'](arg1); -} - -export function SetRescanHooks(arg1) { - return window['go']['library']['Library']['SetRescanHooks'](arg1); -} - -export function SetScanHooks(arg1) { - return window['go']['library']['Library']['SetScanHooks'](arg1); -} - -export function SoftScanAllLibraries() { - return window['go']['library']['Library']['SoftScanAllLibraries'](); -} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts deleted file mode 100755 index 601ebe9..0000000 --- a/frontend/wailsjs/go/models.ts +++ /dev/null @@ -1,2534 +0,0 @@ -export namespace autotagservice { - - export class 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; - - static createFrom(source: any = {}) { - return new AlignmentView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.localIndex = source["localIndex"]; - this.localTitle = source["localTitle"]; - this.localLengthMillis = source["localLengthMillis"]; - this.candidatePosition = source["candidatePosition"]; - this.candidateDiscNumber = source["candidateDiscNumber"]; - this.candidateTitle = source["candidateTitle"]; - this.candidateMbid = source["candidateMbid"]; - this.candidateLength = source["candidateLength"]; - this.titleScore = source["titleScore"]; - this.lengthDeltaMs = source["lengthDeltaMs"]; - this.trackNumberOk = source["trackNumberOk"]; - this.status = source["status"]; - } - } - export class FailureView { - filePath: string; - error: string; - - static createFrom(source: any = {}) { - return new FailureView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.filePath = source["filePath"]; - this.error = source["error"]; - } - } - export class ApplyResultView { - groupKey: string; - succeeded: number; - failed: number; - failures: FailureView[]; - - static createFrom(source: any = {}) { - return new ApplyResultView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.groupKey = source["groupKey"]; - this.succeeded = source["succeeded"]; - this.failed = source["failed"]; - this.failures = this.convertValues(source["failures"], FailureView); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ScoreBreakdownView { - titleAvg: number; - lengthAvg: number; - artistFit: number; - albumFit: number; - trackCountFit: number; - releaseMeta: number; - evidence: number; - - static createFrom(source: any = {}) { - return new ScoreBreakdownView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.titleAvg = source["titleAvg"]; - this.lengthAvg = source["lengthAvg"]; - this.artistFit = source["artistFit"]; - this.albumFit = source["albumFit"]; - this.trackCountFit = source["trackCountFit"]; - this.releaseMeta = source["releaseMeta"]; - this.evidence = source["evidence"]; - } - } - export class 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[]; - - static createFrom(source: any = {}) { - return new CandidateView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.releaseMbid = source["releaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.title = source["title"]; - this.artistCredit = source["artistCredit"]; - this.date = source["date"]; - this.originalDate = source["originalDate"]; - this.country = source["country"]; - this.status = source["status"]; - this.primaryType = source["primaryType"]; - this.trackCount = source["trackCount"]; - this.score = source["score"]; - this.breakdown = this.convertValues(source["breakdown"], ScoreBreakdownView); - this.source = source["source"]; - this.provenance = source["provenance"]; - this.coverArtUrl = source["coverArtUrl"]; - this.alignments = this.convertValues(source["alignments"], AlignmentView); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - export class LocalTrackView { - audioFileId: number; - filePath: string; - title: string; - artist: string; - trackNumber: number; - discNumber: number; - lengthMillis: number; - recordingMbid: string; - - static createFrom(source: any = {}) { - return new LocalTrackView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.audioFileId = source["audioFileId"]; - this.filePath = source["filePath"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.trackNumber = source["trackNumber"]; - this.discNumber = source["discNumber"]; - this.lengthMillis = source["lengthMillis"]; - this.recordingMbid = source["recordingMbid"]; - } - } - export class PendingItem { - groupKey: string; - libraryId: number; - libraryName: string; - folderSubPath: string; - trackCount: number; - albumName: string; - albumArtist: string; - discNumber: number; - bestMatchReleaseMbid: string; - score: number; - status: string; - synthetic: boolean; - likelyMixedBag: boolean; - - static createFrom(source: any = {}) { - return new PendingItem(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.groupKey = source["groupKey"]; - this.libraryId = source["libraryId"]; - this.libraryName = source["libraryName"]; - this.folderSubPath = source["folderSubPath"]; - this.trackCount = source["trackCount"]; - this.albumName = source["albumName"]; - this.albumArtist = source["albumArtist"]; - this.discNumber = source["discNumber"]; - this.bestMatchReleaseMbid = source["bestMatchReleaseMbid"]; - this.score = source["score"]; - this.status = source["status"]; - this.synthetic = source["synthetic"]; - this.likelyMixedBag = source["likelyMixedBag"]; - } - } - - export class ScoreView { - groupKey: string; - localTracks: LocalTrackView[]; - candidates: CandidateView[]; - recommendation: string; - mixedBag: boolean; - synthetic: boolean; - - static createFrom(source: any = {}) { - return new ScoreView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.groupKey = source["groupKey"]; - this.localTracks = this.convertValues(source["localTracks"], LocalTrackView); - this.candidates = this.convertValues(source["candidates"], CandidateView); - this.recommendation = source["recommendation"]; - this.mixedBag = source["mixedBag"]; - this.synthetic = source["synthetic"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class SearchHitView { - mbid: string; - kind: string; - title: string; - artist: string; - detail: string; - - static createFrom(source: any = {}) { - return new SearchHitView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.kind = source["kind"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.detail = source["detail"]; - } - } - -} - -export namespace download { - - export class AutoDownloadPrefs { - minSizeMb: number; - maxSizeMb: number; - preferredSizeMb: number; - allowedFormats: string[]; - - static createFrom(source: any = {}) { - return new AutoDownloadPrefs(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.minSizeMb = source["minSizeMb"]; - this.maxSizeMb = source["maxSizeMb"]; - this.preferredSizeMb = source["preferredSizeMb"]; - this.allowedFormats = source["allowedFormats"]; - } - } - export class QualityScore { - overall: number; - formatRank: number; - bitrate: number; - health: number; - priority: number; - sizeFit: number; - mixed: boolean; - - static createFrom(source: any = {}) { - return new QualityScore(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.overall = source["overall"]; - this.formatRank = source["formatRank"]; - this.bitrate = source["bitrate"]; - this.health = source["health"]; - this.priority = source["priority"]; - this.sizeFit = source["sizeFit"]; - this.mixed = source["mixed"]; - } - } - export class MatchScore { - overall: number; - titleFit: number; - artistFit: number; - albumFit: number; - completeness: number; - anchored: boolean; - - static createFrom(source: any = {}) { - return new MatchScore(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.overall = source["overall"]; - this.titleFit = source["titleFit"]; - this.artistFit = source["artistFit"]; - this.albumFit = source["albumFit"]; - this.completeness = source["completeness"]; - this.anchored = source["anchored"]; - } - } - export class CandidateFile { - path: string; - size: number; - format: string; - bitrate?: number; - isAudio: boolean; - matchedTo?: number; - - static createFrom(source: any = {}) { - return new CandidateFile(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.path = source["path"]; - this.size = source["size"]; - this.format = source["format"]; - this.bitrate = source["bitrate"]; - this.isAudio = source["isAudio"]; - this.matchedTo = source["matchedTo"]; - } - } - export class Candidate { - id: string; - providerId: number; - kind: string; - protocol: string; - title: string; - artist?: string; - origin?: string; - files: CandidateFile[]; - totalSize: number; - health: number; - match: MatchScore; - quality: QualityScore; - score: number; - - static createFrom(source: any = {}) { - return new Candidate(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.providerId = source["providerId"]; - this.kind = source["kind"]; - this.protocol = source["protocol"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.origin = source["origin"]; - this.files = this.convertValues(source["files"], CandidateFile); - this.totalSize = source["totalSize"]; - this.health = source["health"]; - this.match = this.convertValues(source["match"], MatchScore); - this.quality = this.convertValues(source["quality"], QualityScore); - this.score = source["score"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - export class Caps { - canSearch: boolean; - canTransport: boolean; - canDelegate: boolean; - canList: boolean; - canResume: boolean; - canCancel: boolean; - reportsSize: boolean; - transports: string[]; - - static createFrom(source: any = {}) { - return new Caps(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.canSearch = source["canSearch"]; - this.canTransport = source["canTransport"]; - this.canDelegate = source["canDelegate"]; - this.canList = source["canList"]; - this.canResume = source["canResume"]; - this.canCancel = source["canCancel"]; - this.reportsSize = source["reportsSize"]; - this.transports = source["transports"]; - } - } - export class Config { - id: number; - kind: string; - name: string; - enabled: boolean; - priority: number; - settings: Record; - setSecrets?: Record; - - static createFrom(source: any = {}) { - return new Config(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.kind = source["kind"]; - this.name = source["name"]; - this.enabled = source["enabled"]; - this.priority = source["priority"]; - this.settings = source["settings"]; - this.setSecrets = source["setSecrets"]; - } - } - export class Field { - key: string; - label: string; - placeholder?: string; - help?: string; - secret: boolean; - path: boolean; - required: boolean; - default?: string; - - static createFrom(source: any = {}) { - return new Field(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.key = source["key"]; - this.label = source["label"]; - this.placeholder = source["placeholder"]; - this.help = source["help"]; - this.secret = source["secret"]; - this.path = source["path"]; - this.required = source["required"]; - this.default = source["default"]; - } - } - export class Descriptor { - kind: string; - name: string; - summary: string; - caps: Caps; - fields: Field[]; - requiresExternal?: string; - - static createFrom(source: any = {}) { - return new Descriptor(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.kind = source["kind"]; - this.name = source["name"]; - this.summary = source["summary"]; - this.caps = this.convertValues(source["caps"], Caps); - this.fields = this.convertValues(source["fields"], Field); - this.requiresExternal = source["requiresExternal"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class DownloadItem { - id: string; - downloadId: string; - providerId: number; - transportId?: number; - externalId?: string; - candidate: Candidate; - state: string; - bytesDone: number; - bytesTotal: number; - error?: string; - createdAt: time.Time; - updatedAt: time.Time; - - static createFrom(source: any = {}) { - return new DownloadItem(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.downloadId = source["downloadId"]; - this.providerId = source["providerId"]; - this.transportId = source["transportId"]; - this.externalId = source["externalId"]; - this.candidate = this.convertValues(source["candidate"], Candidate); - this.state = source["state"]; - this.bytesDone = source["bytesDone"]; - this.bytesTotal = source["bytesTotal"]; - this.error = source["error"]; - this.createdAt = this.convertValues(source["createdAt"], time.Time); - this.updatedAt = this.convertValues(source["updatedAt"], time.Time); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ExpectedTrack { - position: number; - discNumber: number; - title: string; - artist: string; - lengthMillis: number; - - static createFrom(source: any = {}) { - return new ExpectedTrack(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.position = source["position"]; - this.discNumber = source["discNumber"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.lengthMillis = source["lengthMillis"]; - } - } - export class DownloadView { - id: string; - releaseMbid?: string; - releaseGroupMbid?: string; - recordingMbid?: string; - requestId?: number; - source?: string; - artist: string; - album: string; - query?: string; - expected?: ExpectedTrack[]; - libraryId: number; - createdAt: time.Time; - state: string; - error?: string; - items: DownloadItem[]; - - static createFrom(source: any = {}) { - return new DownloadView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.releaseMbid = source["releaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.recordingMbid = source["recordingMbid"]; - this.requestId = source["requestId"]; - this.source = source["source"]; - this.artist = source["artist"]; - this.album = source["album"]; - this.query = source["query"]; - this.expected = this.convertValues(source["expected"], ExpectedTrack); - this.libraryId = source["libraryId"]; - this.createdAt = this.convertValues(source["createdAt"], time.Time); - this.state = source["state"]; - this.error = source["error"]; - this.items = this.convertValues(source["items"], DownloadItem); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - - - - export class Reconciler { - - - static createFrom(source: any = {}) { - return new Reconciler(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - export class Request { - id: number; - mbid: string; - entity: string; - libraryId: number; - artist: string; - title: string; - scope: string; - secondary: boolean; - state: string; - parentId?: number; - attempts: number; - lastError?: string; - lastTriedAt?: time.Time; - nextTryAt?: time.Time; - externalIds?: Record; - createdAt: time.Time; - updatedAt: time.Time; - - static createFrom(source: any = {}) { - return new Request(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.mbid = source["mbid"]; - this.entity = source["entity"]; - this.libraryId = source["libraryId"]; - this.artist = source["artist"]; - this.title = source["title"]; - this.scope = source["scope"]; - this.secondary = source["secondary"]; - this.state = source["state"]; - this.parentId = source["parentId"]; - this.attempts = source["attempts"]; - this.lastError = source["lastError"]; - this.lastTriedAt = this.convertValues(source["lastTriedAt"], time.Time); - this.nextTryAt = this.convertValues(source["nextTryAt"], time.Time); - this.externalIds = source["externalIds"]; - this.createdAt = this.convertValues(source["createdAt"], time.Time); - this.updatedAt = this.convertValues(source["updatedAt"], time.Time); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class RequestInput { - mbid: string; - entity: string; - libraryId: number; - artist: string; - title: string; - scope: string; - secondary: boolean; - - static createFrom(source: any = {}) { - return new RequestInput(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.entity = source["entity"]; - this.libraryId = source["libraryId"]; - this.artist = source["artist"]; - this.title = source["title"]; - this.scope = source["scope"]; - this.secondary = source["secondary"]; - } - } - export class SearchRequest { - libraryId: number; - releaseMbid: string; - releaseGroupMbid: string; - artist: string; - album: string; - query: string; - expected: ExpectedTrack[]; - - static createFrom(source: any = {}) { - return new SearchRequest(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.libraryId = source["libraryId"]; - this.releaseMbid = source["releaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.artist = source["artist"]; - this.album = source["album"]; - this.query = source["query"]; - this.expected = this.convertValues(source["expected"], ExpectedTrack); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class StartResult { - downloadId: string; - candidates: Candidate[]; - autoPicked: boolean; - - static createFrom(source: any = {}) { - return new StartResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.downloadId = source["downloadId"]; - this.candidates = this.convertValues(source["candidates"], Candidate); - this.autoPicked = source["autoPicked"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class Summary { - expanded: number; - satisfied: number; - attempted: number; - started: number; - synced: number; - waiting: number; - noProviders: boolean; - - static createFrom(source: any = {}) { - return new Summary(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.expanded = source["expanded"]; - this.satisfied = source["satisfied"]; - this.attempted = source["attempted"]; - this.started = source["started"]; - this.synced = source["synced"]; - this.waiting = source["waiting"]; - this.noProviders = source["noProviders"]; - } - } - -} - -export namespace explore { - - export class TierStatus { - name: string; - state: string; - total: number; - completed: number; - error?: string; - detail?: string; - - static createFrom(source: any = {}) { - return new TierStatus(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.state = source["state"]; - this.total = source["total"]; - this.completed = source["completed"]; - this.error = source["error"]; - this.detail = source["detail"]; - } - } - export class IndexStatus { - building: boolean; - ready: boolean; - lastBuilt?: string; - tiers: TierStatus[]; - artists: number; - recordings: number; - releaseGroups: number; - totalRows: number; - - static createFrom(source: any = {}) { - return new IndexStatus(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.building = source["building"]; - this.ready = source["ready"]; - this.lastBuilt = source["lastBuilt"]; - this.tiers = this.convertValues(source["tiers"], TierStatus); - this.artists = source["artists"]; - this.recordings = source["recordings"]; - this.releaseGroups = source["releaseGroups"]; - this.totalRows = source["totalRows"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class LBSimilarArtist { - artistMbid: string; - name: string; - score: number; - - static createFrom(source: any = {}) { - return new LBSimilarArtist(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.artistMbid = source["artistMbid"]; - this.name = source["name"]; - this.score = source["score"]; - } - } - export class LBTopRecording { - recordingMbid: string; - artistName: string; - trackName: string; - totalListenCount: number; - caaReleaseMbid: string; - releaseGroupMbid?: string; - releaseName: string; - length: number; - inLibrary: boolean; - localId?: number; - - static createFrom(source: any = {}) { - return new LBTopRecording(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.recordingMbid = source["recordingMbid"]; - this.artistName = source["artistName"]; - this.trackName = source["trackName"]; - this.totalListenCount = source["totalListenCount"]; - this.caaReleaseMbid = source["caaReleaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.releaseName = source["releaseName"]; - this.length = source["length"]; - this.inLibrary = source["inLibrary"]; - this.localId = source["localId"]; - } - } - export class LBTopReleaseGroup { - releaseGroupMbid: string; - title: string; - artistName: string; - type: string; - date: string; - totalListenCount: number; - caaReleaseMbid: string; - inLibrary: boolean; - localId?: number; - - static createFrom(source: any = {}) { - return new LBTopReleaseGroup(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.title = source["title"]; - this.artistName = source["artistName"]; - this.type = source["type"]; - this.date = source["date"]; - this.totalListenCount = source["totalListenCount"]; - this.caaReleaseMbid = source["caaReleaseMbid"]; - this.inLibrary = source["inLibrary"]; - this.localId = source["localId"]; - } - } - export class LyricsResult { - recordingId: number; - filePath: string; - lengthMs: number; - title: string; - artist: string; - album: string; - - static createFrom(source: any = {}) { - return new LyricsResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.recordingId = source["recordingId"]; - this.filePath = source["filePath"]; - this.lengthMs = source["lengthMs"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.album = source["album"]; - } - } - export class MBArtist { - mbid: string; - name: string; - sortName: string; - englishName?: string; - type: string; - country: string; - disambiguation: string; - score: number; - popularity: number; - listenerCount: number; - inLibrary: boolean; - localId?: number; - - static createFrom(source: any = {}) { - return new MBArtist(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.name = source["name"]; - this.sortName = source["sortName"]; - this.englishName = source["englishName"]; - this.type = source["type"]; - this.country = source["country"]; - this.disambiguation = source["disambiguation"]; - this.score = source["score"]; - this.popularity = source["popularity"]; - this.listenerCount = source["listenerCount"]; - this.inLibrary = source["inLibrary"]; - this.localId = source["localId"]; - } - } - export class MBRecording { - mbid: string; - title: string; - length: number; - artistCredit: string; - artistMbid?: string; - score: number; - popularity: number; - listenerCount: number; - caaReleaseMbid?: string; - releaseGroupMbid?: string; - releaseName?: string; - inLibrary: boolean; - localId?: number; - - static createFrom(source: any = {}) { - return new MBRecording(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.title = source["title"]; - this.length = source["length"]; - this.artistCredit = source["artistCredit"]; - this.artistMbid = source["artistMbid"]; - this.score = source["score"]; - this.popularity = source["popularity"]; - this.listenerCount = source["listenerCount"]; - this.caaReleaseMbid = source["caaReleaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.releaseName = source["releaseName"]; - this.inLibrary = source["inLibrary"]; - this.localId = source["localId"]; - } - } - export class MBTrack { - position: number; - discNumber: number; - title: string; - length: number; - mbid: string; - inLibrary: boolean; - localId?: number; - - static createFrom(source: any = {}) { - return new MBTrack(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.position = source["position"]; - this.discNumber = source["discNumber"]; - this.title = source["title"]; - this.length = source["length"]; - this.mbid = source["mbid"]; - this.inLibrary = source["inLibrary"]; - this.localId = source["localId"]; - } - } - export class MBRelease { - mbid: string; - title: string; - date: string; - country: string; - status: string; - artistCredit?: string; - tracks?: MBTrack[]; - releaseGroupMbid?: string; - - static createFrom(source: any = {}) { - return new MBRelease(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.title = source["title"]; - this.date = source["date"]; - this.country = source["country"]; - this.status = source["status"]; - this.artistCredit = source["artistCredit"]; - this.tracks = this.convertValues(source["tracks"], MBTrack); - this.releaseGroupMbid = source["releaseGroupMbid"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class MBReleaseGroup { - mbid: string; - title: string; - primaryType: string; - secondaryTypes?: string[]; - firstReleaseDate: string; - artistCredit: string; - artistMbid?: string; - popularity: number; - listenerCount: number; - inLibrary: boolean; - localId?: number; - - static createFrom(source: any = {}) { - return new MBReleaseGroup(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.title = source["title"]; - this.primaryType = source["primaryType"]; - this.secondaryTypes = source["secondaryTypes"]; - this.firstReleaseDate = source["firstReleaseDate"]; - this.artistCredit = source["artistCredit"]; - this.artistMbid = source["artistMbid"]; - this.popularity = source["popularity"]; - this.listenerCount = source["listenerCount"]; - this.inLibrary = source["inLibrary"]; - this.localId = source["localId"]; - } - } - export class TopResult { - entityType: string; - mbid: string; - name: string; - artistCredit?: string; - artistMbid?: string; - intentScore: number; - artistType?: string; - country?: string; - primaryType?: string; - year?: string; - length?: number; - caaReleaseMbid?: string; - releaseGroupMbid?: string; - releaseName?: string; - inLibrary: boolean; - - static createFrom(source: any = {}) { - return new TopResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.entityType = source["entityType"]; - this.mbid = source["mbid"]; - this.name = source["name"]; - this.artistCredit = source["artistCredit"]; - this.artistMbid = source["artistMbid"]; - this.intentScore = source["intentScore"]; - this.artistType = source["artistType"]; - this.country = source["country"]; - this.primaryType = source["primaryType"]; - this.year = source["year"]; - this.length = source["length"]; - this.caaReleaseMbid = source["caaReleaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.releaseName = source["releaseName"]; - this.inLibrary = source["inLibrary"]; - } - } - export class MBSearchResult { - artists?: MBArtist[]; - releaseGroups?: MBReleaseGroup[]; - recordings?: MBRecording[]; - topResults?: TopResult[]; - - static createFrom(source: any = {}) { - return new MBSearchResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.artists = this.convertValues(source["artists"], MBArtist); - this.releaseGroups = this.convertValues(source["releaseGroups"], MBReleaseGroup); - this.recordings = this.convertValues(source["recordings"], MBRecording); - this.topResults = this.convertValues(source["topResults"], TopResult); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - export class MusicBrainzClient { - - - static createFrom(source: any = {}) { - return new MusicBrainzClient(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - export class RateLimiter { - - - static createFrom(source: any = {}) { - return new RateLimiter(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - export class Shelf { - id: string; - kind: string; - title: string; - subtitle: string; - albums?: MBReleaseGroup[]; - artists?: MBArtist[]; - - static createFrom(source: any = {}) { - return new Shelf(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.kind = source["kind"]; - this.title = source["title"]; - this.subtitle = source["subtitle"]; - this.albums = this.convertValues(source["albums"], MBReleaseGroup); - this.artists = this.convertValues(source["artists"], MBArtist); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ShelfPage { - shelves: Shelf[]; - state: string; - - static createFrom(source: any = {}) { - return new ShelfPage(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.shelves = this.convertValues(source["shelves"], Shelf); - this.state = source["state"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ThumbnailRequest { - mbid: string; - albumName: string; - artistName: string; - - static createFrom(source: any = {}) { - return new ThumbnailRequest(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.mbid = source["mbid"]; - this.albumName = source["albumName"]; - this.artistName = source["artistName"]; - } - } - - - export class TrackLyrics { - plain: string; - synced: string; - instrumental: boolean; - source: string; - - static createFrom(source: any = {}) { - return new TrackLyrics(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.plain = source["plain"]; - this.synced = source["synced"]; - this.instrumental = source["instrumental"]; - this.source = source["source"]; - } - } - export class TrackThumbnailRequest { - key: string; - releaseMbid: string; - releaseGroupMbid: string; - albumName: string; - artistName: string; - - static createFrom(source: any = {}) { - return new TrackThumbnailRequest(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.key = source["key"]; - this.releaseMbid = source["releaseMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.albumName = source["albumName"]; - this.artistName = source["artistName"]; - } - } - -} - -export namespace home { - - export class Shelf { - id: string; - kind: string; - title: string; - subtitle: string; - albums: library.Album[]; - - static createFrom(source: any = {}) { - return new Shelf(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.kind = source["kind"]; - this.title = source["title"]; - this.subtitle = source["subtitle"]; - this.albums = this.convertValues(source["albums"], library.Album); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - -export namespace jobs { - - export class Caps { - pausable: boolean; - cancellable: boolean; - - static createFrom(source: any = {}) { - return new Caps(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pausable = source["pausable"]; - this.cancellable = source["cancellable"]; - } - } - export class Stat { - label: string; - value: string; - - static createFrom(source: any = {}) { - return new Stat(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.label = source["label"]; - this.value = source["value"]; - } - } - export class Stage { - name: string; - state: string; - current: number; - total: number; - error?: string; - - static createFrom(source: any = {}) { - return new Stage(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.state = source["state"]; - this.current = source["current"]; - this.total = source["total"]; - this.error = source["error"]; - } - } - export class Job { - id: string; - kind: string; - title: string; - subtitle?: string; - state: string; - phase?: string; - current: number; - total: number; - caps: Caps; - stages: Stage[]; - stats: Stat[]; - error?: string; - startedAt: number; - updatedAt: number; - endedAt?: number; - logCount: number; - warnCount: number; - errorCount: number; - - static createFrom(source: any = {}) { - return new Job(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.kind = source["kind"]; - this.title = source["title"]; - this.subtitle = source["subtitle"]; - this.state = source["state"]; - this.phase = source["phase"]; - this.current = source["current"]; - this.total = source["total"]; - this.caps = this.convertValues(source["caps"], Caps); - this.stages = this.convertValues(source["stages"], Stage); - this.stats = this.convertValues(source["stats"], Stat); - this.error = source["error"]; - this.startedAt = source["startedAt"]; - this.updatedAt = source["updatedAt"]; - this.endedAt = source["endedAt"]; - this.logCount = source["logCount"]; - this.warnCount = source["warnCount"]; - this.errorCount = source["errorCount"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class LogEntry { - time: number; - level: string; - message: string; - detail?: string; - - static createFrom(source: any = {}) { - return new LogEntry(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.time = source["time"]; - this.level = source["level"]; - this.message = source["message"]; - this.detail = source["detail"]; - } - } - export class Registry { - - - static createFrom(source: any = {}) { - return new Registry(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - - -} - -export namespace library { - - export class Album { - ID: number; - Name: string; - ArtistName: string; - ArtistMBID: string; - MBID: string; - CoverArtPath: string; - CoverArtSmall: string; - CoverArtMedium: string; - CoverArtLarge: string; - Year: number; - ReleaseYear: number; - - static createFrom(source: any = {}) { - return new Album(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.ID = source["ID"]; - this.Name = source["Name"]; - this.ArtistName = source["ArtistName"]; - this.ArtistMBID = source["ArtistMBID"]; - this.MBID = source["MBID"]; - this.CoverArtPath = source["CoverArtPath"]; - this.CoverArtSmall = source["CoverArtSmall"]; - this.CoverArtMedium = source["CoverArtMedium"]; - this.CoverArtLarge = source["CoverArtLarge"]; - this.Year = source["Year"]; - this.ReleaseYear = source["ReleaseYear"]; - } - } - export class AlbumCompleteness { - owned: number; - expected: number; - known: boolean; - complete: boolean; - - static createFrom(source: any = {}) { - return new AlbumCompleteness(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.owned = source["owned"]; - this.expected = source["expected"]; - this.known = source["known"]; - this.complete = source["complete"]; - } - } - export class Artist { - ID: number; - Name: string; - MBID: string; - ImageSmall: string; - ImageMedium: string; - ImageLarge: string; - - static createFrom(source: any = {}) { - return new Artist(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.ID = source["ID"]; - this.Name = source["Name"]; - this.MBID = source["MBID"]; - this.ImageSmall = source["ImageSmall"]; - this.ImageMedium = source["ImageMedium"]; - this.ImageLarge = source["ImageLarge"]; - } - } - export class GenreWithCount { - Name: string; - TrackCount: number; - - static createFrom(source: any = {}) { - return new GenreWithCount(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Name = source["Name"]; - this.TrackCount = source["TrackCount"]; - } - } - export class Info { - id: number; - name: string; - path: string; - trackCount: number; - - static createFrom(source: any = {}) { - return new Info(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.name = source["name"]; - this.path = source["path"]; - this.trackCount = source["trackCount"]; - } - } - export class RemovalHooks { - - - static createFrom(source: any = {}) { - return new RemovalHooks(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - export class RemovalImpact { - trackCount: number; - playlistsAffected: number; - queueItemCount: number; - - static createFrom(source: any = {}) { - return new RemovalImpact(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.trackCount = source["trackCount"]; - this.playlistsAffected = source["playlistsAffected"]; - this.queueItemCount = source["queueItemCount"]; - } - } - export class RemovalResult { - tracksRemoved: number; - pathsExcluded: number; - - static createFrom(source: any = {}) { - return new RemovalResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.tracksRemoved = source["tracksRemoved"]; - this.pathsExcluded = source["pathsExcluded"]; - } - } - export class RemovalSummary { - tracksDeleted: number; - artistsRemoved: number; - albumsRemoved: number; - genresRemoved: number; - playlistsAffected: number; - queueItemsRemoved: number; - - static createFrom(source: any = {}) { - return new RemovalSummary(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.tracksDeleted = source["tracksDeleted"]; - this.artistsRemoved = source["artistsRemoved"]; - this.albumsRemoved = source["albumsRemoved"]; - this.genresRemoved = source["genresRemoved"]; - this.playlistsAffected = source["playlistsAffected"]; - this.queueItemsRemoved = source["queueItemsRemoved"]; - } - } - export class RescanHooks { - - - static createFrom(source: any = {}) { - return new RescanHooks(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - export class ScanHooks { - - - static createFrom(source: any = {}) { - return new ScanHooks(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - export class ScanWarning { - filePath: string; - phase: string; - err: string; - - static createFrom(source: any = {}) { - return new ScanWarning(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.filePath = source["filePath"]; - this.phase = source["phase"]; - this.err = source["err"]; - } - } - export class ScanMetrics { - total: number; - loadExisting: number; - walkDuration: number; - extractionWallClock: number; - dbWritesWallClock: number; - orphanCleanup: number; - postScanVariants: number; - formatExtraction: Record; - formatCount: Record; - tagExtraction: number; - durationExtraction: number; - batchCommits: number; - coverArtSave: number; - thumbnailWallClock: number; - thumbnailGeneration: number; - thumbnailSmall: number; - thumbnailMedium: number; - thumbnailLarge: number; - clearQueue: number; - clearDatabase: number; - clearCoverFiles: number; - added: number; - updated: number; - skipped: number; - removed: number; - cancelled: boolean; - libraryId: number; - libraryName: string; - warnings: ScanWarning[]; - - static createFrom(source: any = {}) { - return new ScanMetrics(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.total = source["total"]; - this.loadExisting = source["loadExisting"]; - this.walkDuration = source["walkDuration"]; - this.extractionWallClock = source["extractionWallClock"]; - this.dbWritesWallClock = source["dbWritesWallClock"]; - this.orphanCleanup = source["orphanCleanup"]; - this.postScanVariants = source["postScanVariants"]; - this.formatExtraction = source["formatExtraction"]; - this.formatCount = source["formatCount"]; - this.tagExtraction = source["tagExtraction"]; - this.durationExtraction = source["durationExtraction"]; - this.batchCommits = source["batchCommits"]; - this.coverArtSave = source["coverArtSave"]; - this.thumbnailWallClock = source["thumbnailWallClock"]; - this.thumbnailGeneration = source["thumbnailGeneration"]; - this.thumbnailSmall = source["thumbnailSmall"]; - this.thumbnailMedium = source["thumbnailMedium"]; - this.thumbnailLarge = source["thumbnailLarge"]; - this.clearQueue = source["clearQueue"]; - this.clearDatabase = source["clearDatabase"]; - this.clearCoverFiles = source["clearCoverFiles"]; - this.added = source["added"]; - this.updated = source["updated"]; - this.skipped = source["skipped"]; - this.removed = source["removed"]; - this.cancelled = source["cancelled"]; - this.libraryId = source["libraryId"]; - this.libraryName = source["libraryName"]; - this.warnings = this.convertValues(source["warnings"], ScanWarning); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - export class Track { - TrackName: string; - ArtistName: string; - TrackLength: string; - FilePath: string; - TrackNumber: number; - DiscNumber: number; - Album: string; - Genre: string[]; - 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; - - static createFrom(source: any = {}) { - return new Track(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.TrackName = source["TrackName"]; - this.ArtistName = source["ArtistName"]; - this.TrackLength = source["TrackLength"]; - this.FilePath = source["FilePath"]; - this.TrackNumber = source["TrackNumber"]; - this.DiscNumber = source["DiscNumber"]; - this.Album = source["Album"]; - this.Genre = source["Genre"]; - this.Year = source["Year"]; - this.Composer = source["Composer"]; - this.FileType = source["FileType"]; - this.SampleRate = source["SampleRate"]; - this.BitDepth = source["BitDepth"]; - this.Channels = source["Channels"]; - this.Bitrate = source["Bitrate"]; - this.FileSize = source["FileSize"]; - this.PlayCount = source["PlayCount"]; - this.LastPlayed = source["LastPlayed"]; - this.RecordingMBID = source["RecordingMBID"]; - this.ArtistMBID = source["ArtistMBID"]; - this.ReleaseGroupMBID = source["ReleaseGroupMBID"]; - this.CoverArtPath = source["CoverArtPath"]; - this.CoverArtSmall = source["CoverArtSmall"]; - this.CoverArtMedium = source["CoverArtMedium"]; - this.CoverArtLarge = source["CoverArtLarge"]; - } - } - export class TrackMBIDs { - recordingMbid: string; - releaseGroupMbid: string; - artistMbid: string; - - static createFrom(source: any = {}) { - return new TrackMBIDs(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.recordingMbid = source["recordingMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.artistMbid = source["artistMbid"]; - } - } - -} - -export namespace player { - - export class TrackInfo { - fileName: string; - filePath: string; - state: string; - 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; - - static createFrom(source: any = {}) { - return new TrackInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.fileName = source["fileName"]; - this.filePath = source["filePath"]; - this.state = source["state"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.album = source["album"]; - this.coverArt = source["coverArt"]; - this.coverArtSmall = source["coverArtSmall"]; - this.coverArtMedium = source["coverArtMedium"]; - this.coverArtLarge = source["coverArtLarge"]; - this.trackLength = source["trackLength"]; - this.seekPosition = source["seekPosition"]; - this.trackChangeId = source["trackChangeId"]; - this.artistMbid = source["artistMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.recordingMbid = source["recordingMbid"]; - } - } - -} - -export namespace playlist { - - export class CandidateTrack { - FilePath: string; - Title: string; - Artist: string; - Album: string; - Duration: string; - Score: number; - - static createFrom(source: any = {}) { - return new CandidateTrack(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.FilePath = source["FilePath"]; - this.Title = source["Title"]; - this.Artist = source["Artist"]; - this.Album = source["Album"]; - this.Duration = source["Duration"]; - this.Score = source["Score"]; - } - } - export class DuplicateTrackInfo { - FilePath: string; - Title: string; - Artist: string; - Album: string; - Duration: string; - - static createFrom(source: any = {}) { - return new DuplicateTrackInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.FilePath = source["FilePath"]; - this.Title = source["Title"]; - this.Artist = source["Artist"]; - this.Album = source["Album"]; - this.Duration = source["Duration"]; - } - } - export class DuplicateCheckResult { - Duplicates: DuplicateTrackInfo[]; - Unique: string[]; - - static createFrom(source: any = {}) { - return new DuplicateCheckResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Duplicates = this.convertValues(source["Duplicates"], DuplicateTrackInfo); - this.Unique = source["Unique"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - export class PhantomMatch { - PhantomPath: string; - PhantomTitle: string; - Candidate: CandidateTrack; - - static createFrom(source: any = {}) { - return new PhantomMatch(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.PhantomPath = source["PhantomPath"]; - this.PhantomTitle = source["PhantomTitle"]; - this.Candidate = this.convertValues(source["Candidate"], CandidateTrack); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class PhantomSearchResult { - AutoMatched: PhantomMatch[]; - Unmatched: string[]; - - static createFrom(source: any = {}) { - return new PhantomSearchResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.AutoMatched = this.convertValues(source["AutoMatched"], PhantomMatch); - this.Unmatched = source["Unmatched"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class Summary { - ID: number; - Name: string; - CreatedAt: string; - UpdatedAt: string; - IsSmart: boolean; - - static createFrom(source: any = {}) { - return new Summary(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.ID = source["ID"]; - this.Name = source["Name"]; - this.CreatedAt = source["CreatedAt"]; - this.UpdatedAt = source["UpdatedAt"]; - this.IsSmart = source["IsSmart"]; - } - } - export class 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; - - static createFrom(source: any = {}) { - return new Track(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.ID = source["ID"]; - this.Position = source["Position"]; - this.FilePath = source["FilePath"]; - this.Title = source["Title"]; - this.Artist = source["Artist"]; - this.Album = source["Album"]; - this.CoverArtPath = source["CoverArtPath"]; - this.CoverArtSmall = source["CoverArtSmall"]; - this.CoverArtMedium = source["CoverArtMedium"]; - this.CoverArtLarge = source["CoverArtLarge"]; - this.Duration = source["Duration"]; - this.Phantom = source["Phantom"]; - this.ArtistMBID = source["ArtistMBID"]; - this.ReleaseGroupMBID = source["ReleaseGroupMBID"]; - this.RecordingMBID = source["RecordingMBID"]; - } - } - export class WithTracks { - Summary: Summary; - Tracks: Track[]; - - static createFrom(source: any = {}) { - return new WithTracks(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Summary = this.convertValues(source["Summary"], Summary); - this.Tracks = this.convertValues(source["Tracks"], Track); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - -export namespace queue { - - export class Source { - type: string; - id: number; - label: string; - - static createFrom(source: any = {}) { - return new Source(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.type = source["type"]; - this.id = source["id"]; - this.label = source["label"]; - } - } - export class Track { - id: number; - audioFileId: number; - filePath: string; - position: number; - title: string; - artist: string; - album: string; - coverArtPath: string; - artistMbid: string; - releaseGroupMbid: string; - recordingMbid: string; - - static createFrom(source: any = {}) { - return new Track(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.audioFileId = source["audioFileId"]; - this.filePath = source["filePath"]; - this.position = source["position"]; - this.title = source["title"]; - this.artist = source["artist"]; - this.album = source["album"]; - this.coverArtPath = source["coverArtPath"]; - this.artistMbid = source["artistMbid"]; - this.releaseGroupMbid = source["releaseGroupMbid"]; - this.recordingMbid = source["recordingMbid"]; - } - } - export class State { - tracks: Track[]; - currentIndex: number; - shuffleMode: boolean; - repeatMode: string; - source: Source; - - static createFrom(source: any = {}) { - return new State(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.tracks = this.convertValues(source["tracks"], Track); - this.currentIndex = source["currentIndex"]; - this.shuffleMode = source["shuffleMode"]; - this.repeatMode = source["repeatMode"]; - this.source = this.convertValues(source["source"], Source); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - -export namespace sqlcgen { - - export class Library { - ID: number; - Name: string; - Path: string; - CreatedAt: time.Time; - AutotagWarningAcked: number; - - static createFrom(source: any = {}) { - return new Library(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.ID = source["ID"]; - this.Name = source["Name"]; - this.Path = source["Path"]; - this.CreatedAt = this.convertValues(source["CreatedAt"], time.Time); - this.AutotagWarningAcked = source["AutotagWarningAcked"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - -export namespace tagwriter { - - export class BatchFailure { - filePath: string; - error: string; - - static createFrom(source: any = {}) { - return new BatchFailure(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.filePath = source["filePath"]; - this.error = source["error"]; - } - } - export class BatchResult { - total: number; - succeeded: number; - failed: number; - cancelled: boolean; - failures: BatchFailure[]; - - static createFrom(source: any = {}) { - return new BatchResult(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.total = source["total"]; - this.succeeded = source["succeeded"]; - this.failed = source["failed"]; - this.cancelled = source["cancelled"]; - this.failures = this.convertValues(source["failures"], BatchFailure); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - -export namespace time { - - export class Time { - - - static createFrom(source: any = {}) { - return new Time(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - -} - -export namespace tracklist { - - export class Column { - id: string; - - static createFrom(source: any = {}) { - return new Column(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - } - } - -} - diff --git a/frontend/wailsjs/go/player/Player.d.ts b/frontend/wailsjs/go/player/Player.d.ts deleted file mode 100755 index 2a948a6..0000000 --- a/frontend/wailsjs/go/player/Player.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {player} from '../models'; -import {context} from '../models'; -import {mediacontrols} from '../models'; - -export function ChangeVolume(arg1:number):Promise; - -export function CurrentPosition():Promise; - -export function CurrentPositionSeconds():Promise; - -export function EmitCurrentState():Promise; - -export function GetCurrentTrackInfo():Promise; - -export function InitSpeaker():Promise; - -export function IsPlaying():Promise; - -export function LoadFile(arg1:string):Promise; - -export function MuteToggle():Promise; - -export function Muted():Promise; - -export function Pause():Promise; - -export function Play():Promise; - -export function RestoreState():Promise; - -export function SaveState():Promise; - -export function Seek(arg1:number):Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetMediaControls(arg1:mediacontrols.Handler):Promise; - -export function SetPlaybackFinishedHandler(arg1:any):Promise; - -export function SetVolume(arg1:player.UserVolume):Promise; - -export function TrackLengthInSeconds():Promise; - -export function UnloadTrack():Promise; diff --git a/frontend/wailsjs/go/player/Player.js b/frontend/wailsjs/go/player/Player.js deleted file mode 100755 index e3885be..0000000 --- a/frontend/wailsjs/go/player/Player.js +++ /dev/null @@ -1,87 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function ChangeVolume(arg1) { - return window['go']['player']['Player']['ChangeVolume'](arg1); -} - -export function CurrentPosition() { - return window['go']['player']['Player']['CurrentPosition'](); -} - -export function CurrentPositionSeconds() { - return window['go']['player']['Player']['CurrentPositionSeconds'](); -} - -export function EmitCurrentState() { - return window['go']['player']['Player']['EmitCurrentState'](); -} - -export function GetCurrentTrackInfo() { - return window['go']['player']['Player']['GetCurrentTrackInfo'](); -} - -export function InitSpeaker() { - return window['go']['player']['Player']['InitSpeaker'](); -} - -export function IsPlaying() { - return window['go']['player']['Player']['IsPlaying'](); -} - -export function LoadFile(arg1) { - return window['go']['player']['Player']['LoadFile'](arg1); -} - -export function MuteToggle() { - return window['go']['player']['Player']['MuteToggle'](); -} - -export function Muted() { - return window['go']['player']['Player']['Muted'](); -} - -export function Pause() { - return window['go']['player']['Player']['Pause'](); -} - -export function Play() { - return window['go']['player']['Player']['Play'](); -} - -export function RestoreState() { - return window['go']['player']['Player']['RestoreState'](); -} - -export function SaveState() { - return window['go']['player']['Player']['SaveState'](); -} - -export function Seek(arg1) { - return window['go']['player']['Player']['Seek'](arg1); -} - -export function SetContext(arg1) { - return window['go']['player']['Player']['SetContext'](arg1); -} - -export function SetMediaControls(arg1) { - return window['go']['player']['Player']['SetMediaControls'](arg1); -} - -export function SetPlaybackFinishedHandler(arg1) { - return window['go']['player']['Player']['SetPlaybackFinishedHandler'](arg1); -} - -export function SetVolume(arg1) { - return window['go']['player']['Player']['SetVolume'](arg1); -} - -export function TrackLengthInSeconds() { - return window['go']['player']['Player']['TrackLengthInSeconds'](); -} - -export function UnloadTrack() { - return window['go']['player']['Player']['UnloadTrack'](); -} diff --git a/frontend/wailsjs/go/playlist/Service.d.ts b/frontend/wailsjs/go/playlist/Service.d.ts deleted file mode 100755 index b569a8c..0000000 --- a/frontend/wailsjs/go/playlist/Service.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {playlist} from '../models'; -import {library} from '../models'; -import {context} from '../models'; - -export function AddToDefaultPlaylist(arg1:Array):Promise; - -export function AddTracksToPlaylist(arg1:number,arg2:Array):Promise; - -export function CreatePlaylist(arg1:string):Promise; - -export function CreatePlaylistWithTracks(arg1:string,arg2:Array):Promise; - -export function CreateSmartPlaylist(arg1:string,arg2:string):Promise; - -export function DeletePlaylist(arg1:number):Promise; - -export function EnsureDefaultPlaylist():Promise; - -export function EvaluateSmartPlaylist(arg1:number):Promise>; - -export function FindDuplicateTracksInPlaylist(arg1:number,arg2:Array):Promise; - -export function FindPhantomMatches(arg1:number,arg2:Array):Promise; - -export function GetAllPlaylists():Promise>; - -export function GetAllPlaylistsWithTracks():Promise>; - -export function GetDefaultPlaylistInfo():Promise; - -export function GetDefaultPlaylistTrackPaths():Promise>; - -export function GetPhantomCandidates(arg1:number,arg2:string):Promise>; - -export function GetPlaylistTracks(arg1:number):Promise>; - -export function GetSmartPlaylistRules(arg1:number):Promise; - -export function GetSmartPlaylistTracks(arg1:number):Promise>; - -export function ImportPlaylist(arg1:string):Promise; - -export function ImportPlaylists(arg1:Array):Promise>; - -export function MaterializeUnmaterializedSmartPlaylists():Promise; - -export function PreviewSmartPlaylist(arg1:string):Promise>; - -export function RefreshSmartPlaylist(arg1:number):Promise; - -export function RemoveFromDefaultPlaylist(arg1:Array):Promise; - -export function RemovePhantomTracks(arg1:number,arg2:Array):Promise; - -export function RemoveTracksFromPlaylist(arg1:number,arg2:Array):Promise; - -export function RenamePlaylist(arg1:number,arg2:string):Promise; - -export function RepopulateFromM3U():Promise; - -export function ResolvePhantomTracks(arg1:number,arg2:Record):Promise; - -export function ResolvePhantomTracksAfterScan():Promise; - -export function RestoreAllPlaylists():Promise; - -export function SearchLibrary(arg1:string):Promise>; - -export function SetContext(arg1:context.Context):Promise; - -export function SetFavoritesConfig(arg1:playlist.FavoritesConfigProvider):Promise; - -export function ToggleDefaultPlaylistTrack(arg1:string):Promise; - -export function UpdateSmartPlaylistRules(arg1:number,arg2:string):Promise; diff --git a/frontend/wailsjs/go/playlist/Service.js b/frontend/wailsjs/go/playlist/Service.js deleted file mode 100755 index c144cb4..0000000 --- a/frontend/wailsjs/go/playlist/Service.js +++ /dev/null @@ -1,147 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AddToDefaultPlaylist(arg1) { - return window['go']['playlist']['Service']['AddToDefaultPlaylist'](arg1); -} - -export function AddTracksToPlaylist(arg1, arg2) { - return window['go']['playlist']['Service']['AddTracksToPlaylist'](arg1, arg2); -} - -export function CreatePlaylist(arg1) { - return window['go']['playlist']['Service']['CreatePlaylist'](arg1); -} - -export function CreatePlaylistWithTracks(arg1, arg2) { - return window['go']['playlist']['Service']['CreatePlaylistWithTracks'](arg1, arg2); -} - -export function CreateSmartPlaylist(arg1, arg2) { - return window['go']['playlist']['Service']['CreateSmartPlaylist'](arg1, arg2); -} - -export function DeletePlaylist(arg1) { - return window['go']['playlist']['Service']['DeletePlaylist'](arg1); -} - -export function EnsureDefaultPlaylist() { - return window['go']['playlist']['Service']['EnsureDefaultPlaylist'](); -} - -export function EvaluateSmartPlaylist(arg1) { - return window['go']['playlist']['Service']['EvaluateSmartPlaylist'](arg1); -} - -export function FindDuplicateTracksInPlaylist(arg1, arg2) { - return window['go']['playlist']['Service']['FindDuplicateTracksInPlaylist'](arg1, arg2); -} - -export function FindPhantomMatches(arg1, arg2) { - return window['go']['playlist']['Service']['FindPhantomMatches'](arg1, arg2); -} - -export function GetAllPlaylists() { - return window['go']['playlist']['Service']['GetAllPlaylists'](); -} - -export function GetAllPlaylistsWithTracks() { - return window['go']['playlist']['Service']['GetAllPlaylistsWithTracks'](); -} - -export function GetDefaultPlaylistInfo() { - return window['go']['playlist']['Service']['GetDefaultPlaylistInfo'](); -} - -export function GetDefaultPlaylistTrackPaths() { - return window['go']['playlist']['Service']['GetDefaultPlaylistTrackPaths'](); -} - -export function GetPhantomCandidates(arg1, arg2) { - return window['go']['playlist']['Service']['GetPhantomCandidates'](arg1, arg2); -} - -export function GetPlaylistTracks(arg1) { - return window['go']['playlist']['Service']['GetPlaylistTracks'](arg1); -} - -export function GetSmartPlaylistRules(arg1) { - return window['go']['playlist']['Service']['GetSmartPlaylistRules'](arg1); -} - -export function GetSmartPlaylistTracks(arg1) { - return window['go']['playlist']['Service']['GetSmartPlaylistTracks'](arg1); -} - -export function ImportPlaylist(arg1) { - return window['go']['playlist']['Service']['ImportPlaylist'](arg1); -} - -export function ImportPlaylists(arg1) { - return window['go']['playlist']['Service']['ImportPlaylists'](arg1); -} - -export function MaterializeUnmaterializedSmartPlaylists() { - return window['go']['playlist']['Service']['MaterializeUnmaterializedSmartPlaylists'](); -} - -export function PreviewSmartPlaylist(arg1) { - return window['go']['playlist']['Service']['PreviewSmartPlaylist'](arg1); -} - -export function RefreshSmartPlaylist(arg1) { - return window['go']['playlist']['Service']['RefreshSmartPlaylist'](arg1); -} - -export function RemoveFromDefaultPlaylist(arg1) { - return window['go']['playlist']['Service']['RemoveFromDefaultPlaylist'](arg1); -} - -export function RemovePhantomTracks(arg1, arg2) { - return window['go']['playlist']['Service']['RemovePhantomTracks'](arg1, arg2); -} - -export function RemoveTracksFromPlaylist(arg1, arg2) { - return window['go']['playlist']['Service']['RemoveTracksFromPlaylist'](arg1, arg2); -} - -export function RenamePlaylist(arg1, arg2) { - return window['go']['playlist']['Service']['RenamePlaylist'](arg1, arg2); -} - -export function RepopulateFromM3U() { - return window['go']['playlist']['Service']['RepopulateFromM3U'](); -} - -export function ResolvePhantomTracks(arg1, arg2) { - return window['go']['playlist']['Service']['ResolvePhantomTracks'](arg1, arg2); -} - -export function ResolvePhantomTracksAfterScan() { - return window['go']['playlist']['Service']['ResolvePhantomTracksAfterScan'](); -} - -export function RestoreAllPlaylists() { - return window['go']['playlist']['Service']['RestoreAllPlaylists'](); -} - -export function SearchLibrary(arg1) { - return window['go']['playlist']['Service']['SearchLibrary'](arg1); -} - -export function SetContext(arg1) { - return window['go']['playlist']['Service']['SetContext'](arg1); -} - -export function SetFavoritesConfig(arg1) { - return window['go']['playlist']['Service']['SetFavoritesConfig'](arg1); -} - -export function ToggleDefaultPlaylistTrack(arg1) { - return window['go']['playlist']['Service']['ToggleDefaultPlaylistTrack'](arg1); -} - -export function UpdateSmartPlaylistRules(arg1, arg2) { - return window['go']['playlist']['Service']['UpdateSmartPlaylistRules'](arg1, arg2); -} diff --git a/frontend/wailsjs/go/queue/Queue.d.ts b/frontend/wailsjs/go/queue/Queue.d.ts deleted file mode 100755 index 11993e0..0000000 --- a/frontend/wailsjs/go/queue/Queue.d.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {queue} from '../models'; -import {context} from '../models'; - -export function AddTrack(arg1:string):Promise; - -export function AddTracks(arg1:Array):Promise; - -export function Clear():Promise; - -export function CompactAfterLibraryRemoval():Promise; - -export function CycleRepeat():Promise; - -export function EmitCurrentState():Promise; - -export function GetState():Promise; - -export function InsertNext(arg1:string):Promise; - -export function InsertNextTracks(arg1:Array):Promise; - -export function InsertTracksAt(arg1:Array,arg2:number):Promise; - -export function MoveQueueTracks(arg1:Array,arg2:number):Promise; - -export function Next():Promise; - -export function OnPlaybackFinished():Promise; - -export function Play():Promise; - -export function PlayIndex(arg1:number):Promise; - -export function Previous():Promise; - -export function RemoveTrack(arg1:number):Promise; - -export function RemoveTracks(arg1:Array):Promise; - -export function RestoreState():Promise; - -export function SaveState():Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetFallbackSource(arg1:queue.FallbackSource):Promise; - -export function SetPlayer(arg1:queue.TrackLoader):Promise; - -export function SetQueue(arg1:Array,arg2:number,arg3:boolean,arg4:queue.Source):Promise; - -export function ToggleShuffle():Promise; diff --git a/frontend/wailsjs/go/queue/Queue.js b/frontend/wailsjs/go/queue/Queue.js deleted file mode 100755 index ed93425..0000000 --- a/frontend/wailsjs/go/queue/Queue.js +++ /dev/null @@ -1,103 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function AddTrack(arg1) { - return window['go']['queue']['Queue']['AddTrack'](arg1); -} - -export function AddTracks(arg1) { - return window['go']['queue']['Queue']['AddTracks'](arg1); -} - -export function Clear() { - return window['go']['queue']['Queue']['Clear'](); -} - -export function CompactAfterLibraryRemoval() { - return window['go']['queue']['Queue']['CompactAfterLibraryRemoval'](); -} - -export function CycleRepeat() { - return window['go']['queue']['Queue']['CycleRepeat'](); -} - -export function EmitCurrentState() { - return window['go']['queue']['Queue']['EmitCurrentState'](); -} - -export function GetState() { - return window['go']['queue']['Queue']['GetState'](); -} - -export function InsertNext(arg1) { - return window['go']['queue']['Queue']['InsertNext'](arg1); -} - -export function InsertNextTracks(arg1) { - return window['go']['queue']['Queue']['InsertNextTracks'](arg1); -} - -export function InsertTracksAt(arg1, arg2) { - return window['go']['queue']['Queue']['InsertTracksAt'](arg1, arg2); -} - -export function MoveQueueTracks(arg1, arg2) { - return window['go']['queue']['Queue']['MoveQueueTracks'](arg1, arg2); -} - -export function Next() { - return window['go']['queue']['Queue']['Next'](); -} - -export function OnPlaybackFinished() { - return window['go']['queue']['Queue']['OnPlaybackFinished'](); -} - -export function Play() { - return window['go']['queue']['Queue']['Play'](); -} - -export function PlayIndex(arg1) { - return window['go']['queue']['Queue']['PlayIndex'](arg1); -} - -export function Previous() { - return window['go']['queue']['Queue']['Previous'](); -} - -export function RemoveTrack(arg1) { - return window['go']['queue']['Queue']['RemoveTrack'](arg1); -} - -export function RemoveTracks(arg1) { - return window['go']['queue']['Queue']['RemoveTracks'](arg1); -} - -export function RestoreState() { - return window['go']['queue']['Queue']['RestoreState'](); -} - -export function SaveState() { - return window['go']['queue']['Queue']['SaveState'](); -} - -export function SetContext(arg1) { - return window['go']['queue']['Queue']['SetContext'](arg1); -} - -export function SetFallbackSource(arg1) { - return window['go']['queue']['Queue']['SetFallbackSource'](arg1); -} - -export function SetPlayer(arg1) { - return window['go']['queue']['Queue']['SetPlayer'](arg1); -} - -export function SetQueue(arg1, arg2, arg3, arg4) { - return window['go']['queue']['Queue']['SetQueue'](arg1, arg2, arg3, arg4); -} - -export function ToggleShuffle() { - return window['go']['queue']['Queue']['ToggleShuffle'](); -} diff --git a/frontend/wailsjs/go/tagwriter/TagWriter.d.ts b/frontend/wailsjs/go/tagwriter/TagWriter.d.ts deleted file mode 100755 index 9657f52..0000000 --- a/frontend/wailsjs/go/tagwriter/TagWriter.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {tagwriter} from '../models'; -import {context} from '../models'; - -export function BatchWriteTrackTags(arg1:Array,arg2:tagwriter.TagChanges):Promise; - -export function CancelBatchWrite():Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function WriteTrackTags(arg1:number,arg2:tagwriter.TagChanges):Promise; - -export function WriteTrackTagsByPath(arg1:string,arg2:tagwriter.TagChanges):Promise; - -export function WriteUntrackedFileTags(arg1:string,arg2:tagwriter.TagChanges):Promise; diff --git a/frontend/wailsjs/go/tagwriter/TagWriter.js b/frontend/wailsjs/go/tagwriter/TagWriter.js deleted file mode 100755 index bb5fa8b..0000000 --- a/frontend/wailsjs/go/tagwriter/TagWriter.js +++ /dev/null @@ -1,27 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function BatchWriteTrackTags(arg1, arg2) { - return window['go']['tagwriter']['TagWriter']['BatchWriteTrackTags'](arg1, arg2); -} - -export function CancelBatchWrite() { - return window['go']['tagwriter']['TagWriter']['CancelBatchWrite'](); -} - -export function SetContext(arg1) { - return window['go']['tagwriter']['TagWriter']['SetContext'](arg1); -} - -export function WriteTrackTags(arg1, arg2) { - return window['go']['tagwriter']['TagWriter']['WriteTrackTags'](arg1, arg2); -} - -export function WriteTrackTagsByPath(arg1, arg2) { - return window['go']['tagwriter']['TagWriter']['WriteTrackTagsByPath'](arg1, arg2); -} - -export function WriteUntrackedFileTags(arg1, arg2) { - return window['go']['tagwriter']['TagWriter']['WriteUntrackedFileTags'](arg1, arg2); -} diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json deleted file mode 100644 index 1e7c8a5..0000000 --- a/frontend/wailsjs/runtime/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@wailsapp/runtime", - "version": "2.0.0", - "description": "Wails Javascript runtime library", - "main": "runtime.js", - "types": "runtime.d.ts", - "scripts": { - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wailsapp/wails.git" - }, - "keywords": [ - "Wails", - "Javascript", - "Go" - ], - "author": "Lea Anthony ", - "license": "MIT", - "bugs": { - "url": "https://github.com/wailsapp/wails/issues" - }, - "homepage": "https://github.com/wailsapp/wails#readme" -} diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts deleted file mode 100644 index 4445dac..0000000 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export interface Position { - x: number; - y: number; -} - -export interface Size { - w: number; - h: number; -} - -export interface Screen { - isCurrent: boolean; - isPrimary: boolean; - width : number - height : number -} - -// Environment information such as platform, buildtype, ... -export interface EnvironmentInfo { - buildType: string; - platform: string; - arch: string; -} - -// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) -// emits the given event. Optional data may be passed with the event. -// This will trigger any event listeners. -export function EventsEmit(eventName: string, ...data: any): void; - -// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. -export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) -// sets up a listener for the given event name, but will only trigger a given number times. -export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; - -// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) -// sets up a listener for the given event name, but will only trigger once. -export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) -// unregisters the listener for the given event name. -export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; - -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all listeners. -export function EventsOffAll(): void; - -// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) -// logs the given message as a raw message -export function LogPrint(message: string): void; - -// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) -// logs the given message at the `trace` log level. -export function LogTrace(message: string): void; - -// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) -// logs the given message at the `debug` log level. -export function LogDebug(message: string): void; - -// [LogError](https://wails.io/docs/reference/runtime/log#logerror) -// logs the given message at the `error` log level. -export function LogError(message: string): void; - -// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) -// logs the given message at the `fatal` log level. -// The application will quit after calling this method. -export function LogFatal(message: string): void; - -// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) -// logs the given message at the `info` log level. -export function LogInfo(message: string): void; - -// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) -// logs the given message at the `warning` log level. -export function LogWarning(message: string): void; - -// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) -// Forces a reload by the main application as well as connected browsers. -export function WindowReload(): void; - -// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) -// Reloads the application frontend. -export function WindowReloadApp(): void; - -// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) -// Sets the window AlwaysOnTop or not on top. -export function WindowSetAlwaysOnTop(b: boolean): void; - -// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) -// *Windows only* -// Sets window theme to system default (dark/light). -export function WindowSetSystemDefaultTheme(): void; - -// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) -// *Windows only* -// Sets window to light theme. -export function WindowSetLightTheme(): void; - -// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) -// *Windows only* -// Sets window to dark theme. -export function WindowSetDarkTheme(): void; - -// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) -// Centers the window on the monitor the window is currently on. -export function WindowCenter(): void; - -// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) -// Sets the text in the window title bar. -export function WindowSetTitle(title: string): void; - -// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) -// Makes the window full screen. -export function WindowFullscreen(): void; - -// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) -// Restores the previous window dimensions and position prior to full screen. -export function WindowUnfullscreen(): void; - -// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) -// Returns the state of the window, i.e. whether the window is in full screen mode or not. -export function WindowIsFullscreen(): Promise; - -// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) -// Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; - -// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) -// Gets the width and height of the window. -export function WindowGetSize(): Promise; - -// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) -// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMaxSize(width: number, height: number): void; - -// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) -// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMinSize(width: number, height: number): void; - -// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) -// Sets the window position relative to the monitor the window is currently on. -export function WindowSetPosition(x: number, y: number): void; - -// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) -// Gets the window position relative to the monitor the window is currently on. -export function WindowGetPosition(): Promise; - -// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) -// Hides the window. -export function WindowHide(): void; - -// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) -// Shows the window, if it is currently hidden. -export function WindowShow(): void; - -// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) -// Maximises the window to fill the screen. -export function WindowMaximise(): void; - -// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) -// Toggles between Maximised and UnMaximised. -export function WindowToggleMaximise(): void; - -// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) -// Restores the window to the dimensions and position prior to maximising. -export function WindowUnmaximise(): void; - -// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) -// Returns the state of the window, i.e. whether the window is maximised or not. -export function WindowIsMaximised(): Promise; - -// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) -// Minimises the window. -export function WindowMinimise(): void; - -// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) -// Restores the window to the dimensions and position prior to minimising. -export function WindowUnminimise(): void; - -// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) -// Returns the state of the window, i.e. whether the window is minimised or not. -export function WindowIsMinimised(): Promise; - -// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) -// Returns the state of the window, i.e. whether the window is normal or not. -export function WindowIsNormal(): Promise; - -// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) -// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. -export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; - -// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) -// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. -export function ScreenGetAll(): Promise; - -// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) -// Opens the given URL in the system browser. -export function BrowserOpenURL(url: string): void; - -// [Environment](https://wails.io/docs/reference/runtime/intro#environment) -// Returns information about the environment -export function Environment(): Promise; - -// [Quit](https://wails.io/docs/reference/runtime/intro#quit) -// Quits the application. -export function Quit(): void; - -// [Hide](https://wails.io/docs/reference/runtime/intro#hide) -// Hides the application. -export function Hide(): void; - -// [Show](https://wails.io/docs/reference/runtime/intro#show) -// Shows the application. -export function Show(): void; - -// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) -// Returns the current text stored on clipboard -export function ClipboardGetText(): Promise; - -// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) -// Sets a text on the clipboard -export function ClipboardSetText(text: string): Promise; - -// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) -// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. -export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void - -// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) -// OnFileDropOff removes the drag and drop listeners and handlers. -export function OnFileDropOff() :void - -// Check if the file path resolver is available -export function CanResolveFilePaths(): boolean; - -// Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js deleted file mode 100644 index 623397b..0000000 --- a/frontend/wailsjs/runtime/runtime.js +++ /dev/null @@ -1,238 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export function LogPrint(message) { - window.runtime.LogPrint(message); -} - -export function LogTrace(message) { - window.runtime.LogTrace(message); -} - -export function LogDebug(message) { - window.runtime.LogDebug(message); -} - -export function LogInfo(message) { - window.runtime.LogInfo(message); -} - -export function LogWarning(message) { - window.runtime.LogWarning(message); -} - -export function LogError(message) { - window.runtime.LogError(message); -} - -export function LogFatal(message) { - window.runtime.LogFatal(message); -} - -export function EventsOnMultiple(eventName, callback, maxCallbacks) { - return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); -} - -export function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); -} - -export function EventsOff(eventName, ...additionalEventNames) { - return window.runtime.EventsOff(eventName, ...additionalEventNames); -} - -export function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); -} - -export function EventsEmit(eventName) { - let args = [eventName].slice.call(arguments); - return window.runtime.EventsEmit.apply(null, args); -} - -export function WindowReload() { - window.runtime.WindowReload(); -} - -export function WindowReloadApp() { - window.runtime.WindowReloadApp(); -} - -export function WindowSetAlwaysOnTop(b) { - window.runtime.WindowSetAlwaysOnTop(b); -} - -export function WindowSetSystemDefaultTheme() { - window.runtime.WindowSetSystemDefaultTheme(); -} - -export function WindowSetLightTheme() { - window.runtime.WindowSetLightTheme(); -} - -export function WindowSetDarkTheme() { - window.runtime.WindowSetDarkTheme(); -} - -export function WindowCenter() { - window.runtime.WindowCenter(); -} - -export function WindowSetTitle(title) { - window.runtime.WindowSetTitle(title); -} - -export function WindowFullscreen() { - window.runtime.WindowFullscreen(); -} - -export function WindowUnfullscreen() { - window.runtime.WindowUnfullscreen(); -} - -export function WindowIsFullscreen() { - return window.runtime.WindowIsFullscreen(); -} - -export function WindowGetSize() { - return window.runtime.WindowGetSize(); -} - -export function WindowSetSize(width, height) { - window.runtime.WindowSetSize(width, height); -} - -export function WindowSetMaxSize(width, height) { - window.runtime.WindowSetMaxSize(width, height); -} - -export function WindowSetMinSize(width, height) { - window.runtime.WindowSetMinSize(width, height); -} - -export function WindowSetPosition(x, y) { - window.runtime.WindowSetPosition(x, y); -} - -export function WindowGetPosition() { - return window.runtime.WindowGetPosition(); -} - -export function WindowHide() { - window.runtime.WindowHide(); -} - -export function WindowShow() { - window.runtime.WindowShow(); -} - -export function WindowMaximise() { - window.runtime.WindowMaximise(); -} - -export function WindowToggleMaximise() { - window.runtime.WindowToggleMaximise(); -} - -export function WindowUnmaximise() { - window.runtime.WindowUnmaximise(); -} - -export function WindowIsMaximised() { - return window.runtime.WindowIsMaximised(); -} - -export function WindowMinimise() { - window.runtime.WindowMinimise(); -} - -export function WindowUnminimise() { - window.runtime.WindowUnminimise(); -} - -export function WindowSetBackgroundColour(R, G, B, A) { - window.runtime.WindowSetBackgroundColour(R, G, B, A); -} - -export function ScreenGetAll() { - return window.runtime.ScreenGetAll(); -} - -export function WindowIsMinimised() { - return window.runtime.WindowIsMinimised(); -} - -export function WindowIsNormal() { - return window.runtime.WindowIsNormal(); -} - -export function BrowserOpenURL(url) { - window.runtime.BrowserOpenURL(url); -} - -export function Environment() { - return window.runtime.Environment(); -} - -export function Quit() { - window.runtime.Quit(); -} - -export function Hide() { - window.runtime.Hide(); -} - -export function Show() { - window.runtime.Show(); -} - -export function ClipboardGetText() { - return window.runtime.ClipboardGetText(); -} - -export function ClipboardSetText(text) { - return window.runtime.ClipboardSetText(text); -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - return window.runtime.OnFileDrop(callback, useDropTarget); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - return window.runtime.OnFileDropOff(); -} - -export function CanResolveFilePaths() { - return window.runtime.CanResolveFilePaths(); -} - -export function ResolveFilePaths(files) { - return window.runtime.ResolveFilePaths(files); -} \ No newline at end of file diff --git a/lefthook.yml b/lefthook.yml index 4afa86d..4cfaafd 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -30,8 +30,11 @@ pre-commit: exit 1 fi - # frontend/wailsjs is generated by `wails`, not `go generate`, so the - # check above does not cover it. Takes ~1.5s. + # frontend/bindings is generated by `wails3`, not `go generate`, so + # the check above does not cover it. ~20s: v3's generator is a + # static analyser over the whole package graph, where v2's built the + # app with a `bindings` tag and ran it. Slower than v2's ~1.5s and + # still the cheapest place to catch a renamed field. bindings-check: glob: "*.go" run: ./scripts/bindings-check.sh diff --git a/scripts/bindings-check.sh b/scripts/bindings-check.sh index 7623b8d..6f8025f 100755 --- a/scripts/bindings-check.sh +++ b/scripts/bindings-check.sh @@ -1,21 +1,26 @@ #!/usr/bin/env bash # -# Fails when frontend/wailsjs/ is stale against the bound Go structs. +# Fails when frontend/bindings/ is stale against the bound Go services. # -# frontend/wailsjs/ is generated by `wails`, not by `go generate`, so the -# pre-commit codegen check does not cover it at all. Without this, a +# frontend/bindings/ is generated by `wails3`, not by `go generate`, so +# the pre-commit codegen check does not cover it at all. Without this, a # renamed Go struct field or a changed method signature first shows up at # runtime, inside a window, as a binding that never settles. # -# `wails generate module` builds the app with the `bindings` tag and runs -# it to dump the bindings — about three seconds, so it is cheap enough to -# gate a commit on. +# v3's generator is a static analyser rather than a build-and-run of the +# app, which is what makes this cheap enough to gate a commit on — and +# also what makes the build tags matter. It sees only the configuration +# it is told about; the one that matters is the one users run, which is +# the default tag set (GTK4 + WebKitGTK 6.0). The other two +# configurations this repo builds — `indexbuild` and `dev` — add no bound +# service, so generating under them would only widen the surface past +# what ships. set -euo pipefail cd "$(dirname "$0")/.." -TARGET="frontend/wailsjs" +TARGET="frontend/bindings" if [ -n "$(git status --porcelain -- "$TARGET")" ]; then echo "bindings-check: $TARGET has uncommitted changes; stage or stash them first" >&2 @@ -23,21 +28,21 @@ if [ -n "$(git status --porcelain -- "$TARGET")" ]; then exit 1 fi -go tool wails generate module >/dev/null 2>&1 +go tool wails3 generate bindings -clean=true -ts -i >/dev/null 2>&1 -# `wails generate module` rewrites the three runtime files as 755 every -# time. That is not drift, so compare content only. -if ! git -c core.fileMode=false diff --quiet -- "$TARGET"; then +if ! git diff --quiet -- "$TARGET"; then echo "bindings-check: $TARGET is out of date with the Go bindings." >&2 echo "Run 'make bindings' and stage the result." >&2 - git -c core.fileMode=false diff --stat -- "$TARGET" >&2 + git diff --stat -- "$TARGET" >&2 exit 1 fi -# Restore the modes the generator churned, so the tree is left clean. -chmod 644 \ - frontend/wailsjs/runtime/runtime.js \ - frontend/wailsjs/runtime/runtime.d.ts \ - frontend/wailsjs/runtime/package.json +# An added or removed *file* is a rename or a new service, which a diff +# of tracked paths alone does not see. +if [ -n "$(git status --porcelain -- "$TARGET")" ]; then + echo "bindings-check: $TARGET gained or lost files." >&2 + git status --short -- "$TARGET" >&2 + exit 1 +fi -echo "bindings-check: frontend/wailsjs is current" +echo "bindings-check: frontend/bindings is current"