Tuesday, November 12, 2019

Converging Shipments: A Headphone Synergy

A few days ago a massdrop special, offering the Sennheiser HD58X Jubilee headphones, caught my attention at a price I couldn’t pass up ($135, less the $20 for being a new drop member). I’ve been listening to Bose QC25s at the office and QC35 IIs at home, but they just lacked a certain je ne sais quoi. (In particular: Bass. Bury a Friend just gets murdered on them.) Meanwhile, I still haven’t listened to Fear Inoculum, as I haven’t found the right time to sit down with it, without distraction. (I named my first Labrador Maynard, y’all...) Finally, cleaning house, I found (again) my old iPod mini first generation, and toyed with the idea (again) of modernizing it. It still works, but the battery is pretty weak, and the MicroDrive is a moving part that will fail sooner or later. So I ordered:

  • A high capacity replacement battery (750mAh EC003, 3.7 VDC, 2.77 Wh) ($17 when I ordered it, free shipping) (no reviews; wish me luck) (I would have gone with the relative known quantity of the iFixit.com battery, but they’re out of stock)
  • SanDisk Ultra 16GB Compact Flash card (SDCFHS-016G-A46) ($21) (better bang for the buck might have been a 32GB card, which are around $1/GB, but larger cards use more power and I kinda wanted to - maybe irrationally - keep this on the cheap side; it’s still going to have 4x the capacity it did when new)
  • Some spudger tools ($7), which haven’t shipped yet and which I’m not sure are strictly necessary, given everything else I have in my toolbox.
The battery was supposed to arrive yesterday but the office was closed, so it showed up today. The CompactFlash card was always supposed to arrive today, and hopefully it will. The headphones were supposed to arrive tomorrow, but the most recent tracking information now shows them out for delivery and expected delivery today. And the old friend I was supposed to have dinner with tonight had to kick it back at least a week while he deals with employee issues.

Tonight may finally be the night I sit down and properly listen to the first TOOL album in ~13 years...


Sunday, November 10, 2019

iPad in the Duchess

Renting a Duchess for multiengine training. Grabbed old pieces to put together an iPad rig:
Works well. (I also got an iPhone X RAM cradle (RAM-HOL-AP25U (comes with screws) and RAM-B-238U base), visible in the second photo; it's better than nothing, but the iPad + Stratus is the much, much better solution.)


Wednesday, October 30, 2019

Dog Show.

For the first time in my life, I have a dog that’s AKC. Time to start competing. (I have my reasons.) He’s still so much a puppy, but growing so quickly!


I'm just going to throw this out there: The dog the AKC used for their website is basically my Labrador. White, with darker ears:



I have no idea where to start. Talked to opposing counsel on one of our cases who was requesting a trial continuance because her Border Collie keeps winning. She pointed me towards the “Jack Bradshaw” website, mentioned “conformation classes,” and described a new AKC 4-6 month puppy “competition” that doesn’t count but gets dogs and handlers familiar with what to expect. Okay, now I’m on a mission... I don’t think my boy is registered with AKC yet, so that’s step 1:

https://www.akc.org/register/

I’m so glad I scan everything. I needed his AKC number to register, and found it at the very top of the registration form his breeder gave me:


But at least finding the paperwork was as simple as ⌘-Space to pull up Spotlight. Getting him registered was easy enough, and now his name is fixed (I dropped “Maximus,” it was a bit much; his dad sire is Hatz-Off’s Harley Mac, and his mom dam is Riley Sunshine Christmas Candy) and I’ve got a PDF of his certificate.

Now, where to begin vis-a-vis competing?

Here’s that 4-6 month puppy competition I heard about: https://www.akc.org/sports/conformation/4-6-month-puppy/ She’d mentioned something in December in Costa Mesa, which I was able to find using this search: https://webapps.akc.org/event-search/#/search (4-6 Month Beginner Puppy Competition - 2019235603)

Edit: This document helps explain the entry form ...

Tuesday, October 22, 2019

.ssh/config and BBEdit

I’ve been using SSH to remotely access UNIX (Linux, Mac OS X / macOS, etc) computers for more than 20 years (switching over when the perils of plaintext telnet were made very clear to me during HIP ’97). Not sure how I never knew to setup an .ssh/config file before! Super convenient.

Had to figure it out to get BBEdit talking to a remote web server (BBEdit only groks FTP/SFTP), and I’ve configured the web server to only accept key pair authenticated logins. Good tutorial:

https://linuxize.com/post/using-the-ssh-config-file/

Also, this is in the manual, but it still took me a second to find it ... FTP/SFTP bookmarks are configured under the BBEdit menu → Setup... option. (There’s no way to bookmark an open connection, or to configure one from File → Open from FTP/SFTP Server..., which is a little surprising / obscure, for a Mac-native application with such a long history.)

Mac-Pro:~ wingedgeek$ cat .ssh/config 
Host webserver.mydomain.com
IdentityFile ~/.ssh/webserver.key 
Host webserver
Hostname webserver.mydomain.com
IdentityFile ~/.ssh/webserver.key
User winged geek
Host nas
Hostname 192.168.1.10

User filemgr

Tuesday, October 8, 2019

Alternative(s) to an SQL-based word-level inverted index

So I built a database of documents. As of this writing it contains 18,693 HTML documents, which  combined contain 1,453,866 paragraphs. Once the most common words have been excluded ('the', 'of', 'to', 'and', 'a', 'that', 'in', 'is', 'for', 'or', 'on'), there are 272,708 distinct words. So far so good. The issue I’m slamming up against now, though, is the word-level inverted index (wordIndex) I built so I could do phrase searching, find WordA within 5 words of WordB, find documents with WordC, WordD, and WordE all appearing in the same paragraph, etc. Kind of a poor man’s LexisNexis. I rolled my own for the mental exercise, but now I’m wondering if there’s a better way.

Profiling the PHP code that builds the index, the single most expensive call is writing to that wordIndex table. It takes an average of about 10 minutes to index each document; here’s a representative import session:

Words¶sSeconds Elapsed
3571118324.9
6791873.4
215277206.7
157267197.6
7652175730.1
7639104694.4
273553249.2
3301158355.5
472692466.3
10318252844.8
155075174.5
3535118357.6
2371124.5
4316168385.8
217268197.8
488599457.1
6876181635.1
5342170488.1

The reverse word index is 44,697,684 rows. The structure is:

+----------------+----------------------+------+-----+---------+-------+
| Field          | Type                 | Null | Key | Default | Extra |
+----------------+----------------------+------+-----+---------+-------+
| uuid           | char(36)             | NO   | PRI | NULL    |       |
| word_uuid      | char(36)             | YES  | MUL | NULL    |       |
| document_uuid  | char(36)             | YES  | MUL | NULL    |       |
| paragraph_no   | int(10) unsigned     | YES  | MUL | NULL    |       |
| word_no        | int(10) unsigned     | YES  | MUL | NULL    |       |
+----------------+----------------------+------+-----+---------+-------+

(I’m using uuids throughout because I ran into issues with autoincrement primary key integers and older MySQL / MariaDB replication. (I’m hosting the master database on a server I run at home, pushing to a copy on an inexpensive Linux VPS where I host an interface to the database for other lawyers to use.) (CentOS 6.10 at home - I put the server online about 8 years ago - currently running MariaDB 5.5.60 - and CentOS 7.6.1810 on the remote instance, same MariaDB version.)

Anyway. I’m starting to wonder if I’m using the right approach - or more specifically, where I should start educating myself, to put me on the path to understanding and deploying the right technology. My little hobby project has actually become a very usable tool - even got some beer money through the donation link 😋. So now I want to level it up. My skills, too.

Where do I start? My initial focuses (foci?):

  • Best way to implement the word-level inverted index or its functional equivalent. (Maybe in a way that’s “webscale”?)
  • Best way to do word grouping (stemming, thesauri lookup (where, e.g., dog, puppy, canine would all find the same word, if strict matching wasn’t specified in the query))


‡ A little about me: I’ve been dabbling in programming since my days of banging out AppleSoft BASIC on an 8-bit Apple //e, and took some programming courses in college, but I’m mostly a self-taught hacker with no real formal or rigorous training. I last banged out PHP and Perl code for a payday about a decade ago, and became an Oracle Certified Professional Java Programmer about six years ago mostly to see if I could...

Tuesday, October 1, 2019

Specifying a calendar when importing an ICS file into Outlook?

This shouldn’t be that hard, but, it’s Microsoft, so ... I built a little tool that automatically calculates litigation deadlines based on a set of rules I coded based on the California Code of Civil procedure. In Firefox and Safari and probably others (but not Edge or MSIE, sigh), it also, in JavaScript, builds on the fly .ics files for easy importation into your calendar app of choice.


When that file gets opened on the Mac (using Calendar.app), quite sensibly the user is prompted as to which calendar the event should be added to:


But ... Not on Outlook. In both the Outlook 2010 and 2016 desktop applications, the iCalendar file gets automatically opened by Outlook:


Outlook populates a window with the event information, but does not provide any option to specify which calendar the event should be added to:


When the event gets saved, it’s created on the user’s personal calendar, and has to be manually dragged and dropped onto the shared calendar used throughout the company - not ideal!


There should be a way to specify what calendar to add the event to, or a way to set the default calendar to which imported iCalendar events are added ... no? My Microsoft-fu is (somewhat intentionally) rusty...

Friday, September 20, 2019

JavaScript Unit Testing with Jasmine

So I built a fairly involved single-page JavaScript / HTML app that generates litigation calendar events using some convoluted logic (calculating holidays, when those holidays are observed (e.g., if November 11th falls on a Saturday, Veterans Day is observed on the preceding Friday, but if it falls on a Sunday, it's observed the next Monday - CRC 1.11), etc. It’s large, and important, enough, I finally sat down and learned how to build Jasmine unit tests for JavaScript. My code may be spaghetti (I never really sat down to learn JavaScript, and kind of winged it with ES6 for this app), but at least it will be validated spaghetti.


I have a book, JavaScript Unit Testing, that covers Jasmine 1.2, so I’m using the ancient 1.3.1-standalone version here. It works, for now (I’ll learn the new stuff as I have time).

src/testCode.js
The code being tested. This is a silly, simple example:


function returnsTrue() { return true; }

spec/testCodeSpec.js
A “suite” is a group of test cases used to test JavaScript code, specified with describe():

describe( title of suite, function implementing test suite )

A “spec” is a test case within a suite, containing one or more expectations. All expectations must evaluate true or test case fails.

it( title of test case, function implementing test case )


The expection format is straightforward, where .matcher() is one of the list at the bottom (e.g., .toEqual(), .toBeLessThan(), etc):

expect( code_to_test ).matcher( [expected_value] )


Here’s a simple, silly example:

describe("Test suite", function() {
    it("returnsTrue() evaluates as true", function() {
        expect( returnsTrue() ).toEqual( true );
    })
});


 SpecRunner.html
The file that actually runs the unit tests, when opened in a browser. This file is supplied in the standalone distribution of Jasmine, and modified with the filenames of the source and test suite files (changes in bold, code omitted for brevity):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Jasmine Spec Runner</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine/jasmine.css">
  <script type="text/javascript" src="lib/jasmine/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine/jasmine-html.js"></script>

  <!-- include source files here... -->
  <script type="text/javascript" src="src/testCode.js"></script>
  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/testCodeSpec.js"></script>
  <script type="text/javascript">
    (function() {
// [...]

    })();
  </script>
</head>
<body>
</body>
</html>


Expectations that can be supplied:

.toEqual( expectedValue )Passes if the value returned is equal to the value expected (including, e.g., arrays).
.toBe( expectedValue )Passed if the value returned is of the same type and value as that of the expected value (uses '===').
.toBeDefined()
Passed if the property or value is defined (var object = new Date(); ).
.toBeUndefined()
Passed if the property or value is undefined (var object; ).
.toBeNull()
Passed if the property or value is null (var object = null; ).
.toBeTruthy()
.toBeFalsy()

Passed when a value is true or false, respectively (var object = true; ).
.toContain( needle )
Passed if the haystack of the returned value contains the supplied needle. In a string, does a substring search. In an array, looks for an element that matches needle:
    expect( "Sunday, August 9").toContain("Sunday");
    expect( ["Dog", "Cat"] ).toContain("Dog");
.toBeLessThan()
.toBeGreaterThan()
Simple mathematical less-than / greater-than operations, respectively:  
    expect(4).toBeLessThan(5);
.toMatch()
Passed when a value matches a string or regular expression. I.e., check that parameter is a digit:
    expect( 420 ).toMatch("[0-9]+");
.not.matcher()
E.g. (and i.e.): .not.toBe(), .not.toEqual(), etc. Inverts the test.

There’s more to Jasmine, and I’ve only scratched the surface - of an obsolete version!

You can execute code before each test:

describe("CalendarLASC.js - Judicial Holidays", function() {
    var c;
   
    beforeEach(function() {
        c = new CalendarLASC();
    });

   
    // Judicial Holidays in 2015
    it("Thursday January 1, 2015 New Year's Day (2015)", function() {
        expect( c.isDateJan1( new Date(2015, 0, 1) ) ).toBeTruthy();
    });

//...
 
 And test suites can be nested:

describe("CalendarLASC.js - Judicial Holidays", function() {
    var c;
   
    beforeEach(function() {
        c = new CalendarLASC();
    });
   
    describe("2015 Dates", function() {
        // Judicial Holidays in 2015
        it("Thursday January 1, 2015 New Year's Day (2015)", function() {
            expect( c.isDateJan1( new Date(2015, 0, 1) ) ).toBeTruthy();
        });
 
        // ...
    });
    describe("2016 Dates", function() {
        // ...   
    });
//...

A failed test will look something like this:
Where the value on the left is what was actually returned by the tested code, and the value on the right is what the unit test was told to expect.