<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>grahl/ch</title>
        <link>https://www.grahl.ch/</link>
        <description>Recent content on grahl/ch</description>
        <language>en-us</language>
        <lastBuildDate>Mon, 02 Jan 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://www.grahl.ch/index.xml" rel="self" type="application/rss+xml" />
        <item>
            <title>Tolino needs better notes support</title>
            <link>https://www.grahl.ch/2023/01/02/tolino-needs-better-notes-support/</link>
            <pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2023/01/02/tolino-needs-better-notes-support/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/tolino.png&#34; alt=&#34;Highlights in a book in the Tolino webreader&#34;&gt;&lt;/p&gt;
&lt;p&gt;A while ago I traded my Kindle in for a Tolino Vision 6 to no longer give money to that awful company and I’m overall satisfied.&lt;/p&gt;
&lt;p&gt;The hardware is excellent, importing books worked fine, and I haven’t found a book yet via &lt;a href=&#34;https://www.orellfuessli.ch&#34;&gt;Orell Füssli&lt;/a&gt; that they did not have.&lt;/p&gt;
&lt;p&gt;The only bad aftertaste is that international titles often have family sharing restrictions, which means I cannot share them with my partner, and they would have to re-buy (booh). But that’s a manageable trade-off compared to notes management.&lt;/p&gt;
&lt;h2 id=&#34;notes-and-highlights&#34;&gt;Notes and highlights&lt;/h2&gt;
&lt;p&gt;Notes, or much more often just highlights, are a function that basically any e-reader supports and that should be a core use-case with a robust implementation similar to page navigation, book purchase or backlight adjustment.&lt;/p&gt;
&lt;p&gt;Tolino supports highlights and collects them in a „My notes“ book on your e-reader. This book is not available in the „webreader“. However, when you open a book in the webreader, in which you highlighted passages (on the e-reader), you see the same highlights and can open and edit them one by one. You do not have a download or export button, but you can see them. This means that &lt;strong&gt;they have full cloud syncing of your notes,&lt;/strong&gt; they exist in their database and &lt;strong&gt;Tolino can make them available how they choose.&lt;/strong&gt; They chose to make them inaccessible.&lt;/p&gt;
&lt;h2 id=&#34;exporting-the-notestxt&#34;&gt;Exporting the notes.txt&lt;/h2&gt;
&lt;p&gt;Okay, but how about that „My notes“ book? Could I get that from the e-reader and avoid manual work that way? Actually, yes, the device contains a notes.txt file with a well formatted reference to the title a predictable delimiter, nice.&lt;/p&gt;
&lt;p&gt;So let’s open it on a computer and attach the reader via USB like it’s 2005. And then nothing happens on a Mac since they chose MTP, for heaven’s sake. &lt;a href=&#34;https://mytolino.com/faq/im-having-trouble-pairing-my-tolino-ereader-with-my-computer-what-do-i-do/&#34;&gt;Their own documentation&lt;/a&gt; says that „&lt;em&gt;This enables users to continue to use the tolino e-reader while it is connected to the computer&lt;/em&gt;.“ Cool, nobody needs that and they are not offering a legacy option. So let’s install unwanted third-party software for which they so eloquently point out that they cannot take responsibility for.&lt;/p&gt;
&lt;p&gt;And there is a &lt;code&gt;notes.txt&lt;/code&gt; file and there are…not all highlights in there. It turns out that the device file &lt;strong&gt;only&lt;/strong&gt; contains notes made on the device since the last factory reset and does not sync from other devices including reading in the iOS app. So the book I have, which has 12 highlights, now has 2 entries in notes.txt. Fantastic. So, basically, my only option is to script something in the webreader to get the information out.&lt;/p&gt;
&lt;h2 id=&#34;feature-requests&#34;&gt;Feature requests&lt;/h2&gt;
&lt;p&gt;It would be a fairly simply feature to add a button in the webreader and let users get their notes. They already have it in their database and a TXT file would suffice.&lt;/p&gt;
&lt;p&gt;JSON or CSV would be nice but plain text would work just fine. It would be cool if they’d offer me the option for a webhook to integrate a highlight in real-time into my notes application of choice, but I’m really just looking to get a few lines of text here without copy and pasting every single item.&lt;/p&gt;
&lt;p&gt;With the current implementation it’s faster for me to photograph the page and use OCR on my phone to get a quote out of a book for reference. That can’t be it.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Comparing Lando performance on M1 Pro</title>
            <link>https://www.grahl.ch/2021/11/21/comparing-lando-performance-on-m1-pro/</link>
            <pubDate>Sun, 21 Nov 2021 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2021/11/21/comparing-lando-performance-on-m1-pro/</guid>
            <description>&lt;p&gt;&lt;em&gt;This post was updated 2021-11-27 with additional numbers from the experimental
feature &amp;ldquo;Use the new Virtualization framework.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&#34;https://www.grahl.ch/2021/05/13/lando-mac-performance&#34;&gt;previous post&lt;/a&gt;
we saw how the file system mounts can have a huge impact on overall Docker
performance. Since then Apple has introduced their new Macbook Pro on their M1
architecture and significant work has happened so that there are now images
available for Lando.&lt;/p&gt;
&lt;h2 id=&#34;apples-and-oranges&#34;&gt;Apples and oranges&lt;/h2&gt;
&lt;p&gt;When running Docker Desktop images they can actually be built for an Intel
architecture but performance issues are likely to arise there. For the time
being this includes the default Lando database backend from bitnami/mysql.&lt;/p&gt;
&lt;p&gt;You can see this in Docker Desktop with the &amp;ldquo;amd64&amp;rdquo; warning label or
follow &lt;a href=&#34;https://stackoverflow.com/a/66942433&#34;&gt;this post&lt;/a&gt; from Stack Overflow to
find the image architecture.&lt;/p&gt;
&lt;p&gt;Unfortunately the official &lt;code&gt;mariadb&lt;/code&gt; is not a drop-in replacement for
&lt;code&gt;bitnami/mariadb&lt;/code&gt; so it can&amp;rsquo;t be used a service override, or if you do know what
it would need consider adding it
to &lt;a href=&#34;https://github.com/lando/lando/issues/2688&#34;&gt;The Great Armification #2688&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;speed-comparison&#34;&gt;Speed comparison&lt;/h2&gt;
&lt;p&gt;As previously we use PHPUnit to run a test suite, this time a bit smaller with
19 tests and 227 assertions on PHPUnit 9.5 with PHP 7.4. Numbers are averaged
across 2-3 tests. (Since some suggest PHP 8 might make a difference: Numbers
seemed identical on M1.)&lt;/p&gt;
&lt;p&gt;A notable difference from the last comparison is that we are using SQLite in
memory as our test backend for the database to avoid any measurement error since
the I/O bottleneck is the largest contributor to any performance issues for
Docker on macOS.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Test execution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;i5 4 core Linux Docker&lt;/td&gt;
&lt;td&gt;47s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i9 8 core macOS native&lt;/td&gt;
&lt;td&gt;24s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i9 8 core Docker&lt;!-- raw HTML omitted --&gt;*&lt;!-- raw HTML omitted --&gt;&lt;/td&gt;
&lt;td&gt;32s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i9 8 core Docker&lt;!-- raw HTML omitted --&gt;†&lt;!-- raw HTML omitted --&gt;&lt;/td&gt;
&lt;td&gt;1m54s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M1 Pro 10 core macOS native&lt;/td&gt;
&lt;td&gt;28s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M1 Pro 10 core Docker&lt;!-- raw HTML omitted --&gt;*&lt;!-- raw HTML omitted --&gt;&lt;/td&gt;
&lt;td&gt;59s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M1 Pro 10 core Docker&lt;!-- raw HTML omitted --&gt;†&lt;!-- raw HTML omitted --&gt;&lt;/td&gt;
&lt;td&gt;3m54s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M1 Pro 10 core Docker experimental&lt;!-- raw HTML omitted --&gt;*&lt;!-- raw HTML omitted --&gt;&lt;/td&gt;
&lt;td&gt;36s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M1 Pro 10 core Docker experimental&lt;!-- raw HTML omitted --&gt;†&lt;!-- raw HTML omitted --&gt;&lt;/td&gt;
&lt;td&gt;1m46s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;*) with Drupal core/contrib/vendor in exclude &lt;!-- raw HTML omitted --&gt;
†) without exclude&lt;/p&gt;
&lt;p&gt;Of course this one testsuite is highly subjective, not generally applicable and
only looks at burst performance but that&amp;rsquo;s also something quite common for
developers to need. Iterate, rerun, and not wanting to wait.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s noteworthy that on M1 nearly all tests ran slower, even native, so there is
likely some optimization in the runtime still possible in future versions of PHP
on ARM, since from the commonly published single-core scores the M1 Pro
&lt;em&gt;should&lt;/em&gt; be significantly faster.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Lots of improvements have gone into Docker on ARM and it&amp;rsquo;s generally usable now
with Lando but Docker I/O performance is still a hurdle.&lt;/p&gt;
&lt;p&gt;I recommend upgrading to at least Docker 4.2.0 and using the experimental
virtualization framework, otherwise you have significant and unnecessary
overhead from qemu-system-aarch64. Others have reported slow I/O with it but at
least in this version it&amp;rsquo;s the opposite for me.&lt;/p&gt;
&lt;p&gt;Working with the M1 Pro is &lt;em&gt;much&lt;/em&gt; nicer given how much the fan kicked on in
Intel models, let alone running on battery, but it&amp;rsquo;s not the leapfrogging that
other disciplines are seeing in their workloads with PHP.&lt;/p&gt;
&lt;p&gt;One last thing to note is that these tests ran sequentially. The tests above can
be processed in parallel for example with
&lt;a href=&#34;https://github.com/paratestphp/paratest&#34;&gt;ParaTest&lt;/a&gt; but in my tests the
differences between ARM and Intel only amplify there with 338 tests natively
coming in at &lt;em&gt;1m59s&lt;/em&gt; on Intel with 16 threads while AARCH64 comes in at around
&lt;em&gt;4m30s&lt;/em&gt; (experimental &lt;em&gt;03m03s&lt;/em&gt;).&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Tweaking a slow Lando site</title>
            <link>https://www.grahl.ch/2021/05/13/tweaking-a-slow-lando-site/</link>
            <pubDate>Thu, 13 May 2021 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2021/05/13/tweaking-a-slow-lando-site/</guid>
            <description>&lt;p&gt;Docker for Mac is notoriously slow, especially in terms of file syncing. It has
gotten better in the past but it’s still the platform underperforming the most.&lt;/p&gt;
&lt;p&gt;Recent versions of Lando have introduced some optimizations we can make use of
to improve performance. To know if that actually benefits us, let’s measure.&lt;/p&gt;
&lt;h5 id=&#34;establishing-a-baseline&#34;&gt;Establishing a baseline&lt;/h5&gt;
&lt;p&gt;First, we need a fairly stable scenario, I choose a module in a project with a
decent amount of Drupal kernel tests  (70 tests, 1685 assertions). The number
of tests should iron out any variations of other things on the host influencing
the test.&lt;/p&gt;
&lt;p&gt;Executing that on a mediocre i7 running Linux gives us a baseline number without
any filesystem overhead:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Time: 5.11 minutes, Memory: 8.00 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Beefy i9 with lots of RAM but macOS:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Time: 18.71 minutes, Memory: 8.00 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ouch. So let’s try to use excludes.&lt;/p&gt;
&lt;h5 id=&#34;excludes&#34;&gt;Excludes&lt;/h5&gt;
&lt;p&gt;We can tell Lando to sync certain directories only once, they will then no
longer be copied to and from the container. If you are working in a team, where
others don&amp;rsquo;t have this bottleneck you can put that into &lt;code&gt;.lando.local.yml&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;excludes:
  - docroot/core
  - docroot/libraries
  - docroot/modules/contrib
  - docroot/themes/contrib
  - docroot/themes/custom/your_theme/node_modules
  - vendor
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that changes in these folders require a &lt;em&gt;rebuild&lt;/em&gt; every time you change
something in there, or you execute the command locally as well as in the
container for consistency.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Time: 8.34 minutes, Memory: 8.00 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It turns out that this particular project consisted of 238k files due to
multiple node_modules folders and that 221k of those files could be excluded
with little effort.&lt;/p&gt;
&lt;p&gt;Just to see what would happen I added the entire repository to the container
in an exclude and reran the test, it came in at &lt;code&gt;3.76 minutes&lt;/code&gt;. Not practical
for everyday use but showing that with that bottleneck removed, the i9 is now
outperforming the other system.&lt;/p&gt;
&lt;h5 id=&#34;disabling-the-home-mount&#34;&gt;Disabling the home mount&lt;/h5&gt;
&lt;p&gt;I’m not a fan of the fact that Lando mounts my home directory into the
container, apart from performance, that just doesn’t belong there and I’m
much happier not having my SSH keys in there.&lt;/p&gt;
&lt;p&gt;I have made a fairly trivial change to the CLI you can use to disable this:
&lt;a href=&#34;https://github.com/grahl/cli/pull/1&#34;&gt;Remove home mount by grahl · Pull Request #1 · grahl/cli · GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This should help with sluggishness and system load overall. I did not expect it
to have a measurable impact when PHPUnit wasn&amp;rsquo;t looking in &lt;code&gt;/user&lt;/code&gt; but it seems
to have helped:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Time: 6.16 minutes, Memory: 8.00 MB
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id=&#34;removing-unnecessary-app-mounts&#34;&gt;Removing unnecessary app mounts&lt;/h5&gt;
&lt;p&gt;I have not done extensive tests on this but by default every service gets the
&amp;ldquo;app&amp;rdquo; mount. That’s unnecessary, you can disable it with: &lt;code&gt;app_mount: disabled&lt;/code&gt;&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Daily shutdown script for Timingapp</title>
            <link>https://www.grahl.ch/2021/03/24/daily-shutdown-script-for-timingapp/</link>
            <pubDate>Wed, 24 Mar 2021 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2021/03/24/daily-shutdown-script-for-timingapp/</guid>
            <description>&lt;p&gt;When I&amp;rsquo;m in the middle of work I often tend to overlook how long I&amp;rsquo;ve already
worked on any given day, especially when working from home during Covid where I
mostly don&amp;rsquo;t have fixed start or end dates, like so many of us.&lt;/p&gt;
&lt;p&gt;To avoid having my overtime balance increase unintentionally by working half
an hour here and there I&amp;rsquo;ve created a script for myself based on the excellent
&lt;a href=&#34;https://timingapp.com/&#34;&gt;Timing&lt;/a&gt; to see when I&amp;rsquo;ve worked about 7.5h to
allow me to begin to shut down my work for the day.&lt;/p&gt;
&lt;p&gt;You can do that with a small Applescript by looking for the total time tracked
today in seconds and then creating a notification. There&amp;rsquo;s probably a more
efficient way than running this command every minute and just comparing number
of seconds, but it seems to do the trick.&lt;/p&gt;
&lt;p&gt;In addition to the notification I also force dark mode to nudge myself a bit
more proactively.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-applescript&#34; data-lang=&#34;applescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt; application &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TimingHelper&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; scripting support available &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Scripting support requires a Timing Expert license. Please contact support via https://timingapp.com/contact to upgrade.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt; application &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TimingHelper&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; usageData &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; time summary between current date &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; current date
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; total &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; overall total &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; usageData
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	 &lt;span style=&#34;color:#75715e&#34;&gt;# work day	8 * 60 * 60: 	28&amp;#39;800s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	 &lt;span style=&#34;color:#75715e&#34;&gt;# threshold 7.5 * 60 * 60:	27&amp;#39;000s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; total &lt;span style=&#34;color:#f92672&#34;&gt;≥&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;27000&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; total &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;27060&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		display notification &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;You have worked 7.5h, begin shutting down.&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Timing alert&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sound&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Frog&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt; application &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;System Events&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt; appearance preferences
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; dark mode &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	delete usageData
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now add an agent to ~/Library/LaunchAgent such as the one shown below like
com.example.timing_monitor.plist and enable it with
&lt;code&gt;launchctl load com.example.timing_monitor.plist&lt;/code&gt; and you should be good to go.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!DOCTYPE plist PUBLIC &amp;#34;-//Apple//DTD PLIST 1.0//EN&amp;#34; &amp;#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;#34;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;plist&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;version=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.example.timing_monitor&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Program&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/bin/osascript&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;osascript&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/your/path/your script.scpt&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartInterval&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;60&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
        </item>
        
        <item>
            <title>Sharing Drupal&#39;s public file storage</title>
            <link>https://www.grahl.ch/2019/12/20/sharing-drupals-public-file-storage/</link>
            <pubDate>Fri, 20 Dec 2019 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2019/12/20/sharing-drupals-public-file-storage/</guid>
            <description>&lt;p&gt;In a client project we recently had the challenge of not having persistent file
storage available for public/private files across multiple instances.&lt;/p&gt;
&lt;p&gt;The only realistic solution, at the moment, is using a module such as &lt;a href=&#34;https://www.drupal.org/project/s3fs&#34;&gt;S3 File System&lt;/a&gt; to employ a different storage backend. The S3 module for
Drupal isn&amp;rsquo;t perfect but let&amp;rsquo;s look at more of why this is even difficult.&lt;/p&gt;
&lt;h2 id=&#34;drupals-public-storage-structure&#34;&gt;Drupal&amp;rsquo;s public storage structure&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ls -l sites/default/files
drwxrwxr-x 2 www-data www-data 20480 Dec 20 13:57 css
drwxrwxr-x 2 www-data www-data 16384 Dec 20 13:56 js
drwxrwxr-x 3 www-data www-data  4096 Jul 28  2018 media
drwxrwxr-x 3 www-data www-data  4096 Dec 20 13:56 php
drwxrwxr-x 9 www-data www-data  4096 Jun 24 21:29 styles
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This minimal example already gives us four distinct behaviors which should
really be addressed through distinct storage solutions. Unfortunately we are
currently stuck with one for historic reasons. I&amp;rsquo;ll address css/js at the end,
since it&amp;rsquo;s the tricky one.&lt;/p&gt;
&lt;h3 id=&#34;filesmedia-or-any-upload-folder&#34;&gt;files/media (or any upload folder)&lt;/h3&gt;
&lt;p&gt;You can define arbitrary folders in Drupal&amp;rsquo;s Field UI to manage uploads coming
in from end users (even though that&amp;rsquo;s risky and should probably be in private),
as well as images, videos, etc. from content editors.&lt;/p&gt;
&lt;p&gt;We need this to be synced across all instances so this is something where we
absolutely need something like an S3 bucket or NFS storage.&lt;/p&gt;
&lt;h3 id=&#34;filesphp&#34;&gt;files/php&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m assuming that you are not using config folders in here (keep them in your
repo) but rather have Twig generating caches in php/cache.&lt;/p&gt;
&lt;p&gt;This is a pure cache which could just as well live in a temp directory.
End users do not receive its data. If files are deleted in that folder they get
regenerated on-demand without a manual cache clear. Nothing to do here, it&amp;rsquo;s
correct on all instances.&lt;/p&gt;
&lt;h3 id=&#34;filesstyles&#34;&gt;files/styles&lt;/h3&gt;
&lt;p&gt;This folder is basically a cache, one we want to persist if at all possible, but
if it were to vanish and be empty, we would have no problem. It would simply
take a bit more processing when incoming requests to them are generating assets
on the fly (like php/twig does).&lt;/p&gt;
&lt;p&gt;It would be nice if this were available on a shared storage but it&amp;rsquo;s not
absolutely necessary. It should persist in some form since always recomputing
all images is not really acceptable but that&amp;rsquo;s going to vary depending on what your
infrastructure makes available on instantiation of additional instances.&lt;/p&gt;
&lt;h3 id=&#34;filescss--filesjs&#34;&gt;files/css &amp;amp; files/js&lt;/h3&gt;
&lt;p&gt;During cache rebuilding (i.e. &lt;code&gt;drush cr&lt;/code&gt;) the asset dumper writes the relevant
assets to disk. Afterwards they can be served directly by the web server
without involving PHP.&lt;/p&gt;
&lt;p&gt;This process makes sense insofar as that we need everything bootstrapped to know
which assets to build. It works mostly fine with the S3 module unless you change
buckets while having to regenerate caches and have messy metadata (e.g.
prod ➡ dev).&lt;/p&gt;
&lt;p&gt;Unfortunately these files &lt;strong&gt;cannot&lt;/strong&gt; be regenerated on-demand. This means that if
I deploy and the cache is rebuild on instance 1 (assuming a change in assets)
the end user will get a 404 if it&amp;rsquo;s requested from instance 3.&lt;/p&gt;
&lt;p&gt;At this point there does not seem to be a separate shorthand available to just
dump the assets, so per instance one would need to &lt;code&gt;drush cr&lt;/code&gt;, which is not an
acceptable penalty.&lt;/p&gt;
&lt;p&gt;Thus, we need this on a persistent storage as well. Or do we?&lt;/p&gt;
&lt;p&gt;There has been some work on aggregation in core: &lt;a href=&#34;https://www.drupal.org/node/1014086&#34;&gt;Race conditions, stampedes and cold cache performance issues with css/js aggregation (#1014086)&lt;/a&gt;.
However, &lt;a href=&#34;https://www.drupal.org/project/advagg&#34;&gt;advagg&lt;/a&gt; promises us to do just
that in terms of lazy-loading. Without file I/O so it might even be worthwhile
when you do have a shared storage but no CDN.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used advagg in the past just to get a bit better of an asset preprocessor
but clearly this module provides much more essential features and should
probably be on your list of standard performance optimizations.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Linkdump July 2019</title>
            <link>https://www.grahl.ch/2019/07/31/linkdump-july-2019/</link>
            <pubDate>Wed, 31 Jul 2019 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2019/07/31/linkdump-july-2019/</guid>
            <description>&lt;p&gt;Noteworthy links, I probably posted them on Twitter, too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://caniuse.com/#search=prefers-color-scheme&#34;&gt;Can I use prefers-color-scheme?&lt;/a&gt;  Yes you can, and you should. (Just added it to this blog ✌)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://boringtechnology.club/&#34;&gt;Choose boring technology&lt;/a&gt; «We should generally pick the smallest set of tech that covers our problem domain, and lets us get the job done»&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://b612-font.com/&#34;&gt;B612 is an highly legible open source font family designed and tested to be used on aircraft cockpit screens.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nngroup.com/articles/popups/&#34;&gt;Popups: 10 Problematic Trends and Alternatives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
        <item>
            <title>Linkdump May 2019</title>
            <link>https://www.grahl.ch/2019/05/31/linkdump-may-2019/</link>
            <pubDate>Fri, 31 May 2019 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2019/05/31/linkdump-may-2019/</guid>
            <description>&lt;p&gt;Noteworthy links, I probably posted them on Twitter, too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.liip.ch/de/blog/sending-lots-of-mail-with-drupal&#34;&gt;Sending lots of mail with Drupal&lt;/a&gt; &lt;strong&gt;My post on our company&amp;rsquo;s blog&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://chrome.google.com/webstore/detail/drupal-issue-chrome/hkgdkgbnhjmnplfpcdjhonlgilklepnm?hl=en&amp;amp;gl=US&#34;&gt;Renders links to Drupal.org issues based on issue status&lt;/a&gt; (Chrome extension)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/docs/8/api/render-api/the-drupal-8-render-pipeline&#34;&gt;The Drupal 8 render pipeline&lt;/a&gt; (in all its glory)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://semver.mwl.be/#!?package=madewithlove%2Felasticsearcher&amp;amp;version=%5E0.5.2&amp;amp;minimum-stability=stable#%3Fpackage=drupal%2Fcore&amp;amp;version=~8.6.0&amp;amp;minimum-stability=stable&#34;&gt;Packagist semver checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://postmeritocracy.org/&#34;&gt;The Post-Meritocracy Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://events.drupal.org/seattle2019/sessions/building-intuitive-admin-usability-forgotten-end-user&#34;&gt;Building an Intuitive Admin: Usability for the Forgotten End-User&lt;/a&gt; Very good overview on backend UX and what the site builder needs to do to make the customer happy.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing-for-projects&#34;&gt;How to prepare for Drupal 9&lt;/a&gt; You can tell d.o to check your project for deprecated code automatically.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://events.drupal.org/seattle2019/sessions/drupal-admin-ui&#34;&gt;Drupal Admin UI&lt;/a&gt; See where Drupal&amp;rsquo;s UI is going and what Claro will bring.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.echidna.ca/new-search-overrides-module&#34;&gt;Search Overrides&lt;/a&gt; A Search API module to override specific terms&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.jetbrains.com/phpstorm/2019/03/phpstorm-2019-1-beta/&#34;&gt;PHPStorm 2019.1&lt;/a&gt; Twig breakpoints (!)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://glamanate.com/blog/writing-better-drupal-code-static-analysis-using-phpstan&#34;&gt;Writing better Drupal code with static analysis using PHPStan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.google.com/web/tools/lighthouse/audits/noopener&#34;&gt;Links to cross-origin destinations are unsafe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://newrelic.com/devops/how-to-reduce-mttr&#34;&gt;Reducing MTTR the Right Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nngroup.com/articles/footers/&#34;&gt;Footers 101: Design Patterns and When to Use Each&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://vihart.com/fifty-fizzbuzzes/&#34;&gt;Fifty Fizzbuzzes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://rockitagile.com/2018/12/18/splitting-user-stories/&#34;&gt;Splitting user stories&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
        <item>
            <title>Linkdump December 2018</title>
            <link>https://www.grahl.ch/2018/12/31/linkdump-december-2018/</link>
            <pubDate>Mon, 31 Dec 2018 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2018/12/31/linkdump-december-2018/</guid>
            <description>&lt;p&gt;Noteworthy links, I probably posted them on Twitter, too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=zGw_xKF47T0&#34;&gt;The Container Operator&amp;rsquo;s Manual - Velocity NY 2018&lt;/a&gt; Video&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.liip.ch/en/blog/entity-explorer-your-trusty-helper&#34;&gt;Entity Explorer, your trusty helper 🐕&lt;/a&gt; I wrote a small debugging module, check it out on our company blog.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.inkandswitch.com/slow-software.html&#34;&gt;On Slow Software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nngroup.com/articles/wishlist-or-cart/&#34;&gt;Shopping Cart or Wishlist? Saving Products for Later in Ecommerce&lt;/a&gt; You should persist carts.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/sharkdp/bat&#34;&gt;&lt;code&gt;bat&lt;/code&gt; is a great way to improve your &lt;code&gt;cat&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mathiasbynens/small&#34;&gt;Smallest possible syntactically valid files of different types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.previousnext.com.au/blog/how-keep-your-drupal-8-contributed-modules-ready-drupal-9&#34;&gt;How to keep your Drupal 8 contributed modules ready for Drupal 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.liip.ch/en/blog/how-content-drives-conversion&#34;&gt;How content drives conversion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nngroup.com/articles/technology-myths/&#34;&gt;Technology Myths and Urban Legends&lt;/a&gt; «When users don’t clearly understand how systems function, they develop unique (and often incorrect) theories to explain their experiences.»&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/node/3001224&#34;&gt;Revisions tab is now visible with one draft&lt;/a&gt; d.o Change record&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://inside.omnifocus.com/james-dempsey&#34;&gt;What’s The Forecast?&lt;/a&gt; OmniFocus 3 fixes some long outstanding issues such as having to use fake due dates.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/blog/drupal-8-6-0&#34;&gt;Drupal 8.6.0 is available&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/drupalorg/blog/developer-tools-initiative-part-5-gitlab-partnership&#34;&gt;Developer Tools Initiative - Part 5: Announcing our Migration&lt;/a&gt; Drupal&amp;rsquo;s getting Gitlab.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://entwickler.de/online/php/php-7-4-typed-properties-579862443.html&#34;&gt;PHP 7.4 erhält Typed Properties: Auf dem Weg zum typsicheren PHP?&lt;/a&gt; Typed properties in PHP 7.4 (German).&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dev.to/ambroselittle/the-top-3-ways-to-fail-at-interviewing-software-developers-17me&#34;&gt;The Top 3 Ways to Fail at Interviewing Software Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.utilitylog.com/full-page-screenshot-chrome/&#34;&gt;How to take full page screenshot in Chrome without extensions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
        <item>
            <title>Linkdump June 2018</title>
            <link>https://www.grahl.ch/2018/06/30/linkdump-june-2018/</link>
            <pubDate>Sat, 30 Jun 2018 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2018/06/30/linkdump-june-2018/</guid>
            <description>&lt;p&gt;Noteworthy links, I probably posted them on Twitter, too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dri.es/when-should-we-release-drupal-9&#34;&gt;When should we release Drupal 9?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/dinedal/textql&#34;&gt;Execute SQL against structured text like CSV or TSV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/project/moderation_note&#34;&gt;Moderation Note&lt;/a&gt; Module to provide annotations in Drupal Content Moderation.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/vickyharp/status/736228835046219779&#34;&gt;Tweet&lt;/a&gt; The senior dev opens a usability bug.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.invisionapp.com/inside-design/web-animation-ux/&#34;&gt;5 examples of web animation done right&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pagefair.com/blog/2018/granular-gdpr-consent/&#34;&gt;GDPR consent design: how granular must adtech opt-ins be?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://2bits.com/articles/when-memcached-slows-your-drupal-site-performance.html&#34;&gt;When Memcached Slows Your Drupal Site&amp;rsquo;s Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://abe-winter.github.io/plea%27s/help/2018/02/11/slack.html&#34;&gt;Slack is the opposite of organizational memory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.lullabot.com/articles/drupal-8-release-planning-in-the-enterprise&#34;&gt;Drupal 8 Release Planning in the Enterprise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://chromatichq.com/blog/be-promiscuous-drushs-corequickdrupal&#34;&gt;Be Promiscuous with Drush&amp;rsquo;s core-quick-drupal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nngroup.com/articles/plain-language-experts/&#34;&gt;Plain Language Is for Everyone, Even Experts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://leanpub.com/ansible-for-devops&#34;&gt;Ansible for DevOps&lt;/a&gt; Book&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.drupal.org/node/2938193&#34;&gt;New option to hide untranslatable field widgets&lt;/a&gt; Drupal change record&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dev.to/isaacdlyman/developer-driven-development&#34;&gt;Developer-driven development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
        <item>
            <title>Demystifying Apple&#39;s USB-C charging</title>
            <link>https://www.grahl.ch/2017/06/10/demystifying-apples-usb-c-charging/</link>
            <pubDate>Sat, 10 Jun 2017 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2017/06/10/demystifying-apples-usb-c-charging/</guid>
            <description>&lt;p&gt;Apple sells three different models of USB-C chargers and includes them respectively with their three Macs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt; Charger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Macbook&lt;/td&gt;
&lt;td&gt; 29W through 14.5V at 2A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Macbook Pro 13&#39;&#39;&lt;/td&gt;
&lt;td&gt; 61W through 20.2V at 3A (and fallback 9V at 3A)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Macbook Pro 15&#39;&#39;&lt;/td&gt;
&lt;td&gt;87W through 20.2V at 4.3A (and fallback 9V at 3A)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So there are now several USB-PD (Power Delivery) available from Apple, at three different voltages. In nearly all cases the higher wattage should be better but there are interesting differences.&lt;/p&gt;
&lt;h2 id=&#34;macbook-2016&#34;&gt;Macbook (2016)&lt;/h2&gt;
&lt;p&gt;The Macbook supports charging at 20.2V and 14.5V respectively. However, the larger charger doesn&amp;rsquo;t actually help this model, since it draws less power at the higher voltage to equal the ~28W of the stock charger:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Charger&lt;/th&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;29W model&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.grahl.ch/img/mb_29w.jpg&#34; alt=&#34;Macbook charging with 29W charger&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61W model&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.grahl.ch/img/mb_61w.jpg&#34; alt=&#34;Macbook charging with 61W charger&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I&amp;rsquo;ve also tested a 5V 3A power bank and the Macbook can also charge at that voltage, though only giving it 15W.&lt;/p&gt;
&lt;h2 id=&#34;macbook-pro-13-2016&#34;&gt;Macbook Pro 13&amp;rsquo;&amp;rsquo; (2016)&lt;/h2&gt;
&lt;p&gt;No surprise here that the Macbook Pro charges faster with the charger that was delivered with it but it&amp;rsquo;s important to note that you can charge it with the smaller one just fine and there is a significant difference in size and weight (approx. 120g vs 200g), which makes the 29W model a great alternative for business travel.&lt;/p&gt;
&lt;p&gt;However, if the battery isn&amp;rsquo;t fully charged and you are putting the machine under heavy load, it will complain constantly that there isn&amp;rsquo;t enough to charge the laptop (charging mode can toggle on and off).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Charger&lt;/th&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;29W model&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.grahl.ch/img/mbp_29w.jpg&#34; alt=&#34;Macbook Pro charging with 29W charger&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61W model&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.grahl.ch/img/mbp_61w.jpg&#34; alt=&#34;Macbook Pro charging with 61W charger&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;ipad-pro-129-2015&#34;&gt;iPad Pro 12.9&amp;rsquo;&amp;rsquo; (2015)&lt;/h2&gt;
&lt;p&gt;The iPad Pro is an interesting outlier since not only does it support the 5.2V profile but also the 9V and 14.5V profile but it cannot do the 20.2V.&lt;/p&gt;
&lt;p&gt;This means that the iPad can actually be charged faster with the smaller charger:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Charger&lt;/th&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;29W model&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.grahl.ch/img/ipad_29w.jpg&#34; alt=&#34;iPad Pro charging with 29W charger&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61W model&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://www.grahl.ch/img/ipad_61w.jpg&#34; alt=&#34;iPad Pro charging with 61W charger&#34;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I did not have a 87W charger at hand to play around with and presumably it could still charge at least the Macbook Pro faster but I don&amp;rsquo;t think the additional cost will actually have much of an impact.&lt;/p&gt;
&lt;p&gt;My takeaway from the comparison is that the cross-device compatibility is great and I&amp;rsquo;ll likely only travel with the 29W model and use the 61W when stationary for the Macbook Pro.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Migrating to BLT - Part 2</title>
            <link>https://www.grahl.ch/2017/06/05/migrating-to-blt-part-2/</link>
            <pubDate>Mon, 05 Jun 2017 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2017/06/05/migrating-to-blt-part-2/</guid>
            <description>&lt;p&gt;OK, so  you have your site locally running after &lt;a href=&#34;https://www.grahl.ch/2017/05/26/migrating-to-blt-part-1/&#34;&gt;Part 1&lt;/a&gt;, now it&amp;rsquo;s time to deploy it. As before I&amp;rsquo;m basing this on BLT version 8.8.3.&lt;/p&gt;
&lt;h2 id=&#34;deployment-basics&#34;&gt;Deployment basics&lt;/h2&gt;
&lt;p&gt;As shown in the last post, you don&amp;rsquo;t need much else besides &lt;code&gt;blt deploy&lt;/code&gt; and as the manual states you can combine multiple commands as such to push directly to the remote repository defined in your project.yml:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;blt deploy -Ddeploy.branch=&amp;quot;master&amp;quot; -Ddeploy.commitMsg=&amp;quot;Your message&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can run this locally and the target repository now should contain your built site.&lt;/p&gt;
&lt;p&gt;I personally prefer to push a tagged branch but as of now one can only push tags without branches, meaning you&amp;rsquo;d be in detached head. Check &lt;a href=&#34;https://github.com/acquia/blt/issues/1483&#34;&gt;this issue&lt;/a&gt; for more information and choose either branch or tag for now.&lt;/p&gt;
&lt;p&gt;Tip: Make sure that your CI server has a recent Git version, or force pushing might give &lt;a href=&#34;https://github.com/acquia/blt/issues/1532&#34;&gt;interesting&lt;/a&gt; results. (It&amp;rsquo;s automatically checked in the next release 😎)&lt;/p&gt;
&lt;h2 id=&#34;automating-it&#34;&gt;Automating it&lt;/h2&gt;
&lt;p&gt;I have all my projects on a Gitlab server with Gitlab CI, so naturally I was looking for a way to do the CI integration without the already supported platforms, such as Travis. If you run &lt;code&gt;blt ci:travis:init&lt;/code&gt; the resulting Travis-file contains basically all the information you need.&lt;/p&gt;
&lt;p&gt;Here for example I&amp;rsquo;ve taken some setup information from the before_install phase and adapted it to for a minimal example, which is triggered on certain tags.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;stages:
  - build
  - deploy

make-release:
  stage: build
  script:
    - composer self-update
    - composer validate --no-check-all --ansi
    - composer install
    - vendor/bin/blt validate:all
    - vendor/bin/blt deploy -Ddeploy.branch=&amp;#34;master-build&amp;#34; -Ddeploy.commitMsg=&amp;#34;Release $CI_COMMIT_REF_NAME&amp;#34;
  only:
    - /^release/.*$/

prod-deployment:
  stage: deploy
  variables:
    LOGIN: &amp;#39;user@host&amp;#39;
    APP_PATH: &amp;#39;/var/www/directory&amp;#39;
  script:
    - ssh -T $LOGIN &amp;#34;cd $APP_PATH &amp;amp;&amp;amp; git pull&amp;#34;
    - ssh -T $LOGIN &amp;#34;cd $APP_PATH &amp;amp;&amp;amp; vendor/bin/blt setup:update&amp;#34;
  only:
    - /^release/.*$/
  dependencies:
    - make-release
  environment:
      name: production
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;caveats-and-tips&#34;&gt;Caveats and tips&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I like to make a database update at the beginning of the deploy stage and usually set the Environment Indicator to the release number via_ &lt;code&gt;drush sset environment_indicator.current_release $VAR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Have a look at vendor/acquia/blt/scripts/travis to see see what would be required to run all tests, which is somewhat out of scope of this tutorial but definitely recommended.&lt;/li&gt;
&lt;li&gt;This example assumes that you&amp;rsquo;d initially set up your prod environment manually and have added the relevant settings in local.project.yml and local.settings.php.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;using-configuration-split&#34;&gt;Using configuration split&lt;/h2&gt;
&lt;p&gt;If you run &lt;code&gt;local|setup|deploy:update&lt;/code&gt; you might have noticed that the rules under &amp;ldquo;modules&amp;rdquo; in project.yml activate and deactivate modules after everything has run, which is not ideal. So let&amp;rsquo;s just empty all those rules with &lt;code&gt;{ }&lt;/code&gt;. But I&amp;rsquo;d be really nice to have environment-specific configuration as the example shows for devel, views_ui and so on, which is where config_split comes in.&lt;/p&gt;
&lt;p&gt;Out-of-the-box BLT provides folders for ci and default but otherwise hasn&amp;rsquo;t enabled these. The &lt;a href=&#34;https://www.jeffgeerling.com/blog/2017/adding-configuration-split-drupal-site-using-blt-and-acquia-cloud&#34;&gt;blog post&lt;/a&gt; by Jeff Geerling provides general information but isn&amp;rsquo;t quite up-to-date.&lt;/p&gt;
&lt;p&gt;However, you hardly need to do anything: Just enable the profiles in the backend with the common names such local, ci, test, prod and start using them. If you have config_filter enabled, you can just use cim/cex without any additional parameters and can ignore csex/csim.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/blacklist_example.png&#34; alt=&#34;Local config_split example&#34;&gt;&lt;/p&gt;
&lt;p&gt;The thing which confused me the longest time is the wording of configuration split, where blacklisting actually means that I want to keep something only within this environment (e.g. devel locally) and graylisting means I don&amp;rsquo;t want to overwrite something (such as webform forms on production). Thankfully, people are &lt;a href=&#34;https://www.drupal.org/node/2865280&#34;&gt;working on that&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So there you go, deployment on BLT with a minimum of moving parts on your own stack.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Migrating to BLT - Part 1</title>
            <link>https://www.grahl.ch/2017/05/26/migrating-to-blt-part-1/</link>
            <pubDate>Fri, 26 May 2017 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2017/05/26/migrating-to-blt-part-1/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/blt.jpg&#34; alt=&#34;blt sandwich&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;https://www.flickr.com/photos/amagill/368648462&#34;&gt;amagill&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m a fan of CI/CD automation it took me a while to reinvent a few wheels until I realized that &lt;a href=&#34;https://github.com/acquia/blt&#34;&gt;Acquia&amp;rsquo;s BLT&lt;/a&gt; for Drupal 8 is the template that I want. In the next few posts I will provide you some tips and tricks, for the things which aren&amp;rsquo;t self-explanatory, yet.&lt;/p&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Make sure you read over the &lt;a href=&#34;http://blt.readthedocs.io/en/stable/&#34;&gt;BLT documentation&lt;/a&gt; once fully to understand certain general concepts. For example, it&amp;rsquo;s imperative to understand that the project repository is not what gets deployed but rather a deployment artifact is pushed to a separate repository that also includes the blt tools but no development dependencies.&lt;/p&gt;
&lt;h2 id=&#34;migrating-an-existing-project&#34;&gt;Migrating an existing project&lt;/h2&gt;
&lt;p&gt;The documentation recommends starting with a fresh site but I find it much easier to see the differences in setups when I have a working real-life site to do the comparison on. The documentation says it&amp;rsquo;s possible to add BLT to an existing project but I find it unhelpful for beginners to figure out if the migration actually was complete and ideal, or not. Plus, you likely have idiosyncrasies in your setup. Here&amp;rsquo;s how you can get there (based on v8.8.3) through manual merging.:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new site with BLT. Follow all steps including setting up the VM and &lt;code&gt;blt local:setup&lt;/code&gt;.
&lt;ol&gt;
&lt;li&gt;Don&amp;rsquo;t forget to set the installation profile to what you had before in project.yml.&lt;/li&gt;
&lt;li&gt;Remember to customize the &lt;code&gt;vagrant_ip&lt;/code&gt; (and of course set &lt;code&gt;php_version&lt;/code&gt; to &lt;code&gt;&amp;quot;70&amp;quot;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t try moving the site to a subdirectory of the repo, weird stuff happens with Vagrant and other scripts.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Delete the git directory that was created for you and move your existing git folder over. You can now add items selectively and &lt;code&gt;git add&lt;/code&gt; them until you have everything. A good start would be your &amp;ldquo;custom&amp;rdquo; folders and the require entries in composer.json, minus core et al. At the end your staged changes should mostly show renames.&lt;/li&gt;
&lt;li&gt;Add a useful target to the site aliases to sync from and set it in project.yml. For example, to sync directly from production set remote under drush.aliases to &lt;code&gt;&#39;${project.machine_name}.prod&#39;&lt;/code&gt; and execute &lt;code&gt;blt local:refresh&lt;/code&gt; afterwards.
&lt;ol&gt;
&lt;li&gt;BLT is actually smart enough that it can reset your site uuid and install from the config directory from scratch but you&amp;rsquo;re going to need relevant data anyway, so why not fetch it now.&lt;/li&gt;
&lt;li&gt;The refresh will sanitize user accounts, so you might want to use &lt;code&gt;drush upwd&lt;/code&gt; or &lt;code&gt;drush uli&lt;/code&gt; afterwards.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;You have essentially removed the installed site now but left all supporting files intact and it&amp;rsquo;s now easy to move over the yml configuration into config/default and you can locally &lt;code&gt;cim&lt;/code&gt; &amp;amp; &lt;code&gt;cex&lt;/code&gt; or use blt commands afterwards.
&lt;ol&gt;
&lt;li&gt;Note that the site hash is in /salt.txt and needs to be updated, too, from the existing site.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Done! Your site should now be locally available with everything you had before but under BLT.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;commands-to-try-out&#34;&gt;Commands to try out&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m far from an expert yet but just the pure joy of use of everyday usage is really worth the effort. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;blt local:refresh&lt;/code&gt; Already mentioned above, it syncs your environment from a remote destination and does all the stuff you might forget to do. It can also fetch the site files, or do that directly with &lt;code&gt;blt sync:files&lt;/code&gt;. To think I used to write &lt;code&gt;drush sql-sync&lt;/code&gt; commands by hand&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blt local:update&lt;/code&gt; Clearly a lot nicer than chaining drush commands, e.g.: &lt;code&gt;drush cim -y &amp;amp;&amp;amp; drush updb -y &amp;amp;&amp;amp; drush cr -y&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blt fix&lt;/code&gt; Just run all the fixes. No weird issues with the phpcs config path, if not installed globally, or having to manually specify paths. It just works. 🙌&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blt deploy&lt;/code&gt; Details on that in a later post but one of the key features of BLT which really improves on simpler scripts, which most of us use to just automate copying files and calling composer. I&amp;rsquo;m extremely satisfied with the output of this command and the abstraction it provides.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a few days I&amp;rsquo;ll post more about how to work with config_split and the relevant deployment commands, when you are not using Travis/Acquia Cloud.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Refactoring complex Drupal modules</title>
            <link>https://www.grahl.ch/2017/01/01/refactoring-complex-drupal-modules/</link>
            <pubDate>Sun, 01 Jan 2017 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2017/01/01/refactoring-complex-drupal-modules/</guid>
            <description>&lt;p&gt;In 2016 I spent a significant amount of time on porting and refactoring &lt;a href=&#34;https://www.drupal.org/project/ldap&#34;&gt;the LDAP module&lt;/a&gt; for Drupal 8. This work is ongoing and lots of work still needs to be done for a proper 1.0 release.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I encountered along the way and &lt;strong&gt;why Drupal 8 is such a massive step forward for complex modules&lt;/strong&gt; like LDAP.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/ldap-code-example.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;good-intentions&#34;&gt;Good intentions&lt;/h2&gt;
&lt;p&gt;If you look at the 7.x codebase you can clearly see that the authors wanted to have a well-tested and supported module which worked as expected even though it contains a cornucopia of features. This was not easy in 7 and it shows.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s visible in the fact that unit tests are really integration tests (due to SimpleTest). It&amp;rsquo;s visible in the fact that integration tests use complex bootstrapping mechanisms which switch out significant amount of code to substitute it with test code in huge, monolithic test classes. Plus, there are multiple lines of test architectures and manual test instructions.&lt;/p&gt;
&lt;p&gt;The most interesting part is that you see that the authors wanted to include object-oriented code but Drupal&amp;rsquo;s structure and the code&amp;rsquo;s legacy architecture prevented them from doing so.&lt;/p&gt;
&lt;h2 id=&#34;first-obstacle-class-loading&#34;&gt;First obstacle: Class loading&lt;/h2&gt;
&lt;p&gt;In 7 there is no real autoloading. There are solutions &lt;a href=&#34;https://www.drupal.org/project/autoload&#34;&gt;now&lt;/a&gt; but those were not available to the authors. What did they do?&lt;/p&gt;
&lt;p&gt;They wrote a custom class loader in each module to fetch their helper classes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ldap_user_conf&lt;/span&gt;($type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;, $reset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FALSE&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $ldap_user_conf;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $ldap_user_conf_admin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ($type &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;admin&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ($reset &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;is_object&lt;/span&gt;($ldap_user_conf_admin))) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ldap_servers_module_load_include&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;php&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ldap_user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LdapUserConfAdmin.class&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $ldap_user_conf_admin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConfAdmin&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;elseif&lt;/span&gt; ($type &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;admin&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ($reset &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;is_object&lt;/span&gt;($ldap_user_conf))) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ldap_servers_module_load_include&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;php&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ldap_user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LdapUserConf.class&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $ldap_user_conf &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConf&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ($type &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; $ldap_user_conf_admin &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; $ldap_user_conf;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that this function not only switches between loading a subclass and its parent class but also contains a custom caching mechanism.&lt;/p&gt;
&lt;p&gt;An additional problem was that these classes could not declare their dependencies so that the following was not uncommon and created code which is tied together only by wishful thinking and manual testing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;module_load_include&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;php&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ldap_user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LdapUserConf.class&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;module_load_include&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;inc&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user.pages&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConfAdmin&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConf&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The refactoring in the D8 branch allowed us to get rid of LdapUserConfAdmin and handle its tasks directly in a standard configuration form class. While we have not yet gotten rid of LdapUserConf, the beginning of the class exemplifies how autoloading has already made a huge difference:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\ldap_user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\Component\Utility\Unicode&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\ldap_servers\Entity\Server&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\ldap_servers\TokenFunctions&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\ldap_user\Exception\LdapBadParamsException&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\user\Entity\User&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\user\UserInterface&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConf&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;second-obstacle-class-and-configuration-structure&#34;&gt;Second obstacle: Class and configuration structure&lt;/h2&gt;
&lt;p&gt;Due to the inefficient mechanisms above it&amp;rsquo;s no wonder that classes weren&amp;rsquo;t separated into functional groupings but rather stuck to one huge class to handle everything, with a subclass for the administration form.&lt;/p&gt;
&lt;p&gt;Additionally, the existing classes tried to provide an interface to the complex attributes required to configure a site with LDAP authentication, user mappings, etc., and thus, we see the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Creation &amp;amp; default value for saveable data.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; $userConflictResolve &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LDAP_USER_CONFLICT_RESOLVE_DEFAULT&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Complex data structure without saveable data.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; $provisionSidFromDirection &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;array&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LDAP_USER_NO_SERVER_SID&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LDAP_USER_NO_SERVER_SID&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; __construct() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Loads all saveable fields via variable_get().
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;load&lt;/span&gt;(); 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Obviously, Drupal&amp;rsquo;s configuration storage can and should take of at least all saveable values. That conversion has already made a huge difference in terms of code complexity but required painstaking manual conversions due to subtle logic differences in many places.&lt;/p&gt;
&lt;p&gt;Also note that once we no longer have to manually load the configuration values from the database the cache for LdapUserConf is not needed anymore. We could then remove a significant amount of code dealing with setting and resetting it.&lt;/p&gt;
&lt;h2 id=&#34;third-obstacle-global-constants&#34;&gt;Third obstacle: Global constants&lt;/h2&gt;
&lt;p&gt;While constants can in general be useful to denote, well, constant values, their usage in the LDAP module is often problematic:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;define&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;define&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LDAP_USER_EVENT_CREATE_DRUPAL_USER&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;define&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;define&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LDAP_USER_EVENT_CREATE_LDAP_ENTRY&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;define&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For one, their numeric values are a problem since it makes reading the configuration settings difficult and cryptic:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ldap_user_conf:
   drupalAcctProvisionTriggers:
    2: &amp;#39;2&amp;#39;
    1: &amp;#39;1&amp;#39;
  ldapEntryProvisionTriggers:
    6: 0
    7: 0
    8: 0
    3: 0
  manualAccountConflict: 3
  acctCreation: 4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;More importantly though they are global in scope and need to be declared at the right time, which is also why they are not normally found in context but just in bulk in the your_module.module file. Thus, they also need to be manually loaded in some cases, e.g. LdapUserConf:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;require_once&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ldap_user.module&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For now we have stashed these constants as static values in a trait, that makes them much easier to read and maintain:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Drupal\ldap_user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;trait&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConfigurationValues&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $eventCreateDrupalUser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $eventSyncToDrupalUser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $eventCreateLdapEntry &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $eventSyncToLdapEntry &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; $eventLdapAssociateDrupalAccount &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can just call them with either depending on the context:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;LdapUserConfigurationValues&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;$eventCreateDrupalUser
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;$eventCreateDrupalUser
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These magic values are now put somewhere safe and core&amp;rsquo;s configuration storage does the rest. Ideally, we would not need even a fraction of these magic values, but it&amp;rsquo;s a decent interim solution which avoids complex update hooks to transform legacy states into new states.&lt;/p&gt;
&lt;h2 id=&#34;fourth-obstacle-unit-testing&#34;&gt;Fourth obstacle: Unit testing&lt;/h2&gt;
&lt;p&gt;Once we have had those taken care of and all those other dependencies cleanly resolved, running unit tests became feasible and lots of pseudo-code for integration tests could be removed.&lt;/p&gt;
&lt;p&gt;For example, here is TokenTests, where we now can easily mock a function buried deep within the LDAP module:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;protected&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;setUp&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;parent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;setUp&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* Mocks the Server due to wrapper for ldap_explode_dn(). */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;serverFactory&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getMockBuilder&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\Drupal\ldap_servers\Entity\Server&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;disableOriginalConstructor&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;getMock&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;serverFactory&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;expects&lt;/span&gt;($this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;any&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ldapExplodeDn&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;willReturn&lt;/span&gt;($this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;container&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ContainerBuilder&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;container&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ldap.servers&amp;#39;&lt;/span&gt;, $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;serverFactory&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;\Drupal&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;setContainer&lt;/span&gt;($this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;container&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testTokenReplacement&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  $nameReplacement &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $tokenHelper&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;tokenReplace&lt;/span&gt;($account&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;reveal&lt;/span&gt;(), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[property.name]&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;user_account&amp;#39;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// Test calling ldapExplodeDN()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  $this&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;assertEquals&lt;/span&gt;($ldap_entry[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;sAMAccountName&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], $nameReplacement);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The process of writing that test also made the code itself more robust since it was necessary to encapsulate the call to the PHP function ldap_explode_dn() within a wrapper. Due to this wrapper we can now run the test even if the LDAP extension is not installed (such as on drupal.org).&lt;/p&gt;
&lt;h2 id=&#34;looking-back&#34;&gt;Looking back&lt;/h2&gt;
&lt;p&gt;Knowing what I know now would I do it the same way again? Definitely not.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s important to note that a huge amount of work was already done by &lt;a href=&#34;https://www.drupal.org/u/larowlan&#34;&gt;larowlan&lt;/a&gt;, &lt;a href=&#34;https://www.drupal.org/u/queenvictoria&#34;&gt;queenvictoria&lt;/a&gt;, and many more to get a first port successfully running. With that already in place, the existing state required a step-by-step refactoring and that is why this approach was needed. It&amp;rsquo;s also why this journey and its results are likely relevant to other module maintainers with partial ports as well.&lt;/p&gt;
&lt;p&gt;Here is how I would now approach a complex module without an existing Drupal 8 port:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start fresh.&lt;/strong&gt; Drupal Console makes it so easy to set up the skeletons, just use those and put all the existing code into a legacy folder.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make a plan.&lt;/strong&gt; Make a list of each file and function and what it should do and what it currently does. Then question each if it&amp;rsquo;s still useful and needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migrate functions not structure.&lt;/strong&gt; Take the useful logic from the legacy folder but question whether the architecture of your files and classes really couldn&amp;rsquo;t be structured better.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profit.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you are still unsure of how good code should look and how to get started with refactoring, I&amp;rsquo;d recommend &lt;a href=&#34;https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882&#34;&gt;Clean Code&lt;/a&gt; for an excellent introduction.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>GTD: Processing email on iOS</title>
            <link>https://www.grahl.ch/2016/12/22/gtd-processing-email-on-ios/</link>
            <pubDate>Thu, 22 Dec 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/12/22/gtd-processing-email-on-ios/</guid>
            <description>&lt;p&gt;If you are a GTD&amp;rsquo;ler and using a Mac there is a good chance that you are using OmniFocus. On that machine processing your email and throwing it into the OmniFocus inbox is a great way to getting to
&lt;a href=&#34;http://www.43folders.com/izero&#34;&gt;Inbox Zero&lt;/a&gt; and still responding quickly and easily right within the message thread.&lt;/p&gt;
&lt;p&gt;This is made possible because OmniFocus uses message:// links to open the archived message in the note, They are embedded when the message is dragged into an OmniFocus window or if you used the &lt;a href=&#34;https://support.omnigroup.com/omnifocus-clip-o-tron/&#34;&gt;Clip-o-Tron&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In an ideal world the same would happen on iOS as well with the share sheet. It doesn&amp;rsquo;t. However, there are third-party solutions to make that possible.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using &lt;a href=&#34;http://www.dispatchapp.net/&#34;&gt;Dispatch&lt;/a&gt; for the last few months and can definitively recommend that to get that setup working. Just make sure that you select the proper link protocol in the settings (i.e. message://).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve rarely had issues with Dispatch but I recommend keeping the stock email client set up as well and just choosing notifications for your preferred one. &lt;em&gt;My personal preference is no notifications except for a very short list of VIP accounts from the stock accounts.&lt;/em&gt; The stock client is also helpful if the rendering of Dispatch isn&amp;rsquo;t perfect or if an attachment fails.&lt;/p&gt;
&lt;p&gt;Now you don&amp;rsquo;t have an excuse anymore for scrolling through the same dozen messages on your mobile and not doing anything about it. ✉️🔨&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Bulk generating calendar events with Applescript</title>
            <link>https://www.grahl.ch/2016/12/17/bulk-generating-calendar-events-with-applescript/</link>
            <pubDate>Sat, 17 Dec 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/12/17/bulk-generating-calendar-events-with-applescript/</guid>
            <description>&lt;p&gt;Sometimes you want to create calendar events which differ in predictable ways such as &amp;ldquo;Read/do chapter/session/workout x&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;A trivial problem for a programmer but basically impossible with the stock macOS calendar or third-party tools such as Fantastical as far as I can tell. They can have complex recurrence rules but all deviations per event are a manual process it seems.&lt;/p&gt;
&lt;p&gt;So here is an Applescript script I quickly googled together for your convenience to create thirty events each containing an integer reference. Adjust as necessary.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-applescript&#34; data-lang=&#34;applescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt; application &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Calendar&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	activate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;--- date() uses your localized date formatting.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; theCurrentDate &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; date (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;17.12.2016 09:00&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; i &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;repeat&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;31&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt; calendar &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Home&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			make new event at &lt;span style=&#34;color:#f92672&#34;&gt;end with&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;properties&lt;/span&gt; {description:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;, summary:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;My task - Day &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; i, &lt;span style=&#34;color:#a6e22e&#34;&gt;location&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;, start date:theCurrentDate, &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; date:theCurrentDate &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; minutes}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; i &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; theCurrentDate &lt;span style=&#34;color:#66d9ef&#34;&gt;to&lt;/span&gt; theCurrentDate &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; days)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;repeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	reload calendars
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The script has obvious limitations with the hardcoded values it contains. A more flexible solution would use dialogs to ascertain the number of events, starting date, calendar to use, or event details.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m just content I don&amp;rsquo;t have to keep adding individual events by hand to get this done.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Export cookies from Safari in Sierra</title>
            <link>https://www.grahl.ch/2016/10/09/export-cookies-from-safari-in-sierra/</link>
            <pubDate>Sun, 09 Oct 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/10/09/export-cookies-from-safari-in-sierra/</guid>
            <description>&lt;p&gt;I wanted to automate a batch processing job and was too lazy to programmatically fetch the cookies by emulating a login form so I looked for a way to just reuse my cookies.&lt;/p&gt;
&lt;p&gt;That should be pretty easy in general, for example with the Python bindings on macOS, but the syntax changed a bit between 10.10 and 10.12 respectively, so I fired up Xcode and the following ten-ish lines of Swift work nicely in generating output which is fit for wget and other tools using the ancient cookie file format.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import Foundation

print(&amp;#34;The url to fetch:&amp;#34;, terminator: &amp;#34; &amp;#34;)
let response = readLine(strippingNewline: true)

let storage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: &amp;#34;Cookies&amp;#34;)
let cookies = storage.cookies(for: URL(string: response!)!)

print(&amp;#34;# HTTP Cookie File\n&amp;#34;)

for cookie in cookies! {
    var secure = &amp;#34;FALSE&amp;#34;
    if (cookie.isSecure) {
        secure = &amp;#34;TRUE&amp;#34;
    }
    var expires:Int = Int(cookie.expiresDate!.timeIntervalSince1970)
    print(&amp;#34;\(cookie.domain)\tTRUE\t\(cookie.path)\t\(secure)\t\(expires)\t\(cookie.name)\t\(cookie.value)&amp;#34;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I don’t get points for exception handling or elegance but I have my 🍪.&lt;/p&gt;
&lt;p&gt;I haven’t yet figured out a good workflow of generating such CLI binaries without hunting for them in the build folder and copying them over, &lt;a href=&#34;https://twitter.com/grahl&#34;&gt;input on that is very welcome&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>First look at Acquia Lightning</title>
            <link>https://www.grahl.ch/2016/09/15/first-look-at-acquia-lightning/</link>
            <pubDate>Thu, 15 Sep 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/09/15/first-look-at-acquia-lightning/</guid>
            <description>&lt;pre&gt;&lt;code&gt;![Some modules in lightning](/img/lightning-modules.png)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lightning is a distribution of Drupal 8 developed by Acquia which attempts to deliver a preconfigured and optimized out-of-the box experience.&lt;/p&gt;
&lt;h2 id=&#34;first-impressions&#34;&gt;First impressions&lt;/h2&gt;
&lt;p&gt;The installation via Composer is smooth and setting up the site with the default profile is all that is necessary. Nice.&lt;/p&gt;
&lt;p&gt;You get a site which provides you with Panels via Panelizer, Media management via media and entity browser as well as moderation via Scheduled Content and Workbench Moderation.&lt;/p&gt;
&lt;p&gt;On the other hand, I&amp;rsquo;m surprised not to see Paragraphs here and the reliance of media embedding in rich-text components seems inelegant. Also power-user essentials, such as Admin Toolbar, are lacking.&lt;/p&gt;
&lt;h2 id=&#34;media&#34;&gt;Media&lt;/h2&gt;
&lt;p&gt;Lightning provides a very nice showcase on how you can easily upload and reuse assets with the entity browser.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/lightning-media.png&#34; alt=&#34;Image reuse in entity browser&#34;&gt;&lt;/p&gt;
&lt;p&gt;I look forward to using that in future projects, though its still odd that there is barely any documentation on organizing or segmenting asset management there.&lt;/p&gt;
&lt;p&gt;Apparently, Views will need to fill that gap, but I hope future improvements here will set a greater focus on image pools, as easily accessible as now but with more fine-grained controls, when you can&amp;rsquo;t trust your editors to do the right thing.&lt;/p&gt;
&lt;h2 id=&#34;moderation&#34;&gt;Moderation&lt;/h2&gt;
&lt;p&gt;Until the 8.2 merge of Workbench moderation as &amp;ldquo;Content Moderation&amp;rdquo; I was not certain which access control suite might come out on top for 8 but Lightning seems to have had the right idea.&lt;/p&gt;
&lt;p&gt;What is surprising is that the experience of scheduled content is rather grim in terms of workflow states and publishing processes.   The user does not have any good feedback what version is currently published, how to go about modifying them, nor do additional pending states seem to work at all out of the box. That needs lots of work and is a major weak point compared to the usual enterprise CMS offerings, which I had hoped Lightning would have a better answer to.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Lightning is a great demo case to show how Drupal 8 is in many ways a different ecosystem than 7 and what it can do but I would not necessarily choose it as a base distribution since it&amp;rsquo;s not hard to take a look at their example and tailor your site to your actual needs.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Spress on AWS with HTTPS</title>
            <link>https://www.grahl.ch/2016/09/11/spress-on-aws-with-https/</link>
            <pubDate>Sun, 11 Sep 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/09/11/spress-on-aws-with-https/</guid>
            <description>&lt;p&gt;When I got started on &lt;a href=&#34;https://www.grahl.ch/2016/08/28/switching-from-drupal-to-spress/&#34;&gt;my migration to Spress&lt;/a&gt; I figured that I would just run the site on a generic hoster that also offers letsencrypt certificates. Turns out, there really wasn’t any hoster who came close to the $7 DigitalOcean VM. So, AWS it is.&lt;/p&gt;
&lt;p&gt;Specifically, deploying to S3, letting it do the static site hosting and putting CloudFront in front (haha) of it to handle HTTPS.&lt;/p&gt;
&lt;p&gt;Amazon has &lt;a href=&#34;https://docs.aws.amazon.com/gettingstarted/latest/swh/website-hosting-intro.html&#34;&gt;an excellent guide&lt;/a&gt; to get you started setting that up. Just follow that and when it comes to the certificates, use AWS Certificate Manager or &lt;a href=&#34;https://github.com/dlapiduz/letsencrypt-s3front&#34;&gt;letsencrypt-s3front&lt;/a&gt; to handle the certificates.&lt;/p&gt;
&lt;h2 id=&#34;caveats&#34;&gt;Caveats&lt;/h2&gt;
&lt;p&gt;I had a terrible time getting letsencrypt-s3front running on macOS, mostly due to problems with Python and build dependencies. When I ran it on a regular CentOS VM with a working letsencrypt setup, everything worked fine. When Docker finally runs half-way stable on my machine I plan on handing that off to an image to rerun without effort when I have to renew the certificate… &lt;strong&gt;Update 2016-12-30:&lt;/strong&gt; Consider using &lt;a href=&#34;https://aws.amazon.com/certificate-manager/&#34;&gt;AWS Certificate Manager&lt;/a&gt; instead of Let&amp;rsquo;s Encrypt, it&amp;rsquo;s much less hassle.&lt;/p&gt;
&lt;p&gt;Apparently, CloudFront does not support HSTS in any way. While that’s not perfect, it’s a reasonable compromise for everything else this AWS setup can do.&lt;/p&gt;
&lt;p&gt;If your homepage works without index.html but your posts do not, see &lt;a href=&#34;http://serverfault.com/questions/581268/amazon-cloudfront-with-s3-access-denied&#34;&gt;this stackoverflow post&lt;/a&gt; and use the S3 endpoint URL (HTTP only) to avoid issues.&lt;/p&gt;
&lt;h2 id=&#34;spress-integration&#34;&gt;Spress integration&lt;/h2&gt;
&lt;p&gt;You don’t have to upload your build manually to S3, the &lt;a href=&#34;https://github.com/SeleneSoftware/spress-plugin-deploy-aws&#34;&gt;AWS deployment plugin&lt;/a&gt; will do that for you. The Github documentation should suffice for setting it up (thanks to the maintainer merging my v2 support and documentation updates already).&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>D8 PSA: Don’t use Multiversion (yet)</title>
            <link>https://www.grahl.ch/2016/09/07/d8-psa-dont-use-multiversion-yet/</link>
            <pubDate>Wed, 07 Sep 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/09/07/d8-psa-dont-use-multiversion-yet/</guid>
            <description>&lt;p&gt;The &lt;a href=&#34;http://www.drupaldeploy.org/&#34;&gt;deploy suite&lt;/a&gt; is an interesting suite of modules, which makes it possible to revision content in such a way that it becomes deployable across different sites or workspaces.&lt;/p&gt;
&lt;p&gt;Also, it makes it possible to move the CRUD model Drupal runs under  into a CRAP model, which anyone will appreciate who has ever gotten a call from a customer who just deleted a dozen nodes.&lt;/p&gt;
&lt;p&gt;To make all that possible, basically all entities need adjustments to their storage base. That is not a trivial task and especially with complex contrib modules such as ECK, Field Collections, etc. the list of problematic and complex cases rises.&lt;/p&gt;
&lt;p&gt;Looking at the current dev state and the last release as of this writing (alpha10), many of those complex cases are not yet supported. Field collections seems broken, content translation has problems with taxonomy terms and many other minor and major bugs are present.&lt;/p&gt;
&lt;p&gt;Also, you should beware from using multiversion since not only is enabling it tricky (migrate all content to a temp storage and back), the same applies to uninstalling and it can leave you stranded with a broken system.&lt;/p&gt;
&lt;p&gt;So, wait at least until the first beta is out.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Switching from Drupal to Spress</title>
            <link>https://www.grahl.ch/2016/08/28/switching-from-drupal-to-spress/</link>
            <pubDate>Sun, 28 Aug 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/08/28/switching-from-drupal-to-spress/</guid>
            <description>&lt;p&gt;This site was powered by Drupal for a full decade. It went through Drupal 5, 6, 7 and 8, but it’s outlived its usefulness in that form. &lt;em&gt;I think it might have even begun as a 4.7 site.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;why&#34;&gt;Why&lt;/h2&gt;
&lt;p&gt;My reasons for switching to a static site generator are quite plain but highlight my shifting needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Over 3/4 of posts here contain a code snippet, this is vastly easier to handle with Markdown or similar tools, than CKEditor.&lt;/li&gt;
&lt;li&gt;The content is by all accounts static. The end-user is not rewarded through interactive features but rather burneded by the wait time of the CMS overhead. Even with the dramatically improved page cache in D8, it doesn’t come close to plain HTML.&lt;/li&gt;
&lt;li&gt;I don’t have nontechnical users to support here.&lt;/li&gt;
&lt;li&gt;I have enough other Drupal sites in my life to use for trying out new modules and experimenting.&lt;/li&gt;
&lt;li&gt;I can stop maintaing a server to have things as flexible and speedy as I like, for a minor site.&lt;/li&gt;
&lt;li&gt;And finally: All posts are revisioned in Git, without any intermediate systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;how&#34;&gt;How&lt;/h2&gt;
&lt;p&gt;My choice fell on &lt;a href=&#34;http://spress.yosymfony.com/&#34;&gt;Spress&lt;/a&gt; even though it’s not that prevalent (yet), since it uses the stack I’ve come to appreciate everywhere else: Symfony, Twig, Composer.&lt;/p&gt;
&lt;p&gt;Converting data is straightfoward and easy. Just write a quick script to fetch all nodes and output their fields as desired.&lt;/p&gt;
&lt;p&gt;League’s &lt;a href=&#34;https://github.com/thephpleague/html-to-markdown&#34;&gt;HTML-to-Markdown&lt;/a&gt; library easily converted CKEditor HTML with only minor corrections necessary afterwards.&lt;/p&gt;
&lt;p&gt;Then just write it to a file with fopen/fwrite/fclose. Yes, of course that could be done more elegantly, but for a single migration it works fine.&lt;/p&gt;
&lt;p&gt;Along the way I pruned the content back to what could still be considered tangentially relevant. I look forward to seeing if this will make posts here more frequent, as I expect, the opposite, or neither.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Continuous integration with Gitlab CI &amp; Drupal</title>
            <link>https://www.grahl.ch/2016/08/06/continuous-integration-with-gitlab-ci-drupal/</link>
            <pubDate>Sat, 06 Aug 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/08/06/continuous-integration-with-gitlab-ci-drupal/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/gitlab_screenshot.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Today I want to highlight a solution that has become one of my favourite tools in web development over the summer: Gitlab with Gitlab CI.&lt;/p&gt;
&lt;p&gt;I used to be a bit skeptical if a continuous integration solution would not be overkill for small projects but with configuration management in Drupal 8 and composer &amp;amp; composer-patches I’m a dedicated convert and would never want to go back to doing it by hand. You might think it’s just a git pull here or a cache-clear there but I&amp;rsquo;m certain that once you&amp;rsquo;ve tried it you don&amp;rsquo;t want to go back.&lt;/p&gt;
&lt;h2 id=&#34;gitlab-setup&#34;&gt;Gitlab setup&lt;/h2&gt;
&lt;p&gt;Fire up a VPN and &lt;a href=&#34;http://docs.gitlab.com/omnibus/&#34;&gt;install the omnibus edition&lt;/a&gt;, then follow the instructions. Just make sure that you have at least 1GB of RAM and several GB of swap. I tried 512MB, you will run into issues that are not worth your time and effort.&lt;/p&gt;
&lt;p&gt;Next you’ll need to actually define a runner, again, &lt;a href=&#34;https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/install/linux-repository.md&#34;&gt;see the documentation&lt;/a&gt; for that. For my case it works perfectly fine to have the runner be a second user on that server. Since I’m using SSH later I’ll need to provide that user with a key-pair, since we need to add that to the authorized_keys of the production server.&lt;/p&gt;
&lt;h2 id=&#34;project-setup&#34;&gt;Project setup&lt;/h2&gt;
&lt;p&gt;First, you’ll need a .gitlab-ci.yml file in the repository root and that can be quite minimalistic. You can execute commands directly there but since most of what I’m doing for automated deployments is happening in an ssh session, I just wrap that in a deploy script:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;.gitlabl-ci.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stages:
  - deploy

prod-deployment:
  stage: deploy
  script:
    -  ssh user@server.ccom &#39;bash -s&#39; &amp;lt; prod-deployment.sh
  only:
    - master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;prod-deployment.sh&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
set -e # We want to fail at each command, to stop execution
cd /var/www/project/drupal/web
drush sql-dump --gzip &amp;gt; ../../somewhere-safe/db/`date +%Y-%m-%d-%H%M`.sql.gz
cd ..
git pull
~/bin/composer install
cd web
~/bin/drush updb -y
~/bin/drush cim -y
~/bin/drush cr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you commit that CI file you will likely get a notification that the build is stuck. Just follow the prompts until you get to the list of runners so that you can enable it for the specific project.&lt;/p&gt;
&lt;p&gt;Of course that could be done more elegantly in a variety of ways (maintenance mode, aliases instead of directories, etc.) but it actually works extremely well. I rarely have to touch the server anymore and adding additional steps such as unit testing and code standard validation is extremely easy with such a lightweight CI solution.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Wemos D1 Mini DHT HomeKit setup</title>
            <link>https://www.grahl.ch/2016/07/09/wemos-d1-mini-dht-homekit-setup/</link>
            <pubDate>Sat, 09 Jul 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/07/09/wemos-d1-mini-dht-homekit-setup/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/IMG_0027.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The tutorial below is based primarily on work done by Aditya Tannu (see &lt;a href=&#34;http://adityatannu.com/blog/post/2016/01/01/WeatherBug-ESP8266-WeatherStation-with-IFTTT-and-HomeKit-support.html&#34;&gt;here&lt;/a&gt; and &lt;a href=&#34;http://adityatannu.com/blog/post/2015/12/13/ESP8266-based-HomeKit-accessories.html&#34;&gt;here&lt;/a&gt;) and tries to make things easier for beginners by reducing the setup to the bare essentials and providing clean and simple examples.&lt;/p&gt;
&lt;p&gt;So, you might have gotten the iOS 10 beta and seen the Home app but don&amp;rsquo;t wan&amp;rsquo;t to spend an inordinate amount on commercial solutions and nowadays ESP8266 solutions are ubiquitous and cheap and those can be made to work with HomeKit.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll need three things running to actually make that work: a bridge for your sensors, an MQTT broker and the sensor itself. While you can certainly use a Raspberry Pi for the first two I chose a Mac mini since I have that already running anyway. Sadly, that solution is Node-based but I haven&amp;rsquo;t seen any other solution that works out there.&lt;/p&gt;
&lt;h2 id=&#34;the-server&#34;&gt;The server&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;HAP (bridge)
&lt;ul&gt;
&lt;li&gt;Clone the &lt;a href=&#34;https://github.com/KhaosT/HAP-NodeJS&#34;&gt;HAP-NodeJS&lt;/a&gt; repository&lt;/li&gt;
&lt;li&gt;Install with &lt;code&gt;npm install&lt;/code&gt; (just Google if you don&amp;rsquo;t have node yet)&lt;/li&gt;
&lt;li&gt;Set a custom pincode in BridgedCore.js&lt;/li&gt;
&lt;li&gt;Run with &lt;code&gt;DEBUG=\* node BridgedCore.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mosca (broker)
&lt;ul&gt;
&lt;li&gt;Install with &lt;code&gt;sudo npm install -g mosca bunyan&lt;/code&gt; (bunyan is only needed for output)&lt;/li&gt;
&lt;li&gt;Run with &lt;code&gt;mosca -v | bunyan&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should now be able to add the_Node bridge_ in your HomeKit management application and see the fake sensors which are present in accessories. Once everything is running smoothly, you can run these as system services with an unprivileged user. See the &lt;a href=&#34;https://github.com/grahl/HAP-NodeJS&#34;&gt;project root&lt;/a&gt; for plist examples, just add your user name where necessary. I had some issues with calling node directly for HAP so I just made a one-liner bash script which then runs &amp;rsquo;node BridgedCore.js&amp;rsquo; in the relevant directory.&lt;/p&gt;
&lt;h4 id=&#34;problems&#34;&gt;Problems&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;The pairing fails after a longish timeout.&lt;/em&gt; Your firewall settings on the server are the likely cause turn if off, if that helps, adjust as necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-sensor&#34;&gt;The sensor&lt;/h2&gt;
&lt;p&gt;We can program the sensor with the NodeMCU stack but in my experience the Arduino one is far less volatile (for now).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the Arduino IDE&lt;/li&gt;
&lt;li&gt;Add the &lt;a href=&#34;http://arduino.esp8266.com/stable/package_esp8266com_index.json&#34;&gt;board definition and ESP8266 package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;a href=&#34;https://github.com/knolleary/pubsubclient&#34;&gt;pubsub&lt;/a&gt; and &lt;a href=&#34;https://github.com/adafruit/DHT-sensor-library&#34;&gt;DHT&lt;/a&gt; libaries (the latter is also available in the IDE library manager)&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t forget to install the &lt;a href=&#34;http://www.wemos.cc/downloads/&#34;&gt;CH340 driver&lt;/a&gt; if you use a Wemos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can now flash something onto the Wemos which will periodically send your data to the server. You can &lt;a href=&#34;https://github.com/grahl/HAP-NodeJS/tree/master/arduino_example&#34;&gt;use my ino templates&lt;/a&gt; as a starting point. You still need set your Wifi SSID, password and the IP of the server you are sending data to.&lt;/p&gt;
&lt;p&gt;Now that you are sending your results to the broker you still need the bridge to tell your HomeKit application about the accessory you wish to use and update its data when an update comes along. You can start with &lt;a href=&#34;https://github.com/grahl/HAP-NodeJS/tree/master/accessories&#34;&gt;my accessory templates&lt;/a&gt; for humidity and temperature. You will only need to update those with the server IP, if you did not change anything else in the Arduino templates.&lt;/p&gt;
&lt;h4 id=&#34;problems-1&#34;&gt;Problems&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Uploading fails.&lt;/em&gt; Check the baud rate, the definition might not have set it to the recommended 115200.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I see the data in my serial monitor, the broker gets a connection but the values are still zero.&lt;/em&gt; Make sure that that the channel you are pushing to is the same as in the accessory file.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;My accessory doesn&amp;rsquo;t show when I start the bridge.&lt;/em&gt; Make sure all files end in &lt;code&gt;_accessory.js&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;caveats&#34;&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Hardcoding the channels for each accessory in the js template and the Arduino sketch is inefficient at best. If someone has a good tutorial for HAP-Nodejs that does that right, &lt;a href=&#34;https://www.grahl.ch/about-myself&#34;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve noticed that the default of 10 minutes until the next update is a bit much and the accessory can be shown as timed out or zero out. Also let me know if you have a good default here or recommendations on power optimisation.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Quick tip: Working with facets directly in D8</title>
            <link>https://www.grahl.ch/2016/06/02/quick-tip-working-with-facets-directly-in-d8/</link>
            <pubDate>Thu, 02 Jun 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/06/02/quick-tip-working-with-facets-directly-in-d8/</guid>
            <description>&lt;p&gt;There are some cases where you want to interact with Facets on a Search API page, apart from just rendering them.&lt;/p&gt;
&lt;p&gt;The API for facets in D8 provides you with services to make that easily possible, however, there aren&amp;rsquo;t many examples out there yet for cases such as these where you work outside the primary rendering pipeline. The trick is to process the facet not build it, to get at the actual data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** @var \Drupal\facets\FacetManager\DefaultFacetManager $manager */
$manager = Drupal::service(&#39;facets.manager&#39;);
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = Drupal::service(&#39;entity_type.manager&#39;)
  -&amp;gt;getStorage(&#39;facets_facet&#39;);
/** @var \Drupal\facets\Entity\facet $facet */
$facet = $storage-&amp;gt;load(&#39;my_facet&#39;);
$processed_facet = $manager-&amp;gt;returnProcessedFacet($facet);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here on out working with the facet is trivial and straightfoward, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;results = $processed_facet-&amp;gt;getResults();

$topics = array();
foreach ($results as $result) {
  if ($result-&amp;gt;isActive()) {
    $topics[] = $result-&amp;gt;getRawValue();
  }
}
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>AwayTracker</title>
            <link>https://www.grahl.ch/2016/03/13/awaytracker/</link>
            <pubDate>Sun, 13 Mar 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/03/13/awaytracker/</guid>
            <description>&lt;p&gt;AwayTracker is a small app for your Mac to display when you locked your screen, to more accurately track time for time management.&lt;img src=&#34;https://www.grahl.ch/img/store_2s.png&#34; alt=&#34;Screenshot of AwayTracker&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;support&#34;&gt;Support&lt;/h2&gt;
&lt;p&gt;While the app itself is provided as-is without any guarantees. Feel free to send &lt;a href=&#34;mailto:hendrik@grahl.ch&#34;&gt;feedback via email&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Getting Linux and OS X to play nice together: OmniFocus</title>
            <link>https://www.grahl.ch/2016/02/07/getting-linux-and-os-x-to-play-nice-together-omnifocus/</link>
            <pubDate>Sun, 07 Feb 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/02/07/getting-linux-and-os-x-to-play-nice-together-omnifocus/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/2016-02-06-224047_1366x768_scrot.png&#34; alt=&#34;&#34;&gt;
Nowadays I primarily use OS X and rely on several commercial tools there to work effectively, specifically, 1Password, TextExpander, OmniFocus and PHPStorm. I still like to play with Linux from time to time though and being able to use it effectively is being able to access their data in similar tools there, where they are not available natively, such as PHPStorm.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I created a working solution for me with an xmonad based desktop (since I don&amp;rsquo;t want to waste my life looking at Gnome &amp;amp; KDE apps), starting with the most difficult one, OmniFocus.&lt;/p&gt;
&lt;h2 id=&#34;interacting-with-omnifocus&#34;&gt;Interacting with OmniFocus&lt;/h2&gt;
&lt;p&gt;The bad news up front: Unless you are willing to invest significant time into reverse-engineering a full client, true read/write access is not possible. My goal was to be able to add items and read items in some form or another and that is possible.&lt;/p&gt;
&lt;h3 id=&#34;collecting-actions&#34;&gt;Collecting actions&lt;/h3&gt;
&lt;p&gt;Collecting actions is actually fairly easy, just set up a &lt;a href=&#34;https://support.omnigroup.com/omnifocus-mail-drop/&#34;&gt;Mail Drop&lt;/a&gt; adress and send yourself an email. A trivial script such as the following one makes it easy to send actions from the command line. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
echo -n &amp;quot;Your task: &amp;quot;
read title
echo -n &amp;quot;Optional description: &amp;quot;
read desc
echo $desc | mutt -s &amp;quot;$title&amp;quot; yourcustomaddress@sync.omnigroup.com 
echo &amp;quot;Task created&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You also have the option of extending the syntax of that mail, if you install an &lt;a href=&#34;https://support.omnigroup.com/omnifocus-2-mail-rule/&#34;&gt;AppleScript extensions&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;accessing-your-existing-data&#34;&gt;Accessing your existing data&lt;/h3&gt;
&lt;p&gt;Getting access to your OmniFocus database isn&amp;rsquo;t as easy, but it isn&amp;rsquo;t that difficult either. Generally, the data sits in an sqlite database on the client. So the first step is to have access to that data.&lt;/p&gt;
&lt;p&gt;I use a &lt;a href=&#34;https://github.com/grahl/of-reader/blob/master/docs/sync-omnifocus.plist&#34;&gt;LaunchAgent&lt;/a&gt; to periodically copy that database to a personal server. From there I can easily run a cron task such as the one below to fetch the database on Linux. I recommend always only working on a copy which isn&amp;rsquo;t written to by any client, to avoid conflicts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*/10 * * * * rsync -u remote-server:~/OmniFocusDatabase2 ~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have the database, so just write some SQL when you want to know what&amp;rsquo;s due. However, you also have option of using the PHP-based tool &lt;a href=&#34;https://github.com/grahl/of-reader&#34;&gt;OFReader&lt;/a&gt; I wrote, which gives you access to the most common queries, such as outputting tasks due:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/demo.png&#34; alt=&#34;screenshot of OFReader&#34;&gt;&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&#34;https://github.com/grahl/of-reader&#34;&gt;Github page&lt;/a&gt; for all the details on installing it.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Diving into Swift Part 2</title>
            <link>https://www.grahl.ch/2016/01/14/diving-into-swift-part-2/</link>
            <pubDate>Thu, 14 Jan 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/01/14/diving-into-swift-part-2/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Bildschirmfoto_2016-01-02_um_16.04.49.png&#34; alt=&#34;&#34;&gt;
This is the second post on &lt;em&gt;Diving into Swift&lt;/em&gt;, if you haven&amp;rsquo;t read &lt;a href=&#34;https://www.grahl.ch/blog/diving-into-swift-part-1&#34;&gt;the first part&lt;/a&gt;, consider reading it for a general overview of the Xcode/Swift ecosystem.&lt;/p&gt;
&lt;p&gt;Here are now the three areas in which I struggled the most and want to give advice to newcomers to Swift, who are used to scripted languages.&lt;/p&gt;
&lt;h2 id=&#34;optionals&#34;&gt;Optionals&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;An optional value either contains a value or contains &lt;em&gt;nil&lt;/em&gt; to indicate that a value is missing. From &amp;ldquo;The Swift Programming Language&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Understanding optionals is critical to writing Swift code, and I can only echo what Touch Code Magazine describes as &amp;ldquo;the Swift code I see published on the Internet tends to rather randomly unwrap Optionals&amp;rdquo;. Xcode will complain if you haven&amp;rsquo;t marked an optional as such or force-unwrapped, so you can stumble towards runnable code with the help of the IDE. However, I highly recommend &lt;a href=&#34;http://www.touch-code-magazine.com/swift-optionals-use-let/&#34;&gt;reading their article&lt;/a&gt;, to understand when you need to do which and why.&lt;/p&gt;
&lt;p&gt;Especially understanding the if-let pattern is required if you have any complex interactions which depend on optionals.&lt;/p&gt;
&lt;h2 id=&#34;asynchronicity&#34;&gt;Asynchronicity&lt;/h2&gt;
&lt;p&gt;In the PHP world you execute all as fast as you can and if something slows down your page load you do it in a batch job or via Ajax. Thread-what? That won&amp;rsquo;t work here for two reasons 1) don&amp;rsquo;t block the main thread, or your UI will become non-responsive and 2) the core libraries are all optimized for asynchronous operation.&lt;/p&gt;
&lt;p&gt;A good example for this is fetching data from the web and responding to it. If you use NSURLSession in a function, this will background that operation. Should you use data from that asynchronous callback in an if-let-construct (i.e. change a variable in the function&amp;rsquo;s scope) and at the end of a function containing it return said data, then don&amp;rsquo;t be surprised if the breakpoint of the function return is hit before the network request has occured. This is fun to debug.&lt;/p&gt;
&lt;p&gt;You could force this request to become synchronous &lt;a href=&#34;https://codemastergabriel.wordpress.com/2015/07/29/using-data-semphores-in-ios-synchronous-request/&#34;&gt;with a semaphore&lt;/a&gt; but the better solution is figure out a way to react dynamically to the request once it comes back or put the network operation into a place before you need to depend on that data (Easily possible in my case, not always possible, of course.)&lt;/p&gt;
&lt;h2 id=&#34;understanding--debugging-storyboards&#34;&gt;Understanding &amp;amp; debugging storyboards&lt;/h2&gt;
&lt;p&gt;While it is quite nice that a GUI editor makes drag-and-drop for creating screens possible, it also comes with many frustrations. You should keep the following in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Base values are good for adding elements and connecting them.&lt;/li&gt;
&lt;li&gt;Final values are required for the actual positioning. &lt;/li&gt;
&lt;li&gt;Even though you can do portrait and landscape for iPhone and iPad in one app. Don&amp;rsquo;t (for now). It reduces unnecessary complexity, since each element on each screen variant is likely to require subtle different constraints.&lt;/li&gt;
&lt;li&gt;Also, don&amp;rsquo;t assume that just because you removed an IBOAction or Outlet, that the link was dropped. You have to find the element in the storyboard and unlink them (via right-click) or you will get a build or runtime error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t actually have any good solutions or tutorials for working with storyboards and their constraints. My recommendation would be to use a layouting tool such as Sketch first, so that you can start cleanly in Xcode and only add what constraints you actually want to set and then add required missing constraints. Feel free to tweet me recommendations for better storyboard work though (&lt;a href=&#34;https://twitter.com/grahl&#34;&gt;@grahl&lt;/a&gt;).&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Diving into Swift Part 1</title>
            <link>https://www.grahl.ch/2016/01/03/diving-into-swift-part-1/</link>
            <pubDate>Sun, 03 Jan 2016 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2016/01/03/diving-into-swift-part-1/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Bildschirmfoto_2016-01-02_um_16.00.06.png&#34; alt=&#34;&#34;&gt;
While I mostly do PHP, I wanted to dive deeper into a more complex software stack and Swift was a good reason to do so: Get into desktop and app development with a modern, readable language and do things that not only live in a browser. &lt;/p&gt;
&lt;p&gt;Turns out, the learning cliff is much steeper than in the usual web stack, at least if you want to build something which doesn&amp;rsquo;t fall neatly into the handful of online tutorial use-cases. Not to put too fine a point on it but a certain CMS with a &lt;a href=&#34;http://sixrevisions.com/web-development/drupal-learning-curve/&#34;&gt;tad higher learning curve&lt;/a&gt; than similar ones is extremely simple in comparison.&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;You need a Mac, otherwise this is pointless. Then you need Xcode from the Mac App Store. Be prepared to shell out $100 each year, if you want to get your code on anything useful, since you&amp;rsquo;ll need to sign up for the developer program to get your builds signed. &lt;/p&gt;
&lt;p&gt;Not essential but pretty nice is the integration OS X Server provides (another $20). If you have that running on, say, a Mac Mini, you can assign build Bots to automatically build and test your application, offloading some work from your local machine, on which you&amp;rsquo;ll be waiting to compile all the time during development anyway.&lt;/p&gt;
&lt;p&gt;I began with a discounted legacy course from &lt;a href=&#34;https://www.bitfountain.io&#34;&gt;Bitfountain&lt;/a&gt;, which was fine in general, but I have not come across any material which blew me away, so consider researching and trying out a few budget alternatives before committing to a pricier course. I do think just reading the documentation is insufficent, unless you have a CS degree and a background in comparable languages and software stacks.&lt;/p&gt;
&lt;h2 id=&#34;first-impressions&#34;&gt;First impressions&lt;/h2&gt;
&lt;p&gt;In a nutshell: It&amp;rsquo;s challenging, but it&amp;rsquo;s fun. &lt;/p&gt;
&lt;p&gt;Some things are a bit special, such as discovering how Storyboards are connected via drag-and-drop with the code. Tutorials are definitely helpful for things such as this. Nonetheless, it&amp;rsquo;s pretty straightforward to get a demo application up and running with the boilerplate code Xcode provides and start experimenting from there.&lt;/p&gt;
&lt;p&gt;Consider doing the following if you also want to start with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have a look into the &lt;a href=&#34;https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11&#34;&gt;official Swift book&lt;/a&gt; to discover some general principles behind the language and usage patterns.&lt;/li&gt;
&lt;li&gt;Consider starting with an iOS application instead of OS X, in my experience the documentation is vastly greater for the former than the latter.&lt;/li&gt;
&lt;li&gt;Have a look at &lt;a href=&#34;http://www.raywenderlich.com/category/swift&#34;&gt;Wenderlich&amp;rsquo;s tutorial&lt;/a&gt;, I&amp;rsquo;ve found them to be a bit better than the rest. StackOverflow is of course also often helpful / necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;swift-12-20-21-&#34;&gt;Swift 1.2, 2.0, 2.1, &amp;hellip;&lt;/h3&gt;
&lt;p&gt;I initially started experimenting with a version of Swift before 2.0 had come out. This was not perfect timing, since 2.0 broke a lot of things. While Xcode made a valiant attempt to update code which no longer complied with the spec (println is print, etc.), manual intervention is often necessary, especially in terms of error handling and unwrapping optionals (more on that in part 2). &lt;/p&gt;
&lt;p&gt;Those changes by themselves are not dealbreakers but it makes googling a solution quite cumbersome. The majority of tutorials and how-tos use Xcode 6.2 or 6.3 (major changes already between those) and earlier Swift versions, which means it will look different and it won&amp;rsquo;t compile. Xcode does help you in fixing your non-compiling code but it isn&amp;rsquo;t really fun if you don&amp;rsquo;t have good examples to learn from and I have often tried three different solution from the web until I came close enough to something which worked after massaging it. Try your search with &amp;ldquo;$YOURISSUE swift 2&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The reliance on tutorials / help from the IDE is also one of the reasons why I have not yet given &lt;a href=&#34;https://www.jetbrains.com/objc/&#34;&gt;AppCode&lt;/a&gt; a real try, before I am minimally capable in this language, though I&amp;rsquo;d love to use JetBrains better autocompletion support and other niceties.&lt;/p&gt;
&lt;h3 id=&#34;alternative-uses&#34;&gt;Alternative uses&lt;/h3&gt;
&lt;p&gt;Finally, I&amp;rsquo;m hopeful that open sourcing Swift will bring the language further into other venues, though I haven&amp;rsquo;t tried it out on Linux, yet. Without good new cross-platform UI libraries I&amp;rsquo;m skeptical though, if that will ever catch on. However, you aren&amp;rsquo;t limited to running GUI applications on OS X and iOS, you can also use Swift to generate classic binaries akin to javac, as well as executing it as if &lt;a href=&#34;http://practicalswift.com/2014/06/07/swift-scripts-how-to-write-small-command-line-scripts-in-swift/&#34;&gt;it were a script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://www.grahl.ch/blog/diving-into-swift-part-2&#34;&gt;the next post&lt;/a&gt; I&amp;rsquo;ll delve deeper into the specific hurdles I faced when trying to begin building apps.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>AppleScript: How long is my reading list?</title>
            <link>https://www.grahl.ch/2015/12/06/applescript-how-long-is-my-reading-list/</link>
            <pubDate>Sun, 06 Dec 2015 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2015/12/06/applescript-how-long-is-my-reading-list/</guid>
            <description>&lt;p&gt;If you are using Safari it&amp;rsquo;s easy and powerful to interact with your browser via AppleScript. For example, trying to figure out how many items there are in that reading list can be done as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set tfile to (path to library folder from user domain as text) &amp;amp; &amp;quot;Safari:bookmarks.plist&amp;quot;
set counter to 0
tell application &amp;quot;System Events&amp;quot;
    repeat with i in (property list items of property list item &amp;quot;Children&amp;quot; of property list file tfile)
        tell i to try
            if value of property list item &amp;quot;Title&amp;quot; = &amp;quot;com.apple.ReadingList&amp;quot; then
                repeat with thisDict in (get value of property list item &amp;quot;Children&amp;quot;)
                    set counter to counter + 1
                end repeat
                exit repeat
            end if
        end try
    end repeat
end tell
return counter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to do more, such as extract the URL from that list or something similar, check out &lt;a href=&#34;https://discussions.apple.com/thread/7054847?start=0&amp;amp;tstart=0&#34;&gt;this thread from which I started&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Now running Drupal 8</title>
            <link>https://www.grahl.ch/2015/11/13/now-running-drupal-8/</link>
            <pubDate>Fri, 13 Nov 2015 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2015/11/13/now-running-drupal-8/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/d8_migration_0.png&#34; alt=&#34;&#34;&gt;
Over the past year I have tried numerous times to upgrade my personal blog to Drupal 8, after it has been running for the last 9 years on Drupal 5, 6 and 7. Now the migration tasks are finally stable enough that migration is possible, after helpful people found the remaining issue for my case, which was &lt;a href=&#34;https://www.drupal.org/node/2604484#comment-10561328&#34;&gt;file and picture assignment&lt;/a&gt; to nodes.&lt;/p&gt;
&lt;p&gt;And &lt;strong&gt;you should absolutey update if you can&lt;/strong&gt;. The system is not only so much cleaner it also solves long-standing issues in file and image handling, internationalization and much more, it&amp;rsquo;s a pleasure to use, even with several contrib modules still missing.&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;If you want to try a migration, a good starting point is &lt;a href=&#34;https://www.drupal.org/node/2257723&#34;&gt;this Drupal.org page&lt;/a&gt;. This assumes that you already have a Drupal 8 site running and I highly recommend using the Composer template referenced in the &lt;a href=&#34;https://www.lullabot.com/articles/goodbye-drush-make-hello-composer&#34;&gt;excellent Lullabot article &lt;/a&gt;on the topic to do so, you can easily manager your core and contrib modules there already.&lt;/p&gt;
&lt;p&gt;Here are my tips for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don&amp;rsquo;t use the GUI for migrations, calling it via drush is faster and easier. You are likely to rerun your migration many times, until everything fits with migrate-upgrade, migrate-rollback, migrate-status and migrate-reset-status. &lt;/li&gt;
&lt;li&gt;Note that if drush is telling you a migration is still running for something because an error was thrown, you still have to stop it. Rollback will not be enough, you need to reset the task as well.&lt;/li&gt;
&lt;li&gt;Before you start migrating, create a clean database dump, so you can easily verify that your configuration isn&amp;rsquo;t at fault.&lt;/li&gt;
&lt;li&gt;Be careful with disabling extraneous text formats, you might have content which was using it and getting that transferred is incredibly cumbersome (I migrated again, when I noticed that).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;whats-missing&#34;&gt;What&amp;rsquo;s missing&lt;/h2&gt;
&lt;p&gt;Contrib module are very much hit and miss with 8.0 RC 3, there are some which work perfectly and others where work hasn&amp;rsquo;t really started yet. Here&amp;rsquo;s what you can try to manage things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Always google the module name with &amp;ldquo;drupal 8&amp;rdquo;, an issue is likely to exist and in many cases a third-party repository contains a newer state than drupal.org / composer.&lt;/li&gt;
&lt;li&gt;Consider using &lt;a href=&#34;https://github.com/cweagans/composer-patches&#34;&gt;cweagans/composer-patches&lt;/a&gt; to track patches you need to make a particular version run.&lt;/li&gt;
&lt;li&gt;Write the port yourself. &lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
        <item>
            <title>Using Textexpander with Drupal</title>
            <link>https://www.grahl.ch/2015/10/24/using-textexpander-with-drupal/</link>
            <pubDate>Sat, 24 Oct 2015 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2015/10/24/using-textexpander-with-drupal/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/textexpander_drupal.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using Textexpander for a while and recommend using a snippet tool very highly, especially if you have to deal with a significant amount of email, it&amp;rsquo;s a lifesaver. It&amp;rsquo;s also quite useful for development.&lt;/p&gt;
&lt;p&gt;On the one hand you can save yourself time by creating shortcuts to heavily used commands. For most tools, such as Git, there are already existing collections out there but you can also easily and quickly create them with a few clicks yourself. Here&amp;rsquo;s one of my most used ones, which provides me with the cursor exactly where it needs to be to type in a commit message. (When I don&amp;rsquo;t want to auto-add, I just go to then end of the line and delete what&amp;rsquo;s there.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &amp;quot;%|&amp;quot; -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of my favorite ones is setting up a database. I do not want to deal with a third-party tool just to manage what amounts to creating a database a month and also not have to waste 2 minutes to get the syntax of this command right:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE DATABASE %fill:database%; GRANT ALL PRIVILEGES ON %fill:database%.* TO &#39;%fill:username%&#39;@&#39;%filltext:name=hostname:default=localhost%&#39; IDENTIFIED BY &#39;%fill:password%&#39;; FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which then provides me with this nice dialog to focus on what&amp;rsquo;s important. It even automatically syncs the fields used twice (see the screenshot above).&lt;/p&gt;
&lt;h2 id=&#34;drupal-development&#34;&gt;Drupal development&lt;/h2&gt;
&lt;p&gt;When you are working on a Drupal project, the most automation often comes from abbreviating Drush even further and combining it with Git automation. Clear the cache, update, commit, etc.&lt;/p&gt;
&lt;p&gt;For example, I use a snippet for &amp;ldquo;drush up &amp;ndash;security-only -y&amp;rdquo; to update a repository, often calling that in several tabs at once for different projects. If it&amp;rsquo;s a contrib module, I have a wrapper script for automating this even further (I will provide that in a later post), however, core updates are seldom without minor reviews (did robots.txt or .htaccess change or override something, etc.). Thus, the following snippet does the stuff which should be uncritical, i.e. add files, remove versioning info, show me what&amp;rsquo;s left:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add includes misc modules profiles scripts themes%key:enter%
rm CHANGELOG.txt COPYRIGHT.txt INSTALL.mysql.txt INSTALL.pgsql.txt INSTALL.sqlite.txt INSTALL.txt LICENSE.txt MAINTAINERS.txt README.txt UPGRADE.txt%key:enter%
git status%key:enter%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Textexpander is also quite useful, when you need to write PHPTemplate code. Since it&amp;rsquo;s inline PHP, even great IDE such as PHPStorm can&amp;rsquo;t autocomplete well and you are typing the same basic wrappers again and again. That&amp;rsquo;s not necessary. So, next time you want to print out a variable, a t() or maybe render an element, just use a snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php print t(&#39;%|&#39;); ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Should you have any problems with Textexpander behaving erratically in PHPStorm when you use cursor positioning or other modifications, have a look at &lt;a href=&#34;https://youtrack.jetbrains.com/issue/IDEA-69480&#34;&gt;this issue&lt;/a&gt; and set your clipboard retention to something such as 1.&lt;/p&gt;
&lt;p&gt;Here are &lt;a href=&#34;https://www.grahl.ch/assets/other/Drupal.textexpander.zip&#34;&gt;my Drush/Drupal&lt;/a&gt; snippets as of now.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Working with navbar and ember</title>
            <link>https://www.grahl.ch/2015/03/15/working-with-navbar-and-ember/</link>
            <pubDate>Sun, 15 Mar 2015 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2015/03/15/working-with-navbar-and-ember/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/navbar.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;On two sites, including this one, I have switched from &lt;a href=&#34;https://www.drupal.org/project/admin_menu&#34;&gt;Admin Menu&lt;/a&gt; combined with Adminimal &lt;a href=&#34;https://www.drupal.org/project/adminimal_theme&#34;&gt;Theme&lt;/a&gt; / &lt;a href=&#34;https://www.drupal.org/project/adminimal_admin_menu&#34;&gt;Admin Menu&lt;/a&gt; to &lt;a href=&#34;https://www.drupal.org/project/navbar&#34;&gt;Navbar&lt;/a&gt; and &lt;a href=&#34;https://www.drupal.org/project/ember&#34;&gt;Ember&lt;/a&gt;. The reasons for this should be pretty obvious:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Once I start using 7 and 8 sites simultaneously, having a very similar UI reduces mental load since I do not have to keep two ways of interacting in mind.&lt;/li&gt;
&lt;li&gt;I can evaluate how robust and power-user friendly Navbar actually is and if it is likely to need a lot of tweaks such as Admin menu was needed for Drupal 7 to work efficently.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;first-impressions&#34;&gt;First impressions&lt;/h2&gt;
&lt;p&gt;Both modules are still rather beta and have their minor issues but overall it&amp;rsquo;s a decent look-and-feel with what&amp;rsquo;s possible in D7. The most annoying thing is not being able to create a node directly from the main menu, I have now actually started using shortcuts for this (which I previously just deactivated entirely).&lt;/p&gt;
&lt;p&gt;So, as expected, it is a bit slower since the menu obviously relies on clicks but for me the responsive layout is already enough justification to relearn one or two muscle memories.&lt;/p&gt;
&lt;p&gt;To use Devel you&amp;rsquo;ll also need the sandbox &lt;a href=&#34;https://www.drupal.org/project/2042375/git-instructions&#34;&gt;Navbar Devel&lt;/a&gt; plus &lt;a href=&#34;https://www.drupal.org/node/2452309&#34;&gt;my patch&lt;/a&gt; for it makes it look decent. &lt;a href=&#34;https://www.drupal.org/project/environment_indicator&#34;&gt;Environment Indicator&lt;/a&gt; also works right away with Navbar, so the only thing really missing from admin_menu is the fast user switching and command search but since I hardly use those, I don&amp;rsquo;t have any major impediments currently.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m hoping some things like the floating settings block on the right might make it into Ember in the future but for now I have a good working alternative.&lt;/p&gt;
&lt;p&gt;If you are in any way interested in Drupal 8 and are also maintaing any Drupal 7 sites, I highly recommend you check out &amp;ldquo;&lt;a href=&#34;https://drupalize.me/blog/201407/drupal-8-has-all-hotness-so-can-drupal-7&#34;&gt;Drupal 8 Has All the Hotness, but So Can Drupal 7&lt;/a&gt;&amp;rdquo; as well as &amp;ldquo;&lt;a href=&#34;https://amsterdam2014.drupal.org/session/future-proof-your-drupal-7-site&#34;&gt;Future-Proof your Drupal 7 site&amp;rdquo;&lt;/a&gt; for more ways to prepare for the future.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Getting Drupal&#39;s mail out of the spam folder</title>
            <link>https://www.grahl.ch/2015/03/02/getting-drupals-mail-out-of-the-spam-folder/</link>
            <pubDate>Mon, 02 Mar 2015 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2015/03/02/getting-drupals-mail-out-of-the-spam-folder/</guid>
            <description>&lt;p&gt;When your Drupal CMS sends emails, for example to deliver module update mails, password resets and so on, and they just won&amp;rsquo;t arrive as you&amp;rsquo;d like them to, you can safe yourself a lot of time by checking these items first. Gmail &lt;a href=&#34;https://support.google.com/mail/answer/81126?hl=en&amp;amp;authuser=1#authentication&#34;&gt;will give you hints where to look&lt;/a&gt; but it&amp;rsquo;s often unclear if you are actually on the right track.&lt;/p&gt;
&lt;h2 id=&#34;1-is-the-email-getting-delivered&#34;&gt;#1 Is the email getting delivered?&lt;/h2&gt;
&lt;p&gt;Check the mail log of your server, typically /var/log/mail.log or /var/log/mail/something. This should tell you whether the mail gets delivered. If it doesn&amp;rsquo;t google the error message pertaining to your MTA and go from there.&lt;/p&gt;
&lt;p&gt;If that email never arrives, not even in your spam folder, consider using another email account from a different provider to get a copy to start debugging or reduce -all to ~all in your SPF records to get a processed copy, though if you had that set, you will have to wait for the TTL set in your DNS record to time out.&lt;/p&gt;
&lt;h2 id=&#34;2did-you-look-at-the-email&#34;&gt;#2 Did you look at the email?&lt;/h2&gt;
&lt;p&gt;The headers of the email often contain sufficient information to diagnose the problem. Had I done this more thoroughly, I would have found the culprit in no time. For example, there was this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Received-SPF: none (google.com: useraccount@www.example.com does not designate permitted sender hosts) client-ip=2600:3c02::ffff:ffff:ffff:ffff;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means, that Google cannot find my SPF record. This is odd, since I am publishing one. It turns out, that my application settings were incorrect and weren&amp;rsquo;t able to set the proper from address and thus &lt;a href=&#34;mailto:localuser@www.example.com&#34;&gt;localuser@www.example.com&lt;/a&gt; was used instead of &lt;a href=&#34;mailto:myapplication@example.com&#34;&gt;myapplication@example.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This normally doesn&amp;rsquo;t happen with sendmail but you can change the behavior either with -f in the sendmail path in php.ini or &lt;a href=&#34;http://gregsumner.blogspot.ch/2010/03/sending-mail-from-drupal-using-postfix.html&#34;&gt;via this solution&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;I would not have run into this problem had myhostname not referenced the www subdomain but in this case I need it to also forward local mail without conflicting with local user accounts.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;3is-your-spf-record-ok&#34;&gt;#3 Is your SPF record OK?&lt;/h2&gt;
&lt;p&gt;If you still have problems and are publishing an SPF record the headers again should tell you if your SPF record could be found and why it&amp;rsquo;s not being matched. Most likely, your IP address is missing, so verify with the &lt;a href=&#34;http://www.kitterman.com/spf/validate.html&#34;&gt;SPF records checking tools.&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Also, &lt;strong&gt;be on the lookout for IPv6&lt;/strong&gt;, if you have it on your machine&amp;rsquo;s network, even if you are not serving an AAAA record, you need to include such an address, because you might be connecting to it via Google and they want to verify the address they receive content from, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Received-SPF: pass (google.com: domain of myapplication@example.com designates 2600:3c02::ffff:ffff:ffff:ffff as permitted sender) client-ip= 2600:3c02::ffff:ffff:ffff:ffff; Authentication-Results: mx.google.com ... )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that still doesn&amp;rsquo;t help I&amp;rsquo;d suggest moving on to burned offerings to the cloud gods.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Shutting down stgallenwiki.ch</title>
            <link>https://www.grahl.ch/2014/10/26/shutting-down-stgallenwiki.ch/</link>
            <pubDate>Sun, 26 Oct 2014 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2014/10/26/shutting-down-stgallenwiki.ch/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Screen_Shot_2014-10-26_at_15.34.44.png&#34; alt=&#34;&#34;&gt;
Localwiki was one of the &lt;a href=&#34;https://www.kickstarter.com/projects/455852114/localwiki-bring-collaborative-local-media-to-every&#34;&gt;very early projects&lt;/a&gt; on Kickstarter and succeeded in their funding way back in 2010. Since then they have created several great model communities to show off how (hyper) local wikis can be a community enabler. The idea still seems so obvious to me, like Wikipedia is in hindsight, that I am still confused why the idea has not taken off exponentially.&lt;/p&gt;
&lt;h2 id=&#34;running-a-localwiki&#34;&gt;Running a localwiki&lt;/h2&gt;
&lt;p&gt;Creating a localwiki was really easy with the documentation provided by the maintainers, it amounted to spinning up a virtual machine with Ubuntu on one&amp;rsquo;s preferred hosting plattform, adding one repository and installing one package, to get up and running. Which is just what I did under stgallenwiki.ch to get to know the platform itself (not having any Django knowledge) and to get to know my current city better. &lt;/p&gt;
&lt;p&gt;In the beginning of 2014 the service providing free mapping tiles went away and individual localwikis could choose to either migrate to the centrally managed architecture of the core development team or to acquire a license key to continue receiving tiles for maps. Since I have not been able to find enough people in St. Gallen to work on this project and am alone not motivated enough to work on it further I have now taken the site down.&lt;/p&gt;
&lt;p&gt;I am writing this post to document this canceled project and provide &lt;a href=&#34;https://www.grahl.ch/assets/other/stgallenwikich.zip&#34;&gt;an archive&lt;/a&gt; of the few pages which were created should anyone care to try something similar in the future.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>First look at CentOS 7</title>
            <link>https://www.grahl.ch/2014/09/07/first-look-at-centos-7/</link>
            <pubDate>Sun, 07 Sep 2014 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2014/09/07/first-look-at-centos-7/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Screen_Shot_2014-09-07_at_13.37.45.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;In July, a month after the release of &lt;a href=&#34;http://en.wikipedia.org/wiki/Red_Hat_Enterprise_Linux&#34;&gt;RHEL7&lt;/a&gt;, the CentOS team had &lt;a href=&#34;http://www.centos.org&#34;&gt;released their clone of the 7 series&lt;/a&gt;. Time to take a look, since this will likely be the standard version for new projects I will be building, at the very latest by the middle of next year when a minor release has further stabilized the series.&lt;/p&gt;
&lt;h2 id=&#34;new-and-noteworthy&#34;&gt;New and noteworthy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The installer&lt;/strong&gt; now aligns with what we have become familiar with based on the Gnome 3 desktop in Fedora, which is overall a good experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XFS&lt;/strong&gt; is now the default root file-system including boot. That decision seems fairly bizarre when EXT4 is the default everywhere else and has an upstream path towards Btrfs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Systemd&lt;/strong&gt; is now in full effect, so I will have to take some time to finally learn the startup script changes properly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network interfaces&lt;/strong&gt; are now carrying &amp;ldquo;&lt;a href=&#34;http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/&#34;&gt;Predictable Network Interface names&lt;/a&gt;&amp;rdquo;. While that&amp;rsquo;s a nice idea it also means that old common tricks such as &amp;ldquo;dhclient eth0&amp;rdquo; no longer work. Side note: Minimal install does not even contain ifconfig, so find your interfaces in /etc/sysconfig/network-scripts/ifcfg*, if you went with minimal. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP 5.4.16&lt;/strong&gt;, this is the part I&amp;rsquo;m most excited about, since this was the primary reason in my work to install third-party repositories such as &lt;a href=&#34;https://iuscommunity.org/pages/About.html&#34;&gt;IUS&lt;/a&gt; to get above ancient 5.3 versions present in the RHEL6 series.&lt;/li&gt;
&lt;li&gt;You will look for the package &lt;strong&gt;mysql-server&lt;/strong&gt; in vain, MySQL is now to be found via the fork &lt;strong&gt;mariadb-server&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Three lessons from amateur PHP code</title>
            <link>https://www.grahl.ch/2014/05/11/three-lessons-from-amateur-php-code/</link>
            <pubDate>Sun, 11 May 2014 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2014/05/11/three-lessons-from-amateur-php-code/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/3054826636_6c4f00742a_o.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;https://accounts-flickr.yahoo.com/photos/tnarik/3054826636/?rb=1&#34;&gt;tnarik&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;I did not get a CS degree and thus learned web development as a self-taught amateur. While that was fun, it also meant that in the process of building things without a curriculum or a mentor to provide a foundation &lt;!-- raw HTML omitted --&gt;I wrote pretty bad code in the beginning.&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;I have &lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;dug up the PHP code for a site which has been superseded with a Drupal site since 2009 and it&amp;rsquo;s a great way to show failure by example without anyone getting hurt.&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;The following shows common mistakes made by amateur PHP programmers and a few pointers on how to fix them and become a better developer.&lt;/p&gt;
&lt;h2 id=&#34;mistake-1-repeating-yourself&#34;&gt;Mistake #1: Repeating yourself&lt;/h2&gt;
&lt;p&gt;Every time you see redundant code it should give you pause. Even a decade ago I recognized this and tried to move the page header into a separate file. Here is the start of item.php and dozens of other files like it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$pagetitle=&amp;quot;Item Details​&amp;quot;;
include &amp;quot;main.php&amp;quot;;
echo &amp;quot;&amp;lt;div id=\&amp;quot;content\&amp;quot;&amp;gt;&amp;quot;;
// [...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Viewing an item would thus be the URL /item.php?id=123. This structure exemplifies that the author has not thought enough about inheritance and overall structure. Furthermore, we can see in main.php that the file is just dumping content to the output like the all other PHP files in that application:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot; xml:lang=&amp;quot;en&amp;quot; lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;?php
include &amp;quot;settings.php&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&amp;rsquo;s clear that the author has not even fully internalized procedural programming and is using includes simply to reduce redundancy in a static, linear script.&lt;/p&gt;
&lt;h3 id=&#34;fixing-the-mistakes&#34;&gt;Fixing the mistakes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Point all requests to index.php as the sole entry point and use parameters to call the respective logic.&lt;/strong&gt; Not only will you reduce errors but you can also centralize parameter checking and sanitization, as well as authentication and authorization. You can also easily add nice, readable URLs with a one-liner in htaccess.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Convert the static pages such as item.php into functions&lt;/strong&gt;. Which is basically a given, if you restructure everything around your index.php as recommended above. In this case if we had all item.php logic wrapped in a function (or more likely a collection of them), we could call it where required through our routing logic and not attempt to build a linear script for each page. Ideally, you would follow established patterns like &lt;a href=&#34;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&#34;&gt;MVC&lt;/a&gt; for your program structure when doing so.&lt;/p&gt;
&lt;p&gt;**Don&amp;rsquo;t solve this yourself. **Seriously consider if your homebrew application should continue to exist. The above points explain how one would fix something like it if you cannot ditch it but consider carefully if a framework such as &lt;a href=&#34;http://symfony.com/&#34;&gt;Symfony2&lt;/a&gt; or a CMS like &lt;a href=&#34;https://drupal.org/&#34;&gt;Drupal&lt;/a&gt; is not the better solution to start from scratch, since they provide all that essential work of handling I/O, routing pages, abstracting templating, etc. for you, if you invest the time to learn how these systems work.&lt;/p&gt;
&lt;h2 id=&#34;mistake-2-mixing-logic-data-and-display&#34;&gt;Mistake #2: Mixing logic, data and display&lt;/h2&gt;
&lt;p&gt;Here is an additional excerpt from the item.php shown above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if ($row[isbn] != NULL)
echo &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;ISBN/ISSN #:&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt; $row[isbn] &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot;;
echo &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Section:&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt; $row[section]&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot;;
// [...] 20 more &amp;lt;tr&amp;gt; like that
$commentquery = mysql_query(&amp;quot;SELECT comment FROM comments WHERE item=&#39;$row[item_id]&#39;&amp;quot;);
if (!mysql_num_rows($commentquery)==0)
echo &amp;quot;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Comments by members about this item:&amp;quot;;

while($row = mysql_fetch_array( $commentquery )) {
    echo &amp;quot;&amp;lt;div id=\&amp;quot;comdiv\&amp;quot;&amp;gt;&amp;quot;;
    echo nl2br($row[&#39;comment&#39;]);
    echo &amp;quot;&amp;lt;/div&amp;gt;&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thankfully, the query parameter was properly sanitized, so it is not a case of &lt;a href=&#34;https://xkcd.com/327/&#34;&gt;little Bobby Tables&lt;/a&gt; but it is a Smörgåsbord of bad patterns. Not only is redundancy a concern but the indiscriminate mix of data, html markup and query logic shows missing levels of abstraction. The weird string double escaping we&amp;rsquo;ll just ignore.&lt;/p&gt;
&lt;h3 id=&#34;fixing-the-mistakes-1&#34;&gt;Fixing the mistakes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use PDO and prepared statements&lt;/strong&gt;. If you write your SQL by hand and work with user input, you are asking for trouble. &lt;a href=&#34;http://ch2.php.net/pdo.prepared-statements&#34;&gt;PDO&lt;/a&gt; &lt;a href=&#34;http://code.tutsplus.com/tutorials/why-you-should-be-using-phps-pdo-for-database-access--net-12059&#34;&gt;tutorials&lt;/a&gt; &lt;a href=&#34;http://code.tutsplus.com/tutorials/php-database-access-are-you-doing-it-correctly--net-25338&#34;&gt;abound&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Functions, functions, functions.&lt;/strong&gt; The author should have seen the redundancy in his or her code and concluded that a helper function should deal with creating the table (let&amp;rsquo;s ignore whether a table is the right solution here). The block on comments could equally as well be encapsulated in a function. However, that function alone would not add much readability (at least as long as it is as short as it is now), unless one takes out the markup, which the following recommendation encompasses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create templates.&lt;/strong&gt; Even if you do not follow the general advice from mistake #1 on using an established base to build upon, you can improve legacy code by stripping out the markup into templates. Even if you don&amp;rsquo;t want to fully commit to Symfony, you could use their &lt;a href=&#34;http://twig.sensiolabs.org/&#34;&gt;Twig&lt;/a&gt; engine to do so and achieve much more readable and maintainable code. Again, consider not reinventing the wheel.&lt;/p&gt;
&lt;h2 id=&#34;mistake-3-sloppy-code-conventions&#34;&gt;Mistake #3: Sloppy code conventions&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;if (!mysql_num_rows($commentquery)==0)
echo &amp;quot;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Comments by members about this item:&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The line above we already saw in mistake #2. The problem here is that through missing brackets, spaces, and indentations the code becomes significantly harder to read. It&amp;rsquo;s easy to overlook the scope of the if-loop and for example incorrectly assume that the while-loop following it is still in it&amp;rsquo;s scope. The many formatting problems in the code were mainly due to the fact that I didn&amp;rsquo;t use software to help me stick to a standard, since I wrote it all by hand.&lt;/p&gt;
&lt;h3 id=&#34;fixing-the-mistakes-2&#34;&gt;Fixing the mistakes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use an IDE.&lt;/strong&gt; A text editor with syntax highlighting is just not enough, it&amp;rsquo;s too easy to make mistakes. Your environment, whichever you decide upon, should do auto-formatting and code checking as-you-type. I use &lt;a href=&#34;http://www.jetbrains.com/phpstorm/&#34;&gt;PHPStorm&lt;/a&gt; but the same can be achieved with free tools such as Eclipse. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Follow PSR-1/2.&lt;/strong&gt; If you have until now followed your personal preferences, just get over yourself and standardize to &lt;a href=&#34;http://www.php-fig.org/psr/psr-2/&#34;&gt;PSR-2&lt;/a&gt;, that way all your code and all modern libraries look the same. There are also tools to help you &lt;a href=&#34;http://cs.sensiolabs.org/&#34;&gt;convert your existing code&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;All of the above will not make you into a great developer but I hope to have provided helpful advice to others that I wish I had been given at the time.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Goodbye comments</title>
            <link>https://www.grahl.ch/2014/05/03/goodbye-comments/</link>
            <pubDate>Sat, 03 May 2014 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2014/05/03/goodbye-comments/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/3035341452_d6202a724f_o.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;https://accounts-flickr.yahoo.com/photos/arndog/3035341452/?rb=1/&#34;&gt;arndog&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;Today I removed this blog&amp;rsquo;s comment functionality after having it for over seven years. I do not host debates and do not have content which regularly inspires comments apart from &amp;ldquo;thanks&amp;rdquo; so I don&amp;rsquo;t think that this will in any way diminish this blog. I&amp;rsquo;m sure if someone actually needs help or has information they want to give me, they will find the about page. &lt;/p&gt;
&lt;p&gt;And on the other hand, comment spam is simply out of control. At first I had Drupal&amp;rsquo;s &lt;a href=&#34;https://drupal.org/project/captcha&#34;&gt;image captcha&lt;/a&gt; which worked well for a while but had to be exchanged for &lt;a href=&#34;https://www.google.com/recaptcha/intro/index.html&#34;&gt;Recaptcha&lt;/a&gt; but when that let too many spam posts through I switched to &lt;a href=&#34;https://mollom.com/&#34;&gt;Mollom&lt;/a&gt; and they have provided an excellent service but still let about one false negative through every other day for the 500+ attempts every day. &lt;/p&gt;
&lt;p&gt;So, to follow the spirit of &lt;a href=&#34;https://twitter.com/AvoidComments&#34;&gt;@AvoidComments&lt;/a&gt;, no more more comments here.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Using a Kindle as a monitoring tool (or not)</title>
            <link>https://www.grahl.ch/2014/04/20/using-a-kindle-as-a-monitoring-tool-or-not/</link>
            <pubDate>Sun, 20 Apr 2014 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2014/04/20/using-a-kindle-as-a-monitoring-tool-or-not/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/IMG_0290_copy.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I had an old Kindle 3 lying around and really wanted to do something fun with the e-ink display in it, especially since its Linux underneath anyway.&lt;/p&gt;
&lt;p&gt;If you browse around the web you&amp;rsquo;ll find plenty of information to get you started with the prerequisite steps of rooting the device, enabling network and ssh. Since a lot of the information is hidden in forums (ugh), I&amp;rsquo;ll point to &lt;a href=&#34;http://www.turnkeylinux.org/blog/kindle-root&#34;&gt;Liraz Siri&amp;rsquo;s tutorial&lt;/a&gt; which covers all important points.&lt;/p&gt;
&lt;h2 id=&#34;developing-a-kindlet&#34;&gt;Developing a kindlet&lt;/h2&gt;
&lt;p&gt;Unless you want to directly work on the framebuffer you will want to create a java application which the Kindle can run, called a kindlet. I strongly suggest you follow &lt;a href=&#34;https://cowlark.com/kindle/getting-started.html&#34;&gt;David Given&amp;rsquo;s guide&lt;/a&gt;. Getting his Hello World to run consistently is a good starting point for everyone like me who only dabbles with Java. Additionally, the MobileRead wiki has &lt;a href=&#34;http://wiki.mobileread.com/wiki/Kindlet_Developer_HowTo&#34;&gt;additional information&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since developing a kindlet without any resources from Amazon basically amounts to compiling, copying, opening and repeating that process it&amp;rsquo;s essential to automate that process, thus I recommend ammending Given&amp;rsquo;s excellent makekindlet script with the following  two lines (for OS X):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[...]
jarsigner -keystore $KEYSTORE -storepass password $JAR dn$USER

cp $JAR /Volumes/Kindle/documents/
diskutil eject /Volumes/Kindle 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;network-communication-in-a-kindlet&#34;&gt;Network communication in a Kindlet&lt;/h3&gt;
&lt;p&gt;To do anything useful in a Kindlet apparently requires one to have the ability to work with sockets, so the security settings need to be adjusted. A blog post by Alex Hutter &lt;a href=&#34;http://vaguehope.com/2012/01/mqtt-kindlet/&#34;&gt;explains how&lt;/a&gt;, i.e. add the following in external.policy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grant signedBy &amp;quot;Kindlet&amp;quot; {
 permission java.net.SocketPermission &amp;quot;*:443-&amp;quot;, &amp;quot;accept,connect,listen,resolve&amp;quot;;
 permission java.net.SocketPermission &amp;quot;*:80-&amp;quot;, &amp;quot;accept,connect,listen,resolve&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are stuck at such a point it&amp;rsquo;s often helpful to visit the crash.log in developer/APPNAME/work to point you in the right direction of the problem.&lt;/p&gt;
&lt;h3 id=&#34;rendering-content&#34;&gt;Rendering content&lt;/h3&gt;
&lt;p&gt;Now, the rest should be easy, right? Well, rendering local images worked fine with this &lt;a href=&#34;http://www.mobileread.com/forums/showthread.php?t=228542&#34;&gt;example snippet&lt;/a&gt;, however any permutation of loading network resources into images went exactly nowhere, getWidth() returns 0 and no image can be rendered and that&amp;rsquo;s where I&amp;rsquo;m currently stuck and discontinuing this project.&lt;/p&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The Kindle is NOT intended as a general use computing device. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The quote above is from Hutter&amp;rsquo;s blog post, I don&amp;rsquo;t have much to add to that. This was a fun project but it became clear that without getting to know Java a lot better this would not be worth the effort, compared to solving the problem with an Android app, no matter how nice the display is.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Track when you lock your desktop</title>
            <link>https://www.grahl.ch/2014/02/02/track-when-you-lock-your-desktop/</link>
            <pubDate>Sun, 02 Feb 2014 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2014/02/02/track-when-you-lock-your-desktop/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/4112797721_0568762b72_o_0.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;http://www.flickr.com/photos/way2go/4112797721/&#34;&gt;way2go&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;If you do any sort of work where you have to report your activities in 15-minute increments, you have probably experienced that it is very easy to miss things, especially if you often have to deal with interruptions.&lt;/p&gt;
&lt;p&gt;There are numerous solutions to parts of this problem such as apps which continuously poke you to update a tracker, to ones which simply &lt;a href=&#34;http://timingapp.com/&#34;&gt;log everything&lt;/a&gt; and sum it up. I use some of them but I also add another check to my routine to have more accurate timing information when I wasn&amp;rsquo;t paying attention. &lt;strong&gt;What I&amp;rsquo;m doing is writing timestamps to a file which register screen locks/unlocks as well as suspend&lt;/strong&gt; and it has in the past year helped me to significantly become more accurate in my tracking by having reference timestamps such as the following excerpt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Thu Jan 23 09:53:48 CET 2014: SCREEN_UNLOCKED
Thu Jan 23 10:02:21 CET 2014: SCREEN_LOCKED
Thu Jan 23 10:18:16 CET 2014: SCREEN_UNLOCKED
Thu Jan 23 10:27:02 CET 2014: SCREEN_LOCKED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to do this, too, just follow one of the examples below. If you want to do something similar and customize it, the problem is rather trivial if you break it down into a trigger and an action. The first is heavily dependent on your environment, the latter could be anything.&lt;/p&gt;
&lt;h2 id=&#34;linuxgnome&#34;&gt;Linux/GNOME&lt;/h2&gt;
&lt;p&gt;To react to events you&amp;rsquo;ll have to run a command at login which then continuously monitors DBUS to see if the relevant event has fired. Here is a working example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ #!/bin/bash

function write_log {
  if [ -z $1 ]; then
    1=&amp;quot;unspecified&amp;quot;
  fi
  echo -e &amp;quot;$1\t$(date)&amp;quot; &amp;gt;&amp;gt; &amp;quot;/home/youruser/time.log&amp;quot;
}

write_log &amp;quot;LOGGED_IN&amp;quot;

# Monitor gnome for screen locking. Log these events.
dbus-monitor --session &amp;quot;type=&#39;signal&#39;,interface=&#39;org.gnome.ScreenSaver&#39;&amp;quot; | \
  (
    while true; do
      read X;
      if echo $X | grep &amp;quot;boolean true&amp;quot; &amp;amp;&amp;gt; /dev/null; then
        write_log &amp;quot;SCREEN_LOCKED&amp;quot;
      elif echo $X | grep &amp;quot;boolean false&amp;quot; &amp;amp;&amp;gt; /dev/null; then
        write_log &amp;quot;SCREEN_UNLOCKED&amp;quot;
      fi
    done
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;os-x&#34;&gt;OS X&lt;/h2&gt;
&lt;p&gt;The only decent trigger method I found (albeit after only 15 minutes of googling) is the paid app &lt;a href=&#34;https://itunes.apple.com/gb/app/scenario/id423444193?mt=12&#34;&gt;Scenario&lt;/a&gt;. The app itself should be self-explanatory and allows you to put one or several scripts into the respective folders for sleep, lock, log out, etc. Since I&amp;rsquo;m much more familiar with shell scripting than Applescript, I&amp;rsquo;m just using the later to call the former. Obviously, this could be simplified.&lt;/p&gt;
&lt;h4 id=&#34;applescriptin-scenario&#34;&gt;Applescript in Scenario:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;do shell script &amp;quot;~/.bin/screenlock.sh unlock&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;shell-script&#34;&gt;Shell script:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

if [ $1 = &amp;quot;lock&amp;quot; ]
then
  echo -e &amp;quot;`date`: SCREEN_LOCKED&amp;quot; &amp;gt;&amp;gt; ~/time.log
elif [ &amp;quot;$1&amp;quot; = &amp;quot;unlock&amp;quot; ]
then
  echo -e &amp;quot;`date`: SCREEN_UNLOCKED&amp;quot; &amp;gt;&amp;gt; ~/time.log
else
  echo -e &amp;quot;`date`: UNKNOWN_ACTION&amp;quot; &amp;gt;&amp;gt; ~/time.log
fi
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>Running Drupal on a proper PHP version with IUS</title>
            <link>https://www.grahl.ch/2013/11/23/running-drupal-on-a-proper-php-version-with-ius/</link>
            <pubDate>Sat, 23 Nov 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/11/23/running-drupal-on-a-proper-php-version-with-ius/</guid>
            <description>&lt;p&gt;I recently read &lt;a href=&#34;http://www.acquia.com/blog/why-php-real&#34;&gt;Thijs Feryn post&lt;/a&gt; on the maturity of PHP and he makes a convincing argument for actually using what PHP provides with 5.5 now being stable.&lt;/p&gt;
&lt;p&gt;Drupal 7 has seen many improvements over the past year in regards to 5.4 compatibility and as it turns out, it now runs pretty smoothly, though many modules still create E_NOTICEs due to legacy code. The breakage which 5.4 caused in many places when Drupal 7 (and let&amp;rsquo;s not even talk about a 6 site) came out is not comparable with upgrading from 5.4 to 5.5, which is mostly harmless.&lt;/p&gt;
&lt;h2 id=&#34;managing-php-versions&#34;&gt;Managing PHP versions&lt;/h2&gt;
&lt;p&gt;You have basically two options for upgrading PHP on your server: find some pre-compiled packages or build from source. The latter should be avoided by nearly everyone, unless you&amp;rsquo;re willing to diligently subscribe to a release mailing list and compile again and again.&lt;/p&gt;
&lt;p&gt;On CentOS/RHEL, which is my preferred hosting platform at the moment one can rely on the excellent &lt;a href=&#34;http://iuscommunity.org/pages/About.html&#34;&gt;IUS repository&lt;/a&gt;. It makes switching between 5.3, 5.4 and 5.5 extremely easy. Once you have IUS installed, you can select your packages via the name php5&lt;strong&gt;X&lt;/strong&gt;u. Which means you can switch between 5.3 and 5.5 simply by removing the packages installed from php53u and installing again with php55u. All I needed afterwards to get PHP running again was copying the /etc/php-fpm.d/www. conf.rpmsave over www. conf and so 15 minutes later this blog is running on 5.5:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum list installed | grep php
php55u-cli.x86_64 5.5.5-2.ius.centos6 @ius
php55u-common.x86_64 5.5.5-2.ius.centos6 @ius
php55u-fpm.x86_64 5.5.5-2.ius.centos6 @ius
php55u-gd.x86_64 5.5.5-2.ius.centos6 @ius
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An additional benefit is that IUS provides packages in sync with the latest released package. This is very helpful in determining what you are actually running. For example, your server might report that you are running 5.3.3 (as stock CentOS 6.4 does), even if critical fixes have been backported by RedHat, Fedora, et al from later versions, with IUS you&amp;rsquo;ll get a proper 5.3.27.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Caveat: Of course, don&amp;rsquo;t try this on production if you don&amp;rsquo;t know what you&amp;rsquo;re doing.&lt;/em&gt;&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>A commercial marketing database lets you peak inside</title>
            <link>https://www.grahl.ch/2013/10/06/a-commercial-marketing-database-lets-you-peak-inside/</link>
            <pubDate>Sun, 06 Oct 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/10/06/a-commercial-marketing-database-lets-you-peak-inside/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Auswahl_004_b.png&#34; alt=&#34;&#34;&gt;
Array&lt;/p&gt;
&lt;p&gt;There is an old entry in my backlog of ideas for the blog which went along the lines of &lt;em&gt;try to get a copy of what data mining marketing firms have on file for you&lt;/em&gt;. Nothing ever came of that until I saw Ed Felten&amp;rsquo;s post on one of those companies &lt;a href=&#34;https://freedom-to-tinker.com/blog/felten/axciom-opens-some-consumer-data-what-should-you-do/&#34;&gt;doing just that&lt;/a&gt;, with a spiffy site to boot, and I&amp;rsquo;m really surprised about this.&lt;/p&gt;
&lt;h2 id=&#34;a-quick-primer&#34;&gt;A quick primer&lt;/h2&gt;
&lt;p&gt;Most everyone knows that companies as a whole are tracking purchasing habits and customer preferences and correlating them with socio-demographic information to sell you more stuff. The who and what is much less widely know though.&lt;/p&gt;
&lt;p&gt;Most often these collection companies are specialized businesses, which sell subscriptions to their databases, which they feed from multiple sources, especially in the U.S. private as well as public sources. At the time I jotted down a few notes on the topic &lt;a href=&#34;https://en.wikipedia.org/wiki/ChoicePoint&#34;&gt;ChoicePoint&lt;/a&gt; was one of them.&lt;/p&gt;
&lt;p&gt;Most of these companies do not simply stop at providing information on marketing data to business and government (yes, the U.S. government is not prohibited from using private databases, even if they are not allowed to create those on citizens in the first place). They often have a close relationship to data based on or provided by credit scoring agencies, such as &lt;a href=&#34;https://en.wikipedia.org/wiki/Altegrity_Risk_International&#34;&gt;Altegrity&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/TransUnion&#34;&gt;TransUnion&lt;/a&gt; or &lt;a href=&#34;https://en.wikipedia.org/wiki/Insurance_Services_Office&#34;&gt;ISU&lt;/a&gt;; which are listed as direct competitors of ChoicePoint by &lt;a href=&#34;http://www.hoovers.com/company-information/cs/competition.ChoicePoint_Inc.63602692fccd1b89.html&#34;&gt;Hoovers&lt;/a&gt;. The differences in their aggregate datasets are probably small, the impact of the result on individuals often not. ChoicePoint was later acquired by &lt;a href=&#34;https://en.wikipedia.org/wiki/LexisNexis&#34;&gt;LexisNexis&lt;/a&gt;, which primarily focuses on publication databases,  and some divisions by &lt;a href=&#34;https://en.wikipedia.org/wiki/Acxiom&#34;&gt;Acxiom&lt;/a&gt;. It shows the further agglomeration of databases of different types within these data brokers.&lt;/p&gt;
&lt;h2 id=&#34;why-aboutthedatacom&#34;&gt;Why aboutthedata.com?&lt;/h2&gt;
&lt;p&gt;If I had been asked a week ago, I would have concluded that such data brokers have no interest in providing a site such as aboutthedata.com. It seems intuitive that such brokers would want to collect as much data as possible on their subjects without giving them reason to share less data with them. Ideally, to not be noticed at all.&lt;/p&gt;
&lt;p&gt;This could be an initiative of Acxiom to improve the public perception of data mining firms such as themselves. It could be an attempt to preempt any negative coverage on data brokers due to the continuing public discourse on surveillance et al. Or it could just be an attempt to further improve their data.&lt;/p&gt;
&lt;h2 id=&#34;lets-try-it-out&#34;&gt;Let&amp;rsquo;s try it out&lt;/h2&gt;
&lt;p&gt;Since I did spent several years in the U.S. I figured that they should have at least something on me. I entered my personal data from my last residence and was surprised to find that in the categories shown above they had basically nothing, except for an inferred marital status and income bracket, which matched, but that would have been possible to extrapolate from the address and age itself I entered to register. Ed Felten was more successful but also apparently underwhelmed by the level of detail shown.&lt;/p&gt;
&lt;p&gt;My speculations on why this is so fall into two basic groups:&lt;/p&gt;
&lt;h3 id=&#34;1-they-are-only-showing-a-minimal-set&#34;&gt;1. They are only showing a minimal set&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s possible that Acxiom is only providing their basic result data and keeping any other further (and more creepy) analysis results to themselves and their clients, but since that&amp;rsquo;s pure speculation I&amp;rsquo;m going to ignore this avenue for now.&lt;/p&gt;
&lt;p&gt;Also, I have become in general skeptical of the predictive power of big data in shaping or determining customer behavior, which is in my opinion often at best on par with a trained sales representative.&lt;/p&gt;
&lt;h3 id=&#34;2-i-didnt-provide-enough-data&#34;&gt;2. I didn&amp;rsquo;t provide enough data&lt;/h3&gt;
&lt;p&gt;Two of the primary categories provided by Acxiom are basically public databases with vehicle and house ownership, neither applied to me at the time. Also, I generally did not sign up to loyalty cards. It&amp;rsquo;s possible and likely that Acxiom then either really does not have much more information on myself or that they are unable to merge incomplete datasets on my person into a consistent record.&lt;/p&gt;
&lt;p&gt;The latter case highlights the main problem with abouthedata.com:  Basically, I can be pretty certain that I&amp;rsquo;m not seeing the full list of entries Acxiom has on me, simply due to the fact that a human operator would have to make a judgment call, whether a fragment refers to the same canonical person or not. They are unlikely to be able to manually merge a significant number of entries, which would mean that there are have to be significant numbers of entries in their database which they cannot bring together in this web application by algorithm alone. They cannot ask me &amp;ldquo;is this you as well&amp;rdquo; without accidentally disclosing data from someone else in many cases, they have to err on the side of caution, more so than is probably necessary for most of their customers.&lt;/p&gt;
&lt;p&gt;Thus, I&amp;rsquo;m still left wondering what the site is supposed to accomplish. Calm me? Improve the paper spam selection? Not sure.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Bulk create searchable PDF from paper documents in Linux</title>
            <link>https://www.grahl.ch/2013/09/29/bulk-create-searchable-pdf-from-paper-documents-in-linux/</link>
            <pubDate>Sun, 29 Sep 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/09/29/bulk-create-searchable-pdf-from-paper-documents-in-linux/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/IMG_20130929_180819.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;For some documents you have to retain the original in dead tree storage format. For most documents which arrive in the mail, however, a digital copy is just fine and there really isn&amp;rsquo;t any need to retain the paper version, especially if your computer can store millions of them in the space needed for one paper binder.&lt;/p&gt;
&lt;p&gt;To archive such documents one could now buy a scanner, maybe even an ADF office appliance which spits out a searchable PDF and all that and let the device collect dust for the next few months until another batch is processed. However, you can also achieve nearly the same result with your phone and a Linux desktop.&lt;/p&gt;
&lt;h3 id=&#34;step-1-capture&#34;&gt;Step 1: Capture&lt;/h3&gt;
&lt;p&gt;Firstly you will need to actually digitize your documents, you can of course use any scanner for this but a phone can be the perfect device to quickly capture dozens of documents, often vastly faster than with a flatbed scanner optimized for photos.&lt;/p&gt;
&lt;p&gt;I personally am using the &lt;a href=&#34;http://www.wired.com/2012/05/scanner-for-smartphones/&#34;&gt;Scanbox&lt;/a&gt; to do this but any contraption which can hold your phone or digital camera steady such as a tripod mounted at similar distance to the document should do the trick. Speed is the important factor in my solution, not accurately capturing 6pt legalese in the footer.&lt;/p&gt;
&lt;h3 id=&#34;step-2-format&#34;&gt;Step 2: Format&lt;/h3&gt;
&lt;p&gt;After capturing you might need to rotate your photos first to get the page into portrait mode. Watch out though that the generic image preview in Gnome might rotate on-the-fly from EXIF data and not tell you. If you were to OCR those files, you would not get any text from it. You can check if your files still need to be rotated by opening them in GIMP, which will ask you if you want to rotate. You can bulk-rotate according to the camera setting with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mogrify -auto-orient *.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your camera orientation did not match your document orientation, you&amp;rsquo;ll have to convert by hand, the latter will do so 90 degress clockwise:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mogrify -rotate 90 *.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;step-3-process&#34;&gt;Step 3: Process&lt;/h3&gt;
&lt;p&gt;Now you are ready to convert your images to PDF with text in them, basically, all you need to do is to call hocr2pdf and tesseract, the rest of the script below is only concerned with naming things and cleaning up. Thus the packages tesseract-ocr-eng, imagemagick and exactimage should be all that&amp;rsquo;s needed on Debian-based systems, it worked flawlessly for myself with Ubuntu 13.04. Essentially, it&amp;rsquo;s a cruder version of &lt;a href=&#34;http://blog.konradvoelkel.de/2013/03/scan-to-pdfa/&#34;&gt;Konrad Voelkel&amp;rsquo;s solution&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

for f in *.jpg
do
    localname=$(basename &amp;quot;$f&amp;quot;)
    filename=&amp;quot;${localname%.*}&amp;quot;

    tesseract $f $filename -l eng hocr
    hocr2pdf -i $filename.jpg -s -o $filename.pdf &amp;lt; $filename.html

    rm $filename.html
    # I wouldn&#39;t do this, but you could...
    # rm $filename.jpg
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​Et ​voilà, you have a searchable PDF which you can locate with the desktop search of your choice, for example &lt;a href=&#34;https://launchpad.net/~recoll-backports/+archive/recoll-1.15-on&#34;&gt;Recoll&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Exporting Remember the Milk data to CSV</title>
            <link>https://www.grahl.ch/2013/06/22/exporting-remember-the-milk-data-to-csv/</link>
            <pubDate>Sat, 22 Jun 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/06/22/exporting-remember-the-milk-data-to-csv/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/5065419930_532b7c02ac_b.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;http://www.flickr.com/photos/johanl/5065419930/&#34;&gt;johanl&lt;/a&gt;&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.rememberthemilk.com&#34;&gt;Remember the Milk&lt;/a&gt;, RTM for short, is a popular web-based todo list manager. You can access your tasks in your browser, through an app and they provide a print template. Exporting the actual data is a bit more cumbersome: They consider iCalender to be the &lt;a href=&#34;https://www.rememberthemilk.com/help/?ctx=accounts.backup&#34;&gt;primary backup mechanism&lt;/a&gt; and the only other thing they provide is an Atom feed but the content is a bunch of &lt;!-- raw HTML omitted --&gt; tags. Nothing you&amp;rsquo;d want to parse.&lt;/p&gt;
&lt;p&gt;However, they also provide an API and David Waring has made use of it with his &lt;a href=&#34;http://www.davidwaring.net/projects/rtm.html&#34;&gt;rtm-cli python script&lt;/a&gt;. With very little effort I was able to amend his script to include a function to create a csv file for all general RTM fields without notes. Since it&amp;rsquo;s based on his ls function, you can use rtm-cli&amp;rsquo;s general filtering functions. If you wanted to export all unfinished tasks you would execute the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python rtm -c csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would create a UTF-8 file &lt;!-- raw HTML omitted --&gt;output.csv in your working directory with a structure as such:&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ID;List;Title;Completed;Priority;Due date;Tags;Link;
123;&amp;quot;Inbox&amp;quot;;&amp;quot;Error reporting broken&amp;quot;;;N;;;&amp;quot;http://www.example.com&amp;quot;;
124;&amp;quot;Inbox&amp;quot;;&amp;quot;Login problem&amp;quot;;;N;2013-06-14T00:00:00+02:00;&amp;quot;mysql,apache&amp;quot;;;
125;&amp;quot;Inbox&amp;quot;;&amp;quot;Add web service&amp;quot;;;N;2013-07-21T00:00:00+02:00;;;
126;&amp;quot;Inbox&amp;quot;;&amp;quot;Fix IE8&amp;quot;;;N;;&amp;quot;ie&amp;quot;;&amp;quot;http://www.example.com&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can get the code through a &lt;a href=&#34;https://bitbucket.org/grahl/rtm-cli&#34;&gt;forked repository&lt;/a&gt;. &lt;!-- raw HTML omitted --&gt;If this exporter worked for you, you could let the maintainer of rtm-cli know by choosing &lt;a href=&#34;https://bitbucket.org/dwaring87/rtm-cli/pull-request/3/adds-a-basic-csv-exporter-without-notes/diff&#34;&gt;&lt;em&gt;Approve&lt;/em&gt; in the pull request&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;2013-06-24 Update: The maintainer has accepted the pull request.&lt;/p&gt;
&lt;p&gt;2013-07-09 Update: If you encouter problems like Tyler with a missing rtm module, make sure you have pyrtm installed (e.g. pip install pyrtm)&lt;/p&gt;
&lt;p&gt;Let me know if notes is something you&amp;rsquo;d need to export, too.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Load testing a Drupal site with Locust</title>
            <link>https://www.grahl.ch/2013/06/09/load-testing-a-drupal-site-with-locust/</link>
            <pubDate>Sun, 09 Jun 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/06/09/load-testing-a-drupal-site-with-locust/</guid>
            <description>&lt;h2 id=&#34;what-is-locust&#34;&gt;&lt;img src=&#34;https://www.grahl.ch/img/locust_a_0.png&#34; alt=&#34;&#34;&gt;
What is Locust?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;http://locust.io/&#34;&gt;Locust &lt;/a&gt;is an exciting new framework to do load testing on a site. Test scenarios are written in Python and are easily customizable. I used it to create benchmarks for &lt;a href=&#34;https://www.grahl.ch/blog/cloud-performance-compared-new-relic&#34;&gt;a performance comparison &lt;/a&gt;of several cloud hosting providers. &lt;/p&gt;
&lt;p&gt;There are several alternatives to Locust, such as the humble apache benchmark (ab), Siege, Proxysniffer and many others. Though, in my opinion, none of those offer the ability to realistically mimic site specific usage patterns, while still being incredibly easy to set up, providing a great interface for using it, and being open source. &lt;/p&gt;
&lt;p&gt;It also offers features such as distributed load testing through a master/client setup as well as &amp;ldquo;ramping up&amp;rdquo; load testing to determine stability limitations, though I did not make use of those features.&lt;/p&gt;
&lt;h2 id=&#34;writing-scripts&#34;&gt;Writing scripts&lt;/h2&gt;
&lt;p&gt;Since test scenarios are basically just python functions which are extending Locust&amp;rsquo;s base functions, one has to first outline the tasks a simulated user should make.&lt;/p&gt;
&lt;p&gt;There is nothing wrong with simply specifying a list of urls (possibly parsing a sitemap.xml) and then relying on Locust to let requests randomly hit those URLs within predefined, randomized intervals. Based on the base example on &lt;a href=&#34;http://locust.io/&#34;&gt;Locust&amp;rsquo;s front page&lt;/a&gt;, the following works for a sitemap generated by Drupal&amp;rsquo;s XML Sitemap module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/python

import random

from locust import Locust, TaskSet, task
from pyquery import PyQuery

class RandomSitemapWalk(TaskSet):
    def on_start(self):
        r = self.client.get(&amp;quot;/sitemap.xml?page=1&amp;quot;)
        pq = PyQuery(r.content, parser=&#39;html&#39;)
        self.sitemap_links = []
        for loc in pq.find(&#39;loc&#39;):
            self.sitemap_links.append(PyQuery(loc).text())

    @task(10)
    def load_page(self):
        url = random.choice(self.sitemap_links)
        r = self.client.get(url)

class AwesomeUser(Locust):
    task_set = RandomSitemapWalk
    host = &amp;quot;http://www.example.com&amp;quot;
    
    min_wait = 1  * 1000
    max_wait = 10 * 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can start Locust with that, set the number of users you want to be spawned and start testing your application. &lt;img src=&#34;https://www.grahl.ch/img/locust_b.png&#34; alt=&#34;locust start screen&#34;&gt;&lt;/p&gt;
&lt;p&gt;Those tasks by themselves aren&amp;rsquo;t that special, one could as easily parse the sitemap with awk/sed, pass it to Sieve and get a similar results. As the example on their front page shows, procedures such as logging in are trivial and there we can build complex workflows.&lt;/p&gt;
&lt;h3 id=&#34;profiling-logged-in-users&#34;&gt;Profiling logged in users&lt;/h3&gt;
&lt;p&gt;Most of the time, anonymous requests aren&amp;rsquo;t really the optimization problem, at least if your node-to-hit ratio isn&amp;rsquo;t so low that Varnish isn&amp;rsquo;t already serving those users quickly. So, to simulate logged in users (as their primary example shows), all that&amp;rsquo;s needed is to POST to the login form, adapted to Drupal 6 this would be done as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def on_start(self):
    self.client.post(&amp;quot;/user/login&amp;quot;, {
        &amp;quot;name&amp;quot;: &amp;quot;loadtest@example.com&amp;quot;,
        &amp;quot;pass&amp;quot;: &amp;quot;loadtest&amp;quot;,
        &amp;quot;form_id&amp;quot;: &amp;quot;user_login&amp;quot;,
        &amp;quot;op&amp;quot;: &amp;quot;Log in&amp;quot;
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From there on we could either reuse our sitemap and let users randomly hit pages or do something with their second base tutorial &amp;ldquo;Example with HTML parsing&amp;rdquo; and start responding to the content actually delivered to a logged in user. The following would log in, read the front page, index all links, and choose a target at random. By itself that is rather limited compared to the sitemap set but it should be trivial to work out how to extend this to walk through a varying number of link depths:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class WalkPages(TaskSet):
    def on_start(self):
        self.client.post(&amp;quot;/user/login&amp;quot;, {
            &amp;quot;name&amp;quot;: &amp;quot;loadtest@example.com&amp;quot;,
            &amp;quot;pass&amp;quot;: &amp;quot;loadtest&amp;quot;,
            &amp;quot;form_id&amp;quot;: &amp;quot;user_login&amp;quot;,
            &amp;quot;op&amp;quot;: &amp;quot;Log in&amp;quot;
        })
        # assume all users arrive at the index page
        self.index_page()
    

    @task(10)
    def index_page(self):
        r = self.client.get(&amp;quot;/&amp;quot;)
        pq = PyQuery(r.content)
        link_elements = pq(&amp;quot;a&amp;quot;)
        self.urls_on_current_page = []
        for l in link_elements:
          if &amp;quot;href&amp;quot; in l.attrib:
            self.urls_on_current_page.append(l.attrib[&amp;quot;href&amp;quot;])

    @task(30)
    def load_page(self):
        url = random.choice(self.urls_on_current_page)
        r = self.client.get(url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now download the results of these tests as CSV and analyze in depth on the basis of average page performance or fulfillment rate per percentage range per page, or simply store those results for future comparison.&lt;/p&gt;
&lt;h2 id=&#34;where-to-go-from-here&#34;&gt;Where to go from here&lt;/h2&gt;
&lt;p&gt;First of all, I think that defining use cases for a web project at the outset and defining a load testing script along those expected behaviours is a great step in modelling a platform before launch. Furthermore, it makes it easier to evaluate how additional features might impact a live site and its users before deployment. So, just writing a task list and then using Locust&amp;rsquo;s task priorities would be a great way to &lt;!-- raw HTML omitted --&gt; more closely  emulate realistic user navigation paths. &lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;What these examples don&amp;rsquo;t do is act on the Javascript on the pages retrieved. I was considering to attempt this with the &lt;a href=&#34;https://code.google.com/p/pyv8/&#34;&gt;pyv8 wrapper&lt;/a&gt; but haven&amp;rsquo;t gotten around to it yet.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Cloud performance compared with New Relic</title>
            <link>https://www.grahl.ch/2013/06/01/cloud-performance-compared-with-new-relic/</link>
            <pubDate>Sat, 01 Jun 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/06/01/cloud-performance-compared-with-new-relic/</guid>
            <description>&lt;p&gt;Recently I ran into some performance problems with a moderately complex Drupal 6 site running on EC2. &lt;a href=&#34;https://newrelic.com/&#34;&gt;New Relic&lt;/a&gt; reported an average application time around 1000-1200ms, clearly too high. Since the site was already rather optimized with MySQL being tuned, using Nginx and Varnish, as well as APC and Memcache I started to suspect the instance itself.&lt;/p&gt;
&lt;p&gt;I came across &lt;a href=&#34;http://uggedal.com/journal/vps-performance-comparison/&#34;&gt;an interesting post&lt;/a&gt; from 2009 which did seem to indicate that processing performance, especially in regards to I/O, varied widely across cloud hosting providers. This led me to switch to Linode with that site but I made some tests along the way to show how instances differ.&lt;/p&gt;
&lt;h2 id=&#34;test-setup&#34;&gt;Test setup&lt;/h2&gt;
&lt;p&gt;To make relevant comparisons between the individual offerings I created a &lt;a href=&#34;http://locust.io/&#34;&gt;Locust script&lt;/a&gt; (more on that in a later post) to log into the site and crawl a random but limited set of pages. The test ran with an average of 10 concurrent users for around half an hour each.&lt;/p&gt;
&lt;h3 id=&#34;disclaimer&#34;&gt;Disclaimer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The tests wer run on a single instance, they are thus not statistically sound and your mileage &lt;strong&gt;wiIl&lt;/strong&gt; vary; though I do think that the comparisons give valuable insight.&lt;/li&gt;
&lt;li&gt;The EC2 instances were all run from the same EBS volume in US East and should be directly comparable, for Linode the same OS and setup was undertaken but details do differ.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;results&#34;&gt;Results&lt;/h2&gt;
&lt;p&gt;My laptop was used to establish a baseline performance with real hardware, which came in at 272ms. To get below this it would be necessary to look at individual features and see if they can optimized or removed, which isn&amp;rsquo;t really an option for me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_016-0.png&#34; alt=&#34;performance laptop&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;span-stylefont-size-13px-font-weight-normal-line-height-1538emthe-ec2-micro-instance-was-basically-unsable-and-ramped-up-to-an-application-time-of-4030msspan&#34;&gt;&lt;!-- raw HTML omitted --&gt;The EC2 micro instance was basically unsable and ramped up to an application time of 4030ms.&lt;!-- raw HTML omitted --&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_016-1.png&#34; alt=&#34;performanc ec2 micro&#34;&gt;&lt;/p&gt;
&lt;p&gt;The small instance, which was also used in production, performed similarly to the usage I saw on the live site and clocked in at 1180ms.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_016-2.png&#34; alt=&#34;performance ec2 small&#34;&gt;&lt;/p&gt;
&lt;p&gt;When switching to an EC2 medium instance, performance was finally at a decent average with 533ms.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_016-4.png&#34; alt=&#34;performance ec2 medium&#34;&gt;&lt;/p&gt;
&lt;p&gt;In contrast, the entry-level from Linode &amp;ldquo;Linode 1GB&amp;rdquo; averaged 841ms.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_016_1.png&#34; alt=&#34;performance linode 1gb&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;comparison&#34;&gt;Comparison&lt;/h3&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;The conclusion I drew from this is that the performance-to-price ratio is too low for using EC2 when running such a Drupal site with a single host and hosting costs are an issue, such as for non-profit organzisations or private sites.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;These tests are relevant for quickly delivering a site with a predictable and limited usage pattern: speed, not scalibility. &lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;If I had a highly fluctuating user load (e.g. mass media events) the situation might be quite different. In those cases, launching a few more instances and paying spot prices for medium or larger instances for the time needed is probably still cheaper than paying a monthly fee for those instances at a hoster like Linode. &lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;I do want to highlight that this is highly dependent on your application and its users. For example, I&amp;rsquo;m running &lt;!-- raw HTML omitted --&gt;&lt;a href=&#34;http://www.stgallenwiki.ch/&#34;&gt;a Django-based site&lt;/a&gt;&lt;!-- raw HTML omitted --&gt; on a micro instance in EC2 Ireland and it comes in at an impressive &lt;!-- raw HTML omitted --&gt;&lt;strong&gt;92ms&lt;/strong&gt; &lt;!-- raw HTML omitted --&gt;at a very good price:&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_016-5.png&#34; alt=&#34;performance django ec2 micro&#34;&gt;&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>TinyPrinter in a box</title>
            <link>https://www.grahl.ch/2013/05/12/tinyprinter-in-a-box/</link>
            <pubDate>Sun, 12 May 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/05/12/tinyprinter-in-a-box/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/pi1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;After coming across &lt;a href=&#34;http://pikiosk.tumblr.com/post/38866317521/printing-with-raspberry&#34;&gt;a great tutorial on pikiosk&lt;/a&gt; on how to interface a thermal printer with a Raspberry Pi I knew that I had try building one of those. I wasn&amp;rsquo;t as interested in printing graphics rather than making it easily accessible, so I chose to use their code as a basic service for printing text and doing everything else in a small web frontend; which gave me an opportunity to delve into Symfony 2.&lt;/p&gt;
&lt;h2 id=&#34;assembly&#34;&gt;Assembly&lt;/h2&gt;
&lt;p&gt;With the tutorial above, connecting the Pi via GPIO was a piece of cake and most of the time consisted of finding a stable pin connection without soldering it into place (male pin header casing without pin worked very will together with breadboard cabling). The Pi&amp;rsquo;s GPIO connector is really the key element for its usefulness and fills a great niche there. Without a full Linux doing anything non-trivial with networking isn&amp;rsquo;t really fun on. This is especially true for Arduino and Android is still to cumbersome as an intermediary there (but that&amp;rsquo;s a different post).&lt;/p&gt;
&lt;p&gt;The casing itself is a pretty durable spare tupperware container I had lying around, a bit too durable to work smoothly with it. Should I work on another enclosure I&amp;rsquo;ll likely choose a thinner plastic or similar material. Nonetheless, a Dremel with a cut-off wheel and a drill was sufficient to cut a square hole in the top and a hole to feed the power cabling through. A spare wifi adapter provides connectivity.&lt;/p&gt;
&lt;h2 id=&#34;symfony-2&#34;&gt;Symfony 2&lt;/h2&gt;
&lt;p&gt;I really enjoyed working with the structure and tools that Symfony provides and how the documentation makes it easy to create a clean and structured project from scratch. The web debug toolbar (&lt;a href=&#34;http://symfony.com/doc/2.0/quick_tour/the_big_picture.html#understanding-the-fundamentals&#34;&gt;see here&lt;/a&gt;) in particular is a nice tool to efficiently debug your application. Having some of those elements such as routing and Twig in Drupal is going to be a great advancement.&lt;/p&gt;
&lt;p&gt;The only thing which was annyoing here was the template inheritance structure, the documentation could be a bit more detailed on this topic. In particular how and where Twig uses &lt;em&gt;extend&lt;/em&gt; to inherit elements and which are automatically aggregated, which are manually deployed and so on.  &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/pi2.jpg&#34; alt=&#34;web frontend for tinyprinter&#34;&gt;&lt;/p&gt;
&lt;p&gt;Making printouts with TinyPrinter is surprisingly addictive. &lt;!-- raw HTML omitted --&gt;The code is on &lt;!-- raw HTML omitted --&gt;&lt;a href=&#34;https://github.com/grahl/Raspi-Thermal-Printer&#34;&gt;Github&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;.&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Quick ad-hoc file transfer with IPv6</title>
            <link>https://www.grahl.ch/2013/04/28/quick-ad-hoc-file-transfer-with-ipv6/</link>
            <pubDate>Sun, 28 Apr 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/04/28/quick-ad-hoc-file-transfer-with-ipv6/</guid>
            <description>&lt;p&gt;Nowadays I rarely use wired networking at home. However, when you want to transfer 50GB or more, the round-trip over the wireless router can take several hours to half a day. &lt;/p&gt;
&lt;p&gt;The easiest solution is of course to attach two ethernet cables to your home router and be done with it. Depending on your setup, though, that might be cumbersome with a desktop in another room or not having 10m or more of ethernet cabling available. So, in those instances it makes sense to dust off a crossover cable or a switch to connect the devices together.&lt;/p&gt;
&lt;p&gt;In IPv4-land one would now have to configure a local network with static addresses in the 192.168.0.0/16 or 10.0.0.0/8 range. With IPv6 enabled devices, however, such an address is already present in the form of a link-local address. The following example shows how to discover devices on the local network with a broadcast ping and then how to connect as usual via interface em1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ping6 -I em1 ff02::1
PING ff02::1(ff02::1) from fe80::f2de:f1ff:f1ff:f1ff em1: 56 data bytes
64 bytes from fe80::f2de:f1ff:f1ff:f1ff: icmp_seq=1 ttl=64 time=0.079 ms
64 bytes from fe80::250:8dff:8dff:8dff: icmp_seq=1 ttl=64 time=0.393 ms (DUP!)
$ scp myfile user@[fe80::250:8dff:8dff:8dff%em1]:/home/user/target
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>Drupal quick tip: Ignore custom modules when checking for updates</title>
            <link>https://www.grahl.ch/2013/01/22/drupal-quick-tip-ignore-custom-modules-when-checking-for-updates/</link>
            <pubDate>Tue, 22 Jan 2013 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2013/01/22/drupal-quick-tip-ignore-custom-modules-when-checking-for-updates/</guid>
            <description>&lt;p&gt;When you are doing Drupal development and have complex deployment scenarios (I blogged about that &lt;a href=&#34;http://blog.namics.com/2012/07/komplexe-deployments-mit-drupal.html&#34;&gt;on the Namics blog&lt;/a&gt; [German]), it&amp;rsquo;s not unusual to have several modules containing custom functionality, as well as several Features modules. In a large site this can quickly become a dozen or more, depending on the aggregation and splitting of various functionality across modules. All of those Features will be checked for updates, every time the Update status module is run and will not find any releases for it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Selection_013.png&#34; alt=&#34;update status showing features module&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now that&amp;rsquo;s annoying and &lt;a href=&#34;http://drupal.stackexchange.com/questions/27791/how-to-get-rid-of-security-updates-available-warnings&#34;&gt;helpful Stack Overflow/Drupal Answers people&lt;/a&gt; have already found the hook you need to fix it. Of course there is a hook for that. The code snippet taken from there is reproduced below. I amended it to also contain a second hook, which then also excludes these modules from being checked when translations are checked by the &lt;a href=&#34;http://drupal.org/project/l10n_update&#34;&gt;Localization update&lt;/a&gt; module.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function mymodule_update_projects_alter(&amp;amp;$projects) {
  $blacklist = array(
    &#39;collection_feature&#39;,
    //etc.
  );

  foreach ($blacklist as $module) {
    unset($projects[$module]);
  }
}

function mymodule_l10n_update_projects_alter(&amp;amp;$projects) {
  mymodule_update_projects_alter($projects);
}
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>Quick tip: Fixing common name / server name warning messages</title>
            <link>https://www.grahl.ch/2012/11/01/quick-tip-fixing-common-name-/-server-name-warning-messages/</link>
            <pubDate>Thu, 01 Nov 2012 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2012/11/01/quick-tip-fixing-common-name-/-server-name-warning-messages/</guid>
            <description>&lt;p&gt;When you configure Apache httpd to server SSL you&amp;rsquo;ll most likely define a VirtualHost, give it a ServerName, possibly a ServerAlias and various other settings. If you did everything correctly, the site will come up with the familiar green/gold/blue https markers, instead of a big red warning page. If you got to this point but you are still seeing the following error message in your logs, most likely, you have flipped your ServerName and the certificate&amp;rsquo;s alternative name.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[warn] RSA server certificate CommonName (CN) `example.com&#39; does NOT match server name!?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At least this turned out to be the mistake I made when configuring one server with a certificate which had example.com as the common name and &lt;a href=&#34;https://www.example.com&#34;&gt;www.example.com&lt;/a&gt; as the alternative name. You can find what your certificate delivers (in Chrome) under &amp;ldquo;Certificate Fields&amp;rdquo;, &amp;ldquo;Extensions&amp;rdquo;, &amp;ldquo;Certificate Subject Alternative Name&amp;rdquo;. Your VirtualHost has to match the certificate&amp;rsquo;s preference to not throw the warning. So simply change the former to the latter, even if you are primarily (or only) serving from &lt;a href=&#34;https://www.example.com&#34;&gt;www.example.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Logs an error in my case:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ServerName www.example.com
ServerAlias example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Does not log an error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ServerName example.com
ServerAlias www.example.com
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>Migrating Firefox&#39;s password storage to Loxodo</title>
            <link>https://www.grahl.ch/2012/04/06/migrating-firefoxs-password-storage-to-loxodo/</link>
            <pubDate>Fri, 06 Apr 2012 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2012/04/06/migrating-firefoxs-password-storage-to-loxodo/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/2011843092_1608824136_o.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;https://secure.flickr.com/photos/myklroventine/2011843092/&#34;&gt;myklroventine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Passwords are inevitable. At least for the next few years. So it makes sense to find a reasonably secure but efficient way of managing them.&lt;/p&gt;
&lt;p&gt;For the last few years, I&amp;rsquo;ve used a combination of CLI password generator, Firefox&amp;rsquo;s password manager with a master password, a manually encrypted reference and my memory. This was an adequate solution but has certain limitations in terms of dependence on a specific browser profile and manual workarounds when a specific site misbehaves. So I looked around.&lt;/p&gt;
&lt;h2 id=&#34;choosing-a-password-manager&#34;&gt;Choosing a password manager&lt;/h2&gt;
&lt;p&gt;There are hunders of password managers available today, so I&amp;rsquo;m not going to attempt to summarize even a portion of them here. &lt;a href=&#34;https://pwsafe.org/&#34;&gt;Password Safe&lt;/a&gt; stands out from the rest, though. First, it helps that its original author is a &lt;a href=&#34;https://www.schneier.com/passsafe.html&#34;&gt;credible source&lt;/a&gt;. More importantly, though, the file format is documented and &lt;a href=&#34;http://passwordsafe.sourceforge.net/relatedprojects.shtml&#34;&gt;alternative clients and libraries&lt;/a&gt; are available. Also, there is an &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.jefftharris.passwdsafe&#34;&gt;Android app &lt;/a&gt;available, too, and it comes with sensible permissions. I look forward to using this when away from my laptop. &lt;/p&gt;
&lt;p&gt;A Linux client (Beta) now exists of the original Password Manager and I might try it out sometime regarding auto-input, but for now the simplicity of &lt;a href=&#34;https://github.com/sommer/loxodo&#34;&gt;Loxodo&lt;/a&gt; appealed to me. I&amp;rsquo;m unsure whether I&amp;rsquo;ll invest the time to learn wxWidgets to add a grouping pane or whether I&amp;rsquo;ll switch clients at some time. In either case, the underlying file format gives me an easy way to switch back and forth between v3 clients.&lt;/p&gt;
&lt;h2 id=&#34;exporting-from-firefox&#34;&gt;Exporting from Firefox&lt;/h2&gt;
&lt;p&gt;Getting the list of logins in Firefox Password Manager exported requires a separate extension, &lt;a href=&#34;https://addons.mozilla.org/en-us/firefox/addon/password-exporter/&#34;&gt;Password Exporter&lt;/a&gt; worked well for me. I based my work on the CSV output. I suggest cleaning the file up a bit before importing. For example, consider removing superfluous headers. Since there isn&amp;rsquo;t an exact match between the title field in Password Safe and the export from Firefox, I used the host entry as title and formSubmitURL as url. The former really doesn&amp;rsquo;t need the http, so stripping that made sense for me:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:%s/^&amp;quot;https:\/\//&amp;quot;/g
:%s/^&amp;quot;http:\/\//&amp;quot;/g
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;importing-to-loxodo&#34;&gt;Importing to Loxodo&lt;/h2&gt;
&lt;p&gt;Armed with a CSV file, getting the data into Loxodo was a straighforward process and was made extremely easy through the existing CLI components. Iterating through the CSV file and calling the relevant vault function for adding an entry can be done in Python with a handful of lines, you can find &lt;a href=&#34;https://github.com/grahl/loxodo/commit/7063d0f5d0c40fa711202c74f3da065d03f63bd2&#34;&gt;my importer commit&lt;/a&gt; on Github. Working with PHP most of the time, I find the elegance, efficiency and &lt;em&gt;fun&lt;/em&gt; of Python still suprising every time I have a chance to use it.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Drupal Quick Tip: Overriding strings</title>
            <link>https://www.grahl.ch/2012/01/22/drupal-quick-tip-overriding-strings/</link>
            <pubDate>Sun, 22 Jan 2012 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2012/01/22/drupal-quick-tip-overriding-strings/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/5808390293_a902d2605c_o.jpg&#34; alt=&#34;&#34;&gt;
&lt;em&gt;Photo CC-BY by &lt;a href=&#34;https://secure.flickr.com/photos/smithser/5808390293/&#34;&gt;smithser&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Drupal comes with its own terminology and sometimes the strings returned to the user are not quite the vocabulary he or she uses. For example, a small library might refer to the people who have their library card as patrons or members. If a module to manage those users uses a different term this then can cause inconsistency, confusion, etc. One can, of course, just change the strings within the given contrib module but then upgrading becomes a tedious process of merging changes by hand. There is a much nicer solution available.&lt;/p&gt;
&lt;h3 id=&#34;string-overrides&#34;&gt;String Overrides&lt;/h3&gt;
&lt;p&gt;Within settings.php one can specify strings which should be overriden, so that a consistent word choice can be implemented without having to mess up core or contrib. The OpenAtrium documentation has a nice explanation of &lt;a href=&#34;https://community.openatrium.com/documentation-en/node/590###completely-rename-rebrand&#34;&gt;how to use this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t mind adding one more contrib module to your installation, there is also a gui solution to do this for you, called the &lt;a href=&#34;https://drupal.org/project/stringoverrides&#34;&gt;String Overrides module&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Drupal7: Empty nodes or comments after upgrade</title>
            <link>https://www.grahl.ch/2011/08/15/drupal7-empty-nodes-or-comments-after-upgrade/</link>
            <pubDate>Mon, 15 Aug 2011 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2011/08/15/drupal7-empty-nodes-or-comments-after-upgrade/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/empty_1.png&#34; alt=&#34;content stolen by pirate ghosts&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you are trying to upgrade a site running Drupal 6 to 7 and have messed around with your language settings or input formats, you might be in for a surprise. So, if you are seeing nodes or comments which have a title but an empty body on view and edit, it makes sense to check for the following.&lt;/p&gt;
&lt;p&gt;If you have turned the locale module off entirely, but you still have content which is not classified as language neutral, it&amp;rsquo;s likely that you will now encounter problems. This could probably also happen if you have locale enabled but your specific language is not configured. Let&amp;rsquo;s presume you do not want to enable the locale module. Missing input formats for content which still has it assigned can be similarly problematic.&lt;/p&gt;
&lt;p&gt;In either case, the problem can be solved quickly by adjusting a few rows by hand. If you are familiar with the Drupal 6 table layout and, for example, expect text bodies to show up in node_revisions, you&amp;rsquo;ll be in for a surprise, since the database schemas have changed in several important areas.&lt;/p&gt;
&lt;p&gt;Therefore, you&amp;rsquo;ll find a primer below the warning on where to find text body specific definitions for input formats and languages for nodes and comments in Drupal 7.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Big fat warning: Of course you &lt;em&gt;need&lt;/em&gt; an sql-dump so you can restore your database when you make a typo.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;locale&#34;&gt;Locale&lt;/h2&gt;
&lt;p&gt;Reset comments to language neutral and set nodes with language &amp;lsquo;de&amp;rsquo; to &amp;lsquo;und&amp;rsquo; (i.e. language neutral):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE comment SET language=&amp;quot;und&amp;quot;;
UPDATE field_data_comment_body SET language=&amp;quot;und&amp;quot;;
UPDATE node SET language=&amp;quot;und&amp;quot; WHERE language=&amp;quot;de&amp;quot;;
UPDATE field_data_body SET language=&amp;quot;und&amp;quot; WHERE language=&amp;quot;de&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;input-format&#34;&gt;Input format&lt;/h2&gt;
&lt;p&gt;Switches input format from 3 to 1 for all nodes and comments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE field_data_comment_body SET comment_body_format=1 WHERE comment_body_format=3;
UPDATE field_data_body SET body_format=1 WHERE body_format=3;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A quick &lt;em&gt;drush cc all&lt;/em&gt; is definitely encouraged since the menu cache is at the very least affected when it comes to localized url aliases. Case in point: the language specific alias vanished for content which I switched back to &amp;lsquo;und&amp;rsquo; and the page could now be found under node/123.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Minutiae: Return the content of an element with xmlstarlet</title>
            <link>https://www.grahl.ch/2011/02/27/minutiae-return-the-content-of-an-element-with-xmlstarlet/</link>
            <pubDate>Sun, 27 Feb 2011 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2011/02/27/minutiae-return-the-content-of-an-element-with-xmlstarlet/</guid>
            <description>&lt;p&gt;It&amp;rsquo;s not uncommon to find desktop applications storing some information in XML files. While it&amp;rsquo;s nice that it isn&amp;rsquo;t binary and it might be technically human readable, it&amp;rsquo;s hardly as easy to read or edit as a CSV or plaintext file.&lt;/p&gt;
&lt;p&gt;So, when you want to export data from XML, &lt;a href=&#34;http://xmlstar.sourceforge.net/&#34;&gt;xmlstarlet&lt;/a&gt; is a parser that can be a quick solution to the problem. It&amp;rsquo;s documentation, though, is a bit arcane. Every six months or so, I start googling the same problem for xmlstarlet: How to get the content of an element, not just the value of an attribute. I might be using the wrong terms to find what I&amp;rsquo;m looking for, since even &lt;a href=&#34;https://www.grahl.ch/content/blocking-distractions-when-necessary&#34;&gt;old posts of mine&lt;/a&gt; show up in Google for it.&lt;/p&gt;
&lt;p&gt;Either way, the solution is simply a &lt;strong&gt;.&lt;/strong&gt; and here is an example that uses the XML of KDE&amp;rsquo;s Wallet Manager:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xmlstarlet sel -t -m //map -v @name -n -m mapentry -o &amp;quot; &amp;quot; -v @name -o &amp;quot; : &amp;quot; -v . -n wallet.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above will iterate through all map elements, write the name attribute and a newline, iterate through all mapentries of the map and write the value of the subsequent name value indented by two spaces and then finally the actual content of the mapentry element after a colon separated by whitespaces. So there you go, an entire post because I don&amp;rsquo;t think the dot-for-content is documented well. The above example then converts takes XML structured as in the following example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;map name=&amp;quot;http-www.example.com&amp;quot;&amp;gt;
  &amp;lt;mapentry name=&amp;quot;login&amp;quot;&amp;gt;a@example.com&amp;lt;/mapentry&amp;gt;
  &amp;lt;mapentry name=&amp;quot;login-2&amp;quot;&amp;gt;b@example.com&amp;lt;/mapentry&amp;gt;
  &amp;lt;mapentry name=&amp;quot;password&amp;quot;&amp;gt;12345&amp;lt;/mapentry&amp;gt;
  &amp;lt;mapentry name=&amp;quot;password-2&amp;quot;&amp;gt;54321&amp;lt;/mapentry&amp;gt;
&amp;lt;/map&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is then turned into:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; http-www.example.com
  login : a@example.com
  login-2 : b@example.com
  password : 12345
  password-2 : 54321
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>LibreOffice</title>
            <link>https://www.grahl.ch/2010/10/10/libreoffice/</link>
            <pubDate>Sun, 10 Oct 2010 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2010/10/10/libreoffice/</guid>
            <description>&lt;h3 id=&#34;starting-over&#34;&gt;Starting Over&lt;/h3&gt;
&lt;p&gt;A week ago Twitter and Identi.ca were abuzz with news that the &lt;a href=&#34;http://www.documentfoundation.org/&#34;&gt;Document Foundation&lt;/a&gt; had been founded and that they had released a first beta of their OpenOffice.org fork, &lt;a href=&#34;http://www.documentfoundation.org/download/&#34;&gt;LibreOffice&lt;/a&gt;. Some voiced &lt;a href=&#34;http://blogs.computerworld.com/17058/libreoffice_isnt_an_openoffice_fork_yet&#34;&gt;cautious optimism&lt;/a&gt; about the project but were not convinced that it was a true, full fork, yet. The OpenOffice planet aggregator overall sounded rather positive, and a LibreOffice planet is &lt;a href=&#34;http://planet.documentfoundation.org/&#34;&gt;already in place&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So far so good. Now it&amp;rsquo;s time to pull this office suite out of the quicksand it has been slowly sinking into over the last few years. It&amp;rsquo;s not that there were not continuous improvements over that time (document comments alone were a great leap in 3.1) but there was just a lot more sand that was being added (yes, it&amp;rsquo;s a mediocre metaphor, I&amp;rsquo;m sorry).&lt;/p&gt;
&lt;h3 id=&#34;compatibility&#34;&gt;Compatibility&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/lo1_0.png&#34; alt=&#34;Libreoffice screenshot 1&#34;&gt;&lt;/p&gt;
&lt;p&gt;By far the biggest problem is the current state of interoperability. Since 2007/2008 we have needed import/export filters for OOXML. They should have been the absolute highest priority. Now, more than two years along, we still cannot display a generic docx file (with a TOC and a few images), without the format completely breaking.&lt;/p&gt;
&lt;p&gt;I understand that this is difficult, prone to break, and OpenDocument should solve all our problems (alas, Office 2007 SP2, has been a sobering comment on that approach) but the matter of fact is that we have lost ground, tremendously. With Word 2003 and OpenOffice 3.x, formatting bugs were rare and collaborating was not an issue. Since 2007 and especially OOXML that&amp;rsquo;s no longer possible and I, for example, have been unable to use OpenOffice for basically all collaborative tasks that have to look right; and this is not even counting viewing/editing &amp;ldquo;SmartArt&amp;rdquo; or Microsoft&amp;rsquo;s current bibliography features. I don&amp;rsquo;t want to use Office 2007, right now I have to.&lt;/p&gt;
&lt;h3 id=&#34;innovating&#34;&gt;Innovating&lt;/h3&gt;
&lt;p&gt;Apart from absolutely crucial elements like above, LibreOffice now also has an opportunity for innovation. Innovation that is not slavishly copying Microsoft&amp;rsquo;s ribbon interface or similar recent changes in the text-editing-world but finding ways to work more intuitively with this office suite and improving the tasks that matter, i.e. working with text, numbers and doing basic layouting, while maintaining a stable, reliable office suite with sufficient features.&lt;/p&gt;
&lt;p&gt;The difficult question in that context sometimes is: &amp;ldquo;Am I introducing this particular feature so Microsoft documents can be edited well (e.g. SmartArt), or am I slavishly copying functionality because I think users need it or want it?&amp;rdquo; Finding the correct answer to each new feature provided by the big pink elephant in the room is going to be diffcult and take time, I just hope it won&amp;rsquo;t take too much.&lt;/p&gt;
&lt;p&gt;A cautionary tale of why to take this time though is exemplified by weird things one still comes across in OpenOffice and LibreOffice:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/lo_fontwork.png&#34; alt=&#34;Libreoffice fontwork&#34;&gt;&lt;/p&gt;
&lt;p&gt;Did you know you can still create WordArt? It&amp;rsquo;s called Fontwork. You can find it with View -&amp;gt;Toolbars -&amp;gt; Fontwork. It needs to go.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/lo_clipart.png&#34; alt=&#34;Libreoffice clipart&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here is a thumbnail browser. Cool homepage icons, eh? I&amp;rsquo;m trying to remember if there ever was a document that I recall which was visually improved by a stock clipart &amp;ndash; this especially includes presentations. Don&amp;rsquo;t think so. Check out the &amp;ldquo;Rulers&amp;rdquo; and &amp;ldquo;Sounds&amp;rdquo;, too. It&amp;rsquo;s under Tools -&amp;gt; Gallery.&lt;/p&gt;
&lt;p&gt;Once implemented, things are not easy to remove, there will always be someone who depends on them. In the end, though, I hope the community comes down on the side of offering good, modern defaults and sacrificing the odd feature to make it harder to shot yourself in the foot visually. I hope in the end it will make working with LibreOffice pleasing, rather than a chore.&lt;/p&gt;
&lt;h3 id=&#34;cleaning-up&#34;&gt;Cleaning Up&lt;/h3&gt;
&lt;p&gt;One of the elements that gives me the most confidence in this project is their list of &lt;a href=&#34;http://www.freedesktop.org/wiki/Software/LibreOffice/EasyHacks&#34;&gt;Easy Hacks&lt;/a&gt;. It shows that the foundation and its volunteers realize that the existing code needs to be harmonized, corrected and most of all tidied up, so that legacy code will not hold future developments back as much as they have in the past.&lt;/p&gt;
&lt;h3 id=&#34;addendum&#34;&gt;Addendum&lt;/h3&gt;
&lt;p&gt;So, good luck LibreOffice and please, please do not change the name back to OpenOffice.org (the .org especially has been so incredibly annoying over the years!) should Oracle give the name back. Make a fresh, bold new start.&lt;/p&gt;
&lt;p&gt;If you want to help this community, you can &lt;a href=&#34;http://www.documentfoundation.org/contribution/&#34;&gt;contribute your time or funds&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Flash, netbooks and too much bandwidth</title>
            <link>https://www.grahl.ch/2010/04/23/flash-netbooks-and-too-much-bandwidth/</link>
            <pubDate>Fri, 23 Apr 2010 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2010/04/23/flash-netbooks-and-too-much-bandwidth/</guid>
            <description>&lt;p&gt;Several online video sites estimate the bandwidth they can deliver too you and adjust the video bitrate accordingly. A good example is the &lt;a href=&#34;http://www.thedailyshow.com&#34;&gt;Daily Show&lt;/a&gt;. Click on ‘Full Episodes’ and the player has a bar indicator and actually gives you feedback on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Current Available Bandwidth&lt;/li&gt;
&lt;li&gt;Current Video Stream&lt;/li&gt;
&lt;li&gt;Max Video Stream&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Screenshot.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is really nice for testing things. From my location the maximum video stream available was 1720kbs, which should not be a problem for a modern broadband connection. However, the Linux Flash plugin has notoriously bad performance in terms of video decoding on machines with limited resources, such as netbooks. This becomes particularly evident when you download a file and it plays fine but embedded in Flash it is close to 100% CPU load and only a sequence of intermittent still images.&lt;/p&gt;
&lt;p&gt;In these cases, where you cannot choose a video bandwidth (such as 360p on Youtube), you can still reduce it by throttling the connection on an individual network interface. Debian-based distributions can just grab the wondershaper package for that.&lt;/p&gt;
&lt;p&gt;After installing, it&amp;rsquo;s trivial to set the bandwidth. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo wondershaper wlan0 1000 500 # parameters are: interface, kbs down, kbs up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initially I chose 500/500 but the feedback from the Daily Show player showed that 500 was insufficient to deliver their minimum quality at 450kbs due to protocol overhead and everything else. The parameters above turned out to work nicely. Afterwards you can clear the throttling with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo wondershaper clear wlan0
&lt;/code&gt;&lt;/pre&gt;
</description>
        </item>
        
        <item>
            <title>Wikireader (now with stickers!)</title>
            <link>https://www.grahl.ch/2010/02/06/wikireader-now-with-stickers/</link>
            <pubDate>Sat, 06 Feb 2010 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2010/02/06/wikireader-now-with-stickers/</guid>
            <description>&lt;blockquote&gt;
&lt;p&gt;In many of the more relaxed civilizations on the Outer Eastern Rim of the Galaxy, the Hitch Hiker&amp;rsquo;s Guide has already supplanted the great Encyclopedia Galactica as the standard repository of all knowledge and wisdom, [&amp;hellip;] it scores over the older, more pedestrian work in two important respects. First, it is slightly cheaper;&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&amp;rsquo;s how Douglas Adams introduces the &lt;a href=&#34;http://en.wikipedia.org/wiki/Hitchhiker%27s_guide&#34;&gt;Hitchhiker&amp;rsquo;s Guide&lt;/a&gt; in the first novel of his series by the same name. Wikipedia has several similarities to the Guide that are highlighted in that first characteristic. Like the Guide it has supplanted the more scholarly works with its reach and breadth, as well as popularity.&lt;/p&gt;
&lt;h2 id=&#34;enter-wikireader&#34;&gt;I vaguely remember how years ago we were sitting in the Florida sun off-campus and &lt;a href=&#34;http://www.gavinbaker.com/&#34;&gt;Gavin&lt;/a&gt; made the remark that you would just have to put Wikipedia on a memory card, put it in a phone and you&amp;rsquo;d basically have the Guide, including about the same amount of accuracy.&lt;br&gt;
 
Enter Wikireader&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/reader1.JPG&#34; alt=&#34;Wikireader&#34;&gt;&lt;/p&gt;
&lt;p&gt;Granted, it&amp;rsquo;s a device that differs in many aspects from what purists would call the Guide, notably absent are the hundreds of buttons Adams described and it doesn&amp;rsquo;t have a cover, and so on, but in essence it provides the world&amp;rsquo;s knowledge summarized in over three million articles in a memory chip the size of my fingernail.&lt;/p&gt;
&lt;p&gt;Nowadays, with smart phones that have decent browsers being pervasive, it&amp;rsquo;s not quite as impressive as it might have been even a few years ago. Nonetheless, (ignoring a few temporal paradoxes concerning Wikipedia&amp;rsquo;s existence) I&amp;rsquo;m sure that I would have killed to own such a device up until a few years ago. The potential for being a smartass (and having the sources to back it up) were just so darn limited back in my youth.&lt;/p&gt;
&lt;p&gt;Over the holidays I spent several days without an Internet connection (if you don&amp;rsquo;t count the phone) and the Wikireader proved that Wikipedia&amp;rsquo;s addictiveness is also present in this device. The ‘random’ button is especially genius, you never know what weird topic comes up that you have never even heard of. You can then hit the button again and again, with around a second latency, until it looks interesting. The device takes getting used to, since it&amp;rsquo;s counterintuitive that the device is not backlit and thus is hardly readable in low-light conditions but I&amp;rsquo;m looking forward to trying it out in the summer.&lt;/p&gt;
&lt;p&gt;The physical manifestation of Wikipedia in my palm is an interesting thing. As long as I can find some AAA batteries, I&amp;rsquo;ll always have that encyclopedia, no matter how many sea cables are ruptured or how much filtering a government would like to employ. Surprisingly, as long as the Openmoko company runs we even get ‘&lt;a href=&#34;http://thewikireader.com/update.html&#34;&gt;new editions&lt;/a&gt;’ like the Guide. Only those aren&amp;rsquo;t downloaded directly, we have to want to load the new image onto our microSD cards (or have new cards shipped to you). More importantly, though, technically everyone can make their own ‘editions’, so we don&amp;rsquo;t have to accept a “Mostly harmless”, if we don&amp;rsquo;t like it. Admittedly, I tried to ammend non-Mediawiki-content to my image but quickly abandoned that for FBReader on my phone.&lt;br&gt;
 &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;and secondly it has the words DON&amp;rsquo;T PANIC inscribed in large friendly letters on its cover.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/reader2.JPG&#34; alt=&#34;wikireader&#34;&gt;&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Font-Face Kits</title>
            <link>https://www.grahl.ch/2009/11/02/font-face-kits/</link>
            <pubDate>Mon, 02 Nov 2009 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2009/11/02/font-face-kits/</guid>
            <description>&lt;p&gt;When you look at common practices for styling fonts on the web, the majority of sites either tend to be minimalistic to preserve consistency or a bit redundant to have it look as nice as possible with stock fonts on every system.&lt;/p&gt;
&lt;h3 id=&#34;helvetica&#34;&gt;Helvetica&lt;/h3&gt;
&lt;p&gt;A good example is &lt;a href=&#34;http://en.wikipedia.org/wiki/Helvetica&#34;&gt;Helvetica&lt;/a&gt;. It&amp;rsquo;s a beautiful modern typeface, hell, they made a whole documentary about it! Anyway, it&amp;rsquo;s included in OS X and thus of course a web designer certainly likes to tell the browser to use it if it&amp;rsquo;s there. Fallback solutions are then often just Verdana, Arial and a system default sans-serif.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Actually, I should still have a copy of Helvetica from when I had a Macbook. Does anyone know to what license I agreed when I got that? Even if I&amp;rsquo;d null out the drive afterwards I&amp;rsquo;d probably break the license agreement if I&amp;rsquo;d copy it over&amp;hellip;&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;embedding&#34;&gt;Embedding&lt;/h3&gt;
&lt;p&gt;The alternative to specifying fonts one after the other and hoping that at least one is present is embedding the fonts through CSS. This is similar to specifying images within CSS and has been supported for a long time. However, some inconsistencies hampered making full use of these techniques.&lt;/p&gt;
&lt;p&gt;For one, some browsers  support TrueType/OpenType, some only embedded OpenType (EOT), and Firefox 3.6 now comes with something that&amp;rsquo;s called WOFF. Now, even if you had a license for Helvetica, you could of course not embed it since that would basically give every schmuck the font for free (which they would like about 25€ per variant for). Granted, EOT does not make it trivial to get a regular font file from it but someone who really wants to extract the glyphs will probably be able to.&lt;/p&gt;
&lt;h3 id=&#34;font-face-kits&#34;&gt;Font-Face Kits&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/Sansation_1.2_.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;A very cool solution to the technical as well as legal problems are so called font-face kits for freely licensed fonts. Sites like &lt;a href=&#34;http://www.fontsquirrel.com&#34;&gt;Font Squirrel&lt;/a&gt; provide packages that can simply be copied and pasted into existing CSS files. Since they take care of the obscure things, such as converting to EOT, this can be used by basically any CSS designer without further knowledge about fonts and font formats.&lt;/p&gt;
&lt;p&gt;All that&amp;rsquo;s needed then is to upload the provided font files and change the font-family reference for each instance, in your CSS files, where you would like that font to be used. You can change the whole look of your site in about half an hour, without resorting to ugly hacks such as embedding images.&lt;/p&gt;
&lt;p&gt;I liked this solution so much that I converted my blog to &lt;a href=&#34;http://www.fontsquirrel.com/fonts/Sansation&#34;&gt;Sansation Regular&lt;/a&gt;. It&amp;rsquo;s a bit playful but if you like more conservative typefaces (they have their uses, my desktop isn&amp;rsquo;t using Sansation&amp;hellip;) they have a great selection of featured fonts for that, too.
 &lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Blocking distractions, when necessary Part 2</title>
            <link>https://www.grahl.ch/2009/07/21/blocking-distractions-when-necessary-part-2/</link>
            <pubDate>Tue, 21 Jul 2009 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2009/07/21/blocking-distractions-when-necessary-part-2/</guid>
            <description>&lt;p&gt;In &lt;a href=&#34;https://www.grahl.ch/content/blocking-distractions-when-necessary&#34;&gt;the first part&lt;/a&gt; we created a list of things we want to block on occasion. The point of this part is to make it easy to switch from full access to limited access. Enter, OpenWrt.&lt;/p&gt;
&lt;h2 id=&#34;openwrt&#34;&gt;OpenWrt&lt;/h2&gt;
&lt;p&gt;A large chunk of wireless routers on the market today can not only run their stock firmware but also OpenWrt. It&amp;rsquo;s useful for creating wireless mesh networks and all sorts of things but the most common task is just a free-as-in-speech router. There are countless tutorials online for installing it and resources for which devices &lt;a href=&#34;http://oldwiki.openwrt.org/TitleIndex.html&#34;&gt;are supported&lt;/a&gt;, so I&amp;rsquo;m not covering that here. Apart from being able to SSH into your router, OpenWrt now exposes many things such as buttons and LEDs, which you can use in your scripts. These examples have been tested with Kamikaze 8.09.1. In older releases, several things were elsewhere, such as /proc/diag.&lt;/p&gt;
&lt;h2 id=&#34;tinyproxy&#34;&gt;Tinyproxy&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/router2.png&#34; alt=&#34;router with filter&#34;&gt;&lt;/p&gt;
&lt;p&gt;Due to the size limitations on many models and feature limitations on certain solutions I ended up using Tinyproxy. Definitely also install the luci package for Tinyproxy, it makes setup a lot quicker. Due to several smaller bugs in the current releases it&amp;rsquo;s probably still necessary to move the Busybox web interface from 80 to 8080.&lt;/p&gt;
&lt;p&gt;At this point, you should let Tinyproxy listen on your router&amp;rsquo;s IP on port 80. Also allow access from your subnet and 127.0.0.1. Most often this will be 192.168.1.0/24. Finally upload your filter list and turn your proxy on. Due to the way the current OpenWrt releases works this will not yet router your traffic through the filter. Also, if Tinyproxy doesn&amp;rsquo;t seem to start properly, make sure the logging directories are also present.&lt;/p&gt;
&lt;h2 id=&#34;putting-it-together&#34;&gt;Putting it together&lt;/h2&gt;
&lt;p&gt;All that&amp;rsquo;s missing is a script, to run once a button is pushed, let an LED blink as long until it is pushed again and toggle routing the traffic through the proxy. &lt;a href=&#34;https://www.grahl.ch/img/01-nettoggle.tar&#34;&gt;My script&lt;/a&gt; is largely based on &lt;a href=&#34;http://oldwiki.openwrt.org/CensorUnwantedHostsHowTo.html&#34;&gt;a howto from the OpenWrt wiki&lt;/a&gt; (as is much in this second part). It uses the SES LED and works with a Buffalo router running a 2.4. kernel due to the wireless drivers (otherwise iptables redirect could be used). Placing it into /etc/hotplug.d/button/, for example as 01-nettoggle, should make it work. Though you&amp;rsquo;ll probably have to adjust LEDs, buttons, and maybe also the router IP. Now have fun with tricking that dumb part of your brain into getting things done, to paraphrase &lt;a href=&#34;http://inboxzero.com&#34;&gt;Merlin Mann&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Blocking distractions, when necessary</title>
            <link>https://www.grahl.ch/2009/07/14/blocking-distractions-when-necessary/</link>
            <pubDate>Tue, 14 Jul 2009 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2009/07/14/blocking-distractions-when-necessary/</guid>
            <description>&lt;h2 id=&#34;the-dilemma&#34;&gt;The dilemma&lt;/h2&gt;
&lt;p&gt;This semester I had a handful of exams rather late in the year, weeks after classes ended. Now, the Internets are a pretty interesting place and I find it extremely difficult to not glance at the feed reader and all the other gizmos rather often, when in front of the screen.&lt;/p&gt;
&lt;p&gt;I saw three possible solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a) Work analog. (Pretty unrealistic, and think of the poor trees…)&lt;/li&gt;
&lt;li&gt;b) Force yourself to not get distracted. (Sounds as likely as most New Year&amp;rsquo;s resolutions.)&lt;/li&gt;
&lt;li&gt;c) Turn off only the distractions, or at least minimize them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Naturally, I went for C. It gave me an excuse to go write the scripts for it. The first attempt consisted of a list of domains added to /etc/hosts on my main machine. It worked, but just blocking domain names often gives inconsistent results in respect to subdomains and other problems. I have split this tutorial into two parts to make it more readable. The first part deals with collecting domain names, the second with filtering them by hitting a button on an OpenWrt router.&lt;/p&gt;
&lt;h2 id=&#34;collecting-urls&#34;&gt;Collecting URLs&lt;/h2&gt;
&lt;p&gt;Many programs dealing with RSS today will often export to OPML (just an XML file) and manipulating these with XmlStarlet works great. Let&amp;rsquo;s try it on Miro.&lt;/p&gt;
&lt;p&gt;We have a list of outline elements, some are groups, some are feeds. The following will extract the URL from all outline elements.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xmlstarlet sel  -t -m //outline -v @xmlUrl -n ~/miro_subscriptions.opml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Appeding &lt;code&gt;| grep . &lt;/code&gt; should also get rid of the empty lines. Just repeat the procedure for the other XML variants your programs produce.&lt;/p&gt;
&lt;h3 id=&#34;without-proper-xml&#34;&gt;Without proper XML&lt;/h3&gt;
&lt;p&gt;Sometimes, even when an XMLish notation like HTML is used you get nonvalidating files. For example with Firefox bookmarks (other browsers do it, too). Tidy is an easy way to automatically fix those problems. The following will produce a validating bookmarks file. &lt;code&gt;tidy -asxml -o cleanbookmarks.html bookmarks.html&lt;/code&gt; With this you can run it through XmlStarlet without problems. I adapted the following somewhat cryptic line from the &lt;a href=&#34;http://xmlstar.sourceforge.net/doc/UG/xmlstarlet-ug.html&#34;&gt;XmlStarlet User Guide&lt;/a&gt;. Additionally, &lt;code&gt;| grep -v &amp;quot;place:&amp;quot;&lt;/code&gt; removes the lines from Firefox which aren&amp;rsquo;t URLs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xmlstarlet  sel --html -T -t -m &amp;quot;//*[local-name()=&#39;a&#39;]&amp;quot;  -v @href -n -n a.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;getting-domains&#34;&gt;Getting domains&lt;/h2&gt;
&lt;p&gt;From the previous steps you now have a list of URLs. With these, you could already jump ahead to step 2 since the proxy in this project can also filter on URLs or patterns.&lt;/p&gt;
&lt;p&gt;If, however, you want to broaden it to domain names, there is one more step. The following Python script takes two arguments, a file with URLs and a filename to write the domains to. &lt;a href=&#34;https://www.grahl.ch/other/getdomains.py&#34;&gt;Download it directly&lt;/a&gt;. Example usage: &lt;code&gt;./getdomains.py urllist domainlist&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you quickly look over the script, you might notice the four lines below. Since many sites have inconsistent uses of the www prefix, the script looks for all domains with a www prefix. If successful, the domain is added to the list with www and without. This does not cover any other subdomain.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;domainshort=domain[4:]
o.write(&amp;quot;%s \n&amp;quot; % domain)
if domain[:3] == &amp;quot;www&amp;quot;:
    o.write(&amp;quot;%s \n&amp;quot; % domainshort)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you don&amp;rsquo;t have OpenWrt, you can also &lt;a href=&#34;https://www.grahl.ch/other/getdomains127.py&#34;&gt;download&lt;/a&gt; a version with two tiny changes to make the ouput readable by /etc/hosts. Just append your entries at the end of that file.&lt;/p&gt;
&lt;h2 id=&#34;upcoming&#34;&gt;Upcoming&lt;/h2&gt;
&lt;p&gt;In the next post, we&amp;rsquo;ll see how one can easily switch using this list on and off with a button, rather than meddling with configuration files each time.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Use your remote with Amarok and Last.fm</title>
            <link>https://www.grahl.ch/2009/06/11/use-your-remote-with-amarok-and-last.fm/</link>
            <pubDate>Thu, 11 Jun 2009 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2009/06/11/use-your-remote-with-amarok-and-last.fm/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/remote.png&#34; alt=&#34;Remote&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you have an amplifier (or something similar) with a remote and your computer attached to it, there are probably several buttons that don&amp;rsquo;t do anything in this configuration. In my case, it&amp;rsquo;s the buttons available to control a CD or tape deck. Wouldn&amp;rsquo;t it be neat to repurpose those? In short, all you need is an IR receiver, &lt;a href=&#34;http://www.lirc.org/&#34;&gt;LIRC&lt;/a&gt; and a handful of commands. There are already quite a few tutorials on the net for doing this but many are a bit dated and so I think this post still brings something new, if simply by addressing installing LIRC on Ubuntu Jaunty to use Amarok 2.&lt;/p&gt;
&lt;h3 id=&#34;ir-receiver&#34;&gt;IR Receiver&lt;/h3&gt;
&lt;p&gt;First off, get an infrared receiver. I got a serial one off of ebay very cheaply, just search for &amp;ldquo;lirc.&amp;rdquo; You can often reuse receivers from other gadgets, LIRC is quite flexible that way. Let&amp;rsquo;s now begin with a plugged in receiver but nothing else. How do you know it&amp;rsquo;s even working? First, load the appropriate module. In my case, &lt;code&gt;lirc_serial&lt;/code&gt;. You could let the LIRC script load it but &lt;code&gt;/etc/modules&lt;/code&gt; works just as well. Put the model name for your remote in &lt;code&gt;/etc/lirc/hardware.conf&lt;/code&gt;, start &lt;code&gt;/etc/init.d/lirc&lt;/code&gt; and you should be mostly done.
The command &lt;a href=&#34;http://www.lirc.org/html/irw.html&#34;&gt;irw&lt;/a&gt; should now return correct values for the remote on individual button signals. If the device cannot be found, consider running the command with root privileges and make sure the LIRC device exists. In Ubuntu, you often have to reference /dev/lirc0 because /dev/lirc is the default many scripts assume.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/lirc1.png&#34; alt=&#34;Lirc screenshot&#34;&gt;&lt;/p&gt;
&lt;p&gt;If your remote doesn&amp;rsquo;t work, try adding it to &lt;code&gt;/etc/serial.conf&lt;/code&gt; with &lt;code&gt;/dev/ttyS0 uart none&lt;/code&gt;, of course, adjust ttyS0 as necessary and consider rebooting.&lt;/p&gt;
&lt;h3 id=&#34;unknown-remotes&#34;&gt;Unknown Remotes&lt;/h3&gt;
&lt;p&gt;However, what do you do if your remote isn&amp;rsquo;t in the list of remotes LIRC knows? No problem, LIRC can learn new remote signals, too. Just call &lt;a href=&#34;http://www.lirc.org/html/irrecord.html&#34;&gt;irrecord&lt;/a&gt; and follow the on-screen instructions.&lt;/p&gt;
&lt;p&gt;After that you can move the resulting file to &lt;code&gt;/etc/lirc/lircd.conf&lt;/code&gt; and the remote should work. To complete the setup you still have to tell LIRC what to do when you press a button. There are several alternatives to settings things up but calling &lt;code&gt;irexec -d&lt;/code&gt; from the autostart folder is probably one of the quickest. Irexec by default calls &lt;code&gt;~/.lircrc&lt;/code&gt;. Here is a short excerpt from my configuration, of course the button names of your remote might be completely different.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;begin
prog = irexec
remote = abc123
button = play
config = dbus-send --type=method_call --dest=org.kde.amarok /Player org.freedesktop.MediaPlayer.Pause
end


begin
prog = irexec
remote = abc123
button = stop
config = dbus-send --type=method_call --dest=org.kde.amarok /Player org.freedesktop.MediaPlayer.Stop
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;lastfm&#34;&gt;Last.fm&lt;/h3&gt;
&lt;p&gt;Now, what was this talk about &lt;a href=&#34;http://www.last.fm/&#34;&gt;Last.fm&lt;/a&gt; earlier? Well, Amarok has decent support for Last.fm built-in and even has a little ❤ button on the main toolbar so you can favorite a song. You can easily call this feature over DBUS, too:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dbus-send --type=method_call --dest=org.kde.amarok /amarok/MainWindow org.kde.amarok.MainWindow.loveTrack
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, now you can flip through your music collection from your couch, skip a track and even favorite it in Last.fm without having to to use your computer. What could be improved? Lots. For one, moving to the MPRIS namespace in DBUS would make the whole thing more flexible, but I&amp;rsquo;m skeptical that it will ever support things like the ❤ button. Also, I tried org.freedesktop.MediaPlayer.Seek but it didn&amp;rsquo;t work as expected.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>OpenDocument in SP 2 mostly useless</title>
            <link>https://www.grahl.ch/2009/05/21/opendocument-in-sp-2-mostly-useless/</link>
            <pubDate>Thu, 21 May 2009 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2009/05/21/opendocument-in-sp-2-mostly-useless/</guid>
            <description>&lt;p&gt;I actually wanted to do a whole series on Office 2007/OpenOffice 3 interoperability but it just didn&amp;rsquo;t work out. New developments, however, give me an opportunity to tackle on the more import parts: file formats.&lt;/p&gt;
&lt;h3 id=&#34;background-information&#34;&gt;Background information&lt;/h3&gt;
&lt;p&gt;Microsoft used a binary file format (doc/xsl/ppt) until Office 2003, then they switched to OOXML and now it is (docx/xslx/pptx). OpenOffice (OOo) has very good support for reading and writing the binary format. They have a rudimentary import filter for OOXML but it is nowhere near complete, having big gaps in such areas as SmartArt and others. Also, no export filter exists, OOo users have to use the binary format to send files back to Microsoft Office users (yes, they could now use ODF but not really, more on that later).&lt;/p&gt;
&lt;p&gt;OOo uses ODF (odt/ods/odp) and this became an ISO standard. Microsoft promised OpenDocument support based on the current spec, which does lack some definition on formulas and OOo 3 is basing its file format on the not finalized newer version of OpenDocument.&lt;/p&gt;
&lt;h3 id=&#34;now&#34;&gt;Now&lt;/h3&gt;
&lt;p&gt;So, Service Pack 2 for Office 2007 with OpenDocument support was released and many people &lt;a href=&#34;http://www.groklaw.net/article.php?story=20090503215045379&#34;&gt;are not happy&lt;/a&gt;. Apart from the obvious Excel blunder, several things are simply not supported, rather than &amp;lsquo;differently implemented.&amp;rsquo;&lt;/p&gt;
&lt;p&gt;While these changes do send negative messages, OpenDocument is also option #4 under &amp;lsquo;Save as&amp;rsquo;, right after the native format, template and 97-03 DOC, which at least looks like good intentions. Amongst many other items, the Office help files (referenced when you export to ODF) point out that the following things are just dropped during the export: - &lt;em&gt;Track Changes&lt;/em&gt;, collaborative editing is now quite difficult (the main point for not using PDF!). It&amp;rsquo;s a showstopper for my frequent group assignments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Table of Contents is converted to text&lt;/em&gt;, which is more or less ok.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Page break&lt;/em&gt; sometimes breaks other fields.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from these shortcomings, some acceptable, others not, and probably not fixed until SP 3, there are the things OpenOffice does not display right. Who&amp;rsquo;s fault these are is probably impossible to tell without consulting Oasis, Microsoft, Sun and then choosing an opinion at random. Here are a few I ran into when opening 3 documents in OpenOffice:&lt;/p&gt;
&lt;h4 id=&#34;from-word&#34;&gt;From Word&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Table of Contents: every item on a singe page&amp;hellip;your 30 page paper is now 75.&lt;/li&gt;
&lt;li&gt;Formatting rules: inconsistenly applied. No consistency between &amp;ldquo;style&amp;rdquo; and overriding &amp;ldquo;font&amp;rdquo; definition.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;from-excel&#34;&gt;From Excel&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Formula bug: forget it.&lt;/li&gt;
&lt;li&gt;No graphs:, only an OLE placeholder is shown, at least with a simple 3D bar chart.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;from-powerpoint&#34;&gt;From PowerPoint&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Empty line items: show bullet points, on Office they are just greyed out when the line is actively selected and empty otherwise.&lt;/li&gt;
&lt;li&gt;Default placeholders: sometimes present, especially when Master slides applied and not all fields used.&lt;/li&gt;
&lt;li&gt;Indentations: have different settings, text thus is easily flowing over regions occupied by other things. Especially if paragraph indention has been modified by hand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these things are not terribly bad, most could probably be fixed in 1 or 2 revision of the respective software packages. The problem, however, is time. The next Office service pack could be years away and still not solve outstanding issues.&lt;/p&gt;
&lt;p&gt;Even OpenOffice doesn&amp;rsquo;t have a major release until Fall (and let&amp;rsquo;s face it, major changes would be necessary) and i think there is at least somewhat of a case to be made to not water down the OpenOffice implementation just because Microsoft can&amp;rsquo;t do it right. Finally, these issues are only from Office to OpenOffice, at least a few problems, and likely many more, are also present when going from OpenOffice to Office.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the browser wars all over again, and I still can&amp;rsquo;t exchange a presentation without relying on the doc format to even get close to be able to run it and not be embarrassed about what pops up on the screen.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>Building a Lego ATM machine</title>
            <link>https://www.grahl.ch/2009/05/08/building-a-lego-atm-machine/</link>
            <pubDate>Fri, 08 May 2009 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2009/05/08/building-a-lego-atm-machine/</guid>
            <description>&lt;p&gt;The end of the semester is finally inching closer and one course in particular ended with a quite successful final presentation for our team of 3. The objective was to use several crates of Lego Mindstorms material to build a working model of two atm machines.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/comp2.jpg&#34; alt=&#34;comparison of atms&#34;&gt;&lt;/p&gt;
&lt;p&gt;Rebooting ATM image is CC BY-NC-NA from &lt;a href=&#34;http://www.flickr.com/photos/n2b/465387084/&#34;&gt;n2b&lt;/a&gt;. Our ATM not quite identical to original.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s what we did. OK, not quite, our currency consisted of square wooden blocks (about 50mm*50mm*4mm) and our bank cards only contain a black and white strip that encodes a binary number. We reused some old &lt;a href=&#34;http://en.wikipedia.org/wiki/ISO_7810#ID-1&#34;&gt;credit card sized&lt;/a&gt; plastic cards and used the pattern on the right for the cards.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/card10s.png&#34; alt=&#34;demo card&#34;&gt;&lt;/p&gt;
&lt;p&gt;Then we built a PIN pad with a cut up connector cable, a project box, 6 buttons and a handful of resistors. Additionally, we had team members that were tweaking the construction until every single &amp;ldquo;bill&amp;rdquo; dropped perfectly. Then, all that was missing was an actual backend. For this we simulated a database with an array (loaded from, and saved on exit to, a text file) and set aside one of the NXT control units to serve as the bank server. Then, our ATMs send requests as a string over the weird Bluetooth stack to the server and got a reply back, depending on the account&amp;rsquo;s state. Here is the end result. It comes in at just about 1200 lines of NXC code with support for German, English and French, as well as administrative functions on the bank to manage accounts.&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=u5Pg3QP-fOA&amp;amp;list=PL6lXhBL-RwtvNlshoXEmjeFUPj4mq1AV7&#34;&gt;This and three more videos&lt;/a&gt;.&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>A student&#39;s guide to OpenOffice 3.0: Part 1</title>
            <link>https://www.grahl.ch/2008/12/13/a-students-guide-to-openoffice-3.0-part-1/</link>
            <pubDate>Sat, 13 Dec 2008 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2008/12/13/a-students-guide-to-openoffice-3.0-part-1/</guid>
            <description>&lt;h2 id=&#34;foreword&#34;&gt;Foreword&lt;/h2&gt;
&lt;p&gt;Before the summer, hardly anyone I knew had Office 2007. Now, however, many have bought new laptops for the Fall semester and it&amp;rsquo;s no surprise that they come preloaded with Office 2007. Combine that with the fact that my new college really likes group presentations and group papers and you get a field study of OpenOffice 3.0 vs Microsoft Office 2007.&lt;/p&gt;
&lt;h2 id=&#34;results&#34;&gt;Results&lt;/h2&gt;
&lt;p&gt;Short version: OpenOffice 3.0 is in many ways inferior to Office 2007 and exchanging files has gone from nearly perfect to rather limited. After five weeks I had to bite the bullet and purchase Office 2007.&lt;/p&gt;
&lt;p&gt;Long version: It&amp;rsquo;s not the end of the world and OpenOffice will probably catch up in most areas within a release or two. However, there are several pitfalls I encountered along the way and so I want to present a list here on what is not possible, when it will likely be fixed and how to work around it.&lt;/p&gt;
&lt;p&gt;The posts will roughly cover the following topics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Styles, Colors and Fonts&lt;/li&gt;
&lt;li&gt;OOXML&lt;/li&gt;
&lt;li&gt;Citations and Sources&lt;/li&gt;
&lt;li&gt;Powerpoint and SmartArt&lt;/li&gt;
&lt;li&gt;Excel vs Calc&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;styles-colors-and-fonts&#34;&gt;Styles, Colors And Fonts&lt;/h2&gt;
&lt;p&gt;After you have seen half a dozen presentations and papers done in Office 2007 you will notice that they actually did a really nice job of making things look decent. I&amp;rsquo;m sure within the next five years we will all not be able to stand the default Powerpoint templates but for now they are a welcome change when compared to the often crude looks of Office 2003 and OpenOffice (especially before 3.0).&lt;/p&gt;
&lt;p&gt;New fonts embedded in Vista and Office 2007 have been a major factor in improving visual design by mostly retiring the ancient Arial and Times New Roman combination in favor of Cambria, Calibri etc. These fonts are possibly also the most problematic thing because they cannot easily be patched away.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s why: you cannot get them for free and there are no equivalent substitutes. If you google &amp;lsquo;office 2007 fonts linux&amp;rsquo; you will find out a few ways to get them installed but that&amp;rsquo;s dubious at best. For one, several bloggers point out that you can get the fonts from the free PowerPoint viewer but the license expressly states that use of the fonts is only valid on the Windows operating system. I&amp;rsquo;m pretty sure that the license I paid for grants me the right to use them outside the virtual machine but without delving into the EULAs I&amp;rsquo;m not going to speculate. The fonts directly from the manufacturer cost $300 by the way.&lt;/p&gt;
&lt;p&gt;Which then leads to the second point: substitutes. As pointed out in &lt;a href=&#34;http://www.oooninja.com/2008/02/metrical-equivalent-fonts-and-font.html&#34;&gt;this article&lt;/a&gt;, fonts that are metrically equivalent matter. For now you&amp;rsquo;ll either have to pay for the fonts one way or another or resort to less pretty fonts. These will still become the expected standard in the near-term so expect to be the odd one out.&lt;/p&gt;
&lt;p&gt;Therefore let&amp;rsquo;s hope (or start a fundraiser so) that the same things happens that did for the &lt;a href=&#34;http://en.wikipedia.org/wiki/Core_fonts_for_the_Web&#34;&gt;Core Fonts&lt;/a&gt;package. Red Hat paid for the Liberation fonts and they are close enough so that you didn&amp;rsquo;t have to use the Core fonts if you didn&amp;rsquo;t want to. I&amp;rsquo;m not holding my breath, though, since the same firm which did the Liberation fonts worked on the C* fonts from Microsoft. &lt;/p&gt;
&lt;p&gt;Better default color schemes are one of the additions that really add to the value of Office 2007 and where OpenOffice definitely needs to catch up. It was certainly possible to create tables which looked just as harmonious and professional in Office 2003 given enough time and talent but these defaults make that possible for the average person.&lt;/p&gt;
&lt;p&gt;It just is really nice if you can get from a plain table such as the one on the left to a table such as on the right with just one click:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.grahl.ch/img/batable.png&#34; alt=&#34;Table&#34;&gt;&lt;/p&gt;
&lt;p&gt;Default colors are a point where it could be comparatively easy to improve the look and feel of open source software but it is often overlooked. Take &lt;a href=&#34;http://kontact.kde.org/korganizer/&#34;&gt;KOrganizer&lt;/a&gt; for example, you could take a look at the &lt;a href=&#34;http://www.oxygen-icons.org/?page_id=2#colors&#34;&gt;KDE/Oxygen color palette&lt;/a&gt; and set the colors accordingly and have a really nice looking calendar, similar to how iCal looks really nice on default on OS X, instead of the greenish nightmare that it is today. Why is this important? Because most people do not change their defaults. They often don&amp;rsquo;t know where so good defaults make a difference, especially for an office suite.&lt;/p&gt;
&lt;p&gt;These table styles and such are also not imported very well in OpenOffice but that will be covered in Part 2. To summarize, defaults have improved, making documents better looking without having a designated designer on hand but it isn&amp;rsquo;t impossible for OpenOffice to get there. If you have such abilities to produce great defaults, consider giving your input to the OOo devs.&lt;/p&gt;
&lt;p&gt;Check back in a few days or subscribe to the RSS feed on your right to find out even more about the mysteries of MS Office, OpenOffice and stuff…yay!&lt;/p&gt;
</description>
        </item>
        
        <item>
            <title>How to break your git repository</title>
            <link>https://www.grahl.ch/2008/08/31/how-to-break-your-git-repository/</link>
            <pubDate>Sun, 31 Aug 2008 00:00:00 +0000</pubDate>
            
            <guid>https://www.grahl.ch/2008/08/31/how-to-break-your-git-repository/</guid>
            <description>&lt;p&gt;I was cleaning up a few things on my drive and moved several folders around. Unbeknownst to me at the time, one flash drive was still vfat when I thought it was ext2/3. For some reason no obvious warning such as &amp;ldquo;cannot change permissons on file xyz&amp;rdquo; appeared when moving files. So I assumed everything worked fine and I committed a small change to a file in my main git repository, which was moved, too. That wasn&amp;rsquo;t so smart. As I ran &lt;strong&gt;git status&lt;/strong&gt; too see how things were looking I got a listing of every file in the repo having been modified. Next I noticed that nearly all files had the executable bit set, all in .git as well. Then I noticed it was a vfat partition and moved the directory back to a Linux partition. Then, back on the old drive, the only error message that came back was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fatal: Not a git repository
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing I tried to remedy this was removing the executable bit from all files and a &lt;a href=&#34;http://episteme.arstechnica.com/eve/forums/a/tpc/f/96509133/m/348006021931/inc/-1&#34;&gt;helpful forum post&lt;/a&gt; supplied the useful one-liner:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find . -type f -print0 | xargs -0 chmod a-x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still, the fatal error message persisted. It had to be somehow connected to the .git directory which looked fine but when compared to a newly created test repository it became obvious that the one commit on the vfat partition had changed the filename HEAD to head…must be a default policy for case-insensitive filesystems… Anyway, all worked well after renaming head back to HEAD and a simple &lt;strong&gt;git checkout -f&lt;/strong&gt; got rid of any remaining problems with incongruent files. Maybe this post will be a helpful shortcut to somebody else who wasn&amp;rsquo;t paying attention.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
