Skip to content

Commit d671c31

Browse files
MathieuDubartclaude
andcommitted
docs: update README and CHANGELOG for v0.7.0
Add capabilities caching API examples (loadCapabilities, KnownExtension), getLyricsBySongId usage example, updated endpoint table, and full v0.7.0 CHANGELOG entry covering all new types, fields, and public inits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6027fb0 commit d671c31

2 files changed

Lines changed: 78 additions & 12 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
---
99

10+
## [0.7.0] — 2026-05-04
11+
12+
### Added
13+
14+
- **OpenSubsonic server identification** — `ServerCapabilities` now exposes `serverType` and `serverVersion` populated from the `type` / `serverVersion` fields returned by OpenSubsonic-compliant servers (Navidrome, gonic, …).
15+
16+
- **`KnownExtension` enum** — compile-time–safe identifiers for every OpenSubsonic extension defined in the spec (`songLyrics`, `apiKeyAuthentication`, `playbackReport`, `transcodeOffset`, `formPost`, `indexBasedQueue`, `sonicSimilarity`, `transcoding`, `getPodcastEpisode`). Use `caps.supports(.songLyrics)` instead of the stringly-typed overload.
17+
18+
- **`ServerCapabilities.extensionList`** — computed property that derives a `[OpenSubsonicExtension]` array from the extensions dictionary, matching the shape returned by `getOpenSubsonicExtensions()`.
19+
20+
- **`ServerCapabilities.legacy()`** — factory that creates a non-OpenSubsonic capability snapshot with empty extensions, useful as a safe default when `fetchCapabilities()` has not been called.
21+
22+
- **`ServerCapabilities` public init** — all five fields are now constructible from outside the module, enabling consumer-side mocking and testing.
23+
24+
- **`loadCapabilities()`** — lazy capability accessor on `SwiftSonicClient`. Fetches from the server on first call; returns the cached value on all subsequent calls. `@discardableResult`.
25+
26+
- **`refreshCapabilities()`** — forces a new `fetchCapabilities()` pass and updates the cached `serverCapabilities` property. Use after re-authentication or when you suspect the server configuration has changed. `@discardableResult`.
27+
28+
- **`getLyricsBySongId(id:)`** — implements the OpenSubsonic `songLyrics` extension. Returns a `LyricsList` containing an array of `StructuredLyrics` sets, each with a language code, sync flag, optional display metadata, and an array of `Line` values (with optional millisecond `start` timestamps for synced lyrics).
29+
30+
- **New model types** (`Lyrics.swift`):
31+
- `LyricsList` — top-level container; `structuredLyrics` defaults to `[]` when absent from the response.
32+
- `StructuredLyrics` — one language set; fields `lang`, `synced`, `line`, `displayArtist`, `displayTitle`, `offset`.
33+
- `Line` — a single lyric line; `value` (String) + optional `start` (Int, milliseconds).
34+
35+
- **`OpenSubsonicExtension` public init** — `init(name:versions:)` is now public.
36+
37+
- **Public inits for all `SharedModels` types** — `ItemGenre`, `ReplayGain`, `ItemDate`, `DiscTitle`, `RecordLabel`, `ContributorArtist`, and `Contributor` all gain public memberwise initialisers with `nil` defaults on optional fields.
38+
39+
- **OpenSubsonic fields on `Song`** — `mediaType: String?` and `displayComposer: String?` added to `Song` and its public init (both default to `nil`).
40+
41+
- **OpenSubsonic fields on `AlbumID3`** — `explicitStatus: String?` and `version: String?` added to `AlbumID3` and its public init (both default to `nil`).
42+
43+
- **Test count** — test suite grows from 296 to ~370 tests across new suites covering capabilities caching, `KnownExtension`, `getLyricsBySongId` decoding (synced, unsynced, multi-language, empty), and all new public inits.
44+
45+
### Notes
46+
47+
All changes are additive; no breaking changes from v0.6.x. The new `loadCapabilities()` / `refreshCapabilities()` methods complement the existing `fetchCapabilities()` + `serverCapabilities` property pair — choose whichever pattern suits your architecture.
48+
49+
---
50+
1051
## [0.6.1] — 2026-04-27
1152

1253
### Added

‎README.md‎

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,20 @@ let client = SwiftSonicClient(
9898
### Checking server capabilities
9999

100100
```swift
101-
try await client.fetchCapabilities()
101+
// Lazy — fetches once, caches for all subsequent calls
102+
let caps = try await client.loadCapabilities()
103+
print("Server: \(caps.serverType ?? "unknown") \(caps.serverVersion ?? "")")
104+
print("OpenSubsonic: \(caps.isOpenSubsonic)")
102105

103-
if let caps = client.serverCapabilities {
104-
print("Server: \(caps.serverType ?? "unknown") \(caps.serverVersion ?? "")")
105-
print("OpenSubsonic: \(caps.isOpenSubsonic)")
106+
// String overload
107+
if caps.supports("songLyrics") { … }
106108

107-
if caps.supports("songLyrics") {
108-
// call OpenSubsonic-specific endpoints
109-
}
110-
}
109+
// Typed KnownExtension overload (compile-time safe)
110+
if caps.supports(.songLyrics) { … }
111+
if caps.supports(.apiKeyAuthentication) { … }
112+
113+
// Force a fresh fetch (e.g. after re-auth)
114+
let refreshed = try await client.refreshCapabilities()
111115
```
112116

113117
### Browsing
@@ -306,7 +310,7 @@ let client = SwiftSonicClient(
306310
|---|---|
307311
| `ping` | `ping()` |
308312
| `getLicense` | `getLicense()` |
309-
| `getOpenSubsonicExtensions` | `getOpenSubsonicExtensions()` / `fetchCapabilities()` |
313+
| `getOpenSubsonicExtensions` | `getOpenSubsonicExtensions()` / `fetchCapabilities()` / `loadCapabilities()` / `refreshCapabilities()` |
310314

311315
### Browsing (ID3)
312316
| Endpoint | Swift API |
@@ -393,9 +397,30 @@ let client = SwiftSonicClient(
393397
| `addChatMessage` | `addChatMessage(_:)` |
394398

395399
### Lyrics
396-
| Endpoint | Swift API |
397-
|---|---|
398-
| `getLyrics` | `getLyrics(artist:title:)` |
400+
| Endpoint | Swift API | Notes |
401+
|---|---|---|
402+
| `getLyrics` | `getLyrics(artist:title:)` | Legacy Subsonic |
403+
| `getLyricsBySongId` | `getLyricsBySongId(id:)` | OpenSubsonic `songLyrics` extension |
404+
405+
```swift
406+
// Legacy plain-text lyrics
407+
if let lyrics = try await client.getLyrics(artist: "Nine Inch Nails", title: "Hurt") {
408+
print(lyrics.value ?? "")
409+
}
410+
411+
// OpenSubsonic structured lyrics (synced + multi-language)
412+
let list = try await client.getLyricsBySongId(id: song.id)
413+
for set in list.structuredLyrics {
414+
print("\(set.lang) synced=\(set.synced)")
415+
for line in set.line {
416+
if let ms = line.start {
417+
print("[\(ms)ms] \(line.value)")
418+
} else {
419+
print(line.value)
420+
}
421+
}
422+
}
423+
```
399424

400425
### User management
401426
| Endpoint | Swift API |

0 commit comments

Comments
 (0)