Saving Lists of URLs with Bookmark Data - MacOS

I have been having some issues saving URLs. I want to be able to save a list of URLs with their bookmark data so that the app can still access some folders after reboot. The folders are on the desktop. I have read and write access to the disk, as was set in the app sandbox settings (User Selected File). It works for as long as the app is open but as soon as I restart it the URLs seem to go invalid, or at least it says that I don't have permission to access the folder that is selected. I then have to clear the urls and re-select them.

You can ignore the blacklist thing as I am not using it in the tests I am running. You can also ignore the sources_list and dest_list they are for the GUI. Here is how the user selects the file:

func inputBrowseClicked(source_or_dest: String) {
    let inputPanel = NSOpenPanel()
    if source_or_dest == "blacklist" {
        inputPanel.canChooseFiles = true
        inputPanel.canChooseDirectories = false
    }
    else {
        inputPanel.canChooseFiles = false
        inputPanel.canChooseDirectories = true
    }
    let userChoice = inputPanel.runModal()
    switch userChoice{
    case .OK :
        if let inputFileChosen = inputPanel.url {
            do {
                // Start accessing a security-scoped resource.
                _ = inputFileChosen.startAccessingSecurityScopedResource()
                
                let bookmarkData = try inputFileChosen.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
                if source_or_dest == "blacklist" {
                    add_blacklist_file(file: inputFileChosen)
                }
                else {
                    add_sources_or_dests_url(url_to_add: bookmarkData, sources_or_dests: source_or_dest)
                }
                
                inputFileChosen.stopAccessingSecurityScopedResource()
                
                sources_list = get_sources_or_dests_urls(sources_or_dests: "source")
                dest_list = get_sources_or_dests_urls(sources_or_dests: "dest")
            }
            catch (let error) {
                print(error)
                return
            }
           return
        }
    case .cancel :
        return
    default:
        return
    }
    return
}

Here is the function that adds one URL to the list:

func add_sources_or_dests_url(url_to_add: Data, sources_or_dests: String) {
    // load currently stored list
    if var source_dest_urls = userDefaults.array(forKey: "saved_"+sources_or_dests) as? [Data] {
        // is the url already in the list
        if !(source_dest_urls.contains(url_to_add)){
            source_dest_urls.append(url_to_add)
            userDefaults.set(source_dest_urls, forKey: "saved_"+sources_or_dests)
            userDefaults.set(source_dest_urls.count,forKey: sources_or_dests+"_index")
        }
    }
    else {
        userDefaults.set([url_to_add], forKey: "saved_"+sources_or_dests)
        userDefaults.set(0,forKey: sources_or_dests+"_index")
    }
}

Here is the function of reading the URLs which I use every time I want to access them which is why I don't understand how it can work until I restart the app.

func get_sources_or_dests_urls(sources_or_dests: String) -> [URL] {
    // load currently stored list
    var source_dest_urls: [URL] = []
        
    if let source_dest_urls_data = userDefaults.array(forKey: "saved_"+sources_or_dests) as? [Data] {
        for bookmarkData in source_dest_urls_data {
            do {
                var isStale = false
                
                let url = try URL(resolvingBookmarkData: bookmarkData, options: [.withSecurityScope], bookmarkDataIsStale: &isStale)
                guard !isStale else {
                    print("Stale URL: "+sources_or_dests)
                    return source_dest_urls
                }
                source_dest_urls.append(url)
            }
            catch (let error) {
                print(error)
                print(sources_or_dests)
            }
        }
        return source_dest_urls
    }
    else {
        return []
    }
}

The funny thing is that when I call the (folder_url).startAccessingSecurityScopedResouce it returns True

Here is an example of the error:

file:///Users/georgeturner/Desktop/Sorted_test/DJI_0274.JPG
2023-06-25 15:20:15.198258+0100 Camera Import[74663:1326092] open on /Users/georgeturner/Desktop/16-07-2022/DJI_0274.JPG: Operation not permitted


Error Domain=NSCocoaErrorDomain Code=513 "“DJI_0274.JPG” couldn’t be copied because you don’t have permission to access “Sorted_test”." UserInfo={NSSourceFilePathErrorKey=/Users/georgeturner/Desktop/16-07-2022/DJI_0274.JPG, NSUserStringVariant=(
    Copy
), NSDestinationFilePath=/Users/georgeturner/Desktop/Sorted_test/DJI_0274.JPG, NSFilePath=/Users/georgeturner/Desktop/16-07-2022/DJI_0274.JPG, NSUnderlyingError=0x600002641170 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

Replies

I can’t spot any obvious problem in your code (although you didn’t post the code that actually does the access, so *shrug*). I recommend that you start by simplifying the problem to just save and restore a single bookmark. Can you get that to work?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

This is the code that is doing the copying of the files. I did have it working withgust saving one source and one destination.

Before this code I run.

let source = get_sources_or_dests_urls(sources_or_dests: "source")[source_index]
let destination = get_sources_or_dests_urls(sources_or_dests: "dest")[destination_index]

print(source.startAccessingSecurityScopedResource())
print(destination.startAccessingSecurityScopedResource())
else {
                    // if files are not being sorted by date
                    for file in files_to_copy {
                        
                        let destination_with_file = destination.appending(path: (file.absoluteString as NSString).lastPathComponent)
                        
                        do {
                            try fm.copyItem(at:file, to:destination_with_file) //this is causing the error
                        }
                        catch (let error) {
                            print("\n")
                            print(error)
                            print("\n")
                        }
                    }
                }

And after I run:

source.stopAccessingSecurityScopedResource()
destination.stopAccessingSecurityScopedResource()

The files_to_copy is a list of files that have been taken out of the source folder. I am assuming that the Security scoped resource should give me access to everything inside that folder?

Thanks for the clarifications.

I am assuming that the Security scoped resource should give me access to everything inside that folder?

That’s correct.

I did have it working with [just] saving one source and one destination.

You mean saving “one source and one destination folder”, right?

So how many are you testing with now?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

First answering your questions, yes previously I had it working with one source and one destination folder. I was testing it with a list of bookmark data, turning into a list of urls, for testing purpose it was a list of length one.

I have managed to solve the issue but I don't know why this has solved it so read on.

I have updated the code I last sent. All I did was before copying the file I called: source.stopAccessingSecurityScopedResource() and destination.stopAccessingSecurityScopedResource(). Immediately after doing this I called: source.startAccessingSecurityScopedResource() and destination.startAccessingSecurityScopedResource() this has caused it to now work. I haven't yet completely checked if I am accidentally calling a stopAccessingSecurityScopedResource() but I was under the impression if you didn't balance the start and stop AccessingSecurityScopedResource() it would lock you out of the filing system until you relaunch the app? It would be that you can have as many stopAccessingSecurityScopedResource()'s as you want? but that is not what I thought.

Here is my updated code:

else {
                    // if files are not being sorted by date
                    for file in files_to_copy {
                        
                        let destination_with_file = destination.appending(path: (file.absoluteString as NSString).lastPathComponent)
                        
                        print(destination_with_file.absoluteString)
                        
                        do {
                            source.stopAccessingSecurityScopedResource()
                            destination.stopAccessingSecurityScopedResource()
                            
                            print(source.startAccessingSecurityScopedResource())
                            print(destination.startAccessingSecurityScopedResource())
                            
                            try fm.copyItem(at:file, to:destination_with_file)
                        }
                        catch (let error) {
                            print("\n")
                            print(error)
                            print("\n")
                        }
                    }
                }