Controlling iOS Network Activity Indicator using ARC

In many iOS applications I have seen, the network activity indicator is either getting out of control or flickering displeasingly when something is being loaded from the network. The reason for the flickering is obviously a repeated and alternating call to

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
...
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
...
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

Well this is indeed the simplest way to trigger the activity indicator, but in order to avoid the flickering there should be a check whether another object still has network activity. In this case the activity indicator should not be hidden. Therefore you should write a helper class which has some kind of counter for the current network activities. E.g.

//Initialization
static NSUInteger counter = 0;
...
-(void)startNetworkActivityIndicator{
   ++counter;
   if(counter==1)
      [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
-(void)stopNetworkActivityIndicator{
   --counter;
   if(counter==0)
      [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

The problem with this approach is that it’s difficult to ensure the counter becomes zero in any situation. You have to be very careful in case of failing network calls. Another problem is that an object which started the network activity indicator may be disposed for some reason. In that case the counter will never become zero again. This is bad. So why not keeping track of the objects which involve network activity instead of using a counter?
My solution uses a mutable set containing weak references to the objects which induce network activity. Thanks to ARC, should any of these objects be disposed, the weak reference becomes nil and is being removed from the set. This adjusts the object count used as condition for triggering automagically.

Here you go:

#import

@interface NetworkActivityIndicatorHelper : NSObject

+(NetworkActivityIndicatorHelper*)sharedHelper;
-(void)stopNetworkActivityForObject:(id)object;
-(void)startNetworkActivityForObject:(id)object;

@end

 

#import "NetworkActivityIndicatorHelper.h"

static NetworkActivityIndicatorHelper* sharedHelper;

@implementation NetworkActivityIndicatorHelper
{
    NSMutableSet* networkActivities;
}

- (id)init {
    self = [super init];
    if (self) {
        networkActivities = [NSMutableSet set];
    }
    return self;
}

+(NetworkActivityIndicatorHelper*)sharedHelper
{
    if (sharedHelper==nil) {
        sharedHelper = [[NetworkActivityIndicatorHelper alloc] init];
    }
    return sharedHelper;
}

-(void)checkIfShouldDisplayNetworkActivity
{
    for (id object in networkActivities.allObjects) {
        if(object != nil){
            [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
            return;
        }
    }
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

-(void)stopNetworkActivityForObject:(id)object
{
    [networkActivities removeObject:object];
    [self checkIfShouldDisplayNetworkActivity];
}

-(void)startNetworkActivityForObject:(id)object
{
    __weak id objectRef = object;
    [networkActivities addObject:objectRef];
    [self checkIfShouldDisplayNetworkActivity];
}

@end

Server Setup for Continuous Integration with iOS Projects

Note for english-speaking readers: Since I already had created this tutorial for a school project, this article is written in German. If you would like me to translate it to English, just drop me a line or give Google Translate a shot.

Dieser Blog Eintrag Beschreibt den Ablauf des Server Setups für die Continuous Integration sowie die Versionsverwaltung von iOS Projekten. Zu diesem Zweck muss ein OS X Betriebssysytem eingesetzt werden welches Objective-C bzw. Cocoa Applikationen kompilieren kann. Für dieses Tutorial wurde eine Mac OS X Server 10.7 Installation inklusive Xcode 4.2 mit iOS 5 verwendet.

Mac Ports Installation

Der einfachste Weg Software unter OS X zu installieren, ist es ein Paket Management System zu verwenden. Unter OS X empfiehlt es sich das Paket Managment System «Mac Ports» zu installieren. Einen Installer dazu finden Sie unter der folgenden URL: http://www.macports.org/install.php. Danach können Software Pakete mit folgendem Befehl installiert werden:
sudo port install ...

SSH Setup

Da wir für die Authentifizierung unter Git SSH einsetzen muss dies ebenfalls eingerichtet werden. Unter OS X gestaltet sich dies sehr einfach. Es muss lediglich in den Systemeinstellungen unter dem Punkt “Freigaben” ein Haken bei “Entfernte Anmeldung” gesetzt werden.
Aktivierung von SSH in den Systemeinstellungen

Abbildung 1: Aktivierung von SSH in den Systemeinstellungen

Git

Für die Versionsverwaltung wird Git eingesetzt. Diese muss auf dem Server erst einmal installiert werden. Da es keine gesonderte Server Version von git gibt kann im Prinzip jeder Computer welcher den Git Client installiert hat auch als Server dienen.
“Es gibt keine spezielle Version von Git für den Server (oder den Client), da jeder pull aus einem Repository das komplette Repository dupliziert, kann jedes Team-Mitglieder einen Server bereit stellen.”[1]

Authentifizierung

“Im folgenden wird die Authentifizierung über SSH beschrieben: Alle Teammitglieder werden auf ein bzw. mehrere Repositories mit Schreibrechten Zugriff haben, alle anderen haben keinen Zugriff.

Wir setzen das Python Script gitosis für die Verwaltung der SSH Zugänge ein. Das besondere an gitosis ist, dass alle Zugänge nur einen einzigen Systemaccount benötigen. Ein Shell-Login über diesen globalen Account wird den Team-Mitgliedern auf dem Server selbst nicht möglich sein, sondern nur der Zugriff auf das GIT-Repository (dies wird über die command= Anweisung in der authorized_keys erreicht). Um dies zu bewerkstelligen verwendet gitosis einen genialen Trick: die Authentifizierung findet über den Namen des öffentlichen SSH Schlüssels statt. Jeder Benutzer sendet seinen öffentlichen Schlüssel an Verwalter des gemeinsamen Repositories. Die öffentlichen Schlüssel werden dann von gitosis automatisch in die Datei authorized_keys übernommen, welche den SSH Zugriff so für mehrere Benutzer möglich macht. Die Unterscheidung zwischen den einzelnen Mitgliedern wird über den Dateinamen des öffentlichen Schlüssels ermöglicht.” [1]

Installation

Aus Sicherheitsgründen werden wir Git unter einem separaten Benutzer laufen lassen. Dazu wird ein neuer Benutzer “git” mit Hilfe der folgenden Anweisungen erstellt:
#Auf dem Server
mkdir -p /Users/Shared/Git/Home
sudo dscl . create /Users/git
sudo dscl . create /Users/git PrimaryGroupID 1
sudo dscl . create /Users/git UniqueID 301
sudo dscl . create /Users/git UserShell /bin/bash
sudo dscl . passwd /Users/git $PASSWORD
sudo dscl . create /Users/git home /Users/Shared/Git/Home/
$PASSWORD kann dabei mit dem gewünschten Passwort ersetzt werden, oder aber auch leer gelassen werden.
Nun installieren wir Git und Python, welches für die Installation Gitosis notwendig ist:
#Auf dem Server
sudo port install git-core python27
“Um gitosis zu initialisieren, wird ein SSH Schlüssel benötigt, den man auf seinen lokalen Arbeitsplatz mittels ssh-keygen anlegt. Dieser Befehl legt die Dateien ./ssh/id_rsa (privater Schlüssel) und ./ssh/id_rsa.pub (öffentlicher Schlüssel) an. Falls schon Schlüssel vorhanden sind, können selbstverständliche auch diese verwendet werden. Die Übertragung des Schlüssels kann z.B. mit scp erfolgen.” [1]
#Auf einem Client
scp ~/.ssh/id_rsa.pub mein-server.ch:/tmp/benutzername.pub
Dabei wird “mein-server.ch” mit der URL des gewünschten Servers und «benutzername” mit dem gewünschten Benutzernamen ersetzt.
Darauf kann man sich wieder beim Server via SSH anmelden und Gitosis aus dem offiziellen Repository laden und installieren:
#Auf dem Server
cd /tmp
git clone git://eagain.net/gitosis.git
cd gitosis
sudo python setup.py install
Falls gewünscht kann das Repository auf einem anderen Volumen erstellt werden. In diesem Fall muss das entsprechende Verzeichnis mittels eines Symlinks verknüpft werden:
#Auf dem Server
sudo mkdir /Volumes/PartitionName/repositories
sudo ln -s /Volumes/PartitionName/repositories /Users/Shared/Git/Home/repositories
sudo chown git /Volumes/PartitionName/repositories
sudo chown git /Users/Shared/Git/Home/repositories
Damit der Benutzer “git” die git Binaries, speziell “git-serve” findet, müssen wir noch die entsprechenden Pfade in sein Bash Profil speichern:
#Auf dem Server
sudo su git
echo "PATH=/opt/local/bin:/usr/local/bin:$PATH" > ~/.bashrc
chown git ~/.bashrc
Der letzte Schritt ist es Gitosis zu initialisieren und sich selber zugriff auf das Admin Repository zu geben, indem der Public Key welcher zuvor auf den Server geladen wurde, hinzugefügt wird:
#Auf dem Server
sudo -H -u git gitosis-init < /tmp/benutzername.pub
sudo chmod +x /srv/git/repositories/gitosis-admin.git/hooks/post-update # muss ausführbar sein!
Wenn alles glatt läuft, werden folgende Meldungen ausgegeben:
Initialized empty Git repository in ./
Initialized empty Git repository in ./
Falls der Fehler “gitosis.repository.GitReadTreeError: git read-tree failed: exit status 128 error” auftritt, muss folgende Code Zeile in den Gitosis Dateien gesucht werden: shutil.rmtree
Dann muss vor dieser Zeile current_dir = os.getcwd() und danach os.chdir(current_dir) eingefügt werden.
“Es empfiehlt sich die SSH Konfiguration für den Git Server (mit Git Server meine ich immer den Server, der als zentraler Teamserver verwendet werden soll) in die lokale ./ssh/config Datei aufzunehmen. Dadurch erspart man sich den Benutzer bei jeder Kommunikation mit dem GIT Server mit anzugeben. Auch kann man durch diese Konfigurationsdatei den Ort des privaten SSH Schlüssels angeben (falls dieser von dem Standard Schlüssel abweicht).” [1]
#Auf dem Client
Host mein-git-server.ch
  User git
  # falls man einen anderen Port verwendet, muss dieser hier gesetzt werden
  # Port 22
  IdentityFile ~/.ssh/id_rsa
“Die weiteren Arbeitsschritte werden auf dem lokalen Rechner durchgeführt (nicht auf dem Server). Zuerst holt man sich das Repository gitosis-admin mit:
cd /www # in das Verzeichnis wechseln, in dem man das Repository führen will
git clone git@mein-git-server.ch:gitosis-admin.git
Hierbei ist zu beachten, dass der Benutzername git immer verwendet wird, für alle Benutzer! gitosis unterscheidet die Benutzer durch den Dateinamen des öffentlichen SSH Schlüssels, welcher verwendet wird.”[1]

Testen des SSH Zugangs

“Mit folgendem Test kannst Du verifizieren, ob der SSH Zugang für den Benutzer git richtig konfiguriert ist. Der Befehl: ssh mein-git-server.ch sollte folgendes ausgeben: PTY allocation request failed on channel 0. Erklärung: Die SSH Authentifizierung war erfolgreich. Ein Login ist allerdings nicht möglich, da dieser für den “git” Benutzer nicht gestattet ist. Diese Meldung bedeutet also, dass alles in bester Ordnung ist. Falls ein Login möglich ist, stimmt etwas nicht! Sieht man folgende Fehlermeldung, stimmt etwas mit dem SSH Zugang nicht: Permission denied (publickey).” [6][1]

Verwaltung der Git Repositories

“Die Verwaltung der Repositories erfolgt über das Repository gitosis-admin. Im Unterverzeichnis keydir werden alle öffentlichen Schlüssel der Benutzer abgelegt, die auf Repositories Zugriff haben. Der Dateiname stellt den Benutzernamen dar.” Hier ein Beispiel:
“Die Konfiguration von Benutzergruppen und wer auf welches Repository zugreifen darf, erfolgt in der Datei gitosis.conf.” [1]
# ls -1 keydir/
alex.pub
andi.pub
claudi.pub
flobruit.pub
reinhard.pub

Neues Repository anlegen

“In der Datei gitosis.conf wird das neue Repository zu den gewünschten Benutzergruppen hinzugefügt. Sofort nach dem Commit dieser, kann von den Benutzern der Benutzergruppen mit Schreibrechten das neue Repository angelegt werden. Hierzu werden folgende Kommandos ausgeführt (auf dem Arbeitsplatzrechnern, nicht auf dem Server!):”
mkdir mein_projekt
cd mein_projekt
git init
touch dummy.txt && git add dummy.txt && git commit -m 'Initial commit with dummy file'
git remote add origin git@mein-git-server.ch:mein_projekt.git
git push origin master:refs/heads/master
“Wichtig ist dabei, dass mindestens ein Commit durchgeführt wurde, bevor man den push ausführt (anderfalls wird eine Fehlermeldung erzeugt). Anstelle der dummy.txt Datei können natürlich auch bestehende Dateien in das Verzeichnis kopiert werden, die ins Repository übernommen werden sollen. Hier im Beispiel wird als Repository-Name mein_projekt verwendet, welches mit dem gleichen Namen in der gitosis.conf als writable für den Benutzer eingetragen sein muss. Nach dem Push ist auf dem Git Server das Verzeichnis /srv/git/repositories/mein_projekt vorhanden.
Das Repository kann jetzt von allen Team Mitglieder mit folgenden Befehl verwendet werden:”
git clone git@mein-git-server.de:mein_projekt
“Wichtig: alle Team-Mitglieder müssen als Benutzername “git” verwenden (und nicht den Benutzernamen, der für den öffentlichen Schlüssel verwendet wurde).” [1]

Repository entfernen

“Will man ein Repository entfernen, müssen alle Verweise auf dieses Repository aus der Datei gitosis.conf enfernt werden. Das Repository selbst kann nur auf dem Git Server gelöscht werden. Dazu löscht man das komplette Verzeichnis /Users⁄Shared⁄Git⁄Home⁄repositories⁄MEIN-REPOSITORY.git” [1]

Neues Team-Mitglied aufnehmen

“In der Datei gitosis.conf den Benutzer in die gewünschten Gruppen hinzufügen, auf der dieser Zugriff bekommen soll. Weiterhin ist es erforderlich einen öffentlichen SSH Schlüssel im Verzeichnis keydir abzulegen, dessen Dateiname mit dem Benutzernamen übereinstimmt. Beispiel: für Benutzer alex muss eine Key mit dem Namen keydir/alex.pub existieren.” [1]

Team-Mitglied entfernen

“In der Datei gitosis.conf den Benutzer aus der gewünschten Gruppe entfernen. Ist der Benutzer in keiner Gruppe mehr vorhanden, kann auch sein öffentlicher Schlüssel aus dem Verzeichnis keydir gelöscht werden.” [1]

Jenkins Installation

Da Git nun Installiert ist, kann der Continuous Integration server Jenkins http://jenkins-ci.org/ installiert werden, welcher dann beim Build die Daten aus dem Git Repository auscheckt. Jenkins ist ein Fork bzw. eine Weiterentwicklung von Hudson. Die meisten Hudson Plugins sind kompatibel mit Jenkins und können ohne anpassungen verwendet werden.

Standard Installation

Die Standard Installation von Jenkins unter Mac OS X (zum Zeitpunkt des Verfassens dieses Dokuments) installiert eine Launch Agent plist in /Library⁄LaunchDaemons⁄org.jenkins-ci.plist. Somit wird Jenkins unter dem Benutzer “daemon” gestartet. Das Problem ist, dass dieser das gleiche Home Directory wie root, nämlich /var/root verwendet. Wenn wir aber möchten, dass sich Jenkins beim Git Server über SSH authentifiziert, muss auf private key im Verzeichnis .ssh zugegriffen werden. Das .ssh Verzeichnis wird aber niemals die richtigen Zugriffsrechte besitzen sodass der private key genutzt werden könnte. Deshalb wird ein separater Benutzer “jenkins” erstellt unter welchem dann der Jenkins Server laufen wird. Das Home directory wird auf /Users/Shared/Jenkins/Home gesetzt. $PASSWORD kann dabei wiederum mit dem gewünschten Passwort ersetzt werden, oder aber auch leer gelassen werden.
sudo dscl . create /Users/jenkins
#Jenkins wird der Gruppe wheel hinzugefügt
sudo dscl . create /Users/jenkins PrimaryGroupID 0
sudo dscl . create /Users/jenkins UniqueID 300
sudo dscl . create /Users/jenkins UserShell /bin/bash
sudo dscl . passwd /Users/jenkins $PASSWORD
sudo dscl . create /Users/jenkins home /Users/Shared/Jenkins/Home/
mkdir -p /User/Shared/Jenkins/Home
chown -R jenkins: /Users/Shared/Jenkins/Home
Falls man die primäre Benutzergruppe später ändern möchte, kann das mit folgenden Befehl gemacht werden: dscl . -change /Users/rod PrimaryGroupID ID_ALT ID_NEU. Darauf wird der Jenkins Server gestoppt: sudo launchctl unload -w /Library⁄LaunchDaemons⁄org.jenkins-ci.plist. Darauf wird die plist Datei angepasst, sodass der Server fortan unter dem Benutzer “jenkins” gestartet wird:
<plist version="1.0">
<dict>
	<key>EnvironmentVariables</key>
	<dict>
		<key>JENKINS_HOME</key>
		<string>/Users/Shared/Jenkins/Home</string>
	</dict>
	<key>GroupName</key>
	<string>wheel</string>
	<key>KeepAlive</key>
	<true/>
	<key>Label</key>
	<string>org.jenkins-ci</string>
	<key>ProgramArguments</key>
	<array>
                <string>/bin/bash</string>
		<string>/Library/Application Support/Jenkins/jenkins-runner.sh</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>UserName</key>
	<string>jenkins</string>
</dict>
</plist>
Mittels sudo launchctl load -w /Library/LaunchDaemons/org.jenkins-ci.plist kann Jenkins darauf wieder gestartet werden. Damit sich der jenkins Benutzer mit dem Git Repository verbinden kann muss noch dessen öffentlicher SSH Schlüssel erstellt und bereitgestellt werden. Für den Schlüssel sollte keine “Passphrase” verwendet werden, da der Benutzer ja als Daemon läuft.
#Auf dem Server
sudo su jenkins
ssh-keygen
#Standard Verzeichnis verwenden, leerer Passphrase verwenden
cat ~/.ssh/id_rsa.pub
Der ausgegebene Inhalt sieht in etwa so aus:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8n6utRXYaKMa4SOBYsc jVx1TPFgxMS77HjkUfJWz5bbch3CPRvUOqpQQyxj5dUedZe/cw7WU305 OyAmR7klXAebJRfDph+CPGkGOjmNsDZXQ/e0OC1RzcPb8C9nl jenkins@macbookpro
Nun wird auf dem Client welcher zuvor das Gitosis Repository initialisiert hatte eine pub Datei erstellt welche gleich wie der Benutzer heisst. In diesem Fall ist das jenkins@macbookpro.pub. Diese wird in das keydir Verzeichnis im gitosis-admin Repository gelegt. Zudem müssen in der gitosis.conf die Berechtigungen für die Repositories auf welche Jenkins zugreifen soll, korrekt gesetzt werden. Dies könnte folgendermassen aussehen:
[gitosis]

[group gitosis-admin]
members = admin@lightforce.local admin@macbookpro jenkins@macbookpro
writable = gitosis-admin documentation graphics server TravelMate
Danach werden die geänderten Gitosis Einstellungen an den Server gesendet. Nun sollte der jenkins Benutzer die Git Repositories klonen können. Am besten man testet manuell ob der Zugriff funktioniert:
#Auf dem Server
sudo su jenkins
mkdir /tmp/gittest
cd /tmp/gittest
git clone git@mein-git-server.ch:mein-repo.git
Dabei sollte kein Fehler auftreten. Damit alle Zugriffsberechtigungen für die Jenkins Build Jobs richtig gesetzt werden, sollte man sich auf dem Server einmal mit dem Benutzer jenkins anmelden.
Anmeldung als jenkins Benutzer auf dem OS X Server

Abbildung 2: Anmeldung als jenkins Benutzer auf dem OS X Server

Build Jobs

Sobald Jenkins installiert und gestartet ist können Build Jobs über das Webinterface konfiguriert werden. Standardmässig läuft Jenkins auf Port 8080. Somit kann auf dem Server über http://localhost:8080 auf Jenkins zugegriffen werden. Zu beachten ist, dass die Buildjobs keine Leerzeichen enthalten sollten. Es könnte zu unerwünschten Effekten führen wenn in den Skipts die Umgebungsvariable $JOB_NAME verwendet wird und jene Leerzeichen enthält.

API Documentation

Die API Dokumentation eines iOS Projekts kann sehr komfortabel mit Hilfe des appledoc Tools erstellt werden. Dabei wird das Look & Feel der Apple eigenen Dokumentation verwendet. Das Tool bietet zudem die Möglichkeit docset Bundles zu erstellen und zu installieren. Die appledoc Software kann von folgendem Repository heruntergeladen werden https://github.com/tomaz/appledoc. Das Verzeichnis beinhaltet ein Installationsskript welches als root Benutzer aufgerufen werden muss: sudo install-appledoc.sh. Um die Dokumentation mittels Jenkins zu erzeugen, muss ein neues Build Projekt angelegt werden. Dieses soll das Git Repository des iOS Source Codes abfragen. Als Buildverfahren wird “Shell ausführen” gewählt mit folgendem Inhalt:
rm -rf /Users/Shared/WebserverData/TravelMate/API-Documentation/*
/usr/local/bin/appledoc -p TravelMate -c "My Name" –create-html –no-create-docset –logformat 1 –verbose 3 –exit-threshold 2 -o /Users/Shared/WebserverData/TravelMate/API-Documentation $WORKSPACE/TravelMate
Der exit-treshold Parameter muss auf 2 gesetzt werden, damit Warnungen welche beim Erzeugen der Dokumentation ausgegeben werden nicht zu einem Exit Code ungleich 0 führen. Das Skript erzeugt die API Dokumentation im HTML Format und legt sie im Webserver Verzeichnis ab.

iPhone Applikation

Auch iOS Projekte können mit Hilfe von Jenkins automatisch erzeugt und überprüft werden. Sind die Build Jobs einmal konfiguriert können deren Stati auf der Dashboardseite begutachtet werden (siehe Abbildung 3).
Jenkins Webinterface mit konfigurierten Build Jobs

Abbildung 3: Jenkins Webinterface mit konfigurierten Build Jobs

Zertifikate

Beim Erzeugen der iOS Applikation wird diese signiert. Deshalb muss auf dem Buildsystem die Entwicklerzertifikate sowie die Provisioning Profiles installiert werden. Die Zertifikate (Devlopment/Distribution) können über das Apple Entwicklerportal bezogen werden. Eine Anleitung ist unter http://developer.apple.com/ios/manage/certificates/team/ zu finden. Entweder werden die Zertifikate direkt auf dem Server erstellt, oder auf dem Rechner, werlcher zur Verwaltung des Servers verwendet wird (siehe Abbildung 4). In letzterem Fall müssen die Zertifikate als .p12 Dateien exportiert werden und danach auf dem Server installiert werden. Wichtig ist, dass die Zertifikate auf dem Server im System Schlüsselbund installiert werden [12]. Der Jenkins Benutzer läuft als Daemon und somit muss der Schlüsselbund im headless modus freigegeben werden können, um den privaten Schlüssel des Zertifikats zu verwenden. Die Befehle welche im Hintergrund verwendet werden um den Schlüsselbund freizugeben sind folgende:
security unlock-keychain -p <passwort>
#oder ohne ein Passwort zu verwenden
security unlock-keychain -u
Schlüsselbundverwaltung mit Entwicklerzertifikaten

Abbildung 4: Schlüsselbundverwaltung mit Entwicklerzertifikaten

Ebenso müssen die Provisioning Profiles auf dem Server installiert werden. Diese legen fest, welches Gerät die Applikation starten bzw. testen darf. Am einfachsten können sie über den Organizer der Xcode IDE installiert werden (siehe Abbildung 5). Nach der Installation sind sie unter ~/Library/MobileDevice/Provisioning Profiles/ zu finden. Eine Anleitung, wie die Provisioning Profiles erzeugt werden können befindet sich unter http://developer.apple.com/ios/manage/provisioningprofiles/.
Xcode Organizer mit installierten Provisioning Profiles

Abbildung 5: Xcode Organizer mit installierten Provisioning Profiles

Build Job

Um Build Jobs für iOS Projekte einrichten zu können, empfiehlt es sich ein Jenkins Plugin für Xcode Projekte zu installieren. Hier wird das Plugin von Ray Hilton verwendet. Die neuste Version dieses Plugins kann direkt vom Git Repository geladen und mittels Maven installiert werden. Dazu muss in den Maven Settings die Plugin Group für das Hudson Maven Plugin eingefügt werden. Dazu folgendes in der ~/.m2/settings.xml Datei einfügen:
<settings>
  <plugingroups>
    <plugingroup>org.jvnet.hudson.tools</plugingroup>
  </plugingroups>
</settings>
Danach kann das Plugin geladen und installiert werden:
Zum Schluss befindet sich die xcode.hpi Datei im target/ Verzeichnis. Diese kann dann über das Webinterface von Jenkins installiert werden (Hudson>Manage Plugins>Advanced>Upload Plugin).
# Clone the repository
$ git clone git://github.com/rayh/xcode-hudson-plugin.git

# Go into the cloned repository
$ cd xcode-hudson-plugin

# Build the project
$ mvn install
Darauf Kann ein neues “Free Style” Projekt angelegt werden. Da die Daten das Projekts vo Git Repository bezogen werden sollen, welches auf dem gleichen System betrieben wird, wie jenkins, kann beim Source-Code Management ein lokales Repository angegeben werden (z.B. git@localhost:TravelMate.git).
Als Build-Auslöser wählen wir “Source Code Management System abfragen”. Als Zeitplan geben wir z.B. @hourly oder @daily an. Beim Xcode Abschnitt wird das gewünschte Target z.B. TravelMate angegeben und die Konfiguration Release ausgewählt. Für die Release Konfiguration sollte zuvor in Xcode konfiguriert werden, dass das AdHoc Provisioning Profile verwendet wird. Es wird ein Haken bei “Update CFBundleVersion with build number?”, “Clean before build?” und “Build IPA?” gesetzt. Danach sollte das Projekt erfolgreich erzeugt werden können.[15]

Deployment zu TestFlightApp

TestFlightApp.com bietet eine Plattform um Beta Apps OTA (Over The Air) zu testen. Die IPA Dateien können automatisiert auf das Portal geladen werden. Die Plattform notifiziert darauf die Tester, dass eine neue Version der App zum Testen bereitsteht. Damit ein fertiger Build automatisch auf TestFlightApp geladen wird, muss dem bestehenden Jenkins Projekt ein “Shell ausführen” Build-Schritt mit folgendem Inhalt hinzugefügt werden [3]:
curl http://testflightapp.com/api/builds.json
-F file=@$WORKSPACE/build/Release-iphoneos/TravelMate-Release-${JOB_NAME}-\"${BUILD_NUMBER}\".ipa
-F api_token='e1619135...'
-F team_token='52aca8b...'
-F notes='This is an autodeploy build from Jenkins!'
-F distribution_lists='Internal'
Da bei der aktuellen Version des Jenkins Xcode Plugins ein Bug besteht, welcher Anführungszeichen vor und nach die Build-Nummer stellt, müssen diese im obigen Skript escaped werden. Sobald der Bug behoben wurde kann auch file=@$WORKSPACE⁄build⁄Release-iphoneos⁄TravelMate-Release-${BUILD_TAG}.ipa als Pfad verwendet werden.
Beim Build wird dann die IPA Datei hochgeladen und ein Resultat im JSON Format wird ausgegeben. Dies sollte etwa wie folgt aussehen:
{
    "bundle_version": "1.0 (\"13\")",
    "install_url": "https://testflightapp.com/install/0c01fa526108f4691004383cd446d455-NDE1Mzgy/",
    "config_url": "https://testflightapp.com/dashboard/builds/complete/415382/",
    "created_at": "2011-09-27 09:32:07",
    "device_family": "iPhone",
    "notify": false,
    "team": "TravelMate",
    "minimum_os_version": "5.0",
    "release_notes": "This is an autodeploy build from Jenkins!",
    "binary_size": 32634
}Finished: SUCCESS
Über https://testflightapp.com/dashboard/builds/ können die Tester direkt über ihr iOS Gerät auf den neuen Build zugreifen (siehe Abbildung 6). Ausserdem können über diese Plattform Testergebnisse, Fragen, Feedback etc. für die einzelnen Builds abgewickelt werden.
TestFlightApp Portal mit Dashboard der aktuellen Builds

Abbildung 6: TestFlightApp Portal mit Dashboard der aktuellen Builds

Build Job für den App-Store Release

Für den Release in den App Store empfiehlt es sich einen separaten Build Job einzurichten. Zuerst muss eine neue Build Konfiguration in Xcode angelegt werden welche auf der Relaese Konfiguration basiert. Diese kann zu diesem Zweck dupliziert werden (siehe Abbildung 7).
Build Konfiguration anlegen in Xcode

Abbildung 7: Build Konfiguration anlegen in Xcode

Die Neue Konfiguration die wir z.B. Publish nennen muss so konfiguriert werden, dass diese das Release Provisioning Profile für den App Store verwendet.
Bei diesem Job wird ebenfalls ein Haken bei “Update CFBundleVersion with build number?”, “Clean before build?” und “Build IPA?” gesetzt. Es muss hier jedoch kein Build-Auslöser angegeben werden, da der Build eine Release Version typischerweise manuell gestartet wird. Die dSYM files sollten zudem archiviert werden, damit ein späterer Crash Log eines Benutzers auch ausgewertet werden kann. Es muss lediglich ein Haken bei “Artefakte archivieren?” gesetzt werden und im Textfeld folgendes eingegeben werden: “**/**.dSYM”. Falls gewünscht kann die IPA Datei gleich noch in das Webserver Verzeichnis kopiert werden, sodass diese leicht zugänglich ist. Dazu muss ein ein weiterer Build Schritt “Shell ausführen” mit folgendem Inhalt hinzugefügt werden:
source="$WORKSPACE/build/Release-iphoneos/*.ipa"
target="/Users/Shared/WebserverData/TravelMate/$JOB_NAME"
rm -rf $target
mkdir $target
cp -R $source $target

Logic & Application Tests

Um die Unit und Application Test laufen lassen zu können muss noch einiges angepasst werden. Da Apple seit Xcode 4 nicht mehr zwischen Logic Tests und Application Tests unterscheidet, werden beim Test Vorgang beide ausgeführt. Im headless Modus will Xcode (Version 4.2 zum Zeitpunkt des Verfassens dieses Textes) die Application Tests aber perse nicht ausführen, da offenbar keine hosted Tests vom iOS Simulator unterstützt werden. Deshalb muss das RunPlatformUnitTests Skript angepasst werden. Ändern Sie Zeile 95 der Datei /Developer⁄Platforms⁄iPhoneSimulator.platform⁄Developer⁄Tools⁄RunPlatformUnitTests von
Warning ${LINENO} "Skipping tests; the iPhoneSimulator platform does not currently support application-hosted tests (TEST_HOST set)."
zu
export OTHER_TEST_FLAGS="-RegisterForSystemEvents"
RunTestsForApplication "${TEST_HOST}" "${TEST_BUNDLE_PATH}"
Dann werden die Application Test trotzdem ausgeführt [16]. Falls Core Data beim Projekt eingesetzt wird, muss zusätzlich im Programm eine Anpassung gemacht werden, da der iOS Simulator im Headless Modus einen Bug hat und das Dokument Verzeichnis für eine laufende Applikation falsch angibt [8]. Die Methode applicationDocumentsDirectory muss angepasst werden, damit im Headless Modus der Pfad anders gesetzt wird:
-(BOOL)isApplicationDirectoryCorrect:(NSString*)directory{
    NSRegularExpression* regex = [[NSRegularExpression alloc] initWithPattern:@"/[0-9A-F]+-[0-9A-F]+-[0-9A-F]+-[0-9A-F]+-[0-9A-F]+/" options:0 error:nil];
    NSUInteger numberOfMatches = [regex numberOfMatchesInString:directory options:0 range:NSMakeRange(0, [directory length])];
    return numberOfMatches>=1;
}

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory
{
    /*Fix to make application tests working in headless (command line) mode
     The problem is that in headless mode the Document directory (for core data) is not set correctly:
     ~/Library/Application Support/iPhone Simulator/Documents   instead of
     ~/Library/Application Support/iPhone Simulator/5.0/Applications/E1941BAA-8C4D-4AD4-8381-F1D128A1C05E/Documents/
     */
    //See http://www.gorillalogic.com/forumpost/1393
    //See http://denizdemir.com/2011/03/24/ios-unit-testing-with-xcode-4-and-core-data/
    if(![self isApplicationDirectoryCorrect:[[NSBundle mainBundle] description]])
        return [NSURL fileURLWithPath:[[NSFileManager defaultManager] currentDirectoryPath]];

    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
Nun kann in Jenkins ein neues “Free-Style” Projekt angelegt werden. Für dieses Projekt spezifizieren wir wiederum einen Xcode Build Step. Als Target konfigurieren wir das Test Target welches in diesem Fall “TravelMateTests” lautet. Das SDK muss dieses mal angegeben werden, da die Unit Tests nur ausgeführt werden können, wenn der Build für das SDK des Simulators erzeugt wird. Als Konfiguration wählen wir Debug. Bei “Update CFBundleVersion with build number?” setzen wir keinen Haken und bei “Build IPA?” ebenfalls nicht. Jedoch müssen die Unit Test Ergebnisse von Jenkins gesammelt werden. Dazu setzten wir bei “Veröffentliche JUnit-Testergebnisse.” einen Haken und setzten “**/test-reports/**.xml” als Suchpfad. Ausserdem sollte ein Clean vor dem Ausführen der Tests ausgeführt werden, da sonst unter Umständen Inkonsistenzen im Core Data Store auftreten können. Zu diesem Zweck muss ein Haken bei «Clean before build?” gesetzt werden.

Code Coverage

Falls die Testabdeckung der iOS Applikation berechnet werden soll, kann dies ebenfalls automatisiert mittels Jenkins erfolgen. Dazu muss das Build Projekt für die Unit Tests angepasst werden. Zunächst muss noch eine neue Buildkonfiguration in Xcode erstellt werden. Dabei kann die Debug Konfiguration dupliziert werden (analog Abbildung 7). Die neue Konfiguration wird Libprofile genannt. Für diese Konfiguration müssen noch einige Flags gesetzt werden [13].
Build Settings für Libprofile Konfiguration

Abbildung 8: Build Settings für Libprofile Konfiguration

Zudem muss noch der Suchpfad “$(DEVELOPER_DIR)/usr/lib” für die libprofile Library hinzugefügt werden. Ausserdem muss noch das Linker Flag “-lprofile_rt” beim Parameter “Other Linker Flags” gesetzt werden [14].
Darauf werden die gcov kompatiblen Programmfluss Analyse Dateien erzeugt. Da es zurzeit kein gcov Plugin für jenkins gibt, verwenden wir das Python Skript gcovr um die Analysedateien in Cobertura XML Dateien umzuwandeln. Die Version 2.2 kann über folgende URL heruntergeladen werden: https://software.sandia.gov/trac/fast/export/2575/gcovr/trunk/scripts/gcovr. Das Skript legen wir in einen Unterordner des Projekts welcher “Automation” getauft wird.
Dann wird das Jenkins Build projekt angepasst. Als Konfiguration im Abschnitt Xcode unter Buildverfahren wird nun Libprofile statt Debug verwendet. Es muss ein zusätzlicher “Shell ausführen” Build-Schritt mit folgendem Inhalt hinzugefügt werden [5, 4]:
python $WORKSPACE/Automation/gcovr.py -x -o $WORKSPACE/coverage.xml -r $WORKSPACE -e ".*/ThirdParty/.*" -e ".*/TravelMateTests/.*"
Damit die erzeugten Testabdeckungsergebnisse gesammelt werden, muss das Cobertura Plugin für Jenkins installiert sein. Dies kann über die Plugin-Verwaltungsseite http://localhost:8080/pluginManager/available vorgenommen werden. Darauf muss ein Haken bei der Option “Veröffentliche die Cobertura Testabdeckung” gesetzt werden und als Suchpfad “**/coverage.xml” definiert werden [2]. Folglich werden beim Buildvorgang die Testergebnisse auf Jenkins veröffentlicht.

Automatischer Build-Start nach einem Commit

Falls gewünscht, kann für den Git Server ein post-receive hook eingerichtet werden, welcher einen Build startet sobald ein neuer Commit erfolgt ist. Das folgende Skript kann zu diesem Zweck als post-receive hook konfigueriert werden[9]:
#!/bin/sh
USER=hudson
PASS=password

HUDSON_URL=http://$USER:$PASS@<my-build-server>:8080
JOB=$1
TOKEN=$2
CAUSE=$2

set -x
lwp-request -ds "$HUDSON_URL/job/$JOB/build?token=$TOKEN"

Webserver Installation

Um auf die Daten welche von Jenkins erzeugt werden über den Browser zugreifen zu können, wird nun noch der Apache Webserver konfiguriert. Dieser wird mit dem OS X Betriebssystem mitgeliefert.
Es wird ein Directory Listing auf dem Webserver Ordner /User/Shared/WebserverData eingerichtet. Dieses wird durch eine Authentifizierung abgesichert. Dies einfachsten zwei Möglichkeiten sind Basic Authentication oder Digest Authentication. Basic Authentication hat den Nachteil, dass die Zugangsdaten unverschlüsselt an den Webserver geschickt und somit gesnifft werden können. Der Vorteil ist, dass sie nahezu von allen Browsern unterstützt wird. Auf Grund des genannten Nachteils empfiehlt es sich jedoch die Digest Authentication zu verwenden. Der Vollständigkeit halber sei hier dennoch kurz erwähnt wie die Basic Authentication eingerichtet würde.

Konfiguration des Virtual Hosts

Damit der Webserver weiss, welches Verzeichnis er auslesen soll, wenn er eine Anfrage erhält, muss ein virtueller Host konfiguriert werden. Dies kann in der Server Administrations Applikation gemacht werden. Es muss ein neuer virtueller Host mit dem gewünschten Domainnamen hinzugefügt werden (siehe Abbildung 9). Sofern der DNS Server nicht entsprechend konfiguriert ist, muss hier die IP Adresse des lokalen Hosts ausgewählt werden (hier 192.168.0.5). Zudem wird noch der Document Root auf /Users/Shared/WebserverData gesetzt.
Konfiguration eines Virtual Hosts

Abbildung 9: Konfiguration eines Virtual Hosts

Da das Directory Listing standardmässig deaktiviert ist, muss dieses in der Konfiguration noch aktiviert werden. Um möglichst flexibel zu bleiben, wird das Listing erst in der .htaccess Datei des entsprechenden Verzeichnisses aktiviert. Es muss jedoch für den virtuellen Host das Überschreiben dieser Einstellung erlaubt werden. Zu diesem Zweck muss die Konfigurationsdatei bearbeitet werden, welche für den neu erstellten virtuellen Host erzeugt wurde:

sudo nano /etc/apache2/sites/0000_192.168.0.5_80_my-host.com.conf
Dort muss innerhalb des Directory Tags das Attribut AllowOverride (Zeile 19) auf den Wert “All” gesetzt werden.
<VirtualHost 192.168.0.5:80>
        ServerName my-host.com
        ServerAdmin admin@example.com
        DocumentRoot "/Users/admin/WebserverData"
        DirectoryIndex index.html index.php /wiki/ default.html
        CustomLog "/var/log/apache2/access_log" combinedvhost
        ErrorLog "/var/log/apache2/error_log"

        <IfModule mod_ssl.c>
                SSLEngine Off
                SSLCipherSuite "ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM"
                SSLProtocol -ALL +SSLv3 +TLSv1
                SSLProxyEngine On
                SSLProxyProtocol -ALL +SSLv3 +TLSv1
        </IfModule>

        <Directory "/Users/admin/WebserverData">
                Options All +MultiViews -ExecCGI -Indexes
                AllowOverride All
                <IfModule mod_dav.c>
                        DAV Off
                </IfModule>
        </Directory>

</VirtualHost>
Danach empfiehlt es sich den Webserver mittels sudo apachectl restart neu zu starten. Sollte etwas schief gehen, kann die Konfiguration jederzeit mittels folgendem Befehl zurück gesetzt werden:
sudo serveradmin command web:command=restoreFactorySettings

Basic Authentication

Die Basic Authentication braucht eine Datei in welcher die Passwort Hashes für die Benutzer speichert. Um eine solche Datei zu erstellen kann der Befehl htpasswd -c FILE USER verwendet werden, wobei FILE mit dem gewünschten Dateinamen und USER mit dem Benutzernamen ersetzt werden müssen.
Darauf wird im Webserver Verzeichnis /User/Shared/WebserverData folgende .htaccess Datei erzeugt:
Options +Indexes
IndexOptions +FancyIndexing

AuthType Basic
AuthName "Protected directory"
AuthUserFile $PATH_TO_PWD_FILE
Require valid-user
Wenn das Verzeichnis im Browser nun abgefragt wird, erscheint ein Authentisierungsdialog.

Digest Authentication

Die Digest Authentication überträgt im Gegensatz zur Basic Authentication die Daten verschlüsselt. Damit diese verwendet werden kann, muss das Apache Modul mod_auth_digest aktiviert sein. Dies sollte standardmässig bereits so konfiguriert sein. Andernfalls muss in der datei /etc/apache2/httpd.conf folgender Eintrag hinzugefügt werden “LoadModule auth_digest_module libexec/apache2/mod_auth_digest.so”.
Darauf wird mit Hilfe des htdigest Tools eine Datei mit dem Passwort Hash des gewünschten Benutzers erzeugt:
htdigest -c ~/digest Private lightforce
Bei diesem Beispiel wird der Hash des Passwortes für den Benutzer “lightforce” in die Datei “digest” gespeichert. Anschliessend wird eine .htaccess Datei im Webserver Verzeichnis /User/Shared/WebserverData mit folgendem Inhalt erzeugt:
Options +Indexes
IndexOptions +FancyIndexing

AuthType Digest
AuthName "Private"
AuthDigestFile ~/digest
Require user lightforce
Darauf sollte bei einem Zugriff via Browser ein Authentisierungsdialog erscheinen wie auf Abbildung 10 zu sehen ist.
Authentisierungsdialog im Browser

Abbildung 10: Authentisierungsdialog im Browser

Nachdem die Authentisierung erfolgt ist, sollte im Browser der Inhalt des Verzeichnisses angezeigt werden (siehe Abbildung 11).
Directory Listing

Abbildung 11: Directory Listing

Referenzen

Secure SSH Access for Apple TV with ATV Flash

If you have a first generation Apple TV you may also have installed ATV Flash. ATV Flash gives you SSH access to you Apple TV. The problem is, that the system user is «frontrow» and the according password is also «frontrow». So everyone who knows you IP address could log into your Apple TV with these insecure credentials and do some nasty stuff there. So what to do? You can either change the password or change the SSH authentication mode. The first option is really not recommended, since system services rely on these predefined credentials. Don’t criticise Apple for this issue, the system is designed as an isolated single user system, so this seems legit. So the way to go is to use public key infrastructure. And this is how you do it:

  1. You will need a pair of private/public keys for your current user on your local machine. Use ssh-keygen to let the system create them for you. It will create the two files id_rsa id_rsa.pub in the directory ~/.ssh. The latter file contains your public key.
  2. Now we want to add this public key to the servers authorized keys. Thus type cat ~/.ssh/id_rsa.pub and copy the output to your clipboard. Log into your Apple TV with ssh frontrow@appletv.local with the passwort frontrow. Then type nano ~/.ssh/authorized_keys to edit the file containing the authorized keys. There you can past your public key from the clipboard and save the file using ctrl + x.
  3. Then we want to disable password authentication of the ssh server and make it use PKI instead.  Then you have to make the disk writeable using sudo mount -uw /dev/disk0s3. Type sudo nano /System/Library/LaunchDaemons/com.aTVFlash.dropbear.plist to edit the configuration file of the SSH server. Make the file look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.aTVFlash.dropbear</string>
	<key>OnDemand</key>
	<false/>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/libexec/dropbear-keygen-wrapper</string>
		<string>-s</string>
		<string>-p</string>
		<string>22</string>
		<string>-b</string>
		<string>/Users/frontrow/.dropbear_banner</string>
	</array>
</dict>
</plist>

Now we are almost done. Type reboot to restart the server. Now you should be able to login to your Apple TV just by typing ssh frontrow@appletv.local! Now you can access your Apple TV from anywhere without having to worry about someone compromising your Apple TV by logging in with the default credentials.

References:

http://anthonyvance.com/blog/security/disable_ssh_passwords/

http://www.kilrathy.net/sites/ssh-public-key-authentication-und-osx.html

http://wl500g.info/showthread.php?t=24177

Useful Keyboard Shortcuts for OS X Terminal

Man page scrolling

If you are a big fan of command line tools like me, you will often find yourself skimming large man pages. Unfortunately navigating within a man page is quite cumbersome when using the arrow keys to scroll up and down because they scroll only one line at a time. Since neither the arrow keys nor the “page up” and “page down” keys work as expected you will have to use the following command on a notebook keyboard:

Fn + shift + arrow (up/down)

If you have a full-size keybord you can similarly use:

Shift + page up/down

Navigating within text line

Beside scrolling vertically you certainly want to navigate within a text line. If you want to jump to the beginning/end of the line you need to press:

Ctrl + A /Ctrl + E

In order to navigate between words on one particular text line you have to use the following shortcuts:

Esc + F / Esc + B

You will notice that you cannot keep the esc key pressed down and continuously navigate by repeating F respectively B. Instead you need to re-press the escape key every time. In order to avoid this and also for a more convenient access we alter the terminal configuration as follows:

  1. Open Terminal settings
  2. Navigate to the «settings» tab
  3. Navigate to the «keyboard» tab
  4. Click «add»
  5. Select left arrow as key and option key as the special key
  6. Insert \033b as the text which should be sent to shell and confirm
  7. Click «add»
  8. Select right arrow as key and option key as the special key
  9. Insert \033f as the text which should be sent to shell and confirm

In the following screenshot you see the preference windows with the selected «keyboard» tab:

The shortcut configurations should look like the following screenshots (the screenshots are in german, but you get the idea):

This solution has originally been found here: http://snipplr.com/view/28113/config-terminal-to-move-wordbyword/

Clear screen

Last but not least you can use the following command to clear the terminal’s screen:

Ctrl + L



jQuery UI Dialog with iFrame

Today I wanted to use the jQuery UI’s beautiful dialog box in order to display an iframe. After some googling I came across an article of Elijah Manor. He proposed a good solution. I modified his solution such that it would be possible to specify the width of the dialog box within the «a» tag. For that purpose you will need to set the «rev» attribute to a composed value of width and height.

For example: rev="300|400". This means that the dialog will have a width of 300 px and a height of 400 px. Additionally I added support for the «rel» attribute. This means that you simply can set the rel attribute to «dialog» and the href attribute to the desired URL and you will get a dialog with an iframe which loads the specified URL.

The following snippet shows a complete example of an «a» tag

<a title="Test dialog" rel="dialog" rev="675|465" href="http://www.google.ch">Dialog</a>

Place the following javascript somewhere in the header section. Make sure you have loaded jQuery and jQuery UI before executing this script.

$(function() {
	$('a[rel*=dialog]').click(function(e) {
		e.preventDefault();
		var $this = $(this);
		var horizontalPadding = 10;
		var verticalPadding = 10;
		var dialogWidth = ($this.attr('rev')) ? $this.attr('rev').split("|")[0] : 800;
		var dialogHeight = ($this.attr('rev')) ? $this.attr('rev').split("|")[1] : 600;

        $('<iframe src="' + this.href + '" />').dialog({
            title: ($this.attr('title')) ? $this.attr('title') : 'External Site',
            autoOpen: true,
            width: dialogWidth,
            height: dialogHeight,
            modal: true,
            resizable: false,
            autoResize: true,
        }).width(dialogWidth - horizontalPadding).height(dialogHeight - verticalPadding);
	});
});

Autostart of HTML file under Windows

Recently I came across the problem of performing an autostart of a HTML file on a CD. Unfortunately most of the scripts or executables found on the internet only perform an «open» command for the specified file. This launches the HTML file with the application which is associated with the .html file extension. On my test System the associated application was notepad++ which obviously wasn’t the desired behaviour. Instead I wanted the default browser to open and display the HTML file. Therefore I wrote my own C++  application which does that. Here is the result. You may freely use it.

#include <windows.h>
#include <string>
#include <iostream>
#include <sstream>
#include <shellapi.h>
#include <algorithm>

using namespace std;

string toLowerCase(string str){
	transform(str.begin(),str.end(),str.begin(),::tolower);
	return str;
}

string replace(const string &search, const string &replace, string haystack){
        string::size_type pos = haystack.find(search, 0);
		const int searchLength = search.length();
        while(pos != string::npos ){
                haystack.replace(pos, searchLength , replace);
                pos = haystack.find(search, pos+searchLength+1);
        }
        return haystack;
}

string getDefaultBrowserPath(){
	unsigned char temp[256] = {""};
	unsigned long size = sizeof(temp);
	HKEY hKey;

	RegOpenKey(HKEY_CLASSES_ROOT, "http\\shell\\open\\command", &hKey);
	RegQueryValueEx(hKey, NULL, NULL, NULL, temp, &size);
	RegCloseKey(hKey);
	stringstream ss;
	ss << temp;
	string path = ss.str();
	string needle(".exe");
	size_t found = toLowerCase(path).rfind(needle);
	path = path.substr(0,found+needle.size());//strip additional parameters for browser exe
	path = replace("\"","",path);//strip quotes
	return path;
}
string getExecutablePath(){
	TCHAR path[2048] = {0};
    GetModuleFileName( NULL, path, 2048 );
    const string exe_path(path);
	string file_path = exe_path.substr(0, exe_path.rfind("\\") + 1 );
	//PathRemoveFileSpec would also be nice
	return file_path;
}
int main(int argc, char **argv){
	string path;
	path = "\"" + getExecutablePath() + ((argc<2) ? "Start.html" : argv[1]) + "\"";
	//cout << getDefaultBrowserPath() << endl << path << endl;
	int result = (int) ShellExecute(NULL,"open",getDefaultBrowserPath().c_str(),path.c_str(),NULL,SW_SHOWNORMAL);
	if(result<=32){
		ShellExecute(NULL,"open",((argc<2) ? "Start.html" : argv[1]),NULL,NULL,SW_SHOWNORMAL);
	}
	return 0;
}

Screen locking under Mac OS X

Have you ever asked yourself how you can lock your screen in OS X the same way you can do in Windows using CTRL + L ? It’s as easy as pie.
Open the keychain application under /Applications/Utilities/Keychain Access.app
Go to the application settings and make sure «Show status in menubar» is checked.

You will then see a lock icon in the status-icon part of the system menu bar. There you can click  «Lock screen». Enjoy.