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.
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
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.
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.
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.
// 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
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…
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
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
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.
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’