importer chnges
This commit is contained in:
@@ -118,6 +118,130 @@ final class KJamsCloneTests: XCTestCase {
|
||||
let song = try XCTUnwrap(try db.song(ids[0]));XCTAssertEqual(URL(fileURLWithPath:song.audio).deletingLastPathComponent().standardizedFileURL,media.standardizedFileURL)
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath:song.audio));XCTAssertNotNil(song.cdg)
|
||||
}
|
||||
func testLegacyEncodedZipNamesImportMatchedAudioAndGraphics() 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 fixtures = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures"))
|
||||
// A valid stored ZIP with an unflagged 0xE9 filename byte, matching older
|
||||
// Windows karaoke archives. No commercial media is part of this test.
|
||||
func number(_ value:UInt32,_ count:Int) -> Data { Data((0..<count).map { UInt8(truncatingIfNeeded:value >> (8*$0)) }) }
|
||||
func crc(_ data:Data) -> UInt32 {
|
||||
var value = UInt32.max
|
||||
for byte in data { value ^= UInt32(byte);for _ in 0..<8 { value = value & 1 == 0 ? value >> 1 : (value >> 1) ^ 0xEDB88320 } }
|
||||
return ~value
|
||||
}
|
||||
var zip = Data(),central = Data()
|
||||
for ext in ["mp3","cdg"] {
|
||||
let name = Data("Original - Caf".utf8)+Data([0xE9])+Data(("."+ext).utf8)
|
||||
let contents = try Data(contentsOf:fixtures.appendingPathComponent("Demo."+ext)),size = UInt32(contents.count),checksum = crc(contents),offset = UInt32(zip.count)
|
||||
zip += number(0x04034b50,4)+number(20,2)+number(0,2)+number(0,2)+number(0,2)+number(0,2)
|
||||
zip += number(checksum,4)+number(size,4)+number(size,4)+number(UInt32(name.count),2)+number(0,2)+name+contents
|
||||
central += number(0x02014b50,4)+number(20,2)+number(20,2)+number(0,2)+number(0,2)+number(0,2)+number(0,2)
|
||||
central += number(checksum,4)+number(size,4)+number(size,4)
|
||||
central += number(UInt32(name.count),2)+number(0,2)+number(0,2)
|
||||
central += number(0,2)+number(0,2)+number(0,4)+number(offset,4)
|
||||
central += name
|
||||
}
|
||||
let centralOffset = UInt32(zip.count);zip += central
|
||||
zip += number(0x06054b50,4)+number(0,2)+number(0,2)+number(2,2)+number(2,2)+number(UInt32(central.count),4)+number(centralOffset,4)+number(0,2)
|
||||
let archive = root.appendingPathComponent("Legacy.zip");try zip.write(to:archive)
|
||||
let db = try Library(url:root.appendingPathComponent("Library.sqlite")),importer = Importer(media:root.appendingPathComponent("Media"))
|
||||
let ids = try importer.run([archive],library:db);XCTAssertEqual(ids.count,1)
|
||||
let song = try XCTUnwrap(try db.song(ids[0]));let graphics = try XCTUnwrap(song.cdg)
|
||||
XCTAssertTrue(song.title.contains("Café"),"Legacy filenames must be renamed to Unicode before import")
|
||||
XCTAssertEqual(try Data(contentsOf:URL(fileURLWithPath:song.audio)),try Data(contentsOf:fixtures.appendingPathComponent("Demo.mp3")))
|
||||
XCTAssertEqual(try Data(contentsOf:URL(fileURLWithPath:graphics)),try Data(contentsOf:fixtures.appendingPathComponent("Demo.cdg")))
|
||||
var progress = ImportProgress();_ = try importer.run([archive],library:db,progress:{progress = $0})
|
||||
XCTAssertEqual(progress.skipped,1);XCTAssertEqual(try db.songs().count,1)
|
||||
}
|
||||
@MainActor func testMediaFolderChoicePersistsAndFutureImportsUseIt() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
defer { try? FileManager.default.removeItem(at:root) }
|
||||
let model = AppModel(storageURL:root.appendingPathComponent("Library")),external = root.appendingPathComponent("External Media")
|
||||
try model.setMediaDirectory(external)
|
||||
let reopened = AppModel(storageURL:root.appendingPathComponent("Library"))
|
||||
XCTAssertEqual(reopened.managedMediaDirectory.resolvingSymlinksInPath().path,external.resolvingSymlinksInPath().path)
|
||||
let fixture = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.zip"))
|
||||
reopened.importFiles([fixture])
|
||||
for _ in 0..<300 { if !reopened.isImporting { break };try await Task.sleep(nanoseconds:10_000_000) }
|
||||
XCTAssertFalse(reopened.isImporting);XCTAssertNil(reopened.importProgress?.failure)
|
||||
let song = try XCTUnwrap(reopened.songs.first)
|
||||
XCTAssertTrue(URL(fileURLWithPath:song.audio).resolvingSymlinksInPath().path.hasPrefix(external.resolvingSymlinksInPath().path+"/"));XCTAssertTrue(URL(fileURLWithPath:try XCTUnwrap(song.cdg)).resolvingSymlinksInPath().path.hasPrefix(external.resolvingSymlinksInPath().path+"/"))
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath:root.appendingPathComponent("Library/Media").path))
|
||||
}
|
||||
func testVerifiedMoveUpdatesLinksPreservesSingerPreferencesAndFreesOriginals() 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"),source = root.appendingPathComponent("Media"),target = root.appendingPathComponent("External")
|
||||
let db = try Library(url:database),fixture = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.zip"))
|
||||
let ids = try Importer(media:source).run([fixture],library:db),id = try XCTUnwrap(ids.first)
|
||||
try db.addEntity("singers",name:"Alex");let singer = try XCTUnwrap(try db.entities("singers").first?.id);try db.assignSong(id,singer:singer)
|
||||
try db.savePreference(song:id,singer:singer,value:.init(pitch:3,tempo:1.1,rating:4));try db.rate(id,5)
|
||||
let before = try XCTUnwrap(try db.song(id)),audio = try Data(contentsOf:URL(fileURLWithPath:before.audio))
|
||||
var studio = StudioSettings();studio.backgroundPath = before.audio
|
||||
try JSONEncoder().encode(studio).write(to:root.appendingPathComponent("StudioSettings.json"))
|
||||
let result = try ManagedMediaStorage.move(database:database,roots:[source],destination:target)
|
||||
XCTAssertTrue(result.finished);XCTAssertNil(result.failure);XCTAssertEqual(result.completed,2)
|
||||
let after = try XCTUnwrap(try db.song(id));XCTAssertEqual(after.id,id);XCTAssertEqual(after.rating,5)
|
||||
XCTAssertTrue(after.audio.hasPrefix(target.resolvingSymlinksInPath().path+"/"))
|
||||
let movedSettings = try JSONDecoder().decode(StudioSettings.self,from:Data(contentsOf:root.appendingPathComponent("StudioSettings.json")))
|
||||
XCTAssertEqual(movedSettings.backgroundPath,after.audio)
|
||||
XCTAssertEqual(try Data(contentsOf:URL(fileURLWithPath:after.audio)),audio)
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath:before.audio));XCTAssertFalse(FileManager.default.fileExists(atPath:try XCTUnwrap(before.cdg)))
|
||||
XCTAssertEqual(try db.preference(song:id,singer:singer).pitch,3)
|
||||
var progress = ImportProgress();_ = try Importer(media:target).run([fixture],library:db,progress:{progress = $0})
|
||||
XCTAssertEqual(progress.skipped,1)
|
||||
}
|
||||
func testCancelledMoveKeepsOriginalMediaAndDatabaseLinks() 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"),source = root.appendingPathComponent("Media"),target = root.appendingPathComponent("External")
|
||||
let db = try Library(url:database),fixture = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.zip"))
|
||||
_ = try Importer(media:source).run([fixture],library:db);let before = try XCTUnwrap(try db.songs().first)
|
||||
var cancel = false
|
||||
XCTAssertThrowsError(try ManagedMediaStorage.move(database:database,roots:[source],destination:target,cancelled:{cancel},progress:{if $0.completed == 1 { cancel = true }})) { XCTAssertTrue($0 is CancellationError) }
|
||||
XCTAssertEqual(try db.song(before.id)?.audio,before.audio);XCTAssertTrue(FileManager.default.fileExists(atPath:before.audio))
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath:try XCTUnwrap(before.cdg)));XCTAssertNil(try db.setting(MediaStorageLocation.key))
|
||||
let retried = try ManagedMediaStorage.move(database:database,roots:[source],destination:target);XCTAssertNil(retried.failure)
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath:before.audio))
|
||||
}
|
||||
func testMoveConflictDoesNotOverwriteDestinationOrDeleteOriginal() 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"),source = root.appendingPathComponent("Media"),target = root.appendingPathComponent("External")
|
||||
let db = try Library(url:database),fixture = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures/Demo.mp3"))
|
||||
_ = try Importer(media:source).run([fixture],library:db);let song = try XCTUnwrap(try db.songs().first)
|
||||
try FileManager.default.createDirectory(at:target,withIntermediateDirectories:true)
|
||||
let conflict = target.appendingPathComponent(URL(fileURLWithPath:song.audio).lastPathComponent),different = Data("Other media".utf8);try different.write(to:conflict)
|
||||
XCTAssertThrowsError(try ManagedMediaStorage.move(database:database,roots:[source],destination:target))
|
||||
XCTAssertEqual(try Data(contentsOf:conflict),different);XCTAssertTrue(FileManager.default.fileExists(atPath:song.audio));XCTAssertEqual(try db.song(song.id)?.audio,song.audio)
|
||||
}
|
||||
func testUnavailableExternalDriveDoesNotCreateALocalFallbackFolder() {
|
||||
let path = URL(fileURLWithPath:"/Volumes/KJMix-Offline-"+UUID().uuidString+"/Media")
|
||||
XCTAssertThrowsError(try ManagedMediaStorage.prepare(path))
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath:path.path))
|
||||
}
|
||||
@MainActor func testMediaMoveCollectsPreviousImportFolders() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
defer { try? FileManager.default.removeItem(at:root) }
|
||||
let model = AppModel(storageURL:root.appendingPathComponent("Library")),a = root.appendingPathComponent("External A"),b = root.appendingPathComponent("External B")
|
||||
let fixtures = try XCTUnwrap(Bundle(for:Self.self).resourceURL?.appendingPathComponent("Fixtures"))
|
||||
model.importFiles([fixtures.appendingPathComponent("Demo.mp3")])
|
||||
for _ in 0..<300 { if !model.isImporting { break };try await Task.sleep(nanoseconds:10_000_000) }
|
||||
model.dismissImport();try model.setMediaDirectory(a)
|
||||
model.importFiles([fixtures.appendingPathComponent("TestTrack.mp3")])
|
||||
for _ in 0..<300 { if !model.isImporting { break };try await Task.sleep(nanoseconds:10_000_000) }
|
||||
model.dismissImport();let ids = Set(model.songs.map(\.id));model.moveMedia(to:b)
|
||||
for _ in 0..<300 { if !model.movingMedia { break };try await Task.sleep(nanoseconds:10_000_000) }
|
||||
XCTAssertFalse(model.movingMedia);XCTAssertNil(model.storageProgress?.failure)
|
||||
XCTAssertEqual(Set(model.songs.map(\.id)),ids);XCTAssertEqual(model.songs.count,2)
|
||||
XCTAssertTrue(model.songs.allSatisfy{$0.audio.hasPrefix(b.resolvingSymlinksInPath().path+"/")})
|
||||
XCTAssertEqual(model.managedMediaDirectory.resolvingSymlinksInPath().path,b.resolvingSymlinksInPath().path)
|
||||
}
|
||||
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