mogenerator 1.29

Much thanks to lieutenants Tom Harrington and Justin Williams for handling this release.

What’s New:

mogenerator Aug 21 2015

Comcast Technicolor Modem SSH Work-around

After Comcast upgraded me from my simple-but-great DOCSIS 2.0 Motorola SURFboard SB5101U modem to a DOCSIS 3.0 Technicolor TC8305C, I started having an issue with my ssh connections. Specifically, idle connections would break after a few minutes of non-use.

This new modem seems to have a too-low timeout for when it drops an active connection from its NAT table. Unfortunately I can’t raise this timeout or tell it not to offer NAT whatsoever.

My work-around is to tell ssh to be more chatty. SSH won’t normally send packets when there’s nothing to say, making it efficient. The ServerAliveInterval tells it to ping the other side at the given interval (in seconds).

I added

ServerAliveInterval 60

to my /etc/ssh_config file. I don’t think I even needed to restart, and now my ssh connections are staying up.

Pinging once per minute is probably too aggressive, but I wanted to see if a low setting had any effect first. I haven’t bothered to discover the exact magic number, but I’m guessing it will be 1.9, 2.9 or 4.9 minutes.

comcast ssh Mar 4 2015

Building a Custom Vagrant Box using VMware Fusion 7

Vagrant automates creating (and destroying) Virtual Machines. This is a big deal when you may create and destroy a VM a couple of hundred times while perfecting an Ansible playbook for automated deployment (read: devops).

Unlike the majority, I use VMware Fusion as my provider (most folks use VirtualBox). The reason I don’t use VirtualBox is that it seems to cause OS X to kernel panic at least once per day. Even when I’m not using it. Maybe one day it will be more stable, but it’s been this way for me for at least three years across different Macs and OS X releases.

Since Fusion costs money and Vagrant’s VMware support adds another $80 on top of that, the vast majority of pre-built VMs don’t support VMware directly. Which means I need to build my own.

Fortunately, they’re not too hard to build. Just create your VM in Fusion as you normally would from a .iso. Create an admin user vagrant, password vagrant, member of the sudo group. Append this to the end of your /etc/sudoers files via sudo visudo:

vagrant ALL=(ALL) NOPASSWD: ALL

Then add Vagrant’s insecure key so it can sign in for you:

# assuming vagrant user:
cd
mkdir .ssh
wget --no-check-certificate https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub -O .ssh/authorized_keys

You may also want to add Port 2222 (in addition to the existing Port 22) line to /etc/ssh/sshd_config to avoid a harmless “Connection Refused” error upon each VM startup.

A Vagrant .box file is just a tarball of the contents of your .vmwarevm package with one extra required file, metadata.json:

{"provider":"vmware_fusion"}

Practically, I don’t use Host-Guest File System (HGFS or “Shared Folders” in Fusion’s UI) since that requires an irksome kernel module. Instead I supply a Vagrantfile that uses rsync instead:

Vagrant.configure('2') do |config|
  config.vm.synced_folder '.', '/vagrant', type: 'rsync', rsync__exclude: '.git/'
end

It’s now easy to build and install your own custom box:

cd rentzsch-trusty64.vmwarevm
tar cvzf rentzsch-trusty64.box *
vagrant box add rentzsch-trusty64 rentzsch-trusty64.box

(Sadly vagrant package doesn’t support VMware, but we really don’t need it or the power+complexity of packer.)

Startup your custom VM like you would an externally-supplied VM:

vagrant init rentzsch-trusty64 && vagrant up

Oh, all this assumes Vagrant 1.6.5 and VMware Fusion 7.1.0.

References:

vmware vagrant Feb 6 2015

OmniFocus Selected Mail Messages.applescript

Short story:

I reimplemented Clip-o-Tron in pure AppleScript.

Highlights:

  • Works on OS X 10.10 Yosemite.
  • Works with both OmniFocus 1 and OmniFocus 2.
  • Populates new tasks with the message’s body and a pretty link back to the original message just like the Good Old Days.

Long story:

OmniGroup’s Clip-o-Tron is a plugin for Mail.app that makes it easy to export selected mail messages into OmniFocus tasks.

Sadly, like many Mail.app plugins, Clip-o-Tron has a checkered compatibility history. This is mostly Apple’s fault, since they revlock plugins against each new version of Mail.app. It’s hard for developers to keep up, even if nothing needs changing. Practically they need to consistently ship new versions of their plugin before the next version of Mail.app comes out, lest their plugin doesn’t load.

Aside: Michael Tsai is a machine with his SpamSieve updates. I can’t remember the last time a new version of Mail.app dropped and he hadn’t already shipped an update to his plugin. Kudos, Michael.

OmniFocus’s release schedule doesn’t fully mesh with Mail.app’s schedule, and sometimes Clip-o-Tron would stop working for a while.

The functionality outage was painful to my productivity.

Clip-o-Tron is currently in non-optimal shape on OS X 10.10. First off, it doesn’t work with OmniFocus 1 at all. I can kind of understand that but I still use OmniFocus 1.

Even with OmniFocus 2, Mail.app 10.10 has changed the way the Copy command works, breaking Clip-o-Tron’s ability to copy the message’s body and link into the new task’s note field.

For my workflow, the “Original Message” link back the originating Mail.app message is critical since it allows rapid follow-up.

I’m hopeful this pure AppleScript implementation will have better ongoing compatibility with OS X, Mail, and OmniFocus than Clip-o-Tron-as-a-Mail.app-plugin.

I invoke it via FastScripts but anything that can fire AppleScripts should work.

omnifocus applescript mail.app Jan 6 2015

Recovering from a failed drive with Apple’s Software RAID

Update: This blog posting was written against 10.8. I’m happy to report you can correctly rebuild RAIDs in Disk Utility on 10.9 and 10.10. Just minus the failed drive in the list, “demote” it, drag the new drive into the list and hit the Rebuild button. Thanks to charlesism for the heads-up.


OK, given the panic-y nature of the topic, perhaps a Q&A format will be best:

My drive failed, but phew I set up RAID Mirroring, so I just need to replace the drive and the Mac will automatically recover?

First off, congratulations on having the forethought to enable RAID Mirroring. You’re mostly in good shape.

You’re forgiven if you thought enabling Disk Utility’s “Automatically rebuild RAID mirror sets” checkbox during initial RAID creation covered this scenario. Unfortunately that’s for when a drive gets knocked offline and is then reconnected. It’s irrelevant in the face of a drive failure, where there’s no data to start with.

Unfortunately OS X’s Software RAID doesn’t offer a Time Machine-like UI, offering to rebuild your RAID for you when it notices a new drive connected and there’s a degraded RAID present.

Man, that would be sweet.

No, you have to do some manual work.

OK, so I need to use Disk Utility to fully recover?

Sorry, while Disk Utility offers some guidance in that degraded RAIDs show up as orange in its UI and missing RAID slices in red, as far as I can tell you can’t actually rebuild your existing RAID using Disk Utility.

Disk Utility is happy to assist you in creating a new RAID, but if you try to do that with an existing slice I can speak from experience it will make good on its threat to delete all existing data before creating recreating the RAID. Which kinda misses the point of rebuilding the RAID from the slice that’s still standing.

No way, Disk Utility will let me create a RAID Mirror, but can’t actually rebuild it?

Way.

Sigh, OK, so what app do I use to rebuild? This “RAID Utility.app” looks promising.

Sorry. RAID Utility.app, available on OS X Server only, is for Apple’s hardware RAID.

As a software RAID pauper, you don’t get an app.

You’re about to tell me I need to drop down to the Unix layer, aren’t you?

Sadly, yes.

But it’s not that bad! It really just boils down to

sudo diskutil appleraid remove $FAILED_SLICE_UUID $RAID_UUID
sudo diskutil appleraid add member $REPLACEMENT_SLICE_DEV $RAID_UUID

But you have to do some legwork first to correctly fill out those variables.

First, you need to partition the replacement drive to match the failed drive. If you don’t remember the exact numbers off hand that’s fine – you can just inspect the still-functioning drive for the numbers. You can use Disk Utility for this.

With the replacement drive properly sliced up like the proverbial Thanksgiving turkey, you need to gather three pieces of information:

  1. The UUID of the RAID to repair. We’ll assign this to $RAID_UUID.

    If you partitioned your system like I previously suggested you’ll have three RAID sets. So you’ll have to repeat these steps three times: one for each RAID.

  2. The UUID of the failed RAID slice. Let’s name this $FAILED_SLICE_UUID.

  3. The device name of the replacement slice. We’ll call this $REPLACEMENT_SLICE_DEV.

You’ll use a one-two punch of diskutil appleraid list and diskutil util to gather these three variables. Here I’ll rebuild the “mini4 Data” RAID set:

$ sudo diskutil appleraid list
AppleRAID sets (3 found)
===============================================================================
Name:                 mini4 Boot
Unique ID:            E73306EA-D188-4F2F-B7E9-05206B92FAC2
Type:                 Mirror
Status:               Online
Size:                 50.0 GB (49999970304 Bytes)
Rebuild:              manual
Device Node:          disk2
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk0s2   53B0A36D-F8A2-4977-9ACC-CBB1BDADD65C  Online     49999970304
1  disk1s2   18189491-D70E-4970-85AD-22AB0AD75617  Online     49999970304
===============================================================================
===============================================================================
Name:                 mini4 Data
Unique ID:            EDFF099E-A897-4D6E-9DE1-544449898CBE
Type:                 Mirror
Status:               Degraded
Size:                 899.6 GB (899592454144 Bytes)
Rebuild:              manual
Device Node:          disk4
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk0s6   2714167E-1A6A-4BAE-B60E-50F333B327EC  Online     899592454144
-  -none-    44632B50-D667-42E7-9837-A1BF53C3C1CC  Missing/Damaged
===============================================================================
===============================================================================
Name:                 mini4 Boot Backup
Unique ID:            4827405E-EEEB-4666-8783-B8CFCE664CCA
Type:                 Mirror
Status:               Online
Size:                 50.0 GB (49999970304 Bytes)
Rebuild:              manual
Device Node:          disk3
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk0s4   1AAB8E00-8206-4985-881B-74346BB1EA62  Online     49999970304
1  disk1s3   63886672-631E-4A91-AF27-22C9EECD45A8  Online     49999970304
===============================================================================
$ sudo diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *1.0 TB     disk0
   1:                        EFI                         209.7 MB   disk0s1
   2:                 Apple_RAID                         50.0 GB    disk0s2
   3:                 Apple_Boot Boot OS X               134.2 MB   disk0s3
   4:                 Apple_RAID mini4_boot_backup       50.0 GB    disk0s4
   5:                 Apple_Boot Boot OS X               134.2 MB   disk0s5
   6:                 Apple_RAID                         899.6 GB   disk0s6
   7:                 Apple_Boot Boot OS X               134.2 MB   disk0s7
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *1.0 TB     disk1
   1:                        EFI                         209.7 MB   disk1s1
   2:                 Apple_RAID 2 mini4 boot            50.0 GB    disk1s2
   3:                 Apple_Boot Boot OS X               134.2 MB   disk1s7
   4:                 Apple_RAID                         50.0 GB    disk1s3
   5:                 Apple_Boot Boot OS X               134.2 MB   disk1s4
   6:                  Apple_HFS 2 mini4 data            899.6 GB   disk1s5
/dev/disk2
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS mini4_boot             *50.0 GB    disk2
/dev/disk3
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS mini4_boot_backup      *50.0 GB    disk3
/dev/disk4
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS mini4_data             *899.6 GB   disk4
$ FAILED_SLICE_UUID=44632B50-D667-42E7-9837-A1BF53C3C1CC
$ RAID_UUID=EDFF099E-A897-4D6E-9DE1-544449898CBE
$ REPLACEMENT_SLICE_DEV=disk1s5
$ sudo diskutil appleraid remove $FAILED_SLICE_UUID $RAID_UUID
Started RAID operation on disk4 mini4_data
Removing disk from RAID
Finished RAID operation on disk4 mini4_data
$ sudo diskutil appleraid add member $REPLACEMENT_SLICE_DEV $RAID_UUID
Started RAID operation on disk4 mini4_data
Unmounting disk
Adding a booter for the RAID partition disk1s5
Adding disk1s5 to the RAID Set
Finished RAID operation on disk4 mini4_data

OK, that’s a lot of scary Terminal spew, but if you focus on the commands I issued and backtrack them to the listings, it’s not so bad. It’s mostly diskutil trying to pretty-print things for you.

Once you’ve kicked off the rebuild, you can monitor its progress by re-issuing the diskutil appleraid list command:

$ sudo diskutil appleraid list
AppleRAID sets (3 found)
===============================================================================
Name:                 mini4 Boot
Unique ID:            E73306EA-D188-4F2F-B7E9-05206B92FAC2
Type:                 Mirror
Status:               Online
Size:                 50.0 GB (49999970304 Bytes)
Rebuild:              manual
Device Node:          disk2
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk0s2   53B0A36D-F8A2-4977-9ACC-CBB1BDADD65C  Online     49999970304
1  disk1s2   18189491-D70E-4970-85AD-22AB0AD75617  Online     49999970304
===============================================================================
===============================================================================
Name:                 mini4 Data
Unique ID:            EDFF099E-A897-4D6E-9DE1-544449898CBE
Type:                 Mirror
Status:               Degraded
Size:                 899.6 GB (899592454144 Bytes)
Rebuild:              manual
Device Node:          disk4
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk0s6   2714167E-1A6A-4BAE-B60E-50F333B327EC  Online     899592454144
1  disk1s5   1B7B0CC8-D06E-4B54-B50C-B3BB260B71A8  9% (Rebuilding)899592454144
===============================================================================
===============================================================================
Name:                 mini4 Boot Backup
Unique ID:            4827405E-EEEB-4666-8783-B8CFCE664CCA
Type:                 Mirror
Status:               Online
Size:                 50.0 GB (49999970304 Bytes)
Rebuild:              manual
Device Node:          disk3
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk0s4   1AAB8E00-8206-4985-881B-74346BB1EA62  Online     49999970304
1  disk1s3   63886672-631E-4A91-AF27-22C9EECD45A8  Online     49999970304
===============================================================================

Weirdly while Disk Utility can’t kick off a rebuild, it will show the rebuild progress in a nice window with a progress bar, complete with a time estimate. It’s worth a VNC session.

A final note: you can totally rebuild the RAID mirror even of the boot volume while the machine is live, but I found it slightly buggy – I had to do it twice (!). The first once didn’t “take”. Scary, yes. But uptime’s worth it. And uptime is what RAID’s all about.

That and restful nights.

sysadmin raid Oct 4 2014

mogenerator 1.28

What’s New:

  • [NEW] --v2 argument. I wanted to enable ARC by default, but decided to take it a step further (while not breaking existing scripts). The new --v2 argument is basically semantic versioning for tool arguments.

    So now instead of this:

    mogenerator --model MyDataModel.xcdatamodeld \
        --template-var arc=true \
        --template-var literals=true \
        --template-var modules=true
    

    You can write this:

    mogenerator --v2 --model MyDataModel.xcdatamodeld
    

    Internally these invocations are equivalent, but new scripts and manual invocations should use the --v2 variant.

    I recommend putting the --v2 in front of the rest of the arguments to call attention to the versioned context of the following arguments.

    This mechanism should allow mogenerator to continue to supply sensible defaults into the future as well. Perhaps --v3 will generate Swift by default. Speaking of which…

  • [NEW] Experimental Swift code generation. Unfortunately basic Core Data functionality (to-one relationships) is broken on 10.9, but we can still try writing theoretically-correct Swift code. Perhaps a future version of mogenerator will supply the needed work-around code for you. (Alexsander Akers, afrederick1, Piet Brauer, rentzsch, Chris Weber, Markus Chmelar, Brent Royal-Gordon)

  • [NEW] Ordered relationships actually work. OMG. I have them working in a new separate OS X test app, even though mogenerator’s test dir fails. I still haven’t figured out why, but I’m not holding this back. (Daniel Tull, Joshua Greene, Dave Wood, Jonathan del Strother)

  • [NEW] Custom scalar types. Specify attributeValueScalarType for the name of the property’s custom type and additionalHeaderFileName if you need to bring in an additional header file for compilation. With this, mogenerator supports C-style and JREnum-style enums. (Quentin ARNAULT)

  • [NEW] Remove unnecessary empty lines in the generated files. (Stephan Michels)

  • [NEW] Ability to forward-declare @protocols for i.e. transformable types. Specify them via a comma delimited string in the entity’s user info under the attributeTransformableProtocols key. (Renaud Tircher)

  • [NEW] Generate *UserInfo key/value pairs as const structs. (Jeremy Foo, rentzsch)

  • [NEW] --template-var literals which, when enabled, generates Obj-C literals. (Brandon Williams, Thomas van der Heijden, rentzsch)

  • [NEW] Specify --template-var modules=true option to avoid treating #import as an import of module 'CoreData' [-Wauto-import]" warning. (Daniel Tull)

  • [NEW] Unsigned integers are generated when a property’s minimum is set to 0 in the Xcode modeler. (Dan Pourhadi)

  • [NEW] Add support for setting command-line options via a JSON config file. (Simon Whitaker)

  • [NEW] Add CONTRIBUTING.md file. It’s now even easier to contribute to mogenerator :) (rentzsch)

  • [NEW] Add MIT LICENSE file to make it clear templates are under the same license. (rentzsch)

  • [CHANGE] Suppress generation of -setPrimativeType: method. issue 16. (rentzsch)

  • [CHANGE] Add a warning when skipping an attribute named ‘type’. (Simon Whitaker)

  • [CHANGE] Add explicit atomic to sooth -Weverything. (Daniel Tull)

  • [CHANGE] iOS 8 changes objectID from a getter into a property, resulting in a warning. Templates updated to match. (Ryan Johnson)

  • [FIX] Support newly-created models when --model=*.xcdatamodeld. issue 137. (Sergey)

  • [FIX] Minor warning fix, 64->32 truncation, format strings. (Sean M)

  • [FIX] Machine headers always #imports their superentity if present. (David Aspinall)

  • [FIX] Fetch requests whose predicate LHS specifies a relationship. issue 15. (rentzsch)

  • [FIX] Don’t emit empty *UserInfo structs. (Jeremy Foo 1 2)

  • [FIX] Don’t emit empty *Attributes, *Relationships, and *FetchedProperties structs. (Daniel Tull)

  • [FIX] MOIDs subclass their superentity (instead of just always inheriting from NSManagedObject). (Daniel Tull)

  • [FIX] Don’t touch aggregate include files if the content didn’t change. (Stephan Michels)

  • [FIX] Don’t attempt to #import "NSManagedObject.h" even in the face of weird (corrupted?) model files. issue 42. (rentzsch)

  • [TEST] Escape spaces in mogenerator build path. (Daniel Tull)

mogenerator Sep 10 2014

Unpopular

Social approval is like sweetness – a signal crucial for survival for the majority of humankind’s existence that in our modern era is so prevalent that it can be toxic.

Identifying sweet foodstuffs in our hunter-gatherer era indicated carbohydrate-dense foods. The school of hard evolutionary knocks hard-wired a craving for sweet foods and gives us pleasure when we ingest them. It got us through the winters.

Once beneficial, this trait is now unfortunately out of step with humankind’s newfound ability to manufacture and consume enough sweetness that it literally makes us sick.

Social approval is likewise crucial for survival. Can’t let your “approval rating” drop so low that you’ll be ousted from your tribe and left to forage alone and die childless.

So we do things that garner approval. That’s always been the case. It’s not all bad but it’s not all good.

What’s new is that Internet social networks allows us to crowdsource social approval. As with the large-scale manufacturing of sweetness, our newfound manufacturing of social approval impacts our arthritic social structures.

It’s never been easier to garner social approval from vast numbers of people. And it’s never meant less. But like chasing sweetness’s empty calories, our lizard brains keeps us on the social approval treadmill.

Like sweetness manufacturing, social approval manufacturing can also make us sick. While social approval has incredible upsides including evolutionary advantage, it also commonly leads to shallow groupthink and hindering of true progress.

Don’t misconstrue that I’m arguing that you shouldn’t care at all what others think and approve of. Tribes provide valuable insights and checks on your experiences and evaluations. But social approval is like salt – even if you ban it from your home chances are its pervasiveness in our society will be more than enough to meet your daily requirement.

Unpopular is my Chrome extension that helps dampen the social approval echo chamber. It hides indicators of social status on GitHub (followers, watchers, stars, forks) and Twitter (followers, retweets, favs).

I find Unpopular useful to focus on what I find interesting and important.

I hope you find it useful as well.

unpopular chrome crx Jun 28 2014

JRTranscriptView 1.0

JRTranscriptView is my new purpose-built text view for logging output.

It scrolls like you think it should.

Requires iOS 7+ and Xcode 5.1+.

jrtranscriptview Apr 14 2014

Apple Claims Mogenerator’s Methods

When generating class files to represent your data model’s entities, mogenerator writes a few convenience class methods for you:

+ (id)insertInManagedObjectContext:(NSManagedObjectContext*)moc_;
+ (NSString*)entityName;
+ (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc_;

These handy class methods allow you to write something like this:

[MyEntity insertInManagedObjectContext:moc];

to create a new instance. Contrast with:

[NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:moc];

The mogenerator way is more obvious, less code and not stringly typed.

So that’s the backgrounder.

The good news is Apple likes mogenerator so much that they used it to implement iOS’s PhotoLibraryServices.framework.

The bad news is that PhotoLibraryServices.framework is a private framework.

As a first line of defense against naughty developers submitting apps that use undocumented APIs, Apple sweeps up every selector (method name) defined in all private frameworks and puts them on a special list. The “non-public selectors” list.

Unfortunately this includes the convenience methods mentioned above.

If your App Store submission is found to be use non-public selectors Apple may do nothing, issue a warning or reject your submission altogether.

※ ※ ※

I’m not sure what to do about this.

I could not supply the convenience methods and use Core Data directly like an animal. No! I won’t go back. I can’t go back.

Prefixing the methods (perhaps with an mo_) would provide relief, but only temporarily. When Apple upgrades PhotoLibraryServices to the newer version of mogenerator the cycle would begin anew.

NS is designed for optional class namespacing, but its concepts could be applied to method names as well. Unfortunately at the method level it uglies up the code and confuses Xcode’s code navigator popup menu. Also it would probably require NS_NAMESPACE preprocessor symbol be defined at the target or project level.

Apple could explicitly whitelist mogenerator’s convenience methods but I’m not holding my breath.

The next version of mogenerator could be released under an “Anyone But Apple” license. The idea is to allow everyone but Apple to use mogenerator. I could prefix the methods and issue the mogenerator upgrade knowing Apple couldn’t upgrade and claim the new methods as their own again.

Sadly, this seems like the most promising option.

(Thanks to Tony Arnold for the wording for the title of this post.)

Update Apr 18: First off, a clarification: it appears Apple has never rejected an application using mogenerator’s methods – they’ve only issued warnings.

Now for the good news: a little birdie has told me Apple has whitelisted mogenerator’s methods.

mogenerator appstore Apr 12 2014

Apps I Love: Acorn

I’m surprised I haven’t already written about Acorn under my Apps I Love tag.

Acorn long ago freed me from having to install Photoshop on my Mac. Thank goodness.

I’m not a graphics professional. Acorn does way more than I need it to do. But those “extra” abilities never get in my way. And when I do need to do something a little beyond my common tasks, Acorn has always delivered.

That’s how Mac apps should be.

appsilove acorn Apr 7 2014