Work diary revisited

Update 8/21/14
You can pretty much ignore this post unless you want to explore the psychology of how someone can get caught in one way of thinking and miss something as plain as the nose on his face.

My first instinct is to write scripts, and I knew I could write Pythonista scripts to do what I wanted. But I didn’t want to have to launch Pythonista to run the scripts, because that would involve a lot of scrolling and would cost more time than they saved. I knew, though, that I could create home screen shortcuts that would run each script with a single tap. You’ll note that there’s no thought of using Launch Center Pro at this point. Although I bought LCP a while ago, I never got into using it.

After making the home screen shortcuts, I found that running a script that way would leave me in Pythonista. Because there’s no URL scheme for Springboard, I couldn’t script in a way to go back to the home screen where the icons lived. I really didn’t want to have to press the home button to get out of Pythonista.

That was when I hit upon using Launch Center Pro, and I was happy because everything worked. But I should’ve realized that LCP, combined with the Work diary action I’d already built in Drafts, could do what I wanted with very simple actions like this:

drafts://x-callback-url/create?text=Leaving%20home&action=Work%20diary&x-success=launch%3A

Not only is this far, far simpler than the Pythonista scripts described below, it runs faster and is more flexible. Later today, I’ll write a much shorter post about using LCP actions like this to create common diary entries.

In the meantime, read what’s below only if you want to look at a case study in complicated foolishness.

The work diary I outlined in this post last year continues to serve me well, but I’ve been looking for more efficient ways to make common entries. After a few attempts that weren’t as helpful as I’d hoped, I think I’ve hit on a solution using a combination of Pythonista and Launch Center Pro.

To review, my work diary is a set of text files stored in a Dropbox folder named “Diary.” Each file is named according to the date, like 2014-08-20.txt, and contains timestamped entries that look like this:

22:06
Started 200 degree test

22:45
Ended 200 degree test

22:54
Started 300 degree test

These entries are created in Drafts. I type or dictate the text of what I’m doing into a blank draft, invoke the Work diary action,

Drafts diary entry

and a new timestamped entry is added to the day file. The Drafts action that does the work is defined this way,

Drafts work diary action

where the file name is

[[date|%Y-%m-%d]]

and the template for the entry is

[[date|%H:%M]]
[[draft]]

This is a slight change from what I started with last year. Back then, the timestamp used a 12-hour clock with an AM/PM marker. I’ve since changed to a 24-hour clock to make it easier to figure out how much time I spent on each activity.

I don’t have the patience or the discipline to track all my time this way, but I have found this system especially useful on days when I travel. On these days, the entries tend to take on a few common forms:

07:22
Leaving home

08:05
At site

09:13
Leaving site

09:58
At office

Because I usually make these entries in my car with the Bluetooth connection on, the ambient noise and car speakerphone make Siri dictation less than perfect. To avoid dictation errors, I set up some shortcuts using the builtin iOS Settings. I soon learned that while shortcuts work fine when they’re part of a longer batch of text, they leave you hanging when the shortcut is the only thing you type.

Shortcut expansion

The little autocorrect bubble just sits there until you either type something else or tap somewhere in the text field.

TextExpander snippets are better for standalone entries because they expand immediately, with no need for additional typing or tapping. So I made a series of snippets for the entries I found myself making again and again.

lhz  -> Leaving home
lwz  -> Leaving work
lsz  -> Leaving site
lhoz -> Leaving hotel
ahz  -> At home
awz  -> At work
asz  -> At site
ahoz -> At hotel

I used these snippets for months, and they never gave me any trouble. Still, it seemed as though the sequence of

  1. Open Drafts
  2. Type abbreviation
  3. Tap action button
  4. Tap Work diary action

was too much for something as routinized as entering canned text like this, especially when my thumbs mistyped the abbreviation and I had to back up and retype. Autocorrect never wanted to turn “adz” into “asz” for me.

So I started thinking about ways to automate these entries further, and Pythonista seemed like my best bet.1 What I ended up with was a series of eight scripts—one for each of the common entries—that I run from Launch Center Pro.

Launch Center Pro shortcuts

A single tap on one of these icons switches me to Pythonista, runs the appropriate script to create a diary entry, and returns to Launch Center Pro. Counting the tap to start LCP itself, that’s two taps to make a diary entry, each on a large target with no chance for mistyping.

The scripts that do the work rely on the Dropbox module that comes with Pythonista. This module is not quite as easy to use as the one described in the Dropbox Python SDK documentation, and unfortunately the Pythonista documentation for the module contains some mistakes, but with a little trial and error I was able to get a set of scripts that worked.

The first step was to go to Dropbox’s App Console and “create an app.” This generated an app key and app secret that I could use to give my scripts access to my Dropbox files. I did that using this script, which is a slight modification of the authorization script in the Pythonista Dropbox documentation:

python:
 1:  from dropbox import client, rest, session
 2:  import clipboard
 3:  
 4:  # Get your app key and secret from the Dropbox developer website
 5:  APP_KEY = 'aaaaaaaaaaaaaaa'
 6:  APP_SECRET = 'bbbbbbbbbbbbbb'
 7:  
 8:  # ACCESS_TYPE should be 'dropbox' or 'app_folder' as configured for your app
 9:  ACCESS_TYPE = 'dropbox'
10:  
11:  sess = session.DropboxSession(APP_KEY, APP_SECRET, ACCESS_TYPE)
12:  
13:  request_token = sess.obtain_request_token()
14:  
15:  url = sess.build_authorize_url(request_token)
16:  
17:  # Make the user sign in and authorize this token
18:  clipboard.set(url)
19:  print "url:", url
20:  print "This URL is on the clipboard. Please visit the website and press the 'Allow' button, then hit 'Enter' here."
21:  raw_input()
22:  
23:  
24:  access_token = sess.obtain_access_token(request_token)
25:  
26:  print 'key:', access_token.key
27:  print 'secret:', access_token.secret
28:  print
29:  client = client.DropboxClient(sess)
30:  print "linked account:", client.account_info()

The APP_KEY and APP_SECRET in Lines 5 and 6 are the strings I got from the Dropbox App Console. Running this script within Pythonista puts a Dropbox authorization URL on the clipboard and then pauses at Line 21. At this point, I went to Safari, pasted the URL, and authorized my app in much the same way I authorized Tweetbot, Storify, Pinboard, and other apps. Returning to Pythonista and the paused script, I tapped the Return key to allow the script to finish. Lines 26 and 27 printed out the access key and secret that are needed in the next script. With that, this script has done its job and is no longer needed.

By the way, the description of obtain_access_token is one of the places where the Pythonista Dropbox module documentation is wrong. It says the return value is a tuple of the key and the secret when it’s actually a single object with key and secret attributes. That took some time to figure out.

To avoid repeating a big chunk of code eight times, I wrote a module, diary.py, with a function that can be called from all the other scripts:

python:
 1:  import dropbox
 2:  from datetime import datetime
 3:  
 4:  def add_entry(entry):
 5:    "Add a timestamped entry to today's diary file."
 6:  
 7:    # Initialize Dropbox.
 8:    app_key = 'aaaaaaaaaaaaaaa'
 9:    app_secret = 'bbbbbbbbbbbbbbb'
10:    access_key = 'ccccccccccccccc'
11:    access_secret = 'ddddddddddddddd'
12:    sess = dropbox.session.DropboxSession(app_key, app_secret, 'dropbox')
13:    sess.set_token(access_key, access_secret)
14:    client = dropbox.client.DropboxClient(sess)
15:  
16:    # Dates, times, and files.
17:    folder = '/Elements/Diary/'
18:    current = datetime.today()
19:    filename = current.strftime(folder +'%Y-%m-%d.txt')
20:    timestamp = current.strftime('%H:%M')
21:    dfiles = [ f['path'] for f in client.metadata(folder, list=True)['contents'] ]
22:  
23:    # Get the contents of today's diary file if it exists.
24:    if filename in dfiles:
25:      f = client.get_file(filename)
26:      log = f.read()
27:      f.close()
28:    else:
29:      log = ''
30:  
31:    # Add a new entry and save. Creates a new file if it doesn't exist.
32:    log += '%s\n%s\n\n' % (timestamp, entry)
33:    client.put_file(filename, log, overwrite=True)
34:  
35:  if __name__ == '__main__':
36:    add_entry('hello')

The function defined in this module, add_entry, does exactly what you’d expect from its name. Its single argument, entry, is what comes after the timestamp in a diary entry.

Lines 8–13 use the keys and secrets generated earlier to gain access to my Dropbox files. Line 14 initializes a Dropbox client process that handles the reading and writing of the files.

Lines 17–20 define the name and location of the diary file and define the timestamp. You’ll notice that my Diary folder is a subdirectory of a folder named “Elements.” Old-timers might remember that when the Elements app was the hot iOS text editor, it forced you to put all your files into a namesake Dropbox folder. I’ve linked every iOS text editor since then—none of which have been so restrictive—to that same folder.

Line 21 gets the names of all the files in the Diary folder. Line 24 then checks to see if there’s a file in there for today. If there is, it reads all the text from that file and puts it in a variable called log. If there’s no preexisting file for today, log is initialized to the empty string.

Line 31 adds the timestamp and entry to the end of log, and Line 32 writes log out to today’s file. This creates the file if it doesn’t already exist and overwrites the existing file if it does.

With diary.py doing all the hard work, the individual scripts for each common entry are easy. Here’s the one named Leave Home.py:

python:
1:  import diary
2:  import sound
3:  import webbrowser
4:  
5:  diary.add_entry('Leaving home')
6:  sound.play_effect('Coin_2')
7:  webbrowser.open('launch://')

Line 5 writes the entry, Line 6 plays a sound so I know when it’s done, and Line 7 takes me back to Launch Center Pro. The other seven scripts are exactly the same except for the argument to add_entry.

Which leaves just the definitions for the LCP actions. Here’s one:

Leave Home LCP action

The URL is

pythonista://Leave%20Home?action=run

I suppose I shouldn’t have left the space in the script name—that’s the reason for the %20. The .py extension isn’t needed in the URL because Pythonista knows to add it.

Apart from the quick, one-handed entry, the best thing about this solution is that it’ll be easy to extend if I start using other common entries in the future.


  1. I think Editorial would work, too, but I’m not as familiar with it.