App Wrapper: App Sandbox

Then use the “save file” dialog, if you do that then it will work. These are Apple’s sandbox rules. They apply for any application added to the App store, no matter what they are written in. If you had used Xcode it would have the very same rules.

They are nothing to do with App Wrapper.

Of course it does. With code made app you can add the capability to write / read everywhere. You just need to ask final user permission first. App Wrapper and do it with additional classes for entitlement but it’s not compatible with API 2.0

Show us your code and what you do in AppWrapper. But this can’t be true in any form.

Totally wrong. It does not change your code in any way, other than signing it. It never even sees your source code, so how could it add classes. Don’t confuse App Wrapper with OAK.

The toggles in App Wrapper are entitlements which make changes in a plist file used by the signing process, in the exact same way that Xcode does. It is the only way of doing this.

1 Like

A lot of people are very agressif here please send solution and be more positif

@Ian_Kennedy sam tell me he wrote classes to help with entitlement !
@Thomas_Roemert you said I’m wrong but you don’t share your expérience

I knew this forum calmer and more cooperative

1 Like

I’m not being aggressive.

I am simply pointing out that you are saying is wrong. Sam did write classes, they are in a product call OAK, which is API 1 only and nothing to do with App Wrapper.

Entitlements are an Apple thing, they are settings in a plist file used in the signing process. They are used by any system that uses Apples signing process. They are usable by Xojo and Xcode and any other language that wants to use them. They allow signed applications certain capabilities.

In a sandboxed application, as many people have said, the only way for the an application to open a file in user space is to present an open dialog and to save with a save dialog. You can ask for permission to write to a given folder and then access anything within that folder. For that you would need to use the correct API.

Which is?

I don’t understand why my App works as expected when I ran it from Xojo and doesn’t work after adding AppWrapper Script

It doesn’t work when you place it in a Sandbox. Sandbox applications are access restricted. They cannot write to places that you have not had approval from the user for. You can get that approval using the Save and Open file dialog boxes:

This error message is not about creating your databasefile, it is about opening it.

You didn’t show any code you wrote, so it is a mere guessing what you are doing, but I am sure you used the “Save Dialog” to create the databasefile outside the sandbox environment (sucessfully)and now want to open it - without any user dialog. And this is not permitted for a sandbox app. If you want access to any file outside the sandbox, you have to open a dialog “Open file”. Even to get access to a file you created a second before…

If I am guessing right, you can do what all other Xojo developer, who have apps in the Mac App Store, do: put all files your app needs direct access to (like databasefiles, prefs etc.) inside the sandbox.

That’s work fine when I run my App directly from Xojo or from the Build created by Xojo. That’s the problem

My code:

// NewFileDialog

// Create file dialog
Var dialog As New SaveFileDialog
dialog.InitialFolder = SpecialFolder.Documents
dialog.SuggestedFileName = kSuggestedFileName
dialog.Title = kCreateNewFile
dialog.Filter = iDocFileTypeGroup.iDoc

// Retrieve file name
Var fileName As FolderItem
fileName = dialog.ShowModal

// Open file if a filename is selected in the dialog on a new MainWindow
If fileName <> Nil Then
  
  // If file already exist, move it to trash
  If fileName.Exists Then fileName.Remove
  
  // Show Main window
  Var mw As New MainWindow(fileName)
End If

MainWindow is a document Window

This method use f as FolderItem parameter to create the user document (with is in fact a SQLiteDatabase)

// Assign database
DB = New SQLiteDatabase
DB.DatabaseFile = f

// Cretate db if file not existing
If (f.Exists = False) Then
  // Create Database
  DB.CreateDatabase
  
  // Create Database
  DB.ExecuteSQL(kSQLDBCreateDb)
  
  // Add DBInfo Version
  DBVersion(kCurrentDbVersion)
  
  // Fill With default value
  DB.ExecuteSQL(kSQLDBDefaultValue)
  
Else
  
  // Try to connect database
  
  Try
    DB.Connect
    // Update DB if needed
    DBUpdateDbToVersion(kCurrentDbVersion)
    
  Catch err As DatabaseException
    DBError("Connection error: " + err.Message, True)
  End Try
  
End If

// Tell everybody that's DB is open
DBOpened

You’re not sandboxed in these cases. And for the MAS, you have to be.

I don’t know if I’m sandboxed but at first launch user receive a message to Allow documents folder use

Exactly what I guessed: creating and opening are two different things in a sandboxed app. Your dialog askls the user for permisson to create a sqlitefile and the user grants this permissions. And then you try to access a file without any user dialog…

This is something difficult to understand, but sandboxing works this way…

Please rethink if it is really neccessary that the user can store the databasefile anywhere he wants. If youwant to go that way, you have to ask the user to open the file, everytime you want to open the db…

1 Like

That does not work when you in the App Sandbox. It’s a shame but it is as designed by Apple. Apps in the App Store are not provided with that mechanism and, no matter what language they are written in. It is quite annoying but it is what it is. If you don’t sandbox then you can’t sell on the App store.

You’re only choice is to ask the use to open the sqlite file directly.

Sandboxing sucks lemons. You have to use security scoped bodgemarks to save and load the location of the file. Check out the forum for some rants by Sam Rowlands.

And we have all been there. Oh, wait until you get your first asinine rejection.

Saving a folderitem:

if fieldName = "" then Return

dim bookmarkData as string = GetBookmarkData(fieldValue)

'make CFObjectMBS
dim theCFObject As CFObjectMBS 
if fieldValue = nil then
  theCFObject = NewCFStringMBS("")
else
  theCFObject = NewCFStringMBS(EncodeBase64(bookmarkData))
end if

'save to prefs
if not CheckForPrefs then Return
theCFPrefs.SetAppValue(NewCFStringMBS(FieldName), theCFObject, theCFPrefs.kCFPreferencesCurrentApplication)
if not theCFPrefs.AppSynchronize(theCFPrefs.kCFPreferencesCurrentApplication) Then
  globals.theErrorLog.DialogErrorProceed(kErrorSynchronize)

Getting a folderitem:

'get data from preferences
if fieldName = "" then Return Nil
if not CheckForPrefs then Return Nil
if not theCFPrefs.AppSynchronize(theCFPrefs.kCFPreferencesCurrentApplication) Then 
  globals.theErrorLog.DialogErrorProceed(kErrorSynchronize)
end if

'get data from preferences
if fieldName = "" then Return Nil
if not CheckForPrefs then Return Nil
if not theCFPrefs.AppSynchronize(theCFPrefs.kCFPreferencesCurrentApplication) Then 
  globals.theErrorLog.DialogErrorProceed(kErrorSynchronize)
end if
Dim theCFObject As CFObjectMBS = theCFPrefs.CopyAppValue(NewCFStringMBS(fieldName), theCFPrefs.kCFPreferencesCurrentApplication)
if theCFObject = Nil then Return nil

'make bookmark data
dim BookmarkData as string
if theCFObject.TypeDescription = "CFString" then BookmarkData = DecodeBase64(CFStringMBS(theCFObject).str)
if BookmarkData = "" then Return nil'no data

dim theFolderitem as FolderItem = ResolveBookmark(BookmarkData)
Return theFolderitem
1 Like

Forgot some function:

Private Function ResolveBookmark(data as String, relativeTo as FolderItem = nil) As Folderitem
  //# Resolves a bookmark and returns the corresponding FolderItem. If you created a relative Bookmark, you MUST provide the same RelativeTo parameter as during creation.
  
  declare function NSClassFromString Lib CocoaLib ( aClassName as CFStringRef ) As Ptr
  declare function URLWithString lib CocoaLib selector "URLWithString:" ( cls as Ptr, URLString as CFStringRef ) as Ptr
  declare function URLByResolvingBookmarkData lib CocoaLib selector "URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:" (cls as Ptr, data as Ptr, options as integer, relativeto as Ptr, byref stale as Boolean, byref err as Ptr) as Ptr
  declare function DataWithBytes lib CocoaLib selector "dataWithBytes:length:" (cls as Ptr, bytes as Ptr, length as integer) as Ptr
  declare function absoluteString lib CocoaLib selector "absoluteString" (id as Ptr) as CFStringRef
  
  dim url as Ptr
  dim relativeNSURL as Ptr
  dim stale as boolean
  dim err as Ptr
  
  if relativeTo<>nil then
    relativeNSURL = URLWithString( NSClassFromString( "NSURL" ), relativeTo.URLPath )
  end if
  
  dim mb as MemoryBlock = data
  dim nsdata as Ptr  = DataWithBytes( NSClassFromString( "NSData" ), mb, mb.size )
  
  url = URLByResolvingBookmarkData( NSClassFromString( "NSURL" ), nsdata, 0, relativeNSURL, stale, err )
  
  if url<>nil then
    return  new FolderItem( absoluteString( url ), FolderItem.PathModes.URL )
  end if
  
End Function
1 Like

I fully sandboxed my app and it’s now waiting for review time :face_with_peeking_eye:

Thanks everybody for helping me

I do not really found a response to my question and I still don’t understand what is the use of this screen on AppWrapper.

It’s always frustrating to not be able todo what you want.

One more time thanks everybody :pray:

I am not sure, but it might be that your have to mark “Hardened” for the MAS. Please have a look at the AppWrapper doc about this option. In my MAS apps its always marked…

The Capabilities are used for creating entitlements.

Because the debug app is not sandboxed.

In these boxes, enter WHY you want your app to have access to things like desktop, Documents etc
Even if it is a basic description such as ‘so that emailed documents can be opened by the app for processing’