IOException.de » programmieren http://www.ioexception.de Ausgewählter Nerdkram von Informatikstudenten der Uni Ulm Wed, 19 Mar 2014 22:01:00 +0000 de-DE hourly 1 http://wordpress.org/?v=3.9.1 Building node-webkit programs with Grunt http://www.ioexception.de/2014/01/10/building-node-webkit-programs-with-grunt/ http://www.ioexception.de/2014/01/10/building-node-webkit-programs-with-grunt/#comments Fri, 10 Jan 2014 14:31:45 +0000 http://www.ioexception.de/?p=2323

The days before Christmas were busy as usual: It’s not just that everyone is hunting gifts for family and friends. There’s also the annual German Youth Team Championship of Chess beginning on 25th December. Together with some friends I’m trying to broadcast this big event with more than 500 young participants to their families left at home, waiting for results.

In the last years, we used a simple mechanism to provide near to live results: The arbiters got a special email adress where they could send their tournament files to. On the server side there was a small node.js mail server (as mentioned in “Einfacher SMTP-Server zur Aufgabenverarbeitung” [german]) that took the proprietary file format, converted it and imported the results of already finished games. Although being a huge progress to the past where the results were imported once all games has been finished, this approach needed an arbiter constantly sending mails around.

Therefore I wanted to try another way: A program that keeps an eye on the tournament file and uploads it once it was changed and automatically triggers the import of new game results. Having just some days for its development it was necessary to stay with the same technology stack we used before for the mail server and tournament file converter: node.js.

As the tournament arbiters aren’t all familiar with command line tools, a graphical user interface was necessary. Strongloop published a blog post about node-webkit, which allows writing native apps in HTML and Javascript, some days before. This blog post is a good entry to the topic. Nettuts+ wrote a nice introduction recently too. Different from their approach I used the plugin for Grunt grunt-node-webkit-builder, which takes on the whole building process. Here’s my project’s setup:

/
├── dist
├── Gruntfile.js
├── package.json
└── src
    ├── index.html
    ├── package.json
    ├── js
    │   └── index.js
    └── css
        └── style.css

By using the grunt-node-webkit-builder it is necessary to keep the source of the building tool (all in the root directory) separate from the source code of the node-webkit program. Otherwise it may happen that the building tools (Grunt, you know?) get bundled in the node-webkit program as well which leads to high file sizes and slow execution times.

So it’s clear we specify in /package.json only the dependencies that are necessary for the building process:

{
  "name": "do-my-build",
  "version": "0.0.1",
  "description": "Using Grunt to build my little program",
  "author": "Falco Nogatz <fnogatz@gmail.com>",
  "private": true,
  "dependencies": {
    "grunt": "~0.4.2",
    "grunt-node-webkit-builder": "~0.1.14"
  }
}

We also have to create the Gruntfile.js:

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('src/package.json'),
    nodewebkit: {
      options: {
        build_dir: './dist',
        // specifiy what to build
        mac: false,
        win: true,
        linux32: false,
        linux64: true
      },
      src: './src/**/*'
    },
  });

  grunt.loadNpmTasks('grunt-node-webkit-builder');

  grunt.registerTask('default', ['nodewebkit']);
};

The real node-webkit program can be written now in the /src directory. As also mentioned in the tutorials linked above, the /src/package.json should be filled with some node-webkit related fields:

{
  "name": "my-program",
  ...
  "main": "index.html",
  "window": {
    "toolbar": false,
    "width": 800,
    "height": 600
  }
}

To build the node-webkit program for the architectures specified in /package.json you simply have to call the command:

grunt

This downloads the up-to-date binaries necessary for the specified architectures and builds an executable program. The result for Windows is simply a .exe file, for Linux an executable file. It contains all needed to run the program, so the user neither has to install node.js nor Chrome. The builds are located in /dist/releases.

By using this setup it was possible to automate the building process and develop the application within some days. The node-webkit runtime extends some native browser properties, for example it is possible to get the full path of a file selected by an <input type="file">. With that it was possible to create a graphical user interface to select tournament files and watch for their changes, which would trigger the update process.

]]>
http://www.ioexception.de/2014/01/10/building-node-webkit-programs-with-grunt/feed/ 1
Today I learned: vi-mode in der shell UND in fast allen Kommandozeilen-Tools http://www.ioexception.de/2013/11/12/today-i-learned-vi-mode-in-der-shell-und-in-fast-allen-kommandozeilen-tools/ http://www.ioexception.de/2013/11/12/today-i-learned-vi-mode-in-der-shell-und-in-fast-allen-kommandozeilen-tools/#comments Mon, 11 Nov 2013 23:36:06 +0000 http://www.ioexception.de/?p=2312 Ich bin ein großer Fan des Text-Editors vi. Wenn auch nicht ganz einfach zu erlernen, ermöglicht er es einem – sobald man sich ein wenig damit auskennt – sehr effizient Texte zu editieren. Die meisten Shells bieten die Möglichkeit eines vi-mode (mit dem Befehl set -o vi). In diesem Modus lässt sich die Kommandozeile mit vi-Tasten bedienen, was das arbeiten damit nochmals deutlich effizienter macht. Mein Problem bisher lag darin, dass der vi-mode nicht funktioniert hat, sobald ich ein anderes interaktives Kommandozeilen-Tool in der Shell öffnete (z.B. sftp). Neulich kam jedoch ein Freund zu mir und sagte: “Doch! Das geht!”
Hier ist die Lösung. Man legt eine Datei ~/.inputrc mit folgendem Inhalt an:
set keymap vi
set editing-mode vi

…und schon funktionieren die vi keybindings in nahezu allen Kommandozeilen-Tools :D

]]>
http://www.ioexception.de/2013/11/12/today-i-learned-vi-mode-in-der-shell-und-in-fast-allen-kommandozeilen-tools/feed/ 1
Writing a Pecha Kucha Presentation-Timer in Java http://www.ioexception.de/2012/12/13/writing-a-pecha-kucha-presentation-timer-in-java/ http://www.ioexception.de/2012/12/13/writing-a-pecha-kucha-presentation-timer-in-java/#comments Thu, 13 Dec 2012 17:56:13 +0000 http://www.ioexception.de/?p=2097 The first question many people will have is: What is this “Pecha Kucha” thing after all? And as I mentioned in my profile, one of my interests is presentations. In short; pecha kucha is a presentation technique where every slides lasts exactly 20 seconds with 20 slides in total.

The motivation for this comes from the lecture Mobile Human Computer Interaction by Enrico Rukzio where the exercises demand such kind of presentation. But I will not go into much detail about this, the more important questions is: Does the current presentation-programs feature this technique? And the answer is: Yes, but often presentations are held with PDFs which do not feature it.

So I decided to write a little java-application that extends every presentation-program with this feature. I used java because it is platform independent and I wanted to be compatible with most programs and most OS. Also java has a very (very) cool class called

 java.awt.Robot

which allows you to send keystrokes system-wide! Yes, pretty cool; and it works for Windows, Linux and Mac.

This also explains the way I implemented it. Just sending Page-Down keystrokes to the system, when the presentation is running. This will click the next slide in a given amount of time automatically. Page-Down because most hardwarepresenters also use this keystroke to fulfill their purpose.
This post is about how I program little applications and should give everyone a little look how I write code. One important thing I learned at the University is the importance of requirements and software engineering. In short: Think about what you do, the program must exist in your head (or at least on papers and diagrams) before a single line of code is written. Even is such small programs like this one.

Let’s do some requirements engineering:
The first thing we need to know is, what should your program do, and more important, what should it not do!

So, we need a program that should automatically switch slides on a presentation in a given amount of time. The program should work with most OS and most presentation-programs. Controlling the program should be possible via a Trayicon and Formdialog. Every controlling-element should indicate if the timer is started or stopped. Before the program should switch slides, the user must have some time to prepare for his presentation. Therefore a delay should be implemented that starts the input-automation after a fix amount of time. The program should always give responses to the internal state, like: How much time is left before the presentation starts and how long will the current slide last until a switch. Also it should be possible to pause the program while in presentation. If the presentation has ended, you should be able to start over. It should be possible to set parameters for: Delay-before-presentation, Time-for-each-slide, Amnout-of-slides-in-total.

The requirements could be more precise, but that should be enough.
The next thing is to think about how to implement it. Usually you draw nice diagrams like Class-diagrams and state charts.

class-presenter1 state-presenter1-1024x594

As we can see in the diagrams above, the Presenter is our Model (the M in MVC), Tray and InfoDisplay are Views but also Controllers. If we switch the state of our Model via the Controller, the Views (which implement the PresenterStateListener) are updated automatically. As timer I used a Ticker-Class which just calls a callback in the presenter ( tick() ) every second. The ticker can be in play or in paused state. PresenterController implements the Play/Pause state machine and the Listeners, Presenter implements the delay/next-slide state machine.

But enough about intention, requirements and design, let’s go see some code!

public class Main {

	public static void main(String[] args) {

	int delayFirst = 10;
	int slideCount = 15;
	int slideSeconds = 20;
	if (args.length == 3){
		delayFirst = Integer.parseInt(args[0]);
		slideCount = Integer.parseInt(args[1]);
		slideSeconds = Integer.parseInt(args[2]);
	}

	Presenter p = new Presenter(delayFirst,slideCount,slideSeconds); InfoDisplay d = new InfoDisplay(p);
	d.setVisible(true);
	Tray t = new Tray(p,d);

	p.addPresenterStateListener(t);
	p.addPresenterStateListener(d);
 }
}

This part should be obvious. We control alternative parameters via command-line args, then delegate everything to the Presenter. After that, we create the Views and connect the Listeners.

Let’s have a look into the Tray-Class, which I developed first, because it’s often easier to begin with a controlling element like a GUI. This allows you to implement the use-cases defined in the requirements-part and also allows you to find errors and misunderstandings between Customer and Contractor in a very early state of development. If all interactions are implemented, you can run usability-tests with real users. But personally I think beginning with controlling elements allows you develop an early logic which can be very good integrated into the final program.

Back to the Tray-Class:

/**
 *
 * Spawns a trayicon to control the presentation (play/pause)
 *
 */
public class Tray implements PresenterStateListener, ActionListener {
//this is a nice way to access ressources in the jar-file. But this also works in the normal filesystem.
	private final Image playImage = Toolkit.getDefaultToolkit().getImage(
			Tray.class.getResource("/resources/play.png"));
	private final Image pauseImage = Toolkit.getDefaultToolkit().getImage(
			Tray.class.getResource("/resources/pause.png"));
	private final String TOOLTIP_PLAYING = "pechakucha - playing";
	private final String TOOLTIP_STOPPED = "pechakucha - stopped";
	private TrayIcon trayIcon;

	private final PopupMenu menu = new PopupMenu();
	private final MenuItem exit_menu_item = new MenuItem("exit");
	private final MenuItem toggle_gui = new MenuItem("toggle gui");

	private final Presenter p;

	private final JFrame gui;

	private boolean disableTrayNotifications = true;

	public boolean isDisableTrayNotifications() {
		return disableTrayNotifications;
	}

	public void setDisableTrayNotifications(boolean disableTrayNotifications) {
		this.disableTrayNotifications = disableTrayNotifications;
	}

	public Tray(Presenter p, JFrame f) {
		gui = f;
		trayIcon = new TrayIcon(playImage, TOOLTIP_STOPPED);
		this.p = p;

		trayIcon.setImageAutoSize(true);

		trayIcon.setPopupMenu(menu);

		menu.add(exit_menu_item);

		exit_menu_item.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);

			}
		});

		toggle_gui.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (gui.isVisible()) {
					gui.setVisible(false);
				} else {
					gui.setVisible(true);
				}

			}
		});
		menu.add(toggle_gui);

		if (SystemTray.isSupported()) {
			SystemTray tray = SystemTray.getSystemTray();
			try {
				tray.add(trayIcon);
			} catch (AWTException e) {
				e.printStackTrace();
			}
		}

		trayIcon.addActionListener(this);
	}

	public void setMessage(String msg) {
		if (disableTrayNotifications)
			return;
		trayIcon.displayMessage("pechacucha", msg.replaceAll("<br>", "\n"),
				MessageType.INFO);
	}

	@Override
	public void presenterPlay() {
		trayIcon.setImage(pauseImage);
		trayIcon.setToolTip(TOOLTIP_PLAYING);

	}

	@Override
	public void presenterPause() {
		trayIcon.setImage(playImage);
		trayIcon.setToolTip(TOOLTIP_STOPPED);

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		this.p.switchState();
	}

	@Override
	public void presenterInfoUpdate(String msg) {
		setMessage(msg);

	}

}

The Trayicon should be very straight forward, there should be no problems to understand the code here.
The InfoDisplay class is also a very straight forward Swing JFrame:

/**
 *
 * Displays a play/pause button for the presentation,
 * a timer that shows when the presentation starts, when the next slide will be shown
 * and how many slides are left.
 *
 */
public class InfoDisplay extends JFrame implements PresenterStateListener,
		ActionListener {

	private static final long serialVersionUID = 1L;
	private JLabel body;
	private final JButton playpause;
	private final Presenter p;

	public InfoDisplay(Presenter p) {
		setSize(800, 300);
		playpause = new JButton("play");
		getContentPane().add(playpause, BorderLayout.SOUTH);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		playpause.addActionListener(this);

		this.p = p;
		body = new JLabel();
		body.setHorizontalAlignment(SwingConstants.CENTER);
		setMessage("press play to start");
		getContentPane().add(body, BorderLayout.CENTER);
		setTitle("pechacucha");
		setLocationRelativeTo(null);
		setAlwaysOnTop(true);
	}

	public void setMessage(String msg) {
//interesting things could be the String.format() method, which works like c's printf. Also nice is the usage of html and css to style the Label.
		this.body
				.setText(String
						.format("<span style="font-size: 40pt; font-weight: bold; text-align: center;">%s</span>",
								msg));
	}

	@Override
	public void presenterInfoUpdate(String msg) {
		setMessage(msg);

	}

	@Override
	public void presenterPlay() {
		this.playpause.setText("pause");
	}

	@Override
	public void presenterPause() {
		this.playpause.setText("play");

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == playpause) {
			this.p.switchState();
		}

	}

}
 

Now we have seen the View and Controllers combined as Trayicon/JFrame and PresenterStateListener
Lets look at some logic:

To make things easier, we will only look at the interesting parts of the logic and omit some boring getters, setters, variable declarations,…

As mentioned earlier the PresenterController implements the play/pause state machine:

protected enum State {
	PLAYING, STOPPED;
}

protected State state;

protected abstract void stateChangedStopped();

protected abstract void stateChangedStarted();

 /**
 * switches the state to play/pause; depends on the previous state
 */
 public void switchState() {
		if (state == State.PLAYING) {
			state = State.STOPPED;

			for (PresenterStateListener l : listeners) {
				l.presenterPause();
			}
			stateChangedStopped();
		} else {
			state = State.PLAYING;

			for (PresenterStateListener l : listeners) {

				l.presenterPlay();
			}
			stateChangedStarted();

		}

	}

On top of that, I created the Presenter class which uses a Ticker to time events. Every second the ticker calls the tick-method of the presenter. The presenter then decides what to do: Delay the presentation, count down the slide-timer or use the robot to send a “next-slide”-keystroke to the operating system.

/**
 *
 * The main control class for presentation.
 * Uses the Ticker to periodically perform an action (tick())
 *
 * Implements the next-slide, presentation starts, ends state machine.
 *
 */
public class Presenter extends PresenterController {

	private final int delayFirstSeconds;
	private int delayFirstSecondsPassed;

	private final int numSlides;
	private int slideCount = 0;

	private boolean firstPlay = true;

	private final int secondsForSlide;
	private int secondsForSlidePassed;

	private Robot robot;

	private final Ticker ticker;

	public Presenter(int delayFirstSeconds, int numSlides, int secondsForSlide) {
		super();
		resetState();
		this.delayFirstSeconds = delayFirstSeconds;
		this.numSlides = numSlides;
		this.secondsForSlide = secondsForSlide;
		ticker = new Ticker(this);
		try {
			robot = new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
		}

	}

	/**
	 * resets the state of the presenter
	 */
	private void resetState() {
		delayFirstSecondsPassed = 0;
		secondsForSlidePassed = 0;
		slideCount = 0;
		firstPlay = true;
	}

	/**
	 * performs a systemwide "next-slide" keystroke (pgdown)
	 */
	public void nextSlide() {
		robot.keyPress(KeyEvent.VK_PAGE_DOWN);
		robot.keyRelease(KeyEvent.VK_PAGE_DOWN);
	}

	@Override
	protected void stateChangedStopped() {
		ticker.pause();

	}

	@Override
	protected void stateChangedStarted() {
		ticker.play();
		if (firstPlay) {
			firstPlay = false;
		}

	}

	/**
	 * callback for the ticker
	 */
	public void tick() {

		delayFirstSecondsPassed++;

		if (delayFirstSecondsPassed >= delayFirstSeconds) {
			secondsForSlidePassed++;
			// delay over, start
			if (firstPlay) {
				firstPlay = false;

			}

			if (secondsForSlidePassed >= secondsForSlide) {
				// next slide
				slideCount++;
				secondsForSlidePassed = 0;

				if (numSlides - slideCount 					// presentation over
					switchState();
					msgPresenterStateListeners("presentation finished");
					resetState();
					return;
				}
				nextSlide();
			}
			msgPresenterStateListeners("Seconds until next slide: "
					+ (secondsForSlide - secondsForSlidePassed)
					+ "
Slides left: " + (numSlides - slideCount - 1));

		} else {
			// delay phase
			msgPresenterStateListeners("Seconds until start: "
					+ (delayFirstSeconds - delayFirstSecondsPassed));
		}

	}

}

Then there is the Ticker class. The idea of the ticker is, that he has three states: PLAY, PAUSE, STOP. However STOP is never active because it would prevent the ticker from starting over. Once the run-method has ended, a Thread will never be alive again..

/**
 *
 * Performs an action (callbacks the Presenter's tick method)
 * every second.
 *
 *  The ticker can be in three states: PLAY, PAUSE, STOP
 *  On Play, every seconds the callback will be called,
 *  on Pause, the ticker-thread will be on wait.
 *  On Stop, the Ticker can never be in play or paused state again.
 *
 */
public class Ticker implements Runnable {
//The volatile keyword is not as easy explained as you might think,
//in short, use it to indicate that multiple threads will access it.
//Also note that the definition of volatile tightened up in java5.
	private volatile boolean stopped = false;

	private Thread ownThread;

	private long lastSecond = 0;

	private int every; //every.. second -> every=1

	private enum TickerState {
		PLAY, PAUSE, STOP;
	}

	private volatile TickerState state;

	private Presenter p;

	public Ticker(Presenter p) {
		this(p, 1);
	}

	public Ticker(Presenter p, int every) {
		this.p = p;
		this.every = every;
		state = TickerState.PAUSE;
		ownThread = new Thread(this);
		ownThread.setDaemon(true); //a daemon-thread will not cause the program to stay alive if no other thread is running.
		ownThread.start();

	}

	/**
	 * starts or continues the ticker (this method is idempotent)
	 */
	public void play() {

		state = TickerState.PLAY;
//notifyAll can only be called if it has the monitor
		synchronized (ownThread) {

			ownThread.notifyAll();
		}

	}

	/**
	 * pauses the ticker (this method is idempotent)
	 */
	public void pause() {

		state = TickerState.PAUSE;
	}

	/**
	 * stops the ticker (this method is idempotent)
	 */
	public synchronized void stop() {
		stopped = true;
	}

	@Override
	public void run() {
//again, we need to synchronize this on ownThread because ownThread.wait(); demands this.
		synchronized (ownThread) {

			while (!stopped) {

				while (state == TickerState.PAUSE) {

					try {
						ownThread.wait();

					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				}

				if (System.currentTimeMillis() - lastSecond > 1000 * every) {
					// tick - a second
					p.tick();
					lastSecond = System.currentTimeMillis();
				}
			}
		}

	}
}

The idea is to loop infinitely if the ticker is in PLAY, and to pause the whole thread if it is on PAUSE. On STOP, we end the infinity-loop in the run-method.
You also see that I use timestamps to trigger events not Thread.sleep(TIME) to do that. The reason is that Thread.sleep is only as accurate as the precision and accuracy of systemtimers and schedulers. By checking everytime if the delta-value of the system-time is greater or equal than one second, we should be very precise.

So what happens if we pause the timer?
The Thread checks in a loop every time if we are in paused state. If that is true, he puts himself to sleep.

				while (state == TickerState.PAUSE) {

					try {
						ownThread.wait();

					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				}

The call ownThread.notifyAll(); in play() wakes him up.

	public void play() {
		state = TickerState.PLAY;
//notifyAll can only be called if it has the monitor
		synchronized (ownThread) {
			ownThread.notifyAll();
		}
	}

You can download the project from github:
https://github.com/philipphock/PechaKucha.git

]]>
http://www.ioexception.de/2012/12/13/writing-a-pecha-kucha-presentation-timer-in-java/feed/ 0
Gitlab: Freie GitHub-Alternative für geschlossene Projekte http://www.ioexception.de/2012/07/18/gitlab-freie-github-alternative/ http://www.ioexception.de/2012/07/18/gitlab-freie-github-alternative/#comments Wed, 18 Jul 2012 13:37:48 +0000 http://www.ioexception.de/?p=1856 Wer einmal mit einer Versionsverwaltung wie Git oder Subversion gearbeitet hat, möchte es für die eigene Arbeit nicht mehr missen: Nie mehr verzweifeln, weil der Texteditor nur die letzten X Änderungen über die allseits beliebte Tastenkombination Strg+Z zurücknehmen lässt. Kein leidiges Abgleichen von Dateien per Hand, wenn mehrere Leute am gleichen Projekt arbeiten.
Für die meisten dieser Versionskontrollsysteme ist ein zentraler Server, auf dem die Datenstände und Versionen allen Projektmitgliedern bereitgestellt werden können, zwingend erforderlich oder zumindest sehr empfehlenswert. In der Open-Source-Welt hat sich hierfür GitHub etabliert, was das kostenfreie Git-Hosting für öffentliche Projekte erlaubt. Wer – wie wir für unser Softwareprojekt (Sopra) im Rahmen des Informatikstudiums – im Team an einem privaten Repository arbeiten möchte, muss bei GitHub schon etwas Geld in die Hand nehmen: Zwar wäre es uns die sieben Dollar im Monat wert gewesen, da ich aber die Vorteile von Git auch für ein paar andere eigene (nicht öffentliche) Projekte verwenden wollte und einen vServer quasi brachliegen hatte, sah ich mich nach selbstgehosteten Alternativen um – und stieß auf Gitlab.

Gleich vorweg: Wenn man Git alleine nutzt, braucht man natürlich überhaupt keinen Server. Und wem grafische Oberfläche ohnehin ein Fremdwort ist, der kann seinen Server sicher auch nur als reinen Git-Server betreiben. Für unser Team waren in der Programmentwicklung jedoch die Features, die auch GitHub bietet, sehr wichtig: Wir wollten Issues online erfassen, Commits direkt im Code kommentieren. Ja, vielleicht sogar MergeRequests direkt über die Web-Oberfläche abarbeiten. All dies wurde durch Gitlab ermöglicht, wenn auch zum Teil erst nach ein paar Versionssprüngen.

Installation und Updates

Versionssprünge? – Ja, Gitlab steckt so gesehen noch in den Kinderschuhen. Oder anders formuliert: Es wird stetig verbessert. Wer sich ein wenig mit Ruby auskennt oder einfach ein paar neue Ideen einbringen will, kann ja mal bei Gitlab auf GitHub vorbeischauen.
Wir fingen im März unter Nutzung der Version 2.2 an, mit Gitlab zu arbeiten. Mittlerweile läuft auf meinem Server die 2.6er-Version, in den kommenden Tagen müsste das Update auf 2.7 veröffentlicht werden. Anfangs weigerte ich mich, das System zu aktualisieren, um das Sopra nicht unnötig zu gefährden. Es zeigte sich jedoch schnell, dass jedes einzelne Update sehr nützliche Features mit sich brachte – so kamen seit Beginn unserer Arbeit die Möglichkeit der Milestones, Taggen von Issues und das Annehmen von MergeRequests über die Web-Oberfläche dazu.

Die Installation auf dem eigenen Server geht relativ einfach von der Hand. Eine Schritt-für-Schritt-Anleitung dazu gibt es im Gitlab-Wiki, die ich eigentlich nur abarbeiten musste. Offiziell unterstützt wird nur Ubuntu, es finden sich im Internet aber mittlerweile genügend Hilfen, wie man Gitlab auch auf CentOS- oder anderen Servern zum Laufen bekommt. Einmal installiert gehen Updates so einfach wie nur möglich von der Hand: Ein einfaches git pull und rake db:migrate reichen in aller Regel aus, um das System auf den neuesten Stand zu bringen.

Features

Wie oben schon geschrieben: Im Prinzip bringt Gitlab alles mit, was man auch von GitHub kennt. Das große Vorbild schwingt in allen Diskussionen um neue Features mit. So birgt die Oberfläche von Gitlab erstmal auch kaum Überraschungen.
Da ich noch nie auf GitHub in einem Team gearbeitet habe, kann ich das leider nicht vergleichen. In Gitlab gibt es eine ganze Reihe an verschiedenen Rollen, vom “Guest” bis “Master”. Die Rollenverteilung auch im Team umzusetzen, erwies sich bereits nach wenigen Tagen gemeinsamer Arbeit am Sopra als sehr nützlich: Durch die Unterscheidung zwischen “Developer” und “Master” konnten nur noch zwei Mitglieder in unseren Master-Branch pushen und waren dementsprechend für das Mergen und Schließen der Issues hauptverantwortlich.

Letztlich nutzten wir im Team mit Ausnahme der sogenannten “Wall” alle Mittel, die uns Gitlab an die Hand gab. Am anschaulichsten wird das vielleicht, wenn man betrachtet, wie sich unser Workflow seit Benutzung von Gitlab geändert hat:

  • Gerade am Anfang war die intensive Arbeit mit Git für uns alle noch ziemlich neu. Das Wiki eignet sich sehr gut, um auch gemeinsam die wichtigsten Kommandos zusammenzufassen. Aber etwa auch um nochmal die Programmierkonventionen zu benennen.
  • Code wird nicht mehr per Skype oder Google Hangout unter Nutzung der Bildschirmübertragung diskutiert, sondern einfach über die Weboberfläche – und zwar gerne auch direkt mit Kommentaren im Code oder Commit.
  • Issues und Arbeitsaufträge werden nicht mehr über Rundmails verteilt, sondern einfach als neue Einträge unter Issues hinterlegt.
  • “Bei mir trat gerade ein Fehler in Deinem Modul auf.” – “Schick mir mal die Fehlermeldung per Skype”. Solche Dialoge gibt es nicht mehr. Wir nutzten Skype zwar natürlich weiter intensiv zur Teamdiskussion, lange Fehlermeldungen haben im Chat aber nichts zu suchen. Und wanderten somit in die Snippets, wo man ihnen eine Expire-Zeit zuweisen kann.
  • Jegliche Dokumente wie die Benutzerdokumentation werden auch im Gitlab hinterlegt. Und nicht wie zuvor über Dropbox verteilt.
  • Unverändert blieb jedoch die Nutzung von GoogleDocs. Für Dokumente, die sich häufig verändern, wie etwa das Projekttagebuch, ist das doch noch einfacher, da Gitlab im Gegensatz zu GitHub (noch?) nicht das Ändern von Dateien via Browser unterstützt.

Grundsätzlich gab es also nichts, was uns noch gefehlt hätte. Nervig waren hin und wieder lediglich die kleinen Aussetzer, auf die ich im nächsten Abschnitt mal kurz eingehen werde.

Limits

Auch wenn sich Gitlab und GitHub nicht nur optisch sehr ähneln, verfolgen beide Systeme eigentlich unterschiedliche Ziele: Gitlab beschränkt sich komplett auf das Hosting von privaten Repositories. Es ist also nicht möglich, Gitlab als selbst gehosteten Ersatz für Github zu nutzen, und seine Open-Source-Projekte damit zu verwalten. Klar, prinzipiell geht das natürlich auch, aber dann kann eben niemand den Code sehen. Im Gegensatz zu GitHub bietet der Klon nämlich keine Möglichkeit, auf Projekte ohne Registrierung zuzugreifen. Und neue Benutzer anlegen kann nur der Admin. Auch wenn in sehr regelmäßigen Abständen von etwa 2 Wochen ein neuer Issue an die Entwickler gerichtet wird, dies doch umzustellen, bleiben diese ihrer Linie treu und grenzen sich damit ganz klar von Github ab. Und das auch ganz explizit mit der Aussage, dass Open-Source-Projekte eben am besten zentral auf GitHub gehostet werden.

Das Bearbeiten von MergeRequests direkt über die Weboberfläche ist zwar sehr komfortabel, sollte naturgemäß aber nur bei einfachen Änderungen benutzt werden. Ohnehin prüft Gitlab, bevor es die Möglichkeit anbietet, ob es zu irgendwelchen Merge-Konflikten kommt. Doch auch wenn dem nicht so ist, habe ich Abstand davon genommen, umfangreiche Änderungen über diesen Weg zu übernehmen. Letztlich schien mir der traditionelle Weg über git merge und ggf. git mergetool doch immer noch am sichersten.

Etwas Schluckauf kann man Gitlab im Moment auch noch durch den intensiven Gebrauch von Umlauten bereiten: Commit-Messages, die Umlaute beinhalten, können mitunter dafür sorgen, dass ein ganzer Dateibaum in der Weboberfläche nicht mehr verfügbar ist. Umlaute in Dateinamen sorgen dafür, dass die ansonsten sehr hilfreiche Funktion, um ein komplettes Repository als *.tar.gz herunterzuladen, plötzlich nicht mehr funktioniert. Schade ist, dass das System in diesen Fällen keine ordentliche Fehlerseite liefert, sondern schlicht mit 404 quittiert. Und man dann händisch versuchen muss, die Sachen in der Datenbank zu korrigieren.
Generell hat die Zahl der Fehler mit jedem Update aber gut abgenommen, sodass man sich einfach auf die Vermeidung von Umlauten einlassen sollte und ein sehr gutes und stabiles System bekommt.

Screenshots

Den Teammitgliedern kann eine von vier Rollen zugewiesen werden: Guest, Reporter, Developer oder Master. Neben den Commits können auch die Branches angezeigt und vergleichen werden. Issues sehen ähnlich aus wie in GitHub. Ab Version 2.7 soll auch das Taggen möglich sein. In den master-Branch können nur Teammitglieder vom Typ "Master" mergen - und seit einigen Versionen sogar aus der Weboberfläche heraus. Im projekteigenen Wiki können etwa nochmal die Programmierkonventionen oder wie hier Git-Cheats aufgelistet werden. Das Benutzerprofil ist bislang noch spärlich, bietet aber bereits verschiedene Designs. Die Commits können direkt im Code kommentiert werden. Wie bei GitHub kann jeder Nutzer seine SSH-Keys direkt über die Weboberfläche pflegen. Projekt anlegen leicht gemacht Über den Network-Graph können Änderungen und Merges leicht nachvollzogen werden. Der Admin-Bereich kommt sehr aufgeräumt daher. Für jeden User kann einzeln eingestellt werden, wieviele Projekte er erstellen kann. Über die Snippets lassen sich bequem kurze Code-Fragmente oder Fehlermeldungen austauschen. Jeder Issue kann einem Teammitglied und Meilenstein zugewiesen werden.

Eine Demo gibt es ebenfalls auf den Seiten von Gitlab.

]]>
http://www.ioexception.de/2012/07/18/gitlab-freie-github-alternative/feed/ 3
Benachrichtigungen für die Piratenpads skripten http://www.ioexception.de/2011/12/27/benachrichtigungen-fur-die-piratenpads-skripten/ http://www.ioexception.de/2011/12/27/benachrichtigungen-fur-die-piratenpads-skripten/#comments Tue, 27 Dec 2011 12:51:43 +0000 http://www.ioexception.de/?p=1371 Die Piratenpads sind eine bequeme, kostenfreie Möglichkeit um kollaborativ an Texten zu arbeiten oder etwa Ideen zu sammeln. In letzter Zeit nutze ich den Service mit einigen anderen Leuten sehr exzessiv. Über die verschiedenen Pads in verschiedenen Teams habe ich aber inzwischen einfach den Überblick verloren. Um herauszufinden, ob es irgendwo Neuerungen gibt muss ich alles manuell abklappern und mich bei jedem einzelnen Team einloggen. Ich bin der Ansicht, dass man genau solche Aufgaben nach Möglichkeit immer automatisieren sollte.

Deswegen habe ich einen Notificationservice für die Piratenpads geschrieben. Das Skript loggt sich ein, fragt die aktuelle Version des Dokuments ab und speichert diese lokal zwischen. Beim nächsten Überprüfen (idealerweise als Cronjob) wird die lokale Kopie mit dem online-Original verglichen. Gibt es eine Differenz wird eine E-Mailbenachrichtigung mit dem diff versandt.

Um das Ganze zu Skripten habe ich node.js mit der Bibliothek request verwendet:

Request is designed to be the simplest way possible to make HTTP calls. It support HTTPS and follows redirects by default.

Tolle Sache! Ohne HTTPS-Support kommen wir eh nicht weit, bei einigen Pads ist der Zugriff auf HTTPS eingeschränkt. Außerdem muss man sich dank der Bibliothek nicht um das mühsame Parsen von “Set-Cookie” Feldern kümmern. request übernimmt die Cookies standardmäßig einfach global für zukünftige Requests.

Um die Session zu initialisieren, also sich vor dem Login einen Cookie zu holen, sieht der Code etwa so aus:

var request = require('request'); 
var base = 'https://foo.piratenpad.de';

(function initiateSession() {
	request.get(base, function (error, response, body) {
		if (!error && response.statusCode == 200) {
			login();
		}
	});
})();

Die Loginfunktion habe ich zusammengebaut, nachdem ich den kompletten Skriptablauf im Firefox durchgespielt habe und alle Requests mittels des (unheimlich praktischen) Live HTTP Headers Add-ons aufgezeichnet habe.

function login() {
	var options = {
			url: base + '/ep/account/sign-in', 
			form: {'email': 'john@doe.de', 'password' : 'mypw'}
	};
	
	request.post(options, function (err, res, body) {	
			request.get(base + '/' + acc.padId, function (err, resp, body) {
				// get the latest version of the pad document
				var linkToLatestVersion = body.match(/[\w\d\/\-\.]*(latest')/gi);
				linkToLatestVersion = linkToLatestVersion[0].replace("'", '');

				getLatestDocument(linkToLatestVersion);
			});			
		}
	);	
}

Die aktuelle Version des Dokuments lässt sich dann mit einem einfachen GET-Request abfragen:

function getLatestDocument(linkToLatestVersion) {
	request.get(base + linkToLatestVersion, function (err, resp, body) {
		var start = body.search('id="padcontent">') + 'id="padcontent">'.length;
		var end = body.search("<!-- /padeditor -->");
		var padContent = body.substring(start, end);
		
		// strip all tags and entities
		padContent = padContent.replace(/(<[^>]+>)|(&[#\w]+;)/gi, '');
		
		console.log(padContent.trim());
	});
}

Das Ganze zusammengefasst als ordentliches, sauber konfigurierbares, Projekt gibt es hier auf GitHub. Das Skript kann sehr einfach für ähnliche Aufgaben wiederverwendet werden. Als Anregung: Beispielsweise wäre es möglich das Uni Hochschulportal anzuzapfen um E-Mailbenachrichtigungen zu versenden, wenn neue Prüfungsergebnisse eingetragen sind.

Update: Ich habe noch die Möglichkeit hinzugefügt, Benachrichtigungen für Änderungen an dem Inhalt hinter einer URL zu konfigurieren (im Ordner simple-webpage/). Ich benutze das als simple Lösung für Seiten, die keinen RSS-Feed bereitstellen.

]]>
http://www.ioexception.de/2011/12/27/benachrichtigungen-fur-die-piratenpads-skripten/feed/ 0
Whitespace http://www.ioexception.de/2011/08/17/whitespace/ http://www.ioexception.de/2011/08/17/whitespace/#comments Tue, 16 Aug 2011 23:10:44 +0000 http://www.ioexception.de/?p=1133 In einigen Vorlesungen der Uni Ulm ist es üblich Programmieraufgaben der Übungen online an ein Framework zu submitten, welches die Programmierlösung automatisch bewertet. Im letzten Semester war bei einer dieser Veranstaltungen die Abgabe in Whitespace erlaubt.
Whitespace ist eine esoterische Programmiersprache. Das Außergewöhnliche an ihr ist, dass ihr Quelltext ausschließlich aus Whitespace (also Leerzeichen u.ä.) besteht. Um genau zu sein aus den drei Zeichen „Leerzeichen“, „Tabulator“ und „Zeilenumbruch“. Jegliche lesbaren Zeichen werden ignoriert. Damit lassen sich interessante Dinge anstellen, wie zum Beispiel Quelltext in anderen Texten zu verstecken.

Und wenn es erlaubt ist Aufgaben in einer so interessanten Sprache abzugeben, dann kann ich es mir natürlich nicht nehmen lassen dies auch zu tun. Ich begann also Whitespace zu lernen und war erstaunt, wie schnell man sich gute Kenntnisse in dieser Programmiersprache aneignen kann. Wer schon einmal mit einer Assembler-Sprache gearbeitet hat, sollte mit Whitespace keine Probleme haben. Innerhalb weniger Stunden hatte ich bereits einen Whitespace Disassembler (whdisasm) geschrieben gehabt, der mir Whitespace Quelltext in lesbare Mnemonics übersetzt. Disassembler ist natürlich eigentlich keine korrekte Bezeichnung für ein Programm mit dieser Funktionalität. Aber der Name klingt meiner Meinung nach einfach besser als „Whitespace-zu-Mnemonics-Konverter“ oder so. Mit dem whdisasm konnte ich die im Web existierenden Beispiele leichter analysieren und damit lernen, wie man die Sprache verwendet.

Ich schrieb einige kurze Programme. Aber das Coden mit Whitespace ist doch nicht gerade einfach (obwohl für viele Editoren Syntax Highlighting möglich ist). Also machte ich mich daran noch einen Assembler, den whasm, zu entwickeln. Auch whasm war in kurzer Zeit lauffähig. In nur zwei Tagen hatte ich also eine neue Sprache gelernt, einen Disassembler und einen Assembler, sowie einige Libraryfunktionen, geschrieben und meine Programmieraufgabe in Whitespace abgegeben.

Viele esoterische Programmiersprachen sind zwar auf den ersten Blick furchtbar abschreckend aber in Wirklichkeit sehr einfache Konstrukte. Whitespace hat zudem eine ausgezeichnete Dokumentation, die meine einzige Informationsquelle für whasm und whdisasm gewesen ist.

Aber wie funktioniert denn jetzt Whitespace?

Ich verwende ab jetzt für Leerzeichen die Abkürzung [space], für Tabulatoren [tab] und für Zeilenumbrüche [lf]. Diese Darstellung wird auch im Whitespace Tutorial so verwendet.

Ich habe bereits erwähnt, dass Whitespace eine Art Assemblersprache ist. Der Whitespace Quelltext (also die Leerzeichen, Tabs und so) werden üblicherweise interpretiert. Im Netz sind dafür leicht verschiedene Interpreter zu finden. Bestimmt gibt es auch irgendwo einen Compiler (und wenn nicht, ist das hier vielleicht ein Anstoß dafür einen zu schreiben).
Whitespace kennt zwei Arten von Speicher: Stack und Heap. Für den Stack gibt es Operationen, wie „auf den Stack pushen„, „vom Stack poppen“, „duplizieren des obersten Elements“ und einige weitere. Im Heap können Daten an beliebigen Adressen gespeichert und gelesen werden. An dieser Stelle passt es vielleicht zu erwähnen, dass Whitespace keine Datentypen kennt, wie sie sonst üblich sind. Die einzige Unterscheidung ist „Zahl“ oder „(ASCII-) Zeichen“. Druckbare Zeichen haben dabei 8 Bit. Zahlen können beliebig lang(!) sein. Eine positive Zahl beginnt mit einem [space] und eine negative mit einem [tab]. Ansonsten ist ein [space] die binäre 0 und ein [tab] die binäre 1.

Hier ein Beispielprogramm, dass zwei Zahlen aus dem Heap liest, diese addiert und das Ergebnis auf dem Bildschirm anzeigt. Ich habe für bessere Lesbarkeit statt echten Leer-/Tab-/Zeilenumbruchszeichen sichtbare Abkürzungen verwendet ;)


// Achtung! Zeilenumbrüche sind nur da, wo [lf] steht. Ich habe die Zeilen im Beispiel
// lediglich für bessere Lesbarkeit umgebrochen.
// Zuerst wollen wir zwei Zahlen vom Heap auf den Stack laden.
// Um eine Zahl zu laden, pushen wir zunächst ihre Adresse auf den Stack
[space] // das erste [space] bedeutet, dass wir den Stack manipulieren wollen
[space] // das zweite [space] sagt, dass wir die Zahl nach dem Befehl auf den Stack pushen wollen
[space][tab][tab][tab][lf]
// [space][tab][tab][tab] ist die binäre 0111 (also 7)
// das [lf] markiert das Ende des Zahlenparameters

[tab][tab] // [space] sagt, dass wir aus dem Heap lesen wollen (an der Adresse im Stack)
[space] // [tab][tab] bedeutet, dass wir auf den Heap zugreifen

// Das Selbe nochmal mit der zweiten Zahl:
[space][space][space][tab][space][space][space][lf] // diesmal die Adresse 8 (01000)
[tab][tab][space]

[tab][space] // leitet eine arithmetischen Befehl ein
[space][space] // Addition der zwei obersten Werte auf dem Stack

[tab][lf] // beginnt eine I/O Operation
[space][tab] // Bildschirmausgabe der Zahl oben auf dem Stack

[lf][lf][lf] // beendet das Programm

Das selbe Programm würde in (mit whasm übersetzbaren) Mnemonics so aussehen:

push 7
retrieve

push 8
retrieve

add
printnum

end

Fazit

Esoterische Programmiersprachen machen Spaß! Das ist zumindest meine Meinung. Ich habe in der Zeit, in der ich mich damit beschäftigt habe, einiges gelernt. Nicht nur Whitespace selbst, sondern den Umgang mit stackbasierten Sprachen oder auch, wie leicht sich solche Konstrukte parsen lassen.

Der Code für whasm und whdisasm sind auf github verfügbar. Dort habe ich auch begonnen Libraryfunktionen zu implementieren, die mit whasm in Whitespace übersetzt werden können.

]]>
http://www.ioexception.de/2011/08/17/whitespace/feed/ 0