importer chnges
This commit is contained in:
@@ -286,6 +286,107 @@ final class KJamsCloneTests: XCTestCase {
|
||||
XCTAssertTrue(model.songs.allSatisfy{$0.audio.hasPrefix(b.resolvingSymlinksInPath().path+"/")})
|
||||
XCTAssertEqual(model.managedMediaDirectory.resolvingSymlinksInPath().path,b.resolvingSymlinksInPath().path)
|
||||
}
|
||||
func testNameTemplateExtractsAlbumTrackArtistAndTitle() throws {
|
||||
let song = Song(id:1,title:"SF285-16 - Michael Bublé - Haven't Met You Yet",audio:"/audio.mp3",cdg:nil,rating:4,plays:7,label:"Sunfly",duration:210)
|
||||
let parsed = try NameTemplate("L-T - R - S").interpret(song.title,before:NameMetadata(song))
|
||||
XCTAssertEqual(parsed.album,"SF285");XCTAssertEqual(parsed.track,"16");XCTAssertEqual(parsed.artist,"Michael Bublé");XCTAssertEqual(parsed.title,"Haven't Met You Yet")
|
||||
let compact = try NameTemplate("L-T-R-S").interpret("SF285 - 01 - Björk - Jóga",before:NameMetadata(song))
|
||||
XCTAssertEqual(compact.track,"01");XCTAssertEqual(compact.artist,"Björk");XCTAssertEqual(compact.title,"Jóga")
|
||||
}
|
||||
func testNameTemplatesRepeatIgnoreAndExtractRendition() throws {
|
||||
let before = NameMetadata(Song(id:1,title:"Original",audio:"/a",cdg:nil,rating:0,plays:0))
|
||||
let artist = try NameTemplate("L-T - R-R - S").interpret("SC1234-04 - Olivia Newton-John - Physical",before:before)
|
||||
XCTAssertEqual(artist.artist,"Olivia Newton John");XCTAssertEqual(artist.title,"Physical")
|
||||
let disc = try NameTemplate("L-L-T-R-S").interpret("SC1234-5-04-The Foobars-The Song",before:before)
|
||||
XCTAssertEqual(disc.album,"SC1234 5");XCTAssertEqual(disc.track,"04")
|
||||
for (template,text) in [("I | T. R - S [V]","unused | 03. Artist - Title [Backing vocals]"),("I | T. R - S (V)","unused | 03. Artist - Title (Backing vocals)"),("I | T. R - S {V}","unused | 03. Artist - Title {Backing vocals}")] {
|
||||
let parsed = try NameTemplate(template).interpret(text,before:before)
|
||||
XCTAssertEqual(parsed.artist,"Artist");XCTAssertEqual(parsed.title,"Title");XCTAssertEqual(parsed.track,"03");XCTAssertEqual(parsed.rendition,"Backing vocals")
|
||||
}
|
||||
}
|
||||
func testNameTemplateDashAndUnderscoreOptionsAreExplicit() throws {
|
||||
let before = NameMetadata(Song(id:1,title:"Original",audio:"/a",cdg:nil,rating:0,plays:0)),parser = try NameTemplate("R - S")
|
||||
XCTAssertThrowsError(try parser.interpret("Artist — Title",before:before))
|
||||
XCTAssertEqual(try parser.interpret("Artist — Title",before:before,emDashes:true).title,"Title")
|
||||
XCTAssertEqual(try parser.interpret("The_Artist - The_Title",before:before).artist,"The Artist")
|
||||
XCTAssertEqual(try parser.interpret("The_Artist - The_Title",before:before,underscores:false).title,"The_Title")
|
||||
}
|
||||
func testInvalidNameTemplatesAndUnmatchedSongsFailSafely() throws {
|
||||
for text in ["","A - S","R_*S","RS","I","T-T-S","R+$S"] { XCTAssertThrowsError(try NameTemplate(text),text) }
|
||||
let songs = [Song(id:2,title:"Artist - Title",audio:"/a",cdg:nil,rating:0,plays:0),Song(id:1,title:"Unmatched",audio:"/b",cdg:nil,rating:0,plays:0)]
|
||||
let rows = try NameTemplate.preview(songs,template:"R - S",source:.song,emDashes:false,underscores:true)
|
||||
XCTAssertEqual(rows.map(\.id),[1,2]);XCTAssertNil(rows[0].after);XCTAssertNotNil(rows[0].issue);XCTAssertEqual(rows.compactMap(\.change).count,1)
|
||||
XCTAssertEqual(rows[1].after?.title,"Title")
|
||||
}
|
||||
func testNameTemplateUsesOriginalFolderMetadataAndRejectsManagedHashFallback() throws {
|
||||
let song = Song(id:1,title:"01",audio:"/Media/"+String(repeating:"a",count:64)+".mp3",cdg:nil,rating:0,plays:0,originalName:"Jóga",originalPath:"/Original/Album/Björk/Jóga.mp3")
|
||||
let parser = try NameTemplate("/L/R/S"),input = try parser.input(song,source:.file)
|
||||
let parsed = try parser.interpret(input,before:NameMetadata(song))
|
||||
XCTAssertEqual(parsed.album,"Album");XCTAssertEqual(parsed.artist,"Björk");XCTAssertEqual(parsed.title,"Jóga")
|
||||
var legacy = song;legacy.originalName = "";legacy.originalPath = ""
|
||||
XCTAssertThrowsError(try parser.input(legacy,source:.file));XCTAssertEqual(try parser.input(legacy,source:.song),"01")
|
||||
}
|
||||
func testImportRecordsOriginalNamesAndBackfillsSkippedArchives() throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at:root,withIntermediateDirectories:true);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let db = try Library(url:root.appendingPathComponent("Library.sqlite")),importer = Importer(media:root.appendingPathComponent("Media"))
|
||||
let archive = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.zip"))
|
||||
let ids = try importer.run([archive],library:db),id = try XCTUnwrap(ids.first)
|
||||
let first = try XCTUnwrap(try db.song(id));XCTAssertFalse(first.originalName.isEmpty);XCTAssertTrue(first.originalPath.hasPrefix(archive.path+"/"));XCTAssertFalse(first.originalName == URL(fileURLWithPath:first.audio).deletingPathExtension().lastPathComponent)
|
||||
try db.updateMetadata(song:id,title:"My Edited Title",artist:"Artist",album:"Album",label:"",rating:5)
|
||||
try db.execute("UPDATE songs SET original_name='',original_path='' WHERE id=?",[String(id)])
|
||||
var progress = ImportProgress();_ = try importer.run([archive],library:db,progress:{progress = $0})
|
||||
let after = try XCTUnwrap(try db.song(id));XCTAssertEqual(after.title,"My Edited Title");XCTAssertEqual(after.rating,5);XCTAssertFalse(after.originalName.isEmpty);XCTAssertEqual(progress.skipped,1);XCTAssertEqual(try db.songs().count,1)
|
||||
}
|
||||
func testReinterpretationPersistsFieldsPreservesRelationshipsAndCanUndo() throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at:root,withIntermediateDirectories:true);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let database = root.appendingPathComponent("Library.sqlite"),db = try Library(url:database)
|
||||
let a = try db.add(title:"SF100-01 - Artist A - Title A (Explicit)",audio:"/a.mp3",cdg:"/a.cdg"),b = try db.add(title:"SF100-02 - Artist B - Title B (Clean)",audio:"/b.mp3",cdg:nil)
|
||||
try db.rate(a,5);try db.addEntity("singers",name:"Alex");let singer = try XCTUnwrap(try db.entities("singers").first?.id)
|
||||
try db.assignSong(a,singer:singer);try db.savePreference(song:a,singer:singer,value:.init(pitch:4,tempo:0.9,rating:3))
|
||||
try db.addEntity("playlists",name:"Set");let playlist = try XCTUnwrap(try db.entities("playlists").first?.id)
|
||||
try db.addToPlaylist(playlist,song:b);try db.addToPlaylist(playlist,song:a)
|
||||
let before = try db.songs(),plan = try NameTemplate.preview(before,template:"L-T - R - S (V)",source:.song,emDashes:false,underscores:true).compactMap(\.change)
|
||||
XCTAssertEqual(try db.reinterpretNames(plan),2)
|
||||
let reopened = try Library(url:database),after = try XCTUnwrap(try reopened.song(a))
|
||||
XCTAssertEqual(after.title,"Title A");XCTAssertEqual(after.artist,"Artist A");XCTAssertEqual(after.album,"SF100");XCTAssertEqual(after.trackNumber,"01");XCTAssertEqual(after.rendition,"Explicit")
|
||||
XCTAssertEqual(after.audio,"/a.mp3");XCTAssertEqual(after.cdg,"/a.cdg");XCTAssertEqual(after.rating,5)
|
||||
XCTAssertEqual(try reopened.preference(song:a,singer:singer),.init(pitch:4,tempo:0.9,rating:3));XCTAssertEqual(try reopened.playlistSongIDs(playlist),[b,a])
|
||||
XCTAssertTrue(try reopened.hasNameUndo());XCTAssertEqual(try reopened.undoNameReinterpretation(),2)
|
||||
XCTAssertEqual(try reopened.songs(),before);XCTAssertFalse(try reopened.hasNameUndo());XCTAssertEqual(try reopened.playlistSongIDs(playlist),[b,a])
|
||||
}
|
||||
func testStaleReinterpretationPreviewRollsBackEntireBatch() throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at:root,withIntermediateDirectories:true);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let db = try Library(url:root.appendingPathComponent("Library.sqlite"))
|
||||
let a = try db.add(title:"Artist - First",audio:"/a",cdg:nil),b = try db.add(title:"Artist - Second",audio:"/b",cdg:nil)
|
||||
let plan = try NameTemplate.preview(db.songs(),template:"R - S",source:.song,emDashes:false,underscores:true).compactMap(\.change)
|
||||
try db.updateMetadata(song:b,title:"Later Edit",artist:"",album:"",label:"",rating:0)
|
||||
XCTAssertThrowsError(try db.reinterpretNames(plan));XCTAssertEqual(try db.song(a)?.title,"Artist - First");XCTAssertEqual(try db.song(b)?.title,"Later Edit");XCTAssertFalse(try db.hasNameUndo())
|
||||
}
|
||||
func testUndoReinterpretationDoesNotOverwriteLaterEdits() throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at:root,withIntermediateDirectories:true);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let db = try Library(url:root.appendingPathComponent("Library.sqlite"))
|
||||
let id = try db.add(title:"Artist - Title",audio:"/a",cdg:nil)
|
||||
_ = try db.reinterpretNames(NameTemplate.preview(db.songs(),template:"R - S",source:.song,emDashes:false,underscores:true).compactMap(\.change))
|
||||
try db.updateMetadata(song:id,title:"Later Edit",artist:"Artist",album:"",label:"",rating:0)
|
||||
XCTAssertThrowsError(try db.undoNameReinterpretation());XCTAssertEqual(try db.song(id)?.title,"Later Edit");XCTAssertTrue(try db.hasNameUndo())
|
||||
}
|
||||
@MainActor func testSavedCustomTemplatesAndAsyncReinterpretUndo() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString);defer { try? FileManager.default.removeItem(at:root) }
|
||||
let model = AppModel(storageURL:root)
|
||||
try model.saveNameTemplates("R | S [V]\nT. S")
|
||||
XCTAssertTrue(model.savedNameTemplates().contains("R | S [V]"));XCTAssertThrowsError(try model.saveNameTemplates("A - S"))
|
||||
let id = try XCTUnwrap(model.library).add(title:"Artist | Title [Clean]",audio:"/a",cdg:nil);try model.refresh()
|
||||
let changes = try NameTemplate.preview(model.songs,template:"R | S [V]",source:.song,emDashes:false,underscores:true).compactMap(\.change)
|
||||
try await model.applyNameChanges(changes)
|
||||
XCTAssertTrue(model.canUndoNames);XCTAssertFalse(model.namesBusy);XCTAssertEqual(model.songs.first?.rendition,"Clean")
|
||||
XCTAssertTrue(SongSearch.matches(try XCTUnwrap(model.songs.first),query:"version:Clean"))
|
||||
try await model.undoNameChanges();XCTAssertFalse(model.canUndoNames);XCTAssertEqual(model.songs.first?.title,"Artist | Title [Clean]")
|
||||
XCTAssertEqual(model.songs.first?.id,id)
|
||||
}
|
||||
func packet(_ instruction: UInt8, _ data: [UInt8]) -> [UInt8] { [9,instruction,0,0] + Array((data + Array(repeating: 0,count:16)).prefix(16)) + [0,0,0,0] }
|
||||
func testPresetTileXORAndInvalidCoordinates() {
|
||||
var d = CDGDecoder()
|
||||
|
||||
Reference in New Issue
Block a user