importer chnges
This commit is contained in:
@@ -651,6 +651,8 @@ struct LibraryView: View {
|
||||
@State private var search = ""
|
||||
@State private var name = ""
|
||||
@State private var importing = false
|
||||
@State private var importingServer = false
|
||||
@State private var serverImportURLs = [URL]()
|
||||
private var source: String { get { model.activeSection } nonmutating set { model.activeSection = newValue } }
|
||||
@State private var singersExpanded = true
|
||||
private var expandedSingers: Set<Int64> { get { model.expandedSingerIDs } nonmutating set { model.expandedSingerIDs = newValue } }
|
||||
@@ -721,7 +723,10 @@ struct LibraryView: View {
|
||||
.searchable(text:$search,placement:.toolbar,prompt:"Search Music")
|
||||
.toolbar {
|
||||
ToolbarItemGroup {
|
||||
Button { importing = true } label: { Label("Import Music",systemImage:"square.and.arrow.down") }.help("Import music or video")
|
||||
Menu {
|
||||
Button("Import Files or Folder…") { importing = true }
|
||||
Button("Import from SMB/NAS…") { importingServer = true }
|
||||
} label: { Label("Import Music",systemImage:"square.and.arrow.down") }.help("Import from files, folders or a server drive")
|
||||
Menu { waveformActions } label: { Label("Waveforms",systemImage:"waveform") }.disabled(model.generatingWaveforms || model.scanningSource != nil).help("Prepare waveforms for a track, selected tracks or the whole library")
|
||||
Menu {
|
||||
Button("New Singer…") { model.beginSinger() }
|
||||
@@ -769,6 +774,10 @@ struct LibraryView: View {
|
||||
updateLibraryRows()
|
||||
if source == "Singer Songs",presentedSinger != singer { showSinger(singer,section:nil) }
|
||||
}
|
||||
.sheet(isPresented:$importingServer,onDismiss:{
|
||||
let urls = serverImportURLs;serverImportURLs = []
|
||||
model.importFiles(urls)
|
||||
}) { ServerImportSheet { serverImportURLs = $0 } }
|
||||
.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)
|
||||
@@ -1394,6 +1403,58 @@ private struct MusicStoreEditor: View {
|
||||
}
|
||||
private func save() { do { try model.saveStore(.init(id:store?.id ?? 0,name:name,url:url,searchTemplate:search));dismiss() } catch { self.error = error.localizedDescription } }
|
||||
}
|
||||
/// Finder owns server authentication and stores credentials through macOS.
|
||||
/// Only the user-selected mounted folder is passed to the ordinary copy importer.
|
||||
enum SMBImportAddress {
|
||||
static func url(_ address:String) throws -> URL {
|
||||
let text = address.trimmingCharacters(in:.whitespacesAndNewlines)
|
||||
guard let url = URL(string:text),url.scheme?.lowercased() == "smb",
|
||||
let host = url.host,!host.isEmpty,url.user == nil,url.password == nil,
|
||||
url.query == nil,url.fragment == nil else {
|
||||
throw LibraryError.message("Enter an SMB address such as smb://nas/Music. Leave usernames and passwords out; macOS will ask you to sign in.")
|
||||
}
|
||||
return url
|
||||
}
|
||||
}
|
||||
private struct ServerImportSheet: View {
|
||||
let selected: ([URL]) -> Void
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var address = "smb://"
|
||||
@State private var choosingFolder = false
|
||||
@State private var error: String?
|
||||
var body: some View {
|
||||
VStack(alignment:.leading,spacing:16) {
|
||||
Text("Import from SMB/NAS").font(.title2.bold())
|
||||
Text("Connect to your server, then choose a folder on its mounted drive. Supported files in that folder and its subfolders will be copied into your media library.").fixedSize(horizontal:false,vertical:true)
|
||||
TextField("smb://nas/Music",text:$address).textFieldStyle(.roundedBorder)
|
||||
Button("Connect in Finder…") {
|
||||
do {
|
||||
let url = try SMBImportAddress.url(address)
|
||||
guard NSWorkspace.shared.open(url) else { throw LibraryError.message("Could not open the server connection in Finder.") }
|
||||
error = nil
|
||||
} catch { self.error = error.localizedDescription }
|
||||
}
|
||||
Text("Finish signing in through macOS. Then use Choose Server Folder to select the connected drive under Locations. You can also choose a drive that is already connected.").font(.callout).foregroundStyle(.secondary).fixedSize(horizontal:false,vertical:true)
|
||||
if let error { Text(error).foregroundStyle(.red).fixedSize(horizontal:false,vertical:true) }
|
||||
HStack {
|
||||
Button("Cancel") { dismiss() }.keyboardShortcut(.cancelAction)
|
||||
Spacer()
|
||||
Button("Choose Server Folder…") {
|
||||
choosingFolder = true
|
||||
}.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
}.padding(24).frame(width:500)
|
||||
.fileImporter(isPresented:$choosingFolder,allowedContentTypes:[.folder],allowsMultipleSelection:false) { result in
|
||||
switch result {
|
||||
case .success(let urls):
|
||||
guard !urls.isEmpty else { return }
|
||||
selected(urls)
|
||||
dismiss()
|
||||
case .failure(let error): self.error = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private struct SingerSongsSheet: View {
|
||||
@ObservedObject var model: AppModel
|
||||
let singer: PersonPlace
|
||||
@@ -1401,6 +1462,8 @@ private struct SingerSongsSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var search = ""
|
||||
@State private var importing = false
|
||||
@State private var importingServer = false
|
||||
@State private var serverImportURLs = [URL]()
|
||||
var body: some View {
|
||||
VStack(alignment:.leading,spacing:14) {
|
||||
Text(tonight ? "Tonight for \(singer.name)" : "Songs for \(singer.name)").font(.title2.bold())
|
||||
@@ -1421,10 +1484,14 @@ private struct SingerSongsSheet: View {
|
||||
if model.songs.isEmpty { Text("Your library is empty. Import audio or MP3+G below.").foregroundStyle(.secondary) }
|
||||
}.frame(maxWidth:.infinity,alignment:.leading)
|
||||
}.frame(height:270)
|
||||
HStack { Button("Import Songs…") { importing = true };Spacer();Button("Done") { dismiss() }.keyboardShortcut(.defaultAction) }
|
||||
HStack { Button("Import Songs…") { importing = true };Button("SMB/NAS…") { importingServer = true };Spacer();Button("Done") { dismiss() }.keyboardShortcut(.defaultAction) }
|
||||
}.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) } }
|
||||
.sheet(isPresented:$importingServer,onDismiss:{
|
||||
let urls = serverImportURLs;serverImportURLs = []
|
||||
model.importFiles(urls)
|
||||
}) { ServerImportSheet { serverImportURLs = $0 } }
|
||||
.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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user