
{"id":329,"date":"2012-11-19T05:31:05","date_gmt":"2012-11-19T05:31:05","guid":{"rendered":"http:\/\/www.starcoder.com\/wordpress\/?p=329"},"modified":"2021-10-30T19:54:15","modified_gmt":"2021-10-30T19:54:15","slug":"329","status":"publish","type":"post","link":"https:\/\/www.starcoder.com\/wordpress\/2012\/11\/329\/","title":{"rendered":"Living in a Sandboxed World"},"content":{"rendered":"<p>No matter what your view on Apple&#8217;s new sandboxing requirement for the Mac App Store, if you want to keep updating your MAS apps, you&#8217;re going to need to sandbox them.  I was able to sandbox Seasonality Core pretty easily.  I don&#8217;t access any files outside of Application Support and the prefs file.  My entitlements just require outgoing network connections, which was pretty easy to enable in the target settings.<\/p>\n<p>However, I distribute two versions of Seasonality Core.  One is the Mac App Store application, and the other is a version for my pre-Mac App Store customers.  The question arose: should I sandbox the non-Mac App Store application?  I wanted the answer to this question to be yes, but unfortunately the serial number licensing framework I am using kept me from doing this.  So I was forced to sandbox the Mac App Store version, but keep the non-Mac App Store version outside the sandbox.  Crap.<\/p>\n<p>You might be wondering what the big deal is here.  Can&#8217;t my Mac App Store customers just use one app, and pre-Mac App Store customers use the other one?  Well, yes, but there are a few situations where some customers might use both versions of the app.<\/p>\n<p>If someone uses both the Mac App Store version and the non-Mac App Store version, things go south quickly.  The first time the sandboxed Mac App Store version is run, all of Seasonality Core&#8217;s data files will be migrated into the sandbox.  That means the next time the non-Mac App Store version is opened, it won&#8217;t be able to see any of the past data Seasonality Core has collected.  That&#8217;s not good.<\/p>\n<p>So how did I get around this?  After taking a quick poll on Twitter, it sounded like the best option for me would be to have the non-Mac App Store version look reach inside my app&#8217;s sandbox if it existed.  To do this, I just had to build some extra code into the method that returns my Application Support path.  Here&#8217;s the new implementation:<\/p>\n\n<p><code>+ (NSString *) seasonalityCoreSupportPath {\n  NSFileManager *fm = [NSFileManager defaultManager];\n\n#ifndef MAC_APP_STORE\n  \/\/ Check if ~\/Library\/Containers\/BundleID\/Data\/Library\/Application Support\/Seasonality Core exists.\n  NSString *sandboxedAppSupportPath = [NSString pathWithComponents:\n    [NSArray arrayWithObjects:@\"~\",\n                              @\"Library\",\n                              @\"Containers\",\n                              [[NSBundle mainBundle] bundleIdentifier],\n                              @\"Data\",\n                              @\"Library\",\n                              @\"Application Support\",\n                              @\"Seasonality Core\",\n                              nil]\n  ];\n  sandboxedAppSupportPath = [sandboxedAppSupportPath stringByExpandingTildeInPath];\n\n  BOOL isDir;\n  if ([fm fileExistsAtPath:sandboxedAppSupportPath isDirectory:&amp;isDir]) {\n    \/\/ We found a sandboxed Application Support directory, return it.\n    if (isDir) return sandboxedAppSupportPath;\n  }\n#endif\n\n  NSArray *appSupportURLs = [fm URLsForDirectory:NSApplicationSupportDirectory\n                                       inDomains:NSUserDomainMask];\n  NSString *appSupportDirectory = nil;\n\n  if (appSupportURLs.count &gt; 0) {\n    NSURL *firstPath = [appSupportURLs objectAtIndex:0];\n    appSupportDirectory = [firstPath path];\n  }\n\n  return [appSupportDirectory stringByAppendingPathComponent:@\"Seasonality Core\"];\n}<\/code><\/p>\n<p>\u00a0<\/p>\n\n<p>The new code only runs if the MAC_APP_STORE isn&#8217;t defined (these are project definitions I have set elsewhere for the different builds).  We check to see if there is a sandbox for the app, and if so it will return the sandboxed directory.  Otherwise it returns the standard Application Support directory.<\/p>\n<p>This is a pretty complete solution, except that I wanted to make sure the user&#8217;s preferences were saved between the two app versions as well.  NSUserDefaults won&#8217;t know to check for the existence of a sandbox.  Daniel Jalkut <a href=\"https:\/\/twitter.com\/danielpunkass\/status\/245635064925417472\">gracefully offered<\/a> this <a href=\"https:\/\/gist.github.com\/3702206\">solution<\/a>, which I have since adapted into my own code as follows:<\/p>\n\n<p><code>+ (BOOL) gsImportNewerPreferencesForBundle:(NSString *)bundleName fromSandboxContainerID:(NSString *)containerID {\n  BOOL didMigrate = NO;\n\t  NSArray *libraryFolders = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);\n  if (libraryFolders.count) {\n    \/\/ Get a path to our app's preference file.\n    NSString *prefsFile = [NSString pathWithComponents:[NSArray arrayWithObjects:\n      [libraryFolders objectAtIndex:0],\n      @\"Preferences\",\n      bundleName,\n      nil\n    ]];\n    prefsFile = [prefsFile stringByAppendingPathExtension:@\"plist\"];\n\n    \/\/ Get a path to the same preference file in the given sandbox container.\n    NSString *containerPrefsFile = [NSString pathWithComponents:[NSArray arrayWithObjects:\n      [libraryFolders objectAtIndex:0],\n      @\"Containers\",\n      containerID,\n      @\"Data\",\n      @\"Library\",\n      @\"Preferences\",\n      bundleName,\n      nil\n    ]];\n    containerPrefsFile = [containerPrefsFile stringByAppendingPathExtension:@\"plist\"];\n\t\t    NSFileManager* fm = [NSFileManager defaultManager];\n    if ([fm fileExistsAtPath:containerPrefsFile]) {\n      NSDate *prefsModDate = [[fm attributesOfItemAtPath:prefsFile error:nil] objectForKey:NSFileModificationDate];\n      NSDate *containerModDate = [[fm attributesOfItemAtPath:containerPrefsFile error:nil] objectForKey:NSFileModificationDate];\n\t\t      if ((prefsModDate == nil) || ([prefsModDate compare:containerModDate] == NSOrderedAscending)) {\n        \/\/ Copy the file.\n        [fm copyItemAtPath:containerPrefsFile toPath:prefsFile error:nil];\n\t\t\t\t        \/\/ Reset so the next call to [NSUserDefaults standardUserDefaults] \n        \/\/ recreates an object to the new prefs file.\n        [NSUserDefaults resetStandardUserDefaults];\n\n        NSLog(@\"Found newer preferences in %@ - importing\", containerPrefsFile);\t\n        didMigrate = YES;\n      }\n    }\n  }\n\n  return didMigrate;\n}\n<\/code><\/p>\n<p>\u00a0<\/p>\n\n<p>I call the above preferences migration code directly from main(), so it executes before the any part of the main app might hit NSUserDefaults.  Works pretty well thus far.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>No matter what your view on Apple&#8217;s new sandboxing requirement for the Mac App Store, if you want to keep updating your MAS apps, you&#8217;re going to need to sandbox them. I was able to sandbox Seasonality Core pretty easily. I don&#8217;t access any files outside of Application Support and the prefs file. My entitlements [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,16],"tags":[],"class_list":["post-329","post","type-post","status-publish","format-standard","hentry","category-coding","category-macintosh","post-preview"],"_links":{"self":[{"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/posts\/329","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/comments?post=329"}],"version-history":[{"count":9,"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/posts\/329\/revisions"}],"predecessor-version":[{"id":558,"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/posts\/329\/revisions\/558"}],"wp:attachment":[{"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/media?parent=329"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/categories?post=329"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.starcoder.com\/wordpress\/wp-json\/wp\/v2\/tags?post=329"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}