问题
I'm really confused about how to properly copy files and grant permission to execute e.g. an AppleScript file from a sandboxed application. I've read several articles and threads but the more I read, the more it confuses me.
The Task
My app needs to run a very simple AppleScript
from an .scpt
file. To do so (if I got this right), I need to copy this file into Users/thisUser/Library/Application \Scripts/.com.developerName.appName/
. Before I can interact with this folder the user needs to grant access to that folder. This can be done by showing the user an NSOpenPanel
where he can select the path. After confirmation the app has access to that path and I can copy the file and later run the script (App Sandbox User Selected File must be read/write). So far so good.
The Problem(s)
I find presenting a Finder window with an empty folder to select very user unfriendly, so I was wondering if there is anything else I can do. The closest what I have found regarding this problem is drag & drop the folder "into the app" - details can be found here.
I guess I'm not the only person ever who created a (sandboxed) app which needs to run specific scripts and I can't believe that the above approach is the only possible solution!? Therefore,
can I not just have a single window with an OK button and some information above that the app needs permission to write into that folder without showing an entire Finder window?
When I was looking around for solutions I also came across several settings for the app itself. Unfortunately, the docs are very limited here and I could not really find out what the specific settings actually do and how I could test them (admittedly this is because this is my first ever app for OSX and I have basically no clue what I'm doing). One of which is the Copy Files option in the Build Phase settings of the app:
This did sound promising to me since I thought that if I install the app it will automatically copy the file to the Scripts destination (probably with some sort of user prompt) and I can use it. But it does nothing. There is no copy happening at any time, even if I deselect the Copy only when installing setting. I have also tried the different destination folders which are available in the dropdown
and unfortunately also here I
- could not find out what the destinations are
- nor the file has been copied to any of the destination folders on
build
.
I know that people here don't really like to answer questions like this in much detail since it is probably more a lack of knowledge on my side but I would really appreciate it if someone could at least help me getting into the right direction and direct me to some resources which tackle my problem!
Thanks!
回答1:
Well, it seems like I have found a solution which (at least for me) seems to be more or less user friendly and within Apple's sandbox guidelines.
Again, I'm very new to app development using Xcode and SwiftUI so I'm not sure if this solution is 100% "the right way of doing it". But since it took me ages to find this out, maybe someone else can use it and speed up development!
Solution
Like I have mentioned in my question above, I was trying to get rid of the (in my opinion) pretty annoying NSOpenPanel
Finder prompt, where the user is supposed to select the folder. I further asked about the Copy Files setting in the app's Build Phase tab - it turned out that this was the solution! Unfortunately, I still don't have any clue about the list of destination which are presented in the dropdown but choosing Absolute Path and inserting
Users/$USER/Library/Application Scripts/$PRODUCT_BUNDLE_IDENTIFIER
did the job! The file gets copied on every build into the app's Application Scripts
directory, from which I can run scripts outside the sandbox. 🙌
The next step was to create a class which executes the script using NSUserScriptTask
import Foundation
class ExecuteAppleScript {
var status = ""
private let scriptfileUrl : URL?
init() {
do {
let destinationURL = try FileManager().url(
for: FileManager.SearchPathDirectory.applicationScriptsDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask,
appropriateFor:nil,
create: true)
self.scriptfileUrl = destinationURL.appendingPathComponent("CreateMailSignature.scpt")
self.status = "Linking of scriptfile successful!"
} catch {
self.status = error.localizedDescription
self.scriptfileUrl = nil
}
}
func execute() -> String {
do {
let _: Void = try NSUserScriptTask(url: self.scriptfileUrl!).execute()
self.status = "Execution of AppleScript successful!"
} catch {
self.status = error.localizedDescription
}
return self.status
}
}
Then I have created a Button View which handles the request
import SwiftUI
struct GenerateSignatureButtonView: View {
@State var status = ""
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.status = ExecuteAppleScript().execute()
})
{
Text("Generate Signature")
}
Text("\(self.status)")
}
}
}
struct GenerateSignatureButtonView_Previews: PreviewProvider {
static var previews: some View {
GenerateSignatureButtonView()
}
}
When clicking the button a window pops up that the app wants access to control (in my case) the Mail app.
This user prompt repeats every time the user closes the app, reopens it and clicks the button again. I guess this can be somehow managed with Security-Scoped-Bookmarks but I'm not yet sure how. Furthermore, the error handling is not really working in this example since the popup appears after the successful message appears in the status field. This is probably another big thing to figure out... Asynchronous?
Well, hope this helps!
来源:https://stackoverflow.com/questions/62213688/how-to-properly-copy-files-to-application-scripts-folder-from-sandboxed-app