<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
   <title>Philipps paper equivalent Blog</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/" />
   <link rel="self" type="application/atom+xml" href="http://www.innoq.com/blog/phaus/atom.xml" />
   <id>tag:www.innoq.com,2011:/blog/phaus//25</id>
   <updated>2011-01-23T13:59:26Z</updated>
   
   <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.31</generator>

<entry>
   <title>Show Build-Information in your iOS App About Panel</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2011/01/show_application_buildinformat.html" />
   <id>tag:www.innoq.com,2011:/blog/phaus//25.3539</id>
   
   <published>2011-01-23T13:44:15Z</published>
   <updated>2011-01-23T13:59:26Z</updated>
   
   <summary>Sometimes it might be useful to have an exact piece of information about what version of an app you have currently running. Especially if you have a decent Testing-Group, it is important to track the versions in which a bug...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   <category term="318" label="iOS" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Sometimes it might be useful to have an exact piece of information about what version of an app you have currently running. Especially if you have a decent Testing-Group, it is important to track the versions in which a bug appears. The goal of this post is to achieve a info panel like this in your application.
You get the Application version (from the Application Bundle), the Repository Revision and the Date of the last Commit.

<p><img alt="BuildInfo.png" src="http://www.innoq.com/blog/phaus/BuildInfo.png" width="433" height="271" /><br /><br/>Picture 1: Example Application About Dialog</p><br />

We are using here the build-in functions of subversion to update given keywords with the repository information. More about this topic <a href="http://wiki.preshweb.co.uk/doku.php?id=svn:propset">here</a>. There is also a way to use this method with git, but i did not test it yet. You may find out more about this <a href="http://stackoverflow.com/questions/62264/dealing-with-svn-keyword-expansion-with-git-svn#72874">here</a>


The first step is to create a File-Template you can import in your code, with which you can access all the necessary details:
<p >
<pre style="background-color:#dedede;font-size:small">

	#define APP_VERSION   \ 
	[[[NSBundle mainBundle] infoDictionary]   \  
	objectForKey:@"CFBundleVersion"]
	#define APP_EXECUTABLE   \ 
	[[[NSBundle mainBundle] infoDictionary]   \  
	objectForKey:@"CFBundleExecutable"]
	#define APP_NAME   \ 
	[[[NSBundle mainBundle] infoDictionary]   \  
	objectForKey:@"CFBundleName"]
	#define APP_BUILD_REVISION @"$Rev$"
	#define APP_BUILD_DATE @"$Date$"
	#define APP_LAST_AUTHOR @"$Author$" 
	
</pre>Code 1: version.h template</p><br />

The next step is to tell Subversion to replace the placeholder with the subversion values.
You can do this with setting the subversion keyword for that file.
After that, with every commit of the file "version.h" the values will be updated. 
<p>
<pre style="background-color:#dedede">

	svn propset svn:keywords 'Revision Author Date' version.h

</pre>Code 2: version.h template</p><br />

The very last step is to make sure, that "version.h" will be updated each time you make a change to your application. Assuming you build your app every time you made a change, you can use the functions, build into Xcode to force an update on "version.h". We use the trick, that every change on the propsets of "version.h" is equal to a file modification itself.
So we create a small bash script, setting the propset "build" to a new value. After that, "version.h" needs to be commited as a new version.

<p>
<pre style="background-color:#dedede">

	#!/bin/sh

	DATE=`date`
	HOST=`hostname`

	svn propset build "$HOST $DATE" Version.h

</pre>Code 3: buildUpdate.sh</p><br />

Now we need to add the run of "buildUpdate.sh" to our Build-Cycle. (Picture 2 & Picture 3).

<p><img alt="TargetSettings.png" src="http://www.innoq.com/blog/phaus/TargetSettings.png" width="172" height="106" /><br /><br/>Picture 2: Project Target Settings</p><br />
<p><img alt="RunScriptSetting.png" src="http://www.innoq.com/blog/phaus/RunScriptSetting.png" width="658" height="227" /><br /><br/>Picture 3: Insert Script Call</p><br />

After a successful commit, the file "version.h" will look something like this:

<pre style="background-color:#dedede;font-size:small">

	#define APP_VERSION   \ 
	[[[NSBundle mainBundle] infoDictionary]   \  
	objectForKey:@"CFBundleVersion"]
	#define APP_EXECUTABLE   \ 
	[[[NSBundle mainBundle] infoDictionary]   \  
	objectForKey:@"CFBundleExecutable"]
	#define APP_NAME   \
	[[[NSBundle mainBundle] infoDictionary]   \  
	objectForKey:@"CFBundleName"]
	#define APP_BUILD_REVISION @"$Rev: 1047 $"
	#define APP_BUILD_DATE @"$Date: 2011-01-21 18:53:38 +0100 (Fri, 21 Jan 2011) $"
	#define APP_LAST_AUTHOR @"$Author: phaus $"

</pre>Code 4: updated version.h</p><br />

You might modify the output (e.g. filter out the $s or reformat the date) to get a more stylish output.
]]>
      
   </content>
</entry>
<entry>
   <title>Using UIAutomation for Multilanguage iOS Applications</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2011/01/using_uiautomation_for_multila.html" />
   <id>tag:www.innoq.com,2011:/blog/phaus//25.3538</id>
   
   <published>2011-01-22T21:44:03Z</published>
   <updated>2011-01-23T13:59:57Z</updated>
   
   <summary>With the appearance of iOS 4.0 Apple introduced a new Test-Framework for automatically UI Testing: UI Automation. Based on Javascript and build-in into Instruments, UI Automation is a very useful tool during the Developing of iOS Application. A very good...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   <category term="318" label="iOS" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[With the appearance of iOS 4.0 Apple introduced a new Test-Framework for automatically UI Testing: UI Automation. Based on Javascript and build-in into Instruments, UI Automation is a very useful tool during the Developing of iOS Application.

A very good introduction in UIAutomation is <a href="http://answers.oreilly.com/topic/1646-how-to-use-uiautomation-to-create-iphone-ui-tests">here</a> and <a href="http://alexvollmer.com/posts/2010/07/03/working-with-uiautomation">here</a>.

During the development of a iOS Application, we decided to port it to iOS 4.0 and therefor use also UIAutomation for regression testing (before that we used GHUnit Tests for Component Testing - but thats another story).

As we are primarily a company dealing with web-based application, we had almost zero afford to deal with the Javascript syntax of UI Automation. But we had to deal with the fact, that we developing a dual language Application (de and en), and therefore need a possibility to test the whole UI in both languages.

If you are familiar with UI Automation, you probably know that the Framework uses the accessibility labels of your UI and also often Button Labels. So you have to deal with the actual language of the current UI Setting. But wait. There is already a valid mapping of different language to a given key. If you internationalize your application you will use so called Localizable.strings to do your language Mapping (more <a href="http://www.icanlocalize.com/site/tutorials/iphone-applications-localization-guide">here</a>).

So we just need a way to move our already existing Mapping into our UI Automation world. UI Automation supports the import of separate JavaScript Files to use your own Libraries and Settings. So i build a conversation script to translate your different Localizable.strings to JavaScript and moving all languages into one big collection.

So for example a String like this:

<pre>
	"Library" = "Bibliothek";
	"Shop" = "Kiosk";
</pre>

Will be converted to:

<pre>
	UIA.Localizables = {
	"de":{
	...
	"Library" : "Bibliothek",
	"Shop" : "Kiosk",
	...
	},
	"English":{
	}
	...
	}
</pre>	

The next step is to determine during your UIAutomation Test which language Setting you need to Load from your Localization File.
It is possible to readout some System Settings during an UIAutomation Test. The basic functions to find your current language and to read the correct language Array look like this:

<pre>
	UIA.getCurrentLang = function(){
		if(application.preferencesValueForKey("AppleLanguages")[0]  == "en")
			return "English";
		else
			return application.preferencesValueForKey("AppleLanguages")[0];
	}
	UIA.getCurrentLocalizables = function(){
		return UIA.Localizables[UIA.getCurrentLang()];
	}

	var Localizable = UIA.getCurrentLocalizables();
</pre>


The first function is necessary to capture a quirk of the recent Xcode Versions (<a href="http://www.cocoabuilder.com/archive/xcode/293712-bug-in-xcode-regarding-english-lproj.html">some people calling it a bug :-)</a> ).

So now we can just use our String within our Test-Cases.

<pre>

#import "lib/Localizables.js"

function	delay(seconds){
	UIATarget.localTarget().delay(seconds);
}

function tapTab(name){
	var window = UIATarget.localTarget().frontMostApp().mainWindow();
	window.tabBar().buttons()[name].tap();
}

var window = UIATarget.localTarget().frontMostApp().mainWindow();
tapTab(Localizable['Library']);
delay(1);
tapTab(Localizable['Shop']);
delay(7);

</pre>

I attached the conversion script to this post.
You just need to alter the source and destination folders of your i18n files and the UIAutomation-Tests directory.

<a href="http://www.innoq.com/blog/phaus/createAllTestLocalizables.php.zip">Download file</a>

]]>
      
   </content>
</entry>
<entry>
   <title>Philipps 5 mins: Graph-Fun with AJAX and Canvas</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2010/08/philipps_5_mins_graphfun_with.html" />
   <id>tag:www.innoq.com,2010:/blog/phaus//25.3537</id>
   
   <published>2010-08-14T23:27:09Z</published>
   <updated>2010-08-17T12:39:46Z</updated>
   
   <summary>I always searched for an efficient way add dynamic diagrams to a web-project without using flash or other plugin-based magic. With the support of the canvas tag element in almost all mainstream browser, i thought it would be a good...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[I always searched for an efficient way add dynamic diagrams to a web-project without using flash or other plugin-based magic.

With the support of the canvas tag element in almost all mainstream browser, i thought it would be a good time for creating a short demo how things workout.

You will need at least two Parts for this demo. First of all you will need a Source JSON feed. For this demo i just hacked together a very basis PHP script:


<pre>
&lt;?php
header('Content-type: application/json');
echo'{';
echo '"value":"' . rand(0, 60) . '"';
echo '}';
?&gt;
</pre>

The result is something like:

<pre>
{"value":"34"}
</pre>

Secondly you need a Webpage, where you want to insert your canvas element, load the data from the json feed and draw the changing values to the canvas element.

For a better performance, we will implementing pulling the data and drawing the data within two parallel cycles. The Common data Storage will be an array of 300 value (for our diagram with a width of 300px). 

We are using two additional JS Files. The first we need for creating our <a target="_BLANK" href="http://www.quirksmode.org/js/xmlhttp.html">XHTTPRequest Object and handling the response within a callback method</a>. The second script is <a target="_BLANK" href="http://www.JSON.org/js.html">for parsing the JSON Feed as a Javascript Object</a> in a safe way (an ordinary eval works, but is to unsecury).

Our main-script works in several steps:

First we initialize an array with empty elements:
<pre>

	function init(){
		for(var i=0; i < 300; i++){
			randomValues[i] = 0;
		}
	}

</pre>
<p>
This step is optional, but then you have a nice "zero line" at the beginning.
</p>
<p>
Secondly we have a method, that pushes a new value to the existing array, and drops the first entry, if the length of the array is greater than 300.
</p>
<pre>

	function addValue(arr, value){
		if(arr.push(value) > 300){
			arr.shift();
		}
	}

</pre>
<p>
The next two methods are necessary for sending our ajax-request and for handling the response in a callback method.
Basically the callback method just calls the addValue method.
</p>
<p>
The timeout variable is set to 200 ms. So the script calls our backend periodically every 200 ms and then adds a new value to our array.
<p>
<pre>

	function pullValue(){
		sendRequest('random.php',handleRandomRequest);
		setTimeout(pullValue, timeout);
	}

	function handleRandomRequest(req) {
		var text = JSON.parse(req.responseText);
		addValue(randomValues, text.value);
	}

</pre>        
<p>
The last method is for the drawing functionality:
</p>
<pre>

	function draw(){
		ctx.clearRect(0, 0, 300, 60);
		ctx.fillStyle = "rgba(101,101,101, 0.5)";
		ctx.fillRect (0, 0, 300, 60);
		ctx.lineWidth = 1;
		ctx.strokeStyle = 'blue';
		ctx.beginPath();
		ctx.moveTo(1, 60-parseInt(randomValues[0]));
		for (var i=1; i&lt;randomValues.length; i++){
			value = 60-parseInt(randomValues[i]);
			ctx.lineTo(i,value);
		}
		ctx.stroke();
		setTimeout(draw, timeout);
	}

</pre>

<p>
ctx is <a target="_BLANK" href="https://developer.mozilla.org/en/Canvas_tutorial/Basic_usage#The_Rendering_Context">a 2d context of the canvas element</a>.
On every call of the draw method, all elements of the array are painted. The first element is always the start point.
Because the canvas coordinate system has the point 0,0 in the upper left corner but the 0,0 point of our diagram should be in the lower left corner, you have to subtract the array-values from 60 to get the right drawing coordinate.
This method also runs periodically every 200 ms. But it also works for two times for pulling the data an drawing it.
</p>
<p>
<a target="_BLANK" href="https://dev.innoq.com/~phl/json-test">Here you can see the script in action</a>
</p>]]>
      
   </content>
</entry>
<entry>
   <title>creating JNI with Swig</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2010/01/creating_jni_with_swig_1.html" />
   <id>tag:www.innoq.com,2010:/blog/phaus//25.3533</id>
   
   <published>2010-01-27T23:45:15Z</published>
   <updated>2010-01-27T23:48:07Z</updated>
   
   <summary>I am currently playing around with JNI and Java due the colleagues question to make the connect features of jack-audio (http://jackaudio.org) accessible to java. There is already a javalib (http://jjack.berlios.de) with some features, there seems still some needes ones missing....</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      I am currently playing around with JNI and Java due the colleagues question to make the connect features of jack-audio (http://jackaudio.org) accessible to java.
There is already a javalib (http://jjack.berlios.de) with some features, there seems still some needes ones missing.

So i started today to have a look into SWIG (http://swig.org).

&quot;SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages.&quot;

After some hours of research i ended up with some facts:

To created yourself a Java binding to a given c/c++ Program or Library you need one or more Interface files (*.I) and swig file with all the necessary swig module descriptions.

There is an example on the swig homepage ( http://www.swig.org/Doc1.3/SWIGDocumentation.html#Introduction) to explain the workflow of SWIG.

There is a c file exmple.c:

/* File : example.c */

double  My_variable  = 3.0;

/* Compute factorial of n */
int  fact(int n) {
    if (n &lt;= 1) return 1;
    else return n*fact(n-1);
}

/* Compute n mod m */
int my_mod(int n, int m) {
    return(n % m);
}


The mapping example.i files looks as the following:

/* File : example.i */
%module example
%{
/* Put headers and other declarations here */
extern double My_variable;
extern int    fact(int);
extern int    my_mod(int n, int m);
%}

extern double My_variable;
extern int    fact(int);
extern int    my_mod(int n, int m);


As you can see, the Interface file has a similar syntax with some additional meta information.

You can now create your JNI bindings:

swig -java example.i


There are also flags for different other languages:

     -allegrocl      - Generate ALLEGROCL wrappers
     -chicken        - Generate CHICKEN wrappers
     -clisp          - Generate CLISP wrappers
     -cffi           - Generate CFFI wrappers
     -csharp         - Generate C# wrappers
     -guile          - Generate Guile wrappers
     -java           - Generate Java wrappers
     -lua            - Generate Lua wrappers
     -modula3        - Generate Modula 3 wrappers
     -mzscheme       - Generate Mzscheme wrappers
     -ocaml          - Generate Ocaml wrappers
     -octave         - Generate Octave wrappers
     -perl           - Generate Perl wrappers
     -php            - Generate PHP wrappers
     -pike           - Generate Pike wrappers
     -python         - Generate Python wrappers
     -r              - Generate R (aka GNU S) wrappers
     -ruby           - Generate Ruby wrappers
     -sexp           - Generate Lisp S-Expressions wrappers
     -tcl            - Generate Tcl wrappers
     -uffi           - Generate Common Lisp / UFFI wrappers
     -xml            - Generate XML wrappers

As a result you get three new files:

example.java
exampleJNI.java
example_wrap.c

The example_wrap.c can be used to compile the needed library file for your JNI access.
The two java Files are the basic JNI implementation:

class exampleJNI {
  public final static native void My_variable_set(double jarg1);
  public final static native double My_variable_get();
  public final static native int fact(int jarg1);
  public final static native int my_mod(int jarg1, int jarg2);
}

And a basic java example how to access these functions:

public class example {
  public static void setMy_variable(double value) {
    exampleJNI.My_variable_set(value);
  }
  public static double getMy_variable() {
    return exampleJNI.My_variable_get();
  }
  public static int fact(int arg0) {
    return exampleJNI.fact(arg0);
  }
  public static int my_mod(int n, int m) {
    return exampleJNI.my_mod(n, m);
  }
}

To get into working with SWIG i can advise the sources of the G4Java Project (http://java.freehep.org/sandbox/G4Java).
There is also a maven plugin to use SWIG from within your maven build: http://java.freehep.org/freehep-swig-plugin.

I am currently trying to create the necessary Interface files from the jack-audio sources to use them for a first run of SWIG. For python and tck you can use cmake to create these files.

      
   </content>
</entry>
<entry>
   <title>Wiederherstellen eines MacOS Festplatten-Backups mit Hilfe von DD</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/11/wiederherstellen_eines_macos_f.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3529</id>
   
   <published>2009-11-08T17:16:12Z</published>
   <updated>2009-11-08T17:20:33Z</updated>
   
   <summary>Das Festplattendienstprogramm von MacOS bietet unter einer übersichtlichen Oberfläche ein umfangreiches Tool um mit Festplatten zu arbeiten. Allerdings gibt es hier einige Probleme, welche oftmals einen Umweg über das Terminal benötigen. Ich habe das Tool dazu benutzt um eine Festplatte...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Das Festplattendienstprogramm von MacOS bietet unter einer übersichtlichen Oberfläche ein umfangreiches Tool um mit Festplatten zu arbeiten.
Allerdings gibt es hier einige Probleme, welche oftmals einen Umweg über das Terminal benötigen.

Ich habe das Tool dazu benutzt um eine Festplatte aus einem neu erworbenen Netbook zu sichern, bevor ich mit verschiedenen Linux Distributionen spiele :-).
Das war notwendig, weil diese Festplatte eine Recovery-Partition enthält, von der man dann ggf. das Windows-System wiederherstellen kann.

Das Festplattendienstprogramm ermöglicht es sehr einfach, ein Image von einem kompletten Device (einer Festplatte) zu ziehen. Hierbei werden auch gleich nicht gefüllte Bereiche ausgespart, sodass von der 160 GB Platte ein knapp 8GB großes Image übrig bleibt. Bis zu diesem Zeitpunkt befand ich mich noch in dem Glauben, dass ich das Image zu einfach wieder zurückspielen könnte.

Achja: Für das Backup habe ich die 2,5" SATA Platte ausgebaut und mit Hilfe eines USB-SATA Adapters meinem MacBook Pro zur Verfügung gestellt. 

Die Struktur der Festplatte sieht wie folgt aus:


bash-3.2# diskutil list
...
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *160.0 GB   disk1
   1:               Windows_NTFS System                  85.9 GB    disk1s1
   2:                 DOS_FAT_32                         69.6 GB    disk1s2
   3:                       0xDE                         4.5 GB     disk1s4

...

Dem unbedarften Leser scheint hier nichts besonderes aufzufallen, allerdings ist die letzte Partition vom Typ EISA-Konfiguration und kann von MacOS nicht gemountet werden. Interessanterweise ist es dem Festplattendienstprogramm aber möglich, die Partition mit in ein Gesamt-Image zu sichern, wenn man das komplette Device sichert. Dummerweise ist eine Wiederherstellung auf Device-Ebene nicht vorgesehen :-).

D.h. es ist möglich die Partition mit der (aktuellen) Windows Partition(NTFS), sowie eine weitere Partition mit Update-Daten(FAT32) wiederherzustellen, aber die eigentlich Revocery-Partition bleibt im Nirvana verschollen :-/. Weiterhin ist es hierzu notwendig, das sowohl Zielfestplatte, als auch Backup-Image die identische Partition-Struktur haben - d.h. legt ein Linux-Installer ein eigenes Partition-Schema an, so ist es nicht mehr so einfach möglich, das Backup wieder einzuspielen.

Was uns bei beiden Problemen hilft ist das Unix-Tool "dd".
Als allererstes ist es wichtig, herauszufinden, wie die beiden Devicenamen lauten. Hierzu mounten wir das Backup-Image und schließen die Festplatte wieder an den Mac an.
Danach lassen wir uns die Disk-Device auflisten:

bash-3.2# diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *200.0 GB   disk0
   1:                        EFI                         209.7 MB   disk0s1
   2:                  Apple_HFS Imotep HD               199.7 GB   disk0s2
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *160.0 GB   disk1
   1:                 DOS_FAT_32 DISK1S1                 84.9 GB    disk1s1
   2:                 Linux_Swap                         970.6 MB   disk1s3
   3:                 DOS_FAT_32                         69.6 GB    disk1s2
   4:                       0xDE                         4.5 GB     disk1s4
/dev/disk2
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *160.0 GB   disk2
   1:               Windows_NTFS System                  85.9 GB    disk2s1
   2:                 DOS_FAT_32                         69.6 GB    disk2s2
   3:                       0xDE                         4.5 GB     disk2s4




Unsere Quelle ist /dev/disk2, unser Ziel /dev/disk1. Als allererstes kopieren wir den MBR vom Image auf die Festplatte (hier ist auch die Partition-Tabelle gespeichert - man erspart sich das aufwendige Neu-Partitionieren). Der MBR befindet sich innerhalb der ersten 512k einer Festplatte.

bash-3.2# sudo dd if=/dev/disk2s1 of=/dev/disk1s1 bs=512 count=1

Nun sind wir in der Lage die beiden sichtbaren Partitionen über das Festplattendienstprogramm wiederherzustellen. Hierzu wählen wir unser Ziel an. Unter dem Tab "Wiederherstellen" ziehen wir einmal unsere Zielpartition in das Input-Feld "Ziel" und aus dem gemounteten Image die Quell-Partition in das Input-Feld "Quelle". Sollte das Programm eine Fehlermedung ausgeben, so ist es ggf. notwendig, die Partitionen erst zu deaktivieren (Partition anwählen und über die Toolbar oben deaktivieren). Nach einigen Minuten sollte das Backup eingespielt sein. Dies ist ein großer Vorteil gegenüber von "dd", weil dd die Daten sektorweise wiederherstellt (also auch Nullsektoren 1:1 überträgt), während das Festplattendienstprogramm nur die reinen Daten überträgt und Nullsektoren ausspart.

Was bleibt ist die letzte nicht-sichtbare Partition. Dies kopieren wir nun abermals per "dd". Um nicht kB-Weise zu kopieren wählen wir hier 512MB Slices:

dd if=/dev/disk2s4 of=/dev/disk1s4 bs=512m

Obwohl es nur knapp 5GB sind, nimmt der Kopiervorgang einiges an Zeit in Anspruch, sodass sich abschätzen lässt, wie zeitaufwendig ein Wiederherstellen der kompletten 160GB per "dd" wäre.

Mich hat diese Erkenntnis eine halbe Nacht gekostet :-). Vielleicht steht irgendjemand einmal vor dem gleichen Problem (z.B. sichern/wiederherstellen von reinen Linux Partitionen).




  <div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>ja,</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/07/ja.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3526</id>
   
   <published>2009-07-01T23:10:38Z</published>
   <updated>2009-07-01T23:11:17Z</updated>
   
   <summary>ich lebe noch. Sobald mir mehr einfällt, schreibe ich mal wieder was :-)....</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      ich lebe noch.
Sobald mir mehr einfällt, schreibe ich mal wieder was :-).
      
   </content>
</entry>
<entry>
   <title>VirtualBox error: fixing VDI already registered</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/05/virtualbox_error_vdi_alreade_r.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3519</id>
   
   <published>2009-05-17T13:03:40Z</published>
   <updated>2009-05-17T13:04:53Z</updated>
   
   <summary>Oftmals ist es zweckmäßig, eine Art Template-Image für eine virtuelle Maschine (VM) zu erstellen, mit welchem man eine saubere Basis erhält, auf der man Software installieren kann, speziell für die einzelne VM. Das Problem ist, dass VirtualBox in jedes VDI...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Oftmals ist es zweckmäßig, eine Art Template-Image für eine virtuelle Maschine (VM) zu erstellen, mit welchem man eine saubere Basis erhält, auf der man Software installieren kann, speziell für die einzelne VM.
Das Problem ist, dass VirtualBox in jedes VDI (virtual disk image) eine eindeutige ID schreibt, welche es verhindert, dass eine identische Copy eines Images mehrmals eingebunden wird.

<a href="http://blogs.sun.com/constantin/entry/try_out_virtualbox_on_zfs">Constantin Gonzalez hat dazu in seinem Blog eine interessante Lösung beschrieben</a>.

Ich habe das zum Anlass genommen, diesen Befehl in ein bash-script zu gießen ;-).

Hier als das Script:


<font size="2">#!/bin/sh
# Copy VDI with unique identifier
if [ $# -ne 2 ]; then
&nbsp;&nbsp;&nbsp; echo "Usage: ./copyVDI.sh &lt;VID-source&gt; &lt;VID-target&gt;"
&nbsp;&nbsp;&nbsp; exit 1
else 
&nbsp;&nbsp;&nbsp; if [ $1 == $2 ]; then
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; echo "VID-source has to be not equal to VID-target!"
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; exit 1
&nbsp;&nbsp;&nbsp; fi
&nbsp;&nbsp;&nbsp; cp $1 $2
&nbsp;&nbsp;&nbsp; dd if=/dev/random of=$2 bs=1 count=6 seek=402 conv=notrunc 
&nbsp;&nbsp;&nbsp; exit 0
fi</font>

Das Script sollte selbsterklärend sein.
Faktisch kann man hiermit eine Copy eines VDIs anlegen, welche gleichzeitig eine eindeutige ID erhält.






  <div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>GnuPG Java Wrapper API</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/03/gnupg_java_wrapper_api.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3511</id>
   
   <published>2009-03-28T18:25:57Z</published>
   <updated>2009-03-28T18:25:58Z</updated>
   
   <summary>Yaniv Yemini wrote a small GnuPG Java Wrapper API. Just had a small look over it. So to get it your version from here: http://www.macnews.co.il/mageworks/java/gnupg Here is just a small demo: import javax.swing.JOptionPane; import org.gpg.java.GnuPG; public class Loader { public...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Yaniv Yemini wrote a small GnuPG Java Wrapper API.

Just had a small look over it. 

So to get it your version from here: http://www.macnews.co.il/mageworks/java/gnupg
 Here is just a small demo:

<pre>
import javax.swing.JOptionPane;

import org.gpg.java.GnuPG;

public class Loader {

public static void main (String args[]){
	GnuPG pgp = new GnuPG ();
		
	String toolChain[] = {"sign", "clearsign", "signAndEncrypt", "encrypt", "decrypt"};
	String message = JOptionPane.showInputDialog(null, "Message you want to encrypt?", "Enter your message", JOptionPane.QUESTION_MESSAGE);
	String keyID = "0x56B69D6B";
	System.out.println("using message: "+message);
	System.out.println("using key ID: "+keyID);
	for(String tool : toolChain){
		System.out.println("running: "+tool);
		if(tool.equals("sign")){
			String passPhrase = enterPassPhrase(tool);
			pgp.sign (message, passPhrase);				
		}

		if(tool.equals("clearsign")){
			String passPhrase = enterPassPhrase(tool);
			pgp.clearSign (message, passPhrase);				
		}			
		if(tool.equals("signAndEncrypt")){
			String passPhrase = enterPassPhrase(tool);
			pgp.signAndEncrypt (message, keyID, passPhrase);				
		}
		if(tool.equals("encrypt")){
			pgp.encrypt (message, keyID);				
		}	
		if(tool.equals("decrypt")){
			String passPhrase = enterPassPhrase(tool);
			pgp.decrypt (message, passPhrase);				
		}				
		System.out.println("result: " + pgp.getGpg_result() + "\n\n");
		System.out.println("error: " + pgp.getGpg_err() + "\n\n");
		System.out.println("exit: " + pgp.getGpg_exitCode() + "\n\n");
	}
}
    
    public static String enterPassPhrase(String usage){
    	return JOptionPane.showInputDialog(null, "Please enter the Passphrase of your private Key for "+usage, "Passphrase", JOptionPane.QUESTION_MESSAGE);
    }

}
</pre>

Unforntunetally there is a Problem with decrypting a message. It is possible to decrypt the String with the gpg CI Version, but within Java it does not work. So maybe the error is on my site :-).

<div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>SortedProperties</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/03/sortedproperties_1.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3510</id>
   
   <published>2009-03-28T17:32:13Z</published>
   <updated>2009-03-28T17:34:52Z</updated>
   
   <summary>Angenommen, man braucht für ein Java Property Set ein geordnete Ausgabe - zum Beispiel um einem Übersetzer eine sortierte Liste mit zu übersetzenden String zu liefern. Man erstellt eine Klasse (zum Beispiel SortedProperties) und lässt diese von Properties erben. Bedingt...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Angenommen, man braucht für ein Java Property Set ein geordnete Ausgabe - zum Beispiel um einem Übersetzer eine sortierte Liste mit zu übersetzenden String zu liefern.

Man erstellt eine Klasse (zum Beispiel SortedProperties) und lässt diese von Properties erben.
Bedingt durch die Kapselung ist es notwendig, dass die Methoden 

private static char toHex(int nibble) ;
private String saveConvert(String theString, boolean escapeSpace);
private static void writeln(BufferedWriter bw, String s);

und Attribute

private static final char[] hexDigit;

in die neue Klasse kopiert werden müssen.

Wir überschreiben die Methode
public synchronized void store(OutputStream out, String comments)

Diese Methode ist für das eigentliche Speichern in eine Datei verantwortlich.

Der neue Inhalt entspricht bist auf eine zusätzliche Sortierung dem alten:
<pre><font size="2">
	public synchronized void store(OutputStream out, String comments)
			throws IOException {
		
		TreeMap<String, String> propTree = new TreeMap<String, String>();
		
		for (Enumeration e = keys(); e.hasMoreElements();) {
			String key = (String) e.nextElement();
			String value = (String) get(key);
			key = saveConvert(key, true);
			value = saveConvert(value, false);
			propTree.put(key, value);
		}
		BufferedWriter awriter;
		awriter = new BufferedWriter(new OutputStreamWriter(out, "8859_1"));
		if (comments != null)
			writeln(awriter, "#" + comments);
		writeln(awriter, "#" + new Date().toString());		
		Set<String> keys = propTree.keySet();
		for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {
			String key = (String) iterator.next();
			writeln(awriter, key + "=" + propTree.get(key));			
		}	
		awriter.flush();
	}
</font></pre>

Dies ist tatsächlich ein sehr einfacher Weg, um vorhandene Java-Methoden für eigene Zwecke anzupassen.

  <div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>advanced XML-Parser</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/03/advanced_xmlparser_1.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3509</id>
   
   <published>2009-03-22T02:25:06Z</published>
   <updated>2009-03-28T20:28:50Z</updated>
   
   <summary><![CDATA[Innerhalb unseres Projektes ist die Notwendigkeit entstanden, XML-Dokumente, die etwas umfangreicher als die Standard-Java-Deskriptoren sind, auf Gleichheit hin zu untersuchen. Folgende XML-Strings sind gegeben: A) &lt;items&gt; &nbsp;&nbsp;&nbsp; &lt;item name="a"&gt; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;1&lt;/value&gt; &nbsp;&nbsp;&nbsp; &lt;/item&gt; &nbsp;&nbsp;&nbsp; &lt;item name="b"&gt; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;2&lt;/value&gt;...]]></summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Innerhalb unseres Projektes ist die Notwendigkeit entstanden, XML-Dokumente, die etwas umfangreicher als die Standard-Java-Deskriptoren sind, auf Gleichheit hin zu untersuchen.
Folgende XML-Strings sind gegeben:

A)
&lt;items&gt;
&nbsp;&nbsp;&nbsp; &lt;item name="a"&gt;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;1&lt;/value&gt;
&nbsp;&nbsp;&nbsp; &lt;/item&gt;
&nbsp;&nbsp;&nbsp; &lt;item name="b"&gt;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;2&lt;/value&gt;
&nbsp;&nbsp;&nbsp; &lt;/item&gt;
&nbsp;&nbsp;&nbsp; &lt;item name="c"&gt;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;3&lt;/value&gt;
&nbsp;&nbsp;&nbsp; &lt;/item&gt;
&lt;/items&gt;

B)
&lt;items&gt;
&nbsp;&nbsp;&nbsp; &lt;item name="a"&gt;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;1&lt;/value&gt;
&nbsp;&nbsp;&nbsp; &lt;/item&gt;
&nbsp;&nbsp;&nbsp; &lt;item name="c"&gt;
 &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;3&lt;/value&gt;
 &nbsp;&nbsp;&nbsp; &lt;/item&gt;
&nbsp;&nbsp;&nbsp; &lt;item name="b"&gt;
  &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;value&gt;2&lt;/value&gt;
  &nbsp;&nbsp;&nbsp; &lt;/item&gt;
&lt;/items&gt;


Diese Untersuchung soll eine Aussagen über:
- Gleichheit: (2 Dateien enthalten das gleiche XML-Modell - sowohl in der gleichen Reihenfolgen A) , als auch in einer anderen Reihenfolge B) ).
- Veränderungen: welche Stellen sind verändert worden.

Während Forderung zwei sich noch mit einem einfachen String-Vergleich lösen lässt, ist Forderung eins nur durch das erkennen eines Modells lösbar.
Hierbei ist es notwendig, die einzelnen Knoten zu erkennen. 
Zudem sind die zu-untersuchenden XML-Dateien &gt; 5 MBb sodass viele - professionelle XML-Tools hier streiken müssen und mit Speicherfehlern aufgeben.

Der Ansatz der hier vorgestellt wird, setzt sich aus drei Stufen zusammen:
<ol>
<li> SAX-basiertes Parsen der Datei und einlesen in eine Datenbank (aus Performance-Gründen wird hier <a href="http://www.h2database.com">H2</a> als <a href="http://www.h2database.com/html/features.html#memory_only_databases">inMemory Datenbank</a> genutzt).
</li>
<li> Um schnelle Vergleiche zu ermöglichen wird ein Modell benutzt, welches u.a. auch in ZFS angewendet wird: <a href="http://www.sun.com/bigadmin/features/articles/zfs_part1.scalable.jsp#checksumming">Erkennen von Veränderungen anhand von Prüfsummen</a>.
Was bei ZFS dazu benutzt wird, um Änderungen innerhalb des Dateisystems zu erkennen, soll hier dazu dienen, Unterschieder zwischen zwei XML-Modellen schnell und zuverlässig zu erkennen.
Hierzu wird für jeden Knoten eine Prüfsumme berechnet. Diese leitet sich jeweils aus den Prüfsummen seiner Kindsknoten, dem Inhalt seiner Attribute und dem Wert des Knotens ab.
Momentan wird über diesen Gesamt-String ein SHA1-Hash gebildet. Eine weitere Prüfsumme wird benötigt, um den Knoten innerhalb des Modells zu lokalisieren (wir verwenden hier den XML-Path+Knotennummer):
<ol>
<li>
<font size="2">&lt;--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Hash des Pfades&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --&gt;
3a52ce780950d4d969792a2559cd519d7ee8c727&nbsp; 
./</font>items<font size="2">/</font>item<font size="2">/value &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
</font></li>
<li>
<font size="2">&lt;--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Hash des Knotens item &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --&gt;
481e6ff69a8402231a3b9c6e46a7fef4f09adbb3
hash von: "item", attribute "name=b", hash von "value"</font>
</li></ol></li>
<li>Da sowohl eine Aussage über Unterschied im sortierten Zustand - die Reihenfolge der (Kinder-)Knoten ist wichtig - als auch im unsortierten Zustand (Die Reihenfolge der Kinder-)Knoten ist egal,
wird vor dem Berechnen des Hashes des Kindes eines Knotens, die Kinder einmal unsortiert und einmal sortiert als Basis für den SHA1-Hash genommen.</li></ol>


Momentan ist das Datenmodell soweit vollständig, die Knotenwerden beim Parsen in die Datenbank eingelesen. Dieser Vorgang wird momentan noch hinsichtlicher Dauer und Speicherverbrauch optimiert. Auch eine aussagekräftige Fortschrittanzeige sollte eingebaut werden. Danach muss der Algorithmus zum Erkennen der unterschiedlichen Stellen implementiert werden.
Als letztes sollen diese Unterschiede in einer übersichtlichen und - für große Dokumente - gut navigierbaren GUI angezeigt werden.<div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>sophisticated Backups mit Rsync Part II</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/02/sophisticated_backups_mit_rsyn_1.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3502</id>
   
   <published>2009-02-22T23:37:11Z</published>
   <updated>2009-02-22T23:39:45Z</updated>
   
   <summary>Version 3.1 Features: Logs werden nun gzip komprimiertwöchentliche Backups aus der vorherigen Woche werden tar.gz komprimiert Download startbackup-3.1.sh Blogged with the Flock Browser...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Version 3.1

Features:

<ul><li>Logs werden nun gzip komprimiert</li><li>wöchentliche Backups aus der vorherigen Woche werden tar.gz komprimiert
</li></ul>  

<a href="http://www.innoq.com/blog/phaus/startbackup-3.1.sh">Download startbackup-3.1.sh</a>

<div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>Das Problem mit der Abstraktion der Arbeit</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/02/das_problem_mit_der_abstraktio.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3498</id>
   
   <published>2009-02-17T11:12:27Z</published>
   <updated>2009-02-17T11:13:35Z</updated>
   
   <summary>Aus aktuellem Anlass möchte ich mich hier einem Thema widmen, welches möglicherweise jeden betrifft. Es geht um die Tatsache, dass man um sich herum Projekte und Arbeit sucht, welche sich über die Zeit ansammelt und mit der Zeit liegen bleibt....</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      Aus aktuellem Anlass möchte ich mich hier einem Thema widmen, welches möglicherweise jeden betrifft. Es geht um die Tatsache, dass man um sich herum Projekte und Arbeit sucht, welche sich über die Zeit ansammelt und mit der Zeit liegen bleibt. Es gibt viele Theorien und Methoden, wie man dieser Situation begegnen soll. Meistens geht es um - mehr oder weniger - hippe Methoden mit noch trendigeren Namen (z.B. GettingThingsDone). Sucht man nach dem Thema Arbeitsmethoden oder Zeitmanagement, finden sich ganze Bücherregale mit Titeln, unterschiedlicher Ausrichtung.

Dennoch habe ich mir persönlich die Frage gestellt, wieso man viele Dinge anfängt, nach einiger Zeit zurückblickt und zu dem Ergebnis kommt, dass man doch zum aktuellen Zeitpunkt weit von einem (dem) gesteckten Ziel entfernt ist, welches man eigentlich hat erreichen wollen. Ich spreche hier nicht von den typischen Kandidaten wie &quot;Ich sollte mal wieder den Keller aufräumen&quot;, oder auch &quot;Irgendwie sollte ich mal wieder die Steuern machen :-(&quot;. Es geht mir hier um die vielen kleineren und größeren Projekte, die man sich (und hin und wieder auch zusammen anderen Mitmenschen) vor langer Zeit gestellt hat und die trotz vieler Möglichkeiten daniederliegen und meist über das erste Planungsstadium nicht hinweg gekommen sind.

Ich möchte nachfolgend ein paar Punkte nenne, die ich an mir selber (als quasi Betroffener), hin und wieder aber auch bei anderen (als unbeteiligter Beobachter), bemerkt habe:


   1. Titelgebend und meiner Meinung nach einer der seltsamsten Gründe - Abstrahierung des Probemes:
      Ich habe lange Zeit überlegt, ob Abstrahierung überhaupt der richtige Begriff ist. Letztendlich ist damit gemeint, dass man ab einem gewissen Punkt erkennt, dass man das gesetzte Ziel rein theoretisch sicher und zu 100% erreichen kann. Dies mag zum einen Daraus resultieren, dass man in dem Arbeitsumfeld schon einiges an Erfahrung gesammelt hat, zum anderen auch daran, dass man im Beruf oder in anderen Situationen schon schwierigere/komplexere Dinge gelöst hat. So werden viele Aufgaben vor sich her geschoben und im Team werden simple, unerfreuliche, langweilige, aber notwendige Aufgaben an Unerfahrende Helfer abgeschoben, weil das eigene Genie sich für höheres berufen fühlt. Während man selber also nur mit dem Kopf die Dinge löst und selber fest der Meinung ist, dass die Verwirklichung der eigenen Ideen nur noch Detailfragen aufwerfen wird, wird man hinterher von dem langsamen Fortschritt und einer fehlerhaften Basis überrascht werden.
   2. Das &quot;Ich mache lieber alles selbst, weil es sonst falsch ist, oder zu lange dauert&quot; - Problem.
   3. &quot;Keine Zeit - das mache ich dann mal, wenn ich (Frei-)Zeit habe&quot; / &quot;sobald X eintritt, mache ich das mal&quot;
   4. &quot;Lasst uns die Wikipedia nachbauen&quot;
   5. &quot;Das sieht so simpel aus - das kann ich auch&quot;
      
   </content>
</entry>
<entry>
   <title>instant jruby &amp; derby enviroment für eine RoR Anwendung</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2009/02/instant_jruby_derby_enviroment.html" />
   <id>tag:www.innoq.com,2009:/blog/phaus//25.3497</id>
   
   <published>2009-02-17T11:04:04Z</published>
   <updated>2009-02-17T11:11:53Z</updated>
   
   <summary>Als angestammter Java-Entwickler geht es mir oftmals schwer von der Hand, einer Ruby on Rails (RoR) Anwendung mit relativ wenig Auffand eine brauchbare Laufzeit-Umgebung zu bieten. Normalerweise sollte das OS (MacOS 10.5.6) alles Brauchbare bieten. So ist oftmals ein Rails...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Als angestammter Java-Entwickler geht es mir oftmals schwer von der Hand, einer Ruby on Rails (RoR) Anwendung mit relativ wenig Auffand eine brauchbare Laufzeit-Umgebung zu bieten.
Normalerweise sollte das OS (MacOS 10.5.6) alles Brauchbare bieten. So ist oftmals ein Rails installiert und auch das (standardmäßig genutzte) SQlite 3 ist vorhanden.

Dennoch sind es oftmals Plugins (spezielle Rails Version / spezielle gems), welche einen zusätzlichen Aufwand benötigen.
Nicht zu vergessen, dass RoR nicht auf allen Systemen vorinstalliert ist und dementsprechend ein interessierter Entwicklung von einem Out-of-the-Box Erlebnis weit entfernt ist.
Sehen wir den Tatsachen ins Auge... die Wahrscheinlichkeit eine installierte JVM vorzufinden ist (noch?) deutlich höher, als eine Lauffähige Ruby-Installation.
Was liegt also näher, als die benötigte Umgebung auf Java fußen zu lassen.

Hierzu werden verwendet:

    * jRuby in Version 1.1.5 (http://jruby.codehaus.org)
    * Derby-DB in Version 10.4.2.0 (http://db.apache.org/derby)
    * weiterhin wird eine installierte JVM (>1.5) vorrausgesetzt



Alles weitere wird mit Hilfe von shell-Scripten bewerkstelligt. Wobei momentan nur Unix-Scripte benutzt werden. Eine Portierung auf Windows sollte aber nur eine Sache von Minuten sein.

Es liegt eine RoR-Anwendung in einem Entwicklungs-Status vor. Diese wurde bisher in einem Netbeans-Enviroment mit einer SQlite-DB betrieben.

Das Verzeichnis ist folgendermaßen aufgebaut:

ROOT
    |
    |- microblog  (dies ist unsere RoR-Anwendung)
    |   
    |- derby (derby-installtion - es werden jeweils das bin und lib Verzeichnis benötigt)
    |    |-bin
    |    |-lib
    |
    |- jruby (jruby-installtion - es werden jeweils das bin und lib Verzeichnis benötigt)
    |    |-bin
    |    |-lib
    |
   
Das Hauptproblem besteht darin, dass alle benötigten gems in das entsprechende Unterverzeichnis installiert werden müssen.
Weiterhin muss die Derby-DB mit dem entsprechenden Rake-Task auf mit der aktuellen Schema-Datei instanziiert werden.
Zuletzt sollen die vorhandenen User-Daten in die Derby-DB eingefügt werden.

Punkt 1)
Anpassen der database.yml
Wir nutzen weiterhin eine jdbc-Connection. Allerdings ändert sich der Treiber auf den der Derby-DB:

database.yml
<pre>
    development:
      adapter: jdbc
      driver: org.apache.derby.jdbc.ClientDriver
      url: jdbc:derby://localhost/microblog_development;create=true
      encoding: utf8
      pool: 5
      username: microblog
      password: microblog
      host: localhost
</pre>

Punkt 2)
Export der alten DB-Daten:
Wir benutzen hierzu das Tool sqlitebrowser (http://sqlitebrowser.sourceforge.net) und erzeugen uns so einen SQL-Dump der alten SQLite-DB. Wir benutzen hierbei nur die SQL-Inserts für den User-Import. Diese speichern wir in die Datei:

    microblog/db/microblog.sql

Punkt 3)
Für den Import erstellen wir einen Rake-Task:

microblog/lib/tasks/sql-import.rake
<pre>
namespace :microblog do
  desc 'Import old SQL Data'
  task :sqlimport => :environment do
  dbConn = ActiveRecord::Base.establish_connection :development
  sql = File.open("db/microblog.sql").read
  sql.split(';').each do |sql_statement|
    dbConn.connection.execute(sql_statement)
  end
  puts "imported user data '#{Time.now}' "  
  end
end
</pre>

Punkt 4)
Erstellen des Setup-Scriptes:
Folgende Schritte sind notwendig:

   1. setzen aller benötiger Verzeichnisse
   2. installieren aller benötigter gems
   3. starten des Derby-DB-Servers
   4. Rake db:migrate
   5. import der alten Daten
   6. beenden des Derby-DB-Servers

Das Script sieht wie folgt aus:

jruby-setup.sh
<pre>
#!/bin/sh

BASE_DIR=`pwd`
CP=".:$BASE_DIR/jruby/lib/*:$BASE_DIR/derby/lib/derbyclient.jar"
JAVA_OPTS="-Djdbc.drivers=org.apache.derby.jdbc.EmbeddedDriver"
JRUBY="$BASE_DIR/jruby/bin/jruby"

DERBY_HOME=`cd derby && pwd`
export DERBY_HOME=$DERBY_HOME
cd $BASE_DIR

echo "setting up jgems..."
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem update --system
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem install jruby-openssl --no-rdoc --no-ri
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem install -v=2.2.2 rails --no-rdoc --no-ri
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem install activerecord-jdbc-adapter activerecord-jdbcderby-adapter --no-rdoc --no-ri

echo "starting derby..."
$BASE_DIR/derby/bin/startNetworkServer &

echo "setting up derby..."
cd microblog
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/rake db:migrate
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/rake microblog:sqlimport
cd $BASE_DIR

echo "stopping derby..."
$BASE_DIR/derby/bin/stopNetworkServer &
</pre>

Es ist zu erwähnen, dass es notwendig ist, jeweils auf die entsprechende jRuby-Installation zu verweisen.
Weiterhin benötigt jRuby den entsprechenden derbyClientDriver, welcher in die (von jRuby später verwendete) JAVA_OPTS-Variabel eingetragen wird.
Ebenfalls musst der Classpath soweit angepasst werden, dass sowohl jRuby, als auch Derby über die notwendigen Bibliotheken verfügen.
Als letztes ist noch erwähnenswert, dass die beiden Rake-Tasks jeweils aus dem App-Verzeichnis ausgeführt werden.

Punkt 5)
Das Start-Script.
Letzendlich sind auch zum eigentlichen Betrieb des Servers Anpassungen notwendig, da auch hier die jRuby-Instanz mit den verwendeten gems benutzt werden sollen.
Das Script sieht wie folgt aus:

run.sh
<pre>
#!/bin/sh
BASE_DIR=`pwd`
CP=".:$BASE_DIR/jruby/lib/*:$BASE_DIR/derby/lib/derbyclient.jar"
JAVA_OPTS="-Djdbc.drivers=org.apache.derby.jdbc.EmbeddedDriver"
JRUBY="$BASE_DIR/jruby/bin/jruby"
export BASE_DIR=$BASE_DIR
export JRUBY=$JRUBY

DERBY_HOME=`cd derby && pwd`
export DERBY_HOME=$DERBY_HOME
cd $BASE_DIR


echo "starting derby..."
$BASE_DIR/derby/bin/startNetworkServer &

echo "setting up derby..."
cd microblog
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/rake db:migrate

echo "starting microblog"
$BASE_DIR/jruby/bin/jruby $BASE_DIR/microblog/script/server

echo "stopping derby..."
$BASE_DIR/derby/bin/stopNetworkServer &
</pre>

Es entspricht also einer abgespeckten Variante des Setup-Scriptes. Hierbei wird auch immer ein db:migrate aufgerufen, für den Fall, dass sich die DB-Struktur in der Zwischenzeit geändert haben sollte.

Punkt 6)
Auslieferung ;-).
Derby und jRuby belegen knapp 80 MB sodass es notwendig ist, die Dateigröße für den Transport zu verringern.
Zuallererst sollten die benötigten gems am besten immer online bezogen werden, sodass man hier ein paar MB sparen kann.
Weiterhin benutzen wir Jar um die vorhandenen Dateien auf ein 13 MB-Archiv zu packen.

Die veränderten Scripte sehen wie folgt aus:

Zuerst das Script, welches die vorhandenen Dateien packt:

pack.sh
<pre>
#!/bin/sh
find . -name '*.DS_Store' -type f -delete
jar -cvf statusQ-runtime.jar derby/ jruby/ run.sh pack.sh microblog/db/microblog.sql microblog/lib/tasks/sql-import.rake
rm -R jruby
rm -R derby
rm run.sh
rm microblog/db/microblog.sql
rm microblog/lib/tasks/sql-import.rake
rm pack.sh
</pre>

Und nun das geänderte jruby-setup.sh Script, welches vor dem eigentlichen Setup noch für das Entpacken aller Dateien verantwortlich ist:

jruby-setup.sh
<pre>
#!/bin/sh
jar -xvf statusQ-runtime.jar
rm -R META-INF
chmod +x run.sh
chmod +x setup.sh
chmod +x pack.sh
chmod +x jruby/bin/jruby
chmod +x derby/bin/startNetworkServer
chmod +x derby/bin/stopNetworkServer

BASE_DIR=`pwd`
CP=".:$BASE_DIR/jruby/lib/*:$BASE_DIR/derby/lib/derbyclient.jar"
JAVA_OPTS="-Djdbc.drivers=org.apache.derby.jdbc.EmbeddedDriver"
JRUBY="$BASE_DIR/jruby/bin/jruby"

DERBY_HOME=`cd derby && pwd`
export DERBY_HOME=$DERBY_HOME
cd $BASE_DIR

echo "setting up jgems..."
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem update --system
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem install jruby-openssl --no-rdoc --no-ri
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem install -v=2.2.2 rails --no-rdoc --no-ri
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/jgem install activerecord-jdbc-adapter activerecord-jdbcderby-adapter --no-rdoc --no-ri

echo "starting derby..."
$BASE_DIR/derby/bin/startNetworkServer &

echo "setting up derby..."
cd microblog
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/rake db:migrate
$BASE_DIR/jruby/bin/jruby $BASE_DIR/jruby/bin/rake microblog:sqlimport
cd $BASE_DIR

echo "stopping derby..."
$BASE_DIR/derby/bin/stopNetworkServer &
</pre>

Als nächstes sollte die Scripte auf Windows portiert werden.
Weiterhin wäre es interessant, die Derby/jRuby Binaries jeweils direkt online zu beziehen.]]>
      
   </content>
</entry>
<entry>
   <title>sophisticated Backups mit Rsync</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2008/11/sophisticated_backups_mit_rsyn.html" />
   <id>tag:www.innoq.com,2008:/blog/phaus//25.3488</id>
   
   <published>2008-11-23T18:38:09Z</published>
   <updated>2008-11-23T18:39:56Z</updated>
   
   <summary>Backups sind wichtig. Jeder der einmal vor einer kaputten, ratternden Festplatte gesessen hat, weiß wie frustrierend das Wissen ist, alle seine Daten ins informationstechnische Nirwana entschwinden zu sehen. Ist ein Backup der persönlichen Daten noch mit relativ geringem Aufwand möglich...</summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[Backups sind wichtig.
Jeder der einmal vor einer kaputten, ratternden Festplatte gesessen hat, weiß wie frustrierend das Wissen ist, alle seine Daten ins informationstechnische Nirwana entschwinden zu sehen.
Ist ein Backup der persönlichen Daten noch mit relativ geringem Aufwand möglich (so es denn regelmäßig veranstaltet wird), so wird ein Backup eines Server-Systems in vielen Dingen anspruchsvoller.

Dies fängt damit an, dass reinen Datenmengen oftmals das Vielfache eines Ein-Benutzer-Systems betragen. Okay in heutige Zeit, wo ein jeder zig GBs an Musik und Videos auf dem Rechner hat, muss man das natürlich relativieren.

In diesem Beitrag möchte ich ein Bash-Script vorstellen, welches folgende Dinge leistet:


&nbsp; 1. Sichern von N-Hosts über RSYNC

&nbsp; 2. Sichern von N-Verzeichnissen auf jedem Host
&nbsp; 3. Inkrementelles Sichern (es werden nur Unterschiede gesichert/übertragen)
&nbsp; 4. Zusammenführen von täglichen (inkrementellen) Backups zu wöchentlichen Vollbackups.
&nbsp; 5. die aktuellen Backups sind jeweils unter einem Hardlink "latest" erreichbar.

Nachfolgend erfolgt eine Beschreibung über den Ablauf des Scriptes.
Es wird Momentan durch eine Sicherung von 3 Serversystemen und ca. 20 GB an reinen Nutzdaten getestet.
&nbsp;
&nbsp; 1. Stelle sicher, dass alle notwendigen Verzeichnisse vorhanden sind:&nbsp; 
&nbsp; &nbsp; 1. Verzeichnis "log" für log-Dateien der Sicherungen&nbsp; 
&nbsp; &nbsp; 2. Verzeichnis "backup" für die eigentlichen Sicherungen
&nbsp;&nbsp; &nbsp; &nbsp; 1. Je Backupquelle ein Verzeichnis
&nbsp; &nbsp; &nbsp;&nbsp; 2. Innerhalb jedes Quellverzeichnisses ein Verzeichnis für tägliche und eines für wöchentliche Sicherungen
&nbsp; 2. Stelle sicher, dass alle Config-Dateien vorhanden sind:
&nbsp; &nbsp; 1. sources.txt mit den Namen der zu sichernden Hosts/Verzeichnisse
&nbsp; &nbsp; 2. exclude.txt mit den Dateien, welche nicht gesichert werden sollten (z.B. Thumbs.db)
&nbsp; &nbsp; 3. innerhalb jedes Quellverzeichnisses eine Datei namens src-root.txt (mit dem ROOT der Sicherung) 
&nbsp; &nbsp; &nbsp; &nbsp; und src-folders.txt (mit den einzelnen Verzeichnissen)

Der Aufbau des Root-Verzeichnisses sieht nun wie folgt aus. &nbsp;&nbsp;&nbsp;&nbsp; 
&nbsp; ROOT
&nbsp; &nbsp; &nbsp; |
&nbsp; &nbsp; &nbsp; |-log
&nbsp; &nbsp; &nbsp; |-sources.txt
&nbsp; &nbsp; &nbsp; |-exclude.txt
&nbsp; &nbsp; &nbsp; |-backup
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |-host1.de
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |-host2.de
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |-src-root.txt
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |-src-folders.txt
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |-daily
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |-weekly

Ein Beispiel für eine sources.txt sieht wie folgt aus:

&nbsp; host1.de
&nbsp; host2.de

Ein Beispiel für eine exclude.txt sieht wie folgt aus:

&nbsp; Thumbs.db
&nbsp; .DS_Store

Eine src-root.txt könnte so aussehen:

&nbsp; user@host1.de:

Eine src-folders.txt könnte so aussehen:

&nbsp; /home
&nbsp; /etc
&nbsp; /var/log
&nbsp; /var/www


Ablauf der Sicherung:
Generiere Log-Datei.
Iteriere über alle Quellen aus sources.txt
Überprüfe ob alle Quellen Verzeichnisse zum Sichern enthalten.
Überprüfe ob lokal Verzeichnisse für die Sicherung existieren - wenn nicht lege sie an.
Erstelle tägliches Backup:
&nbsp; Existiert ein vorheriges Backup, so sichere nur die Änderungen.
&nbsp; Dies passiert mit folgendem Befehl:
&nbsp; 
&nbsp; &nbsp; rsync -zrva --exclude-from=exclude.txt --link-dest=&lt;hardlink-zu-altem-backup&gt;&nbsp; &lt;sourcefolder&gt;/ &lt;backupfolder&gt;/daily/&lt;tag&gt;/&lt;zu-sicherndes-Verzeichnis&gt;

&nbsp; Hierbei werden alle Benutzerrechte mitgesichert.

&nbsp; Setzte Hardlink vom letztem täglichen Backup auf aktuelles tägliches Backup.

&nbsp; Überprüfe ob Sonntag ist ( date +%w = 0)
&nbsp; Wenn ja, synchronisiere letztes tägliches Backup mit wöchentlichem (neuen) Backup.

&nbsp; Setzte Hardlink vom letztem wöchentlichen Backup auf aktuelles wöchentliches Backup.
&nbsp; Lösche alle täglichen Backups.
&nbsp; Setzte Hardlink vom letztem täglichen Backup auf aktuelles wöchentliches Backup.

So werden maximal 7 incrementelle tägliche Backups erstellt und pro Jahr 52 wöchentliche.

Das Backup-Script ist im Anhang zu finden :-).

<a href="http://www.innoq.com/blog/phaus/startbackup.sh">Download file</a>


  <div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>
<entry>
   <title>subversion problem: svn: Can&apos;t move &apos;xyz/.svn/tmp/entries&apos; to &apos;xyz/.svn/entries&apos;: Operation not permitted</title>
   <link rel="alternate" type="text/html" href="http://www.innoq.com/blog/phaus/2008/07/subversion_problem_svn_cant_mo.html" />
   <id>tag:www.innoq.com,2008:/blog/phaus//25.3477</id>
   
   <published>2008-07-11T01:09:01Z</published>
   <updated>2008-07-11T01:09:03Z</updated>
   
   <summary><![CDATA[ &nbsp;&nbsp;&nbsp; svn: Can't move 'xyz/.svn/tmp/entries' to 'xyz/.svn/entries': Operation not permitted Diese Fehler sprang mir grade entgegen, nachdem ich mal wieder ein svn up machen wollte. Nach genauerer Betrachtung, stellte ich dann fest, dass xyz/.svn/entries nur Lesesrechte hat. Ein &nbsp;&nbsp;&nbsp;...]]></summary>
   <author>
      <name>Philipp Haussleiter</name>
      <uri>http://innoq.com/blog/phaus</uri>
   </author>
   
   
   <content type="html" xml:lang="en" xml:base="http://www.innoq.com/blog/phaus/">
      <![CDATA[<font size="2"><span style="font-style: italic;">    &nbsp;&nbsp;&nbsp; svn: Can't move 'xyz/.svn/tmp/entries' to 'xyz/.svn/entries': Operation not permitted</span></font>

Diese Fehler sprang mir grade entgegen, nachdem ich mal wieder ein <span style="font-style: italic;">svn up</span> machen wollte.
Nach genauerer Betrachtung, stellte ich dann fest, dass <span style="font-style: italic;">xyz/.svn/entries </span>nur Lesesrechte hat.

Ein 
<font size="2">&nbsp;&nbsp;&nbsp; sudo chmod 777 </font><span style="font-style: italic;"><font size="2">xyz/.svn/entries </font>
</span>half leider auch nichts:
<font size="2">&nbsp;&nbsp;&nbsp; chmod: ./<span style="font-style: italic;">xyz/.svn/entries</span>: Operation not permitted</font>

Das fand ich dann schon ziemlich dreist irgendwie :-D.
Letztendlich half mir ein:

<font size="2">
&nbsp;&nbsp;&nbsp;<span style="font-style: italic;"> cp xyz/.svn/entries </span><span style="font-style: italic;">xyz</span><span style="font-style: italic;">/.svn/entries2</span><br style="font-style: italic;" /><span style="font-style: italic;">&nbsp;&nbsp;&nbsp; sudo rm </span><span style="font-style: italic;">xyz</span><span style="font-style: italic;">/.svn/entries</span><br style="font-style: italic;" /><span style="font-style: italic;">&nbsp;&nbsp;&nbsp; cp </span><span style="font-style: italic;">xyz</span><span style="font-style: italic;">/.svn/entries2 </span><span style="font-style: italic;">xyz</span><span style="font-style: italic;">/.svn/entries</span><br style="font-style: italic;" /><span style="font-style: italic;"><span style="font-style: italic;">&nbsp;&nbsp;&nbsp; sudo chmod 777&nbsp; </span></span><span style="font-style: italic;">xyz</span></font><span style="font-style: italic;"><span style="font-style: italic;"><font size="2">/.svn/entries</font>

</span></span>
Was zwar auf dem ersten Blick ein wenig aufwendig erscheint, aber in Form eines shell-scriptes angenehm die Arbeit übernimmt.
Allerdings weiß ich jetzt immer noch nicht WARUM dieser Fehler kommt.

Letztendlich habe ich mich ja daran gewöhnt, dass subversion meint, . hätte ein Lock....
Wenn mir da jemand mal erklären könnte, ob ich da was falsch mache oder wie... das fände ich mal toll :-).<span style="font-style: italic;"><span style="font-style: italic;">
</span>
</span>
  <div class="flockcredit" style="text-align: right; color: #CCC; font-size: x-small;">Blogged with the <a href="http://www.flock.com/blogged-with-flock" style="color: #999; font-weight: bold;" target="_new" title="Flock Browser">Flock Browser</a></div>]]>
      
   </content>
</entry>

</feed>

