My friend Claude has a much better camera than the abysmal one on my phone; he was visiting my office on auction business this morning and generously snapped a photo of the completed Youngfolk & The Kettle Black “Cameron Block Blend” coffee bag in all its two-colour splendour.

Cameron Block Blend

One of the nice things about this photo is that it lets you see the physical structure of the bag that influenced some of the design decisions: the creases that run horizontally, for example, forced me to print things closer to the “top” than I would have otherwise (although I see now, looking at this again, that the design would have benefitted from 2 or 3 additional points of leading between “Cameron Block Blend” and the body text, leading that wouldn’t have caused conflict with the creases).

Here’s the red, printed as a second run after the black had a chance to dry for a while. I really gotta get myself a better camera. Available filled with coffee as soon as they can figure out what “Cameron Block Blend” is.

Untitled

Oliver and I were sitting out on a bench on Victoria Row one day this summer when he noticed “Cameron Block” at the crest of the block of buildings that include Youngfolk & The Kettle Black. Which got me thinking: who was Cameron?

Cameron, it turns out, was Ewen Cameron. Among other things he was Speaker of the House of Assembly, insurance agent, realtor, shipbuilder, and teacher. He died an untimely death, at the age of 43, in 1831, drowning off what is now Victoria Park. During his legislative career Cameron was active in the consideration of bills to extend the vote to Roman Catholics and to end slavery.

The Cameron Block wasn’t completed until 54 years after Ewen Cameron’s death – it was developed by his son-in-law Horace Haszard and designed by William Critchlow Harris – but it sits on the site where the family had long roots.

To help memorialize Ewen Cameron – in an email to me, Boyde Beck called him “one of the most most tantalizing ‘might-have-beens’ in that era of politics – and the building that came after him, I’m printing up coffee bags for a “Cameron Block Blend.”  They’ll be two colour; what you see below is the black, which contains a (very) short biography of Cameron (I hope it’s enough to prompt the coffee drinker to dig deeper). Red to follow this afternoon.

Cameron Block Blend

I want to build systems around the information that’s stored about me and the materials I’ve borrowed in the Provincial Library Service’s online system, but the SirsiDynix used for this doesn’t have any hooks to allow me to do this, so I’ll have to build my own.

By way of doing this, I need to understand to “login” to my library account in SirsiDynix from a script. Here’s what I’ve learned.

How SirsiDynix Authentication Works

The link SirsiDynix system from the front page of the Provincial Library Service’s website goes to:

http://library.pe.ca/catalogue-en

This URI is a 302 redirect to:

http://24.224.240.218/

which itself, in turn, gets 302 redirected to:

http://24.224.240.218/uhtbin/cgisirsi.exe/x/x/0/49/

This is the last URI in the journey that doesn’t contain parameter related to my “session”: this URI sends my browser a session_security cookie with a value of 259310010 and then 302 redirects me to a URI that has this value embedded in the ps parameter:

http://24.224.240.218/uhtbin/cgisirsi.exe/?ps=3SoTIEJTW9/PLS/259310010/38/1/X

which sends me a session_number cookie with the same value of 259310010 and then 302 redirects me to:

http://24.224.240.218/uhtbin/cgisirsi.exe/?ps=wyIB2k9pHm/PLS/259310010/60/1180/X

Entering my library card barcode and PIN in the header and clicking “Login” does an HTTP POST to:

http://24.224.240.218/uhtbin/cgisirsi.exe/?ps=8N5QS6vOI2/PLS/259310010/303

sending the card number as the user_id parameter and the PIN as the password parameter.

The response to this POST sets cookies of the same name (user_id and password) and updates the session_security and session_number cookies with a new value.

In the above URIs, the initial part of the ps parameter – 8N5QS6vOI2 for example – appears to be random and meaningless and I found that it could be changed or the same value used all the time without ill effect.

Automating SirsiDynix Authentication

With the above reverse-engineering, we have enough to automate the login process using PHP and cURL.

First, let’s set some variables that we’ll use later:

$user_id = "XXXXXXXXXXXXX"; // replace with your card number
$pin = "YYYY"; // replace with your PIN
$baseurl = "http://24.224.240.218"; // root URI of SirsiDynix server

The first task is to start a SirsiDynix session, and to grab the value of the session_security cookie that we’re sent back; we do this with cURL:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseurl . "/uhtbin/cgisirsi.exe/x/x/0/49/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/sirsidynix-cookies.txt");
curl_setopt($ch, CURLOPT_COOKIEJAR, "/tmp/sirsidynix-cookies.txt");
$result = curl_exec($ch);

preg_match('/^Set-Cookie:\s*([^;]*)/mi', $result, $m);
parse_str($m[1], $cookies);
$session_security_cookie = $cookies['session_security'];

The key ingredients here are the setting of the CURLOPT_HEADER open to true (so that we can parse the cookie out of the headers), and setting the CURLOPT_COOKIEFILE and CURLOPT_COOKIEJAR options so that subsequent requests will pass back the same cookies.

With the value of $session_security_cookie set, we can now login by doing an HTTP POST:

$loginurl = $baseurl . "/uhtbin/cgisirsi.exe/?ps=8N5QS6vOI2/PLS/" . $session_security_cookie . "/303";
$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, $loginurl); 
curl_setopt($ch, CURLOPT_POSTFIELDS, "user_id=$user_id&password=$pin"); 
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/sirsidynix-cookies.txt"); 
curl_setopt($ch, CURLOPT_COOKIEJAR, "/tmp/sirsidynix-cookies.txt"); 
$result = curl_exec($ch);

That’s it: we’re now “logged in” to SirsiDynix, and we can use this session to interact with the system as though we were using a web browser.

Retrieving Checked Out Items

One thing we might want to do now that we’re “logged in” is retrieve a list of the items we have checked out. We can retrieve a list of the paths for all of our checked out items like this:

$itemsurl = $baseurl . "/uhtbin/cgisirsi.exe/?ps=07P15KtNyC/CHA/" . $session_security_cookie . "/30#";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $itemsurl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/sirsidynix-cookies.txt");
curl_setopt($ch, CURLOPT_COOKIEJAR, "/tmp/sirsidynix-cookies.txt");
$result = curl_exec($ch);

$regex = '/\\Details\\<\/a\\>/';
preg_match_all($regex, $result, $matches);
$checked_out_items = $matches[1];

This results in a $checked_out_items array that looks like this:

Array
(
    [0] => /uhtbin/cgisirsi.exe/?ps=obViAQ0yfH/CHA/152430023/5/3?searchdata1=293432{CKEY}&searchfield1=GENERAL^SUBJECT^GENERAL^^
    [1] => /uhtbin/cgisirsi.exe/?ps=TGF3wAfAQt/CHA/152430023/5/3?searchdata1=104284{CKEY}&searchfield1=GENERAL^SUBJECT^GENERAL^^
)

We can now iterate over those paths and parse out the title, author, ISBN and date due:

foreach($checked_out_items as $key => $url) {
    $item = $baseurl . $url;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $item);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/sirsidynix-cookies.txt");
    curl_setopt($ch, CURLOPT_COOKIEJAR, "/tmp/sirsidynix-cookies.txt");
    $result = curl_exec($ch);

    $regex = '/(?:(?Us)\\(.{0,})\\<\/dd\\>)/';
    preg_match($regex, $result, $matches);
    $items[$key]['title'] = trim(html_entity_decode($matches[1]));

    $regex = '/(?:(?Us)\\\\n(.{0,}) .{0,}\\<\/dd\\>)/';
    preg_match($regex, $result, $matches);
    $items[$key]['author'] = trim(html_entity_decode($matches[1]));

    $regex = '/(?:(?Us)\\(.{0,})\\<\/dd\\>)/';
    preg_match($regex, $result, $matches);
    $items[$key]['isbn'] = trim(html_entity_decode($matches[1]));

    $regex = '/(?:(?Us)\\Due:(.{0,})\\<\/td\\>)/';
    preg_match($regex, $result, $matches);
    $items[$key]['datedue'] = trim(html_entity_decode($matches[1]));
    $items[$key]['datedue_unixtime'] = strtotime($items[$key]['datedue']);
}

Using regular expressions (put together and tested using the excellent Debuggex tool), the metadata for each item is parsed out; the result, after all items have been iterated over, is an $items array that looks like this:

Array
(
    [0] => Array
        (
            [title] => Reporting : writings from The New Yorker
            [author] => Remnick, David.
            [isbn] => 0307263584
            [datedue] => 6 Oct 2013
            [datedue_unixtime] => 1381028400
        )

    [1] => Array
        (
            [title] => Start & run a coffee bar
            [author] => Matzen, Thomas, 1963-
            [isbn] => 1551803542
            [datedue] => 6 Oct 2013
            [datedue_unixtime] => 1381028400
        )

)

Created iCalendar Events for Due Dates

Now that we’ve pulled out all the checked out items, we can create iCalendar events for each one; rather than using some sort of heavyweight iCalendar class, we just manually stitch together the iCalendar files:

foreach($items as $key => $item) {

    $ical = "";
    $ical .= "BEGIN:VCALENDAR\n";
    $ical .= "CALSCALE:GREGORIAN\n";
    $ical .= "PRODID:-//Date Due Processor //DateDue 1.1//EN\n";
    $ical .= "VERSION:2.0\n";
    $ical .= "METHOD:PUBLISH\n";
    $ical .= "BEGIN:VEVENT\n";
    $ical .= "TRANSP:TRANSPARENT\n";
    $ical .= "STATUS:CONFIRMED\n";
    $ical .= "SUMMARY:" . $item['title'] . "\n";
    $ical .= "DTSTART;VALUE=DATE:" . strftime("%Y%m%d", $item['datedue_unixtime']) . "\n";
    $ical .= "DTEND;VALUE=DATE:" . strftime("%Y%m%d", $item['datedue_unixtime']) . "\n";
    $ical .= "END:VEVENT\n";
    $ical .= "END:VCALENDAR\n";
    
    $fp = fopen("icalendar-" . $key . ".ics","w");
    fwrite($fp,$ical);
    fclose($fp);
}

When the resulting .ics files get loaded up into iCal on my Mac, they look like this:

iCal showing book dates due on calendar

Grab the Code

You can grab the code outlined above as sirsidynix-to-ical.php from GitHub; to use it in your particular SirsiDynix setup will require that you change the base URL and might require that you modify the URLs for logging in, retrieving items, and so on.

In theory you should be able to automate any action that a patron can otherwise perform interactively using a browser, from renewing loans to placing and reporting on holds. I welcome reports of the use of this code elsewhere.

My friend Catherine Hennessey is turning 80 years old this September. Catherine and I share an interesting age-related mathematical pattern: she was born in 1933, I was born in 1966, so when I was 33 years old, she was 66 years old. And when I was 44 years old, she was 77 years old. In 8 years, when she turns 88, I will turn 55. And in 19 years, when she turns 99, I will turn 66.

In any case, her 80th birthday is, among other things, an occasion to downsize, and as part of that effort she is holding a silent auction: it starts online right now, and continues on September 21, 2013 at her birthday party. I’ve just finished loading up all of the auction items and, if I don’t say so myself, there are some great bargains to be had.

I’m particularly fond of the Dominos and Rummoli collection, the Souris school desk, the old Stanley plane and the board of nautical knots. And, of course given my numerical fascinations, the PEI license plate from 1966.

Send all your friends to auction.catherinehennessey.com.

Catherine Hennessey Auction Screen Shot

Twenty years ago in my career as a professional typist I started to feel the effects of my work on my wrists. I sought the advice of my doctor, who referred me to the Queen Elizabeth Hospital here in Charlottetown where the excellent technicians in the Occupational Therapy department fabricated a custom wrist brace for me.

I’ve been using this brace ever since; I suspect nobody ever thought it would last this long. And, indeed, when it started to show its wear three years ago I sought out a replacement which, though well-made and custom-tailored, has never quite worked as well as the original. So I’ve continued to squeeze use out of old faithful even as various cracks and tears have signalled that its end is near.

Until today.

I realized that some judicious application of Sugru – the miracle “you can fix anything with this stuff” material from the U.K. – I could breath some new life into the old brace and keep it going. Yesterday I formed some yellow-coloured Sugru over the cracks and tears in the brace and today, now that the Sugru has set, it’s almost as good as new:

Wrist Brace + Sugru

Here’s something that happens to me everyday: I’m working on coding a Drupal module and I need to use a function that I know I’ve used elsewhere, and I want to use that earlier case as an example.

What I’ve done to this point is to try to remember where I used the function, then open that code in BBEdit and do a multi-file search for the function name. This is neither reliable nor particularly efficient, so I took a moment to create an Alfred Workflow to solve the problem (Alfred is an excellent “how did I live without this?!” app that is very good for solving this sort of problem).

Here’s how I did it:

First, I created a new Workflow (from the Alfred “Workflows” tab I clicked the “plus”) and selected the “File filter from keyword and open” template:

Alfred Workflows Templates

I then modified the template to create an Alfred keyword “example”:

Alfred Workflow Basic Setup

Under the “Search Scope” tab I dragged-and-dropped the Subversion working copy of my Drupal “sites” filter, where all my custom Drupal code lives:

Alfred Workflow Search Scope

And, finally, under the “Advanced” tab I modified the “Fields” section to use the “kMDItemTextContent” field (the contents of the files):

Alfred Workflow Advanced

With this Workflow in place, I can now simply activate Alfred (Control+Space) and type something like example node_load (if I want to see code where I’ve used the node_load function); I see a list of files that contain that string and I can select one, hit “enter” and the file in question opens in BBEdit.

Alfred Example

Nothing about this is Drupal (or BBEdit) specific, of course: you could use a similar Workflow in many different environments to solve the same sort of problem.

For many years my friend David has had his own special at Timothy’s Coffee; the details escape me, but it’s along the lines of coffee, toast and a copy of the Globe and Mail. I’ve always been jealous of David because of this, and so I’m doubly happy the Casa Mia Café, where I have coffee and a muffin every morning, is about to lauch “Peter’s Special” when their new point-of-sale system launches. I got a sneak peak this morning and Mehrnoosh, personable co-owner of the café, showed me what prints out at the kitchen when a “Peter” is ordered:

Peters Special

This is, among other things, an excellent exemplar of my persnicketiness – “small cup with demitasse spoon” and the like – and I love it.

Of course there’s nothing to prevent you from ordering “Peter’s Special” yourself if you want to join the demitasse, no-butter, no-knife, no-ice revolution. I’ll let you know when it’s available.

The Old Farmer's Almanac cover for 2014.The 222nd edition of The Old Farmer’s Almanac goes on sale today across North America. On Prince Edward Island you can buy your copy of the Canadian Edition at:

  • Sobeys
  • Atlantic Superstore
  • Coop stores
  • Shoppers Drug Mart
  • Walmart
  • Home Depot
  • Indigo

and in better book and magazines stores across the Island.

When you buy the Almanac, you’re not only getting a useful resource filled with reference information and a “pleasant degree of humour,” but you’re also helping to support we here at Reinvented Inc., a Prince Edward Island business that’s been managing the web presence of the Almanac for the last 16 years (which seems like a long time until you realize it only 7% of the publication’s long lifetime!).

Remember to look for the real Almanac when you go to the newsstand, the one with the yellow cover.

I’ve become a big fan of the work of Transom.org, a project of the Massachusetts-based non-profit Atlantic Public Media that “channels new work and voices to public radio and public media.”

Two episodes of the project’s podcast caught my ear:

  • Of Kith and Kids concerns the Plas Madoc Adventure Playground in North Wales. It’s a well-told story and one that makes me want to immediately move to North Wales to escape the wrath of litigious child-coddling that so infects us in North America. I love the distinction between “hazard” and “risk” and wish this distinction could be part of our discussions about education and play and technology here in Prince Edward Island. The same producer is making a documentary about adventure playgrounds fuelled by a Kickstarter that deserves support.
  • Diary of a Bad Year: A War Correspondent’s Dilemma looks at the life of a way correspondent through her own eyes, and is the kind of self-reflection from journalists that you rarely read about outside of their post-retirement memoirs. It’s honest and disturbing and compelling.

About This Blog

Photo of Peter RukavinaI am . I am a writer, letterpress printer, and a curious person.

To learn more about me, read my /nowlook at my bio, listen to audio I’ve posted, read presentations and speeches I’ve written, or get in touch (peter@rukavina.net is the quickest way). 

I have been writing here since May 1999: you can explore the 25+ years of blog posts in the archive.

You can subscribe to an RSS feed of posts, an RSS feed of comments, or a podcast RSS feed that just contains audio posts. You can also receive a daily digests of posts by email.

Search