获取在 macOS 上写入多个文件的权限

huangapple go评论80阅读模式
英文:

Getting permission to write multiple files on macOS

问题

I have a Swift program that helps organise cat shows. It generates several rtf files and an xml file, which should be saved into one folder on disk, and only have minor modifications to their file names - the first one could be named 'Cat show.rtf', then the next 'Cat show judges.rtf', and the next 'Cat show.xml'.

I would like to only display one NSSavePanel to locate the folder to save the files in, rather than keep popping up a save panel for each file.

I tried using a NSSavePanel to get a URL for the first file, and then modifying that URL for each subsequent file. The first file was saved, but each following file got an 'Operation not permitted' error and was not saved.
I tried using NSSavePanel to get the folder to save the files in, but got an 'Operation not permitted' error for all of the files.

I could just use NSSavePanel to get the URL for each file individually, but would rather not do that as it will confuse the users.

Does anyone have any idea how I can do this?

The code I use to change the URL is:

func newURL(from oldURL : URL, with newName: String) -> URL {
  return oldURL.deletingLastPathComponent().appendingPathComponent(newName)
}

let challengesURL  = newURL(from: url, with: "\(name).xml")
英文:

I have a Swift program that helps organise cat shows.
It generates several rtf files and an xml file, which should be saved into one folder on disk, and only have minor modifications to their file names - the first one could be named 'Cat show.rtf', then the next 'Cat show judges.rtf', and the next 'Cat show.xml'.

I would like to only display one NSSavePanel to locate the folder to save the files in, rather than keep popping up a save panel for each file.

I tried using a NSSavePanel to get a URL for the first file, and then modifying that URL for each subsequent file. The first file was saved, but each following file got a 'Operation not permitted' error and was not saved.
I tried using NSSavePanel to get the folder to save the files in, but got a 'Operation not permitted' error for all of the files.
I could just use NSSavePanel to get the URL for each file individually, but would rather not do that as it will confuse the users.

Does anyone have any idea how I can do this?

The code I use to change the URL is:

func newURL(from oldURL : URL, with newName: String) -> URL {
  return oldURL.deletingLastPathComponent().appendingPathComponent(newName)
}

let challengesURL  = newURL(from: url, with: "\(name).xml")

答案1

得分: 1

OK, 我已确认这里发生了什么。首先,您需要启用AppSandbox并对用户选择的文件授予文件访问权限以允许读写。我已经在下面的注释中注明了如何执行此操作,但由于您可以保存用户选择的文件名,我建议这是可以的。

简而言之,NSSavePanel仅返回输入的文件名的安全范围URL,而不返回目录。因此,您可以多次保存该文件,但不能调整文件名(因为它是一个没有权限的新URL)。一个可能的解决方法以及我以前如何保存多个文件的方式是使用NSOpenPanel,它允许用户选择目录。这将返回一个具有安全范围的目录URL,因此您具有目录范围内的权限。这允许您在该目录中保存多个文件。

在下面的代码中,我已放置了演示这两个解决方案的代码,您可以看到使用NSSavePanel,它将输入的文件名保存两次,并更改了内容,但不会写入通过程序更改的文件名。通过使用NSOpenPanel选择目录,并在文本字段中收集来自用户的文件名,所有文件都可以正常保存。

希望这有所帮助。

(以下是您提供的代码,我已经提取了代码部分并进行了翻译,其他文本不受影响。)

struct ContentView: View {
    @State var sampleData: [String] = ["Rob", "Jane", "Freddy", "Peter"]
    @State var filename: String = ""
    
    var body: some View {
        VStack {
            Text("File Tests")
            TextField("Enter Filename:", text: $filename).frame(width: 200)
            Button("Save Using Save Panel") {
                savePanel()
            }
            Button("Save NSOpenPanel Directory") {
                savePanelUsingOpenPanelv2()
            }
        }
    }
    
    // ... (其余部分的翻译与代码保持一致)
}

NSSavePanel和NSOpenPanel文档确认了返回的URL的安全范围。有一些保存安全范围NSURL书签的能力,但我以前没有使用过。有关书签的详细信息可以在文档中找到。这似乎是一个额外的复杂性。

NSURL文档

您可以考虑向特定目录添加应用程序权限,但由于您似乎希望用户可以选择任何位置进行保存,因此使用面向面板的方法是正确的方式,因为不建议使用广泛开放的权限。

AppSandbox权限文档

英文:

OK, I have confirmed what is happening here. First of all you need to have the AppSandbox enabled and the file access permissioned to allow read/write for the User Selected File. I have noted how to do this in the comments below, but as you are able to save the user selected file name I propose this is OK.

In short, the NSSavePanel only returns a Security Scoped URL for the filename entered, not the directory. As such you can save that file many times but not adjust the filename (as it is a new URL without permissions). A possible work around and how I had previously saved multiple files is to use an NSOpenPanel which allows the user to select a directory. This returns a Security Scoped directory url so you have permission to the directory within the scope. This allows you to save multiple files in that directory.

In the code below I have put the two solutions that demonstrate this and you can see with the NSSavePanel, it saves the entered filename twice with amended contents but does not write the programmatically amended filename. By using the NSOpenPanel to select a directory, and collect the filename from the user in a text field, all files are saved fine.

Hope this helps.

	struct ContentView: View {
	@State var sampleData: [String] = ["Rob" , "Jane" , "Freddy" , "Peter"]
	@State var filename: String = ""
	
	var body: some View {
		VStack {
			Text("File Tests")
			TextField("Enter Filename:", text: $filename).frame(width: 200)
			Button("Save Using Save Panel") {
				savePanel()
			}
			Button("Save NSOpenPanel Directory") {
				savePanelUsingOpenPanelv2()
			}
		}
	}
	
	func savePanelUsingOpenPanelv2() {
		let panel = NSOpenPanel()
		// Sets up so user can only select a single directory
		panel.canChooseFiles = false
		panel.canChooseDirectories = true
		panel.allowsMultipleSelection = false
		panel.showsHiddenFiles = false
		panel.title = "Select Save Directory"
		panel.prompt = "Select Save Directory"
		
		let response = panel.runModal()
		if response == .OK {
			if let panelURL = panel.url { // This is a directory url
				let saveURL = panelURL.appending(component: "\(filename).txt")
				self.saveListAsURL(saveURL) // Saving the initial filename
				print(saveURL.description)
				let newName = "NewNameDirectoryTest"
				let updatedURL = newURL(from: saveURL, with: "\(newName).txt") // Updating the url
				// Updating the array saved
				sampleData.append("Joe")
				sampleData.append("Jack")
				// Saving the updated URL / filename.
				saveListAsURL(updatedURL) // Work fine as directory has security scope.
				print(updatedURL.description)
			}
		}
	}
	
	func savePanel() {
		let savePanel = NSSavePanel()
		if savePanel.runModal() == .OK {
			if let url = savePanel.url { // This is a file url
				self.saveListAsURL(url) // Saving with the panel selected filename
				print(url.description)
				let newName = "NewNameSavePanelTest"
				let updatedURL = newURL(from: url, with: "\(newName).txt")
				
				sampleData.append("Joe")
				sampleData.append("Jack")

				saveListAsURL(updatedURL) // Saving updated url fails
				print(updatedURL.description)
				saveListAsURL(url) // saving original url again works fine.
				print(url.description)
			}
		}
	}
	
	func newURL(from oldURL : URL, with newName: String) -> URL {
	  return oldURL.deletingLastPathComponent().appendingPathComponent(newName)
	}
	
	func saveListAsURL(_ url: URL) {
		let fm = FileManager()
		var saveStringArray: [String] = []
		var data = Data()
		
		for name in sampleData {
			let string = name + "\n"
			saveStringArray.append(string)
			let stringData = string.utf8
			data.append(contentsOf: stringData)
		}
		
		let saveResult = fm.createFile(atPath: url.path  , contents: data)
		print("Save results is \(saveResult)")
	}
	
}

The NSSavePanel and NSOpenPanel documentation confirm the Security Scope of the returned urls. There is some ability to save security scoped NSURL bookmarks but I have not used this previously. There are details on bookmarks in the documentation here. It seems an extra level of complexity.

https://developer.apple.com/documentation/foundation/nsurl

You could look in to adding application entitlements to particular directories, but as you seem to want the the user to select anywhere they want to save the Panel approach is the way to go as wide open entitlements are not recommended.

https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AppSandboxTemporaryExceptionEntitlements.html#//apple_ref/doc/uid/TP40011195-CH5-SW7

huangapple
  • 本文由 发表于 2023年3月23日 12:13:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75819198.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定