A slightly better SuperDuper! summary

Time flies. Just over seven (!) years ago, I posted a Perl script that pulls out certain information from a SuperDuper! log file and prints it to standard output. I’ve been using that script ever since as way for GeekTool to display the results of the previous night’s backup on my Desktop. It’s certainly important to know when the backup fails—as happened a couple of weeks ago—but it’s also comforting to know at a glance that it worked.

Old SuperDuper summary

T.J. Luoma thinks so, too, although he uses Pushover to send a similar summary to his iPhone.

Made a script (triggered by @keyboardmaestro) that parses SuperDuper’s log and sends me a summary via Pushover:

i.luo.ma/pushover-super…

  — TJ Luoma (@tjluoma) Tue May 13 2014 1:34 PM

When I saw the notification T.J.’s script sends,

TJ SuperDuper output

I decided I needed to up my game a bit. I really wanted that line that says how much was copied. But my Perl skills have atrophied to such an extent that I didn’t even try to edit the original script. I just started a Python script from scratch.

It wasn’t very hard. The SuperDuper! log file is buried deep within the ~/Library/Application Support/SuperDuper! directory and is in RTF format. Here’s an excerpt:

{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf200
\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 LucidaGrande;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural

\f0\fs20 \cf0 | 09:30:42 PM | Info | SuperDuper!, 2.7.2 (92), path: /Applications/SuperDuper!.app, Mac OS 10.9.3 build 13D65 (i386)\
| 09:30:42 PM | Info | Started on Mon, May 19, 2014 at 9:30 PM\
| 09:30:42 PM | Info | Source Volume: Macintosh HD, mount: /, device: /dev/disk2, media: Customer, interconnect: Internal SATA, file system: "Journaled HFS+", OS: 10.9.3 (13D65), capacity: 3106.19 GB, used: 590.88 GB, directories: 263239, files: 1705700, ejectable: NO, ACLs: Enabled\
| 09:30:42 PM | Info | Target Volume: Backup, mount: /Volumes/Backup, device: /dev/disk4s2, media: WD My Book 1234, interconnect: External USB, file system: "Journaled HFS+", OS: 10.9.3 (13D65), capacity: 3000.21 GB, used: 581.30 GB, directories: 259251, files: 1520195, ejectable: YES, ACLs: Enabled\
| 09:30:42 PM | Info | Copy Mode   : Smart Update\
| 09:30:42 PM | Info | Copy Script : Backup - all files.dset\
| 09:30:42 PM | Info | Transcript  : BuildTranscript.plist\
| 09:30:42 PM | Info | PHASE: 1. Prepare to Copy Files\
| 09:30:42 PM | Info | ...ACTION: Preparing Macintosh HD\
| 09:30:42 PM | Info | ......COMMAND => Verifying the integrity of volinfo.database\
| 09:30:42 PM | Info |       volinfo.database OK\

As you can see, the meat of the file is just plain text; the RTF formatting commands are mostly up and out of the way. My script, called sdsummary, extracts just certain portions of certain lines and prints them:

python:
 1:  #!/usr/bin/python
 2:  
 3:  import os
 4:  
 5:  # Where the SuperDuper! log files are.
 6:  logdir = (os.environ["HOME"] +
 7:            "/Library/Application Support/" +
 8:            "SuperDuper!/Scheduled Copies/" +
 9:            "Smart Update Backup from Macintosh HD.sdsp/Logs/")
10:  
11:  def sdinfo(s):
12:    "Return just the timestamp and process information from a SuperDuper line."
13:    parts = s.split('|')
14:    ratespot = parts[3].find("at an effective transfer rate")
15:    if ratespot > -1:
16:      parts[3] = parts[3][:ratespot]
17:    detailspot = parts[3].find("(")
18:    if detailspot > -1:
19:      parts[3] = parts[3][:detailspot]
20:    return "%s: %s" % (parts[1].strip(), parts[3].strip(' \\\n'))
21:  
22:  # Get the last log file.
23:  logfiles = [x for x in os.listdir(logdir) if x[-5:] == 'sdlog']
24:  logfiles.sort()
25:  lastlog = logdir + logfiles[-1]
26:  
27:  with open(lastlog) as f:
28:    for line in f:
29:      for signal in ["Started on", "PHASE:", "Copied", "Cloned", "Copy complete"]:
30:          if signal in line:
31:            print sdinfo(line)

You can see in Lines 6–9 how deeply buried the log file is.

The sdinfo function in Lines 11–20 takes a line of the log file and returns only the portions I want. For the most part, these are just the first and third sections, where the sections are delimited by pipe (|) characters. The exceptions are the lines that say how much was copied and cloned.

| 09:40:02 PM | Info |       Copied  14502 items totaling  4.75 GB (2123 directories, 12327 files, 52 symlinks)\
| 09:40:02 PM | Info |       Cloned  578.10 GB of data in 434 seconds at an effective transfer rate of 1332.03 MB/s\

I don’t really want all the details of these lines, so Lines 14–19 edit the third section to eliminate extraneous info.

Lines 23–25 get a list of all the log files in the directory and put the full path to the most recent on in the lastlog variable. Lines 27–31 then run through that file, pulling out only the lines that contain certain signal phrases and printing what sdinfo returns for those lines.

The result is a GeekTool display on my Desktop that’s a bit more informative than the one I had before.

New SuperDuper! log summary

If you go back to look at my original script, you’ll see that there are real (as opposed to syntactical) differences between the Perl and Python versions. I wouldn’t attribute this to a difference between the languages, though. I think it has more to do with my changing—and not necessarily improving—taste in programming style.