Find all the files in a directory.

When I find a comic amusing, I grab a screenshot of it. I thought it would be nice to display one comic on my home page each day, so I wrote a little code to look in the comics directory and find all the files, then randomly choose one to display.

There are a few interesting things about the code. First up is scope. Notice that I initialized the two arrays outside of the while loop. That way when I add elements to the arrays, they are not local to the loop. Second, notice that I don’t use an index to add to the arrays. In PHP the preferred way to add an element to an array is with the $arrayName[] notation. Third, I define $location to be the directory handle of the directory. Since I assign it in an if statement, if it fails to find the directory, none of the rest of the code runs. Fourth, I use a similar pattern to read the files in the directory. I read files until there aren’t any more. Finally, I use a flag in my URL so I can proof all of the comics— $all = $_GET[‘all’];.


<?php

$title = array();
$comicsLst = array();

$title = array();
$comicsLst = array();

if ($location = opendir('./Comics')) {

    while (false !== ($entry = readdir($location))) {
   
        if ($entry != "." && $entry != "..") {

            $titleArray = explode(".", $entry);
            $title[] = $titleArray[0];
            $comicsLst[] = $entry;
        }
    }
    closedir($location);
   
    $numComics = count($comicsLst);

    $randomComic = rand(0,$numComics);

    $all  = $_GET['all'];

    if (is_null($all)) {
        echo "<p>";
        echo "<img class='align-left' src='/Comics/$comicsLst[$randomComic]' ";
        echo "alt='$title[$randomComic]' title='$title[$randomComic]' /> ";
        echo "</p>";
        /*
        echo "<p class='attribution'>";
        echo "<a href='index.php?p=Comics&all=y'>Display all comics.</a>";
        echo "</p>";
        */
    } else {

        for($i=0; $i<$numComics; $i++) {
            echo "<p>";
            echo "$title[$i]<br />";
            echo "<img class='align-left' src='/Comics/$comicsLst[$i]' alt='$title[$i]' /> ";
            echo "</p>";
        }
    }
}

I frequently find new comics and add them to my local folder. To keep them synchronized with the server, I wrote a little rsync script.


#!/bin/bash
rsync -a -H -vv -z -l --update --delete --exclude \ Comics --exclude .DS_Store ~wellgolly/Documents/Comics/ \
wellgolly@ wellgolly:/www/WellGolly/Comics/ > \
~ wellgolly/Sites/rsync-backup-comics-`date +%F`.log

Remove all the files in a directory with a specific name

I have a web app that generates temporary files as part of a shell script. The files start with ‘frequency’ and have a hash appended. Sometimes the app doesn’t clear them out. This line does it. At some point I need to put this in a cron job, but I haven’t done it yet.


sudo find /tmp -iname "frequency*" -exec rm -f {} \;

By the way, I think the reason that the files are not cleared out is that they are generated by SpamBots. The code for clearing temporary files is on the display page. It never executes because the page that displays the results is never loaded.

NS_DESIGNATED_INITIALIZER warnings

I programmatically create several tables and the code has worked fine for years. It did not generate any warnings two weeks ago when I last ran it. I’ve since updated to Xcode 8.3 and 10.10.3 and now get three warnings for each UITableViewController.


Method override for the designated initializer of the superclass '-initWithStyle:' not found.
Method override for the designated initializer of the superclass '-initWithCoder:' not found.
Method override for the designated initializer of the superclass '-initWithNibName:bundle:' not found.

The code to initialize the table is similar for all of my tables:


- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context 
                 withScoreKeeper:(ScoreKeeper *)scorer 
                    withWordList:(WordList *)wordlist {

    self = [super initWithStyle:UITableViewStyleGrouped];

    if (self) {
        _mObjContext = context;
        _scoreKeeper = scorer;
        _wordList = wordlist;
    }
    return self;
}

and the .h looks like this:


@interface SettingsTableViewController : UITableViewController {
    UIPopoverController *popover;

}
    - (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context 
                     withScoreKeeper:(ScoreKeeper *)scorer 
                        withWordList:(WordList *)wordlist NS_DESIGNATED_INITIALIZER;

Here’s what I found out. I am not a programmer, so parts could be mistaken.

When I opened my project for the first time with Xcode 5 and Xcode 6, I was prompted to “Convert to Modern Objective-C Syntax”. (The latest version of Xcode puts it under Edit > Convert > Convert to Modern Objective-C Syntax.) I allowed Xcode to convert everything and understood most of the changes. I understood how NS_DESIGNATED_INITIALIZER works but not why it works. Since I had no compiler errors and everything worked as before, I promptly forgot about it. In the latest version of Xcode, they appear to have updated the compiler and that’s what triggered my warning message.

From Apple’s notes: Adopting Modern Objective C

Using this macro introduces a few restrictions:

The implementation of a designated initializer must chain to a superclass init method (with [super init…]) that is a designated initializer for the superclass.

The implementation of a convenience initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init…]).

If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.

Here’s what I think happened. First, I got three warning messages because UITableViewController has three designated initializers.


- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

I violated the third restriction because I did not implement all of the designated initializers of the superclass.

Removing the NS_DESIGNATED_INITIALIZER macro from the .h files made the warnings go away.

The question then arises, Do I care that these classes have no designated initializers? Probably not.
First, there are no other initializers in these classes, so I won’t accidentally call the wrong one. Second, I’m not a programmer by training, so when I started writing apps, I used the procedural programming style that I was used to. Until recently, I had never subclassed a class. So I won’t be subclassing this one and there won’t be any subclasses to worry about. Now that I know a bit more about Objective C, it turns out that every class I wrote was a subclass of one of iOS’s classes and that actually explains a bit about why I was getting the errors.

I did not realize that I could create the table view object by calling a method on its superclass. For example, this call works:


SettingsTableViewController *stvc = [[SettingsTableViewController alloc] initWithStyle: UITableViewStyleGrouped];

It works even when I have NS_DESIGNATED_INITIALIZER set. Presumably no other warnings are sent because the compiler is already complaining about not calling a designated initializer of super.

And as long as the views called in the table view do not need any of the objects that were passed in, everything is fine. If a view that is linked to in the table does need one of the objects, then obviously the app crashes.

Since I never want to call anything but the designated initializer, it was suggested that I use NSAssert() to make sure that I don’t call the designated initializers of my superclass, and make all the warnings go away with this code:


- (instancetype)initWithStyle:(UITableViewStyle)style {
    NSAssert(NO, @"%@", @"Tried to implement with initWithStyle");
    self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
    return self;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSAssert(NO, @"%@", @"Tried to implement with initWithNibName");
    self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
    return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    NSAssert(NO, @"%@", @"Tried to implement with initWithCoder");
    self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
    return self;
}

Now I get this error in the log when I try to call initWithStyle directly.

*** Assertion failure in -[SettingsTableViewController initWithStyle:], /Users/jscarry/Words/Words/Settings/SettingsTableViewController.m:37

This stackoverflow question has some useful info.

This article explains more about why it is implemented.

Update: I found this article (UITableViewController Designated Initializer Woes) where they have a more complete explanation of the issue and a more complete fix.

MySQL example: OUTER JOINs

I often want to know which items in a table are not in another table. In this example, I have two tables of words. The big table has ~68,000 words and the small one has ~29,000. Since they both have the field name, I use an alias in the SELECT statement so that I don’t have to decipher the results. I also put the fields in the same order as the join so that the logic of the query is consistent with the SELECT statement, but you can put them in any order.

Remember that a LEFT OUTER JOIN contains all of the records from the first table. If there is no corresponding record in the second table, the values for its fields are null.


SELECT SmallTable.word AS SmallWord, BigTable.word AS BigWord
FROM SmallTable
LEFT OUTER JOIN BigTable 
ON  SmallTable.word = BigTable.word
WHERE BigTable.word IS NULL
ORDER BY `SmallTable`.`word` ASC

The query yields 364 words and looks like this:

LEFT OUTER JOIN Result

Just for fun, the reverse query yields 38,776 words and looks like this.


SELECT SmallTable.word AS SmallWord, BigTable.word AS BigWord
FROM BigTable
LEFT OUTER JOIN SmallTable 
ON SmallTable.word = BigTable.word 
WHERE SmallTable.word IS NULL
ORDER BY `BigTable`.`word` ASC

LEFT OUTER JOIN Result 2

Google Calendar Data Not Displaying

I use Google Calendar to display the availability status of an airplane that I share with four others. About a month ago, the web page with the embedded calendar stopped displaying events. I also noticed that events don’t show up on other calendars e.g. the local high school’s vacation schedule.

It took a while to figure out what they broke, but it looks like you can’t view events on embedded calendars unless you are logged in with a Google account. Since I don’t use Google on Safari, they won’t let me view my events. When I open the page on Chrome, where I am logged in, I can view calendars. So now you know.