You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
79 lines
3.5 KiB
79 lines
3.5 KiB
import Foundation
|
|
|
|
// The user-editable subset of Track, plus the pure logic for single- and
|
|
// multi-track editing. No UI, no I/O — fully unit-testable.
|
|
//
|
|
// Note: Named `EditableTrackField` (not `TrackField`) to avoid collision with
|
|
// the existing `TrackField` enum in SmartPlaylistCondition.swift, which covers
|
|
// all filterable columns including non-editable ones.
|
|
nonisolated enum EditableTrackField: CaseIterable, Sendable {
|
|
case title, artist, albumArtist, album, genre, composer
|
|
case year, trackNumber, discNumber, bpm, rating
|
|
}
|
|
|
|
nonisolated struct EditableTrackFields: Equatable, Sendable {
|
|
var title: String
|
|
var artist: String
|
|
var albumArtist: String
|
|
var album: String
|
|
var genre: String
|
|
var composer: String
|
|
var year: Int?
|
|
var trackNumber: Int?
|
|
var discNumber: Int?
|
|
var bpm: Int?
|
|
var rating: Int
|
|
|
|
init(from t: Track) {
|
|
title = t.title; artist = t.artist; albumArtist = t.albumArtist
|
|
album = t.album; genre = t.genre; composer = t.composer
|
|
year = t.year; trackNumber = t.trackNumber; discNumber = t.discNumber
|
|
bpm = t.bpm; rating = t.rating
|
|
}
|
|
|
|
func changedFields(to other: EditableTrackFields) -> Set<EditableTrackField> {
|
|
var changed: Set<EditableTrackField> = []
|
|
if title != other.title { changed.insert(.title) }
|
|
if artist != other.artist { changed.insert(.artist) }
|
|
if albumArtist != other.albumArtist { changed.insert(.albumArtist) }
|
|
if album != other.album { changed.insert(.album) }
|
|
if genre != other.genre { changed.insert(.genre) }
|
|
if composer != other.composer { changed.insert(.composer) }
|
|
if year != other.year { changed.insert(.year) }
|
|
if trackNumber != other.trackNumber { changed.insert(.trackNumber) }
|
|
if discNumber != other.discNumber { changed.insert(.discNumber) }
|
|
if bpm != other.bpm { changed.insert(.bpm) }
|
|
if rating != other.rating { changed.insert(.rating) }
|
|
return changed
|
|
}
|
|
|
|
// Returns prefill values (from the first track) plus the set of fields whose
|
|
// values are NOT identical across all tracks (shown as "Mixed" in the UI).
|
|
// Precondition: caller must pass at least one track; an empty array will trap.
|
|
static func shared(across tracks: [Track]) -> (values: EditableTrackFields, mixed: Set<EditableTrackField>) {
|
|
precondition(!tracks.isEmpty, "shared(across:) requires at least one track")
|
|
let base = EditableTrackFields(from: tracks[0])
|
|
var mixed: Set<EditableTrackField> = []
|
|
for t in tracks.dropFirst() {
|
|
mixed.formUnion(base.changedFields(to: EditableTrackFields(from: t)))
|
|
}
|
|
return (base, mixed)
|
|
}
|
|
|
|
// Copies ONLY the edited fields onto the track; everything else is untouched.
|
|
func apply(editing edited: Set<EditableTrackField>, to track: Track) -> Track {
|
|
var t = track
|
|
if edited.contains(.title) { t.title = title }
|
|
if edited.contains(.artist) { t.artist = artist }
|
|
if edited.contains(.albumArtist) { t.albumArtist = albumArtist }
|
|
if edited.contains(.album) { t.album = album }
|
|
if edited.contains(.genre) { t.genre = genre }
|
|
if edited.contains(.composer) { t.composer = composer }
|
|
if edited.contains(.year) { t.year = year }
|
|
if edited.contains(.trackNumber) { t.trackNumber = trackNumber }
|
|
if edited.contains(.discNumber) { t.discNumber = discNumber }
|
|
if edited.contains(.bpm) { t.bpm = bpm }
|
|
if edited.contains(.rating) { t.rating = rating }
|
|
return t
|
|
}
|
|
}
|
|
|