Bookmark Location Example

After much digging around and tweaking, I believe have arrived to a solution for giving a sandboxed mac app permission to save/read data files to directories other than those supplied normally, like Documents or Music folders. I borrowed the code somewhere (unfortunately I don’t remember where) and adjusted it so that it worked cleanly with JUCE.

It needed to use the “bookmark location” class from the native mac framework.

Here is the code I ended up with.

Functions.h

/*
  ==============================================================================

    Functions.h
    Created: 11 Dec 2023 9:25:15am
    Author:  Jacques Mignault

  ==============================================================================
*/

#pragma once

#ifndef BOOKMARKLOCATION_HPP
#define BOOKMARKLOCATION_HPP

#include <string>

/**
 * Class BookmarkLocation
 *
 * This class provides an interface to handle security-scoped bookmarks in a macOS
 * sandboxed environment. It allows the user to choose a folder, create a security-scoped
 * bookmark for it, and save/load this bookmark to/from a file. Additionally, it manages
 * access to the folder using the bookmark.
 */



class BookmarkLocation {
public:
    /**
     * Constructor
     */
    BookmarkLocation();

    /**
     * Destructor
     */
    ~BookmarkLocation();

    
    /**
     * Saves the security-scoped bookmark of the chosen folder to a specified file.
     *
     * @param bookmarkPath The file path where the bookmark data will be saved.
     * @return true if the bookmark is successfully saved, false otherwise.
     */
    bool saveBookmark(const std::string& bookmarkPath);

    /**
     * Loads a security-scoped bookmark from a specified file and resolves it to a URL.
     *
     * @param bookmarkPath The file path from where the bookmark data will be loaded.
     * @return The path of the bookmarked folder if successful, an empty string otherwise.
     */
    std::string loadBookmark(const std::string& bookmarkPath);

    /**
     * Attempts to start accessing the resource associated with the current bookmark.
     * Call this method before trying to access the folder's contents.
     */
    void startAccess();

    /**
     * Stops accessing the resource associated with the current bookmark.
     * Always call this method after you're done accessing the folder's contents.
     */
    void stopAccess();
    
    
    /**
     * Retrieves the path of the currently selected folder.
     *
     * @return A std::string representing the path of the selected folder.
     *         Returns an empty string if no folder is selected.
     */
    std::string getSelectedFolderPath() const;
    
    void setSelectedFolderPath(const std::string folderPath);
        

private:
    /** Implementation class forward declaration */
    class Impl;

    /** Pointer to the implementation class */
    Impl* pImpl;
};




#endif // BOOKMARKLOCATION_HPP

Functions.mm

/*
  ==============================================================================

    Functions.mm
    Created: 11 Dec 2023 9:24:40am
    Author:  Jacques Mignault

  ==============================================================================
*/

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "Functions.hpp"


class BookmarkLocation::Impl {
public:
    NSURL *folderURL;
    NSData *bookmarkData;

    Impl() : folderURL(nil), bookmarkData(nil) {}
    ~Impl() {
        [folderURL release];
        [bookmarkData release];
    }

    bool saveBookmark(const std::string& bookmarkPath) {

        NSError *error = nil;
        bookmarkData = [[folderURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                             includingResourceValuesForKeys:nil
                                              relativeToURL:nil
                                                      error:&error] retain];
        if (bookmarkData) {
            [bookmarkData writeToFile:[NSString stringWithUTF8String:bookmarkPath.c_str()] atomically:YES];
            return true;
        }
        return false;
    }

    std::string loadBookmark(const std::string& bookmarkPath) {

        NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:bookmarkPath.c_str()]];
        NSError *error = nil;
        BOOL isStale;
        folderURL = [[NSURL URLByResolvingBookmarkData:data
                                               options:NSURLBookmarkResolutionWithSecurityScope
                                         relativeToURL:nil
                                   bookmarkDataIsStale:&isStale
                                                 error:&error] retain];
        if (folderURL && !isStale) {
            return [[folderURL path] UTF8String];
        }
        return "";
    }

    void startAccess() {
        [folderURL startAccessingSecurityScopedResource];
    }

    void stopAccess() {
        [folderURL stopAccessingSecurityScopedResource];
    }
    
    // Inside the Impl class
    std::string getSelectedFolderPath() const {
        if (folderURL) {
            return [[folderURL path] UTF8String];
        }
        return "";
    }

    void setSelectedFolderPath(const std::string folderPath) {
        NSString *objcString = [NSString stringWithUTF8String:folderPath.c_str()];
        folderURL = [NSURL fileURLWithPath:objcString];
    }

};

// C++ class implementation using the Pimpl idiom
BookmarkLocation::BookmarkLocation() : pImpl(new Impl()) {}
BookmarkLocation::~BookmarkLocation() { delete pImpl; }

//bool BookmarkLocation::chooseFolder() {
//    return pImpl->chooseFolder();
//}

bool BookmarkLocation::saveBookmark(const std::string& bookmarkPath) {
    return pImpl->saveBookmark(bookmarkPath);
}

std::string BookmarkLocation::loadBookmark(const std::string& bookmarkPath) {
    return pImpl->loadBookmark(bookmarkPath);
}

void BookmarkLocation::startAccess() {
    pImpl->startAccess();
}

void BookmarkLocation::stopAccess() {
    pImpl->stopAccess();
}

// Corresponding method in BookmarkLocation class
std::string BookmarkLocation::getSelectedFolderPath() const {
    return pImpl->getSelectedFolderPath();
}
void BookmarkLocation::setSelectedFolderPath(const std::string folderPath) {
    return pImpl->setSelectedFolderPath(folderPath);
}

You need to include the hpp file, making sure you’re on a mac.

#if JUCE_MAC
    #include "Functions.hpp"
#endif

Then simply create a BookmarkLocation object in your header:

BookmarkLocation bookmark_location;

Afterwards you can use the class like this, where you customSaveFolder was collected from a simple file open dialog:

#if JUCE_MAC
    DBG("opening bookmark : " + getFilesFolder().getFullPathName());
    bookmark_location.setSelectedFolderPath(customSaveFolder.toStdString());
    bookmark_location.loadBookmark(getSettingsFolder().getChildFile("bookmark").getFullPathName().toStdString());
    bookmark_location.startAccess();
#endif

// be sure to stopAccess once you're done.

bookmark_location.stopAccess();



Please let me know if you are experiencing issues with this code, or if you find a better possibility.
Cheers !

2 Likes