# Fit and finish

Lots of people have been talking lately about the innovations Apple is making in manufacturing. The most common topic is how Apple is using its huge cash reserves and purchasing power to, in effect, build manufacturing plants for its suppliers so it can get economies of scale almost immediately. Greg Koening likes to talk about how Apple is tweaking materials and employing formerly exotic machining processes.1 What I find fascinating is how Apple is using technology to combine two manufacturing styles that are usually thought to be contradictory: custom building and mass production.

By “custom building,” I don’t mean having customers choose how much RAM or flash storage their MacBook Pros have. I’m talking about using computer vision to identify which component parts—which are already manufactured to tight tolerances—fit the best and to adjust the flow of components on the assembly line to ensure that they go together in the final product.

I’m pretty sure I first heard of this a couple of years ago in one of those “this is Jony Ive, speaking to you from my white space egg” videos, and I was recently reminded of it by Thomas Brand in this Egg Freckles post, in which he pulls out a quote from Apple on its battery design for the new MacBook:

The battery cells in the new MacBook are not only designed with enormous attention to detail, but are also assembled with extreme precision and care. High-speed cameras first take detailed photos of both the casing and the battery. We use those photos to account for microscopic variations in each enclosure, and carefully place the batteries so that not a single millimeter of space is wasted.

What Apple is doing is analogous to what a cabinetmaker does in building a piece of furniture: sorting through pieces and making small adjustments to account for variations in grain and thickness. Only Apple is doing it for millions of devices every month, taking Eli Whitney’s ideal of interchangeable parts and standing it on its head.

I’m not suggesting Apple invented computer vision or that it’s the first company to use it in mass production. But I think it is unusual for it to be used at Apple’s scale for the purpose of wringing every last bit of tolerance out of its products.

1. If you haven’t already, go read Greg’s iMore article on the durability of the materials used in the Apple Watch.

# I heard your mom likes social media

There’s a tendency among tech writers to use “Mom” as the prototypical average user. This often raises complaints from people who claim that it’s sexist (never ageist, though—funny, that). I have no doubt that in some cases sexism is at the root of it, especially when the comments are mean-spirited or make Mom out to be clueless. But I’ve seen Mom invoked often enough by women tech writers to know that that’s not the whole story.

I believe Mom comes to mind when tech writers need an Every(wo)man user because that’s who, in their own personal experience, best matches the profile. Certainly in my circle of friends—which consists primarily of Mom- and Dad-aged people—it’s the Mom who’s most likely to have an active Facebook account, and to be on Twitter, Pinterest, and Instagram. In years past, it was Mom who first started texting.

The natural consequence of this is that it’s Mom who’s most likely to get crosswise with bad interface design, poorly written dialog boxes, and devious privacy policies—hence the stories using her as the example. Dad sits on his ass in blissful ignorance.1

So most stories about Mom’s difficulties shouldn’t be considered demeaning. She is, as Teddy Roosevelt would say, in the arena, making mistakes because there is no effort without error and shortcoming. Especially when dealing with Facebook.

1. Dad does show up in one common tech-related story, though. You know the “racist uncle” who forwards those atrocious emails? That’s certainly someone’s dad, and is probably Dad himself, hidden behind his brother to avoid embarrassment.

# More AnyBar

If you’ve been reading this blog for any length of time, you won’t be surprised to hear that I’ve made some changes to the AnyBar/SuperDuper setup I talked about a couple of days ago. I think the new system is more robust than my first stab at it.

But before I get to that, I want to mention a couple of other menubar-related items. First, in response to a question/request by T.J. Luoma, Brett Terpstra has made a fork of AnyBar that allows text to be displayed in the menubar in addition to or instead of the little graphics that Nikita Prokopov’s original is limited to. With Brett’s version, you can have

• Just an image
• Just text
• An image and text

T.J.’s a cagy old bird. He knew that acting helpless within earshot would have Brett leaping to his rescue. When I saw T.J.’s tweet, I thought Brett would have a solution before the end of the day. But Brett’s slowing down—it wasn’t ready until the following morning.

Unbeknownst to all of us, though, was TextBar a \$3 app from Rich Somerfield that already does what T.J. wants. It doesn’t display graphics, but with Emoji and the rest of Unicode, I doubt that’s a significant limitation. TextBar seems to have a simpler user interface, especially when you want to display more than one signal in your menubar, but it’s meant to be used only for signals that are updated regularly. AnyBar is more flexible, but that flexibility comes with reduced simplicity.

With those announcements out of the way, let’s talk about the changes I made to my AnyBar/SuperDuper setup. As you may recall, the idea is to have an image in the menubar that tells me whether my most recent backup was successful or not. Originally, I set one of SuperDuper’s advanced options to run a script upon completion that would set the signal image. I decided this wasn’t the best way to go about it. If SuperDuper gets hung up at some point—which can happen—I have no guarantee that the signalling script will run. So I changed things to have the script run independently of SuperDuper via launchd, OS X’s system for automatically running programs.

Because I use several Launch Agents, I popped for LaunchControl by soma-zone to manage them. If you need to manage only one or two, you can probably get by with the command-line tool, launchctl, that Apple provides. Soma-zone has written a nice introduction to both launchctl and the whole launchd system.

The automatic running of the signalling script, sdsignal, by launchd is configured through the plist file com.leancrew.sdsignal.plist, which is saved in my ~/Library/LaunchAgents folder. By saving it here, it gets loaded (but not necessarily run) whenever I log in. It can also be loaded manually by running

launchctl load ~/Library/LaunchAgents/com.leancrew.sdsignal.plist


from the Terminal.

The configuration file is set to run sdsignal at 5:56 AM on Tuesday through Saturday. These days were chosen because I have SuperDuper do its backup every weekday evening. There are no backups on Saturday or Sunday night, so there’s no need to run sdsignal on Sunday or Monday morning. Here’s the complete plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.leancrew.sdsignal</string>
<key>Program</key>
<string>/Users/drdrang/Dropbox/bin/sdsignal</string>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>56</integer>
<key>Weekday</key>
<integer>2</integer>
</dict>
<dict>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>56</integer>
<key>Weekday</key>
<integer>3</integer>
</dict>
<dict>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>56</integer>
<key>Weekday</key>
<integer>4</integer>
</dict>
<dict>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>56</integer>
<key>Weekday</key>
<integer>5</integer>
</dict>
<dict>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>56</integer>
<key>Weekday</key>
<integer>6</integer>
</dict>
</array>
</dict>
</plist>


You’d think the plist format would allow you to set day ranges, but no. You need to create a separate <dict> entry for each day. While this is not a particularly complicated plist file, I can’t take credit for writing it. LaunchControl wrote it for me after I filled in a couple of fields.

In addition to changing how sdsignal is invoked, I’ve changed the way it works, too. Here’s the new version:

python:
1:  #!/usr/bin/python
2:
3:  import os
4:  import socket
5:  from datetime import date, timedelta
6:
7:  # This script is expected to be run on the day after a backup. If it's
8:  # run on the day of a backup or more than one day after a backup, it
9:  # will indicate a failed backup even if the backup was successful.
10:
11:  # AnyBar communication info.
12:  abhost = '127.0.0.1'      # localhost IP number
13:  abport = 1738             # default AnyBar port
14:
15:  # Where the SuperDuper! log files are.
16:  logdir = (os.environ["HOME"] +
17:            "/Library/Application Support/" +
18:            "SuperDuper!/Scheduled Copies/" +
19:            "Smart Update Backup from Macintosh HD.sdsp/Logs/")
20:
21:  # Get the last log file.
22:  logfiles = [x for x in os.listdir(logdir) if x[-5:] == 'sdlog']
23:  logfiles.sort()
24:  lastlog = logdir + logfiles[-1]
25:
26:  # Get yesterday's date in the format SuperDuper! uses in its log file.
27:  y = date.today() + timedelta(days=-1)
28:  ystr = y.strftime('%a, %b %-e, %Y')   # %-e is the day of month w/o space
29:
30:  # Look for the correct "Started on" line.
31:  lastnight = False
32:  with open(lastlog) as f:
33:    for line in f:
34:      if ('| Info | Started on %s at' % ystr) in line:
35:        lastnight = True
36:        break
37:
38:  # If the date is right, look for the "Copy complete" line.
39:  good = False
40:  if lastnight:
41:    with open(lastlog) as f:
42:      for line in f:
43:        if "| Info | Copy complete." in line:
44:          good = True
45:          break
46:
47:  # At this point, good is True if the log file has the right date and
48:  # SuperDuper finished successfully.
49:
50:  # Send AnyBar the black or red signal depending on whether the backup worked.
51:  # AF_INET is for IPv4 and SOCK_DGRAM is for UDP.
52:  anybar = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
53:  anybar.connect((abhost, abport))
54:  if good:
55:    anybar.send('black')
56:  else:
57:    anybar.send('red')


The biggest differences are:

1. It now looks for the date on which the last SuperDuper backup was run and sends the failure signal if that date was not the day before sdsignal is run. This is in keeping with how SuperDuper and sdsignal are scheduled. It also, as before, sends a failure signal if SuperDuper’s log file indicates that the backup never finished.
2. It now uses a black dot to indicate success instead of a green dot. I decided a black dot was less obtrusive and more indicative of “normal” in the menubar.

This is not a particularly exciting example of automation, mainly because SuperDuper is so reliable. From my experience, it’ll be a long time before I see a red dot in my menubar.

# AnyBar and SuperDuper!

For years I’ve been using a combination of this script (or its predecessor) and GeekTool to let me know whether my nightly SuperDuper! backup was successful. It works fine, printing a short summary of the backup log file onto the upper left corner of my Desktop screen.

I’ve always thought, though, that there’s a better, less obtrusive way to let me know if the backup failed.1

Today, One Thing Well posted an article on a utility called AnyBar, written by Nikita Prokopov, that lets you put a colored dot in your menubar by sending your computer a UDP message. As soon as I read the article, I knew I could make some use of it.

There has to be something I can use this for: github.com/tonsky/AnyBar
Dr. Drang (@drdrang) Apr 7 2015 12:54 PM

Justin Scholz rang the bell:

@drdrang hey cool, will be my new “my custom backup scripts” indicator light ;-)
Justin Scholz (@JMoVS) Apr 7 2015 1:12 PM

Instead of putting a bunch of words on my Desktop, I could just put a colored dot in my menubar—green for a successful backup, red for a failure.

It turned out to be pretty simple. Although I know next to nothing about compiling Mac programs, I downloaded and unzipped the AnyBar repository, opened it in Xcode, and built the app without incident. I moved it into my /Applications directory and used the Users & Groups System Preference to make it one of my login items.

When you first start AnyBar, it puts a hollow circle in your menubar. You can then test it out by running commands like this in the Terminal:

echo -n "purple" | nc -4u -w0 localhost 1738


This one sends a “purple” message to UDP port 1738 on your computer and should turn the hollow circle into a solid purple dot. To turn in back to the hollow white circle, do this:

echo -n "white" | nc -4u -w0 localhost 1738


The AnyBar README on GitHub tells you all the messages you can send and different ways you can customize both the image in the menubar and the UDP port that AnyBar listens to.

With AnyBar working, I took my sdsummary script and rewrote parts of it to use Python’s socket library to send a “red” or “green” message to AnyBar depending on the contents of the latest SuperDuper! log file. Here’s the new script, called sdsignal:

python:
1:  #!/usr/bin/python
2:
3:  import os
4:  import socket
5:
6:  # Where the SuperDuper! log files are.
7:  logdir = (os.environ["HOME"] +
8:            "/Library/Application Support/" +
9:            "SuperDuper!/Scheduled Copies/" +
10:            "Smart Update Backup from Macintosh HD.sdsp/Logs/")
11:
12:  # AnyBar communication info.
13:  abhost = '127.0.0.1'
14:  abport = 1738
15:
16:  # Get the last log file.
17:  logfiles = [x for x in os.listdir(logdir) if x[-5:] == 'sdlog']
18:  logfiles.sort()
19:  lastlog = logdir + logfiles[-1]
20:
21:  # Look for the "Copy complete" line.
22:  good = False
23:  with open(lastlog) as f:
24:    for line in f:
25:      if "| Info | Copy complete." in line:
26:        good = True
27:        break
28:
29:  # Send AnyBar the green or red signal depending on whether the backup worked.
30:  # AF_INET is for IPv4 and SOCK_DGRAM is for UDP.
31:  anybar = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
32:  anybar.connect((abhost, abport))
33:  if good:
34:    anybar.send('green')
35:  else:
36:    anybar.send('red')


Lines 7–10 define the folder where the log files are. They’re named according to the date on which they’re generated, so it’s easy to find the latest one by sorting them and choosing the last one. That’s what Lines 17–19 do.

Lines 22–27 scan through the log file, looking for a particular string that appears near the end of the file when the backup has been successful. A Boolean variable called good is set according to whether than string is found.

Lines 31–36 use the socket library to send the appropriate signal. Line 31 creates the socket using IPv4 and UDP. Line 32 connects the socket to the localhost (127.0.0.1) at port 1738. The host and port numbers we need to communicate with AnyBar are set in Lines 13–14. Lines 33–36 then send the appropriate signal.

Because last night’s backup was successful, this is what I got when I ran sdsignal:

I have Bartender running, so I can put the AnyBar dot wherever I want. I’m not sure this is where I’m going to keep it.

Of course, the point of all this is to have sdsignal run automatically sometime after the backup is done. This could be done through the launchd system, but I think it’s simpler to have SuperDuper! do it through a setting in the advanced options pane.

I suspect this isn’t the end of my messing around with AnyBar. One thing I feel certain I’ll do is create new images to use as my SuperDuper! signal. As I said earlier, AnyBar lets you use you own customized menubar icons, and I’d like to have something a little more expressive than a simple dot.

1. It almost never does, because SuperDuper is quite reliable. I had some problems once when an old backup drive was starting to fail, and I’ve occassionally seen errors when the backup drive didn’t get mounted.