importer chnges

This commit is contained in:
Mark Gregory
2026-11-05 10:26:34 +00:00
parent 38a99d94ba
commit faa1fe43d0
4 changed files with 71 additions and 7 deletions
+31 -3
View File
@@ -39,7 +39,35 @@ struct Importer {
return siblingCache[directory]?[url.deletingPathExtension().lastPathComponent.lowercased()] ?? [:]
}
defer { state.total = max(state.total,state.completed);progress(state) }
var files = urls
// Discover before copying: overlapping selections must not import twice,
// and a destination inside the selected tree must not feed itself back in.
let keys: Set<URLResourceKey> = [.isDirectoryKey,.isRegularFileKey,.isSymbolicLinkKey,.isPackageKey]
let supported = Set(["mp3","wav","m4a","mp4","m4v","mov","cdg","zip"])
let managedRoot = media.resolvingSymlinksInPath().standardizedFileURL
var visited = Set<URL>(),discovered = Set<URL>()
func discover(_ input:URL) throws {
if cancelled() { throw CancellationError() }
let url = input.standardizedFileURL
guard visited.insert(url).inserted else { return }
do {
let values = try url.resourceValues(forKeys:keys)
// Do not follow directory symlinks (cycles or files outside the selection).
guard values.isSymbolicLink != true else { return }
if values.isDirectory == true {
guard values.isPackage != true,url.resolvingSymlinksInPath().standardizedFileURL.path != managedRoot.path else { return }
report("Scanning "+url.path)
for child in try FileManager.default.contentsOfDirectory(at:url,includingPropertiesForKeys:Array(keys),options:[.skipsHiddenFiles]) {
try discover(child)
}
} else if values.isRegularFile == true,supported.contains(url.pathExtension.lowercased()) {
discovered.insert(url)
}
} catch is CancellationError { throw CancellationError() }
catch { throw LibraryError.message("Could not scan \(url.path): \(error.localizedDescription)") }
}
report("Scanning selected files and folders…",force:true)
for url in urls { try discover(url) }
var files = discovered.sorted { $0.path < $1.path }
var temporary = [URL]()
defer { for url in temporary { try? FileManager.default.removeItem(at: url) } }
var songIDs = Set<Int64>()
@@ -57,7 +85,7 @@ struct Importer {
if let archive = archiveForFile[audio.resolvingSymlinksInPath().path] { archiveSongIDs[archive,default:[]].append(id) }
else { try library.rememberImport(path:audio.standardizedFileURL.path,signature:signature,songs:[id]) }
}
for zip in urls where zip.pathExtension.lowercased() == "zip" {
for zip in files where zip.pathExtension.lowercased() == "zip" {
if cancelled() { throw CancellationError() }
let signature = try importSignature(zip)
if let existing = try library.importedSongs(path:zip.standardizedFileURL.path,signature:signature) {
@@ -125,7 +153,7 @@ struct Importer {
if let audio = ["mp3","wav","m4a","mp4","m4v","mov"].compactMap({ matching[$0] }).first { files.append(audio) }
}
var imported = 0
let audioFiles = Set(files).filter { ["mp3","wav","m4a","mp4","m4v","mov"].contains($0.pathExtension.lowercased()) }.sorted { $0.path < $1.path }
let audioFiles = Set(files.map { $0.standardizedFileURL }).filter { ["mp3","wav","m4a","mp4","m4v","mov"].contains($0.pathExtension.lowercased()) }.sorted { $0.path < $1.path }
state.total = audioFiles.count+state.completed
report("Importing tracks…",force:true)
for audio in audioFiles {
+2 -2
View File
@@ -769,7 +769,7 @@ struct LibraryView: View {
updateLibraryRows()
if source == "Singer Songs",presentedSinger != singer { showSinger(singer,section:nil) }
}
.fileImporter(isPresented: $importing, allowedContentTypes: [.audio, .movie, .zip, UTType(filenameExtension: "cdg") ?? .data], allowsMultipleSelection: true) { result in
.fileImporter(isPresented: $importing, allowedContentTypes: [.folder, .audio, .movie, .zip, UTType(filenameExtension: "cdg") ?? .data], allowsMultipleSelection: true) { result in
switch result {
case .success(let urls): model.importFiles(urls)
case .failure(let error): model.error = error.localizedDescription
@@ -1425,7 +1425,7 @@ private struct SingerSongsSheet: View {
}.padding(24).frame(width:520).background(StudioTheme.frame)
.disabled(model.importProgress != nil || model.storageProgress != nil)
.overlay { if model.storageProgress != nil { ImportProgressDialog(model:model,storageMode:true) } else if model.importProgress != nil { ImportProgressDialog(model:model) } }
.fileImporter(isPresented:$importing,allowedContentTypes:[.audio,.movie,.zip,UTType(filenameExtension:"cdg") ?? .data],allowsMultipleSelection:true) { result in
.fileImporter(isPresented:$importing,allowedContentTypes:[.folder,.audio,.movie,.zip,UTType(filenameExtension:"cdg") ?? .data],allowsMultipleSelection:true) { result in
switch result { case .success(let urls):model.importFiles(urls);case .failure(let error):model.error = error.localizedDescription }
}
}