<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wikidot="http://www.wikidot.com/rss-namespace">

	<channel>
		<title>Piotr Gabryjeluk blog:dev</title>
		<link>http://piotr.gabryjeluk.pl</link>
		<description>Blog, photos and developer notes of Piotr Gabryjeluk, one of Wikidot.com developers.</description>
				<copyright></copyright>
		<lastBuildDate></lastBuildDate>
		
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:using-caps-key-as-second-tab</guid>
				<title>Using Caps Key As Second Tab</title>
				<link>http://piotr.gabryjeluk.pl/dev:using-caps-key-as-second-tab</link>
				<description>

&lt;p&gt;As we all know, Caps Lock key is useless and annoying, so some folks change it to behave as third Control key. I prefer using two Controls I already have, so I mapped the Caps Lock key to Tab (which I use very often).&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Wed, 23 Dec 2009 13:49:45 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>As we all know, Caps Lock key is useless and annoying, so some folks change it to behave as third Control key. I prefer using two Controls I already have, so I mapped the Caps Lock key to Tab (which I use very often).</p> <div class="content-separator" style="display: none:"></div> <div class="code"> <pre> <code>$ xmodmap - remove Lock = Caps_Lock keycode 0x42 = Tab &lt;hit Ctrl-D now&gt;</code> </pre></div> <p>This works till you logout. If it does work, you can make this persistent by adding those two lines to your empty or missing ~/.Xmodmap file:</p> <div class="code"> <pre> <code>remove Lock = Caps_Lock keycode 0x42 = Tab</code> </pre></div> <p>If this doesn't work for you, it's possible your window manager doesn't take care of this file, but if you have a way to execute commands at each start of GNOME/KDE/whatever, just add the following command to those run it startup sequence:</p> <div class="code"> <pre> <code>xmodmap ~/.Xmodmap</code> </pre></div> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:cleaning-up</guid>
				<title>Cleaning Up</title>
				<link>http://piotr.gabryjeluk.pl/dev:cleaning-up</link>
				<description>

&lt;p&gt;Some of you, following Wikidot code on &lt;a href=&quot;http://github.com/gabrys/wikidot&quot;&gt;GitHub&lt;/a&gt; may see it&#039;s nicely split into templates, php, web and conf directories. But this is the first impression.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 17 Sep 2009 16:42:53 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Some of you, following Wikidot code on <a href="http://github.com/gabrys/wikidot">GitHub</a> may see it's nicely split into templates, php, web and conf directories. But this is the first impression.</p> <div class="content-separator" style="display: none:"></div> <p>Maintaining Wikidot is a bit more complex, because, files uploaded to sites are located in web, side to side with some static Wikidot php and javascript files. Also for historical reasons, there are web/files--common and web/files--local directories, which maps to /common--* and /local--* URLs and in fact, the files--local is never served directly by the web server (need to check permissions first).</p> <p>Also some time ago, we made static files versioned, so that we can apply more aggressive HTTP caching to them (reducing average page load time) and still be able to fix bugs on them without waiting a few days till the cache expire. In current model, URL to static file contains version hash, this may be for example: <a href="http://static.wikidot.com/v--b44e0ce810ee/common--javascript/WIKIDOT.js">http://static.wikidot.com/v--b44e0ce810ee/common--javascript/WIKIDOT.js</a> (notice the b44e0ce810ee). The whole static.wikidot.com is now hosted on Amazon's CloudFront, which means you get static Wikidot files from a server nearby your location and not always from USA.</p> <p>This all become quite complicated, so we decided to make things really clear and simple in the source code. The primary rule: make the source code (updatable from git) separate from files uploaded by users and generated by Wikidot. Second rule: make files that are automatically generated during installation (not in the runtime) separate from persistent files (like the uploaded by users) and from source code.</p> <p>And at the end there needs to be some place for logs and a place for temporary data (we need this to generate some random cool stuff, but after generating them, files are deleted).</p> <p>So we end up with something like this:</p> <ul> <li>WIKIDOT_ROOT <ul> <li><strong>data/</strong> <ul> <li>avatars/ — user avatars</li> <li>sites/ — site files (both generated thumbnails and uploaded files)</li> </ul> </li> <li><strong>generated/</strong> <ul> <li>static/ — generated static files. This dir can be server directly by a fast non-PHP webserver for static.wikidot.com in case we don't want CloudFront anymore</li> </ul> </li> <li><strong>tmp/</strong> — temporary files including Smarty compiled versions of templates. Content of this dir can be safely removed</li> <li><strong>logs/</strong> — Wikidot logs</li> <li>everything else — comes from git and is unchangeable by application</li> </ul> </li> </ul> <p>Application needs write-access to data, tmp and logs. Generated dir needs write access to one installing or upgrading application.</p> <p>Wikidot persistent data is now ONLY database and data/ directory, so it's easy to backup and restore the application (if you have enough time to make full backup of this).</p> <p>There is still one exception to this nice schema which is php/db/base directory, which is autogenerated during installation from XML database definition files, but the cleaning is not over, I still work on this.</p> <p>Nice thing about this work is that it does not need a lot of code changing, because directory paths are usually stored in one (max two) places in application, so this kind of totally reorganizing directory structure does not break things. As such, it is very very worth doing it. In the end we get clean internal structure of files and it's clear which files you can safely remove, which you can restore from git (and thus you can experiment a little on them — in case of crash, just re-download application), which are "state" of the Wikidot and where to look for logs.</p> <p>This all is also very important, because we aim to make current Wikidot.com source open and as such we want it to be a nice code.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:wikidot-crashes</guid>
				<title>Wikidot Crashes</title>
				<link>http://piotr.gabryjeluk.pl/dev:wikidot-crashes</link>
				<description>

&lt;p&gt;Last night we made Wikidot online again after a great crash.&lt;/p&gt;
&lt;p&gt;Wikidot was down for about 12 hours and the time it was down was full of work for us. We got a few things that could be broken starting from recent changes of the Wikidot software, hardware failure, high load-related kernel bugs or limitations to connection number or maximum possible number or file descriptors.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 27 Aug 2009 13:03:23 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Last night we made Wikidot online again after a great crash.</p> <p>Wikidot was down for about 12 hours and the time it was down was full of work for us. We got a few things that could be broken starting from recent changes of the Wikidot software, hardware failure, high load-related kernel bugs or limitations to connection number or maximum possible number or file descriptors.</p> <div class="content-separator" style="display: none:"></div> <p>The problem is Wikidot kind of worked, so some people had their sites loading, some other not and getting "500" errors. We didn't want to stop it, but at some point Wikidot was completely unusable. We switched the database to the other machine, but this was not the solution, then we switched all Wikidot traffic to the machine, still no good, we switched the software to some previous version, but this still seemed bad.</p> <p>Finally we worked out, there was a site, that had so big traffic, that it killed anything else (and itself as well). When we temporarily disabled it, the whole Wikidot started to work nicely again. Then Michał made some improvements for the high traffic site serving and the situation is stable again.</p> <p>In the middle of everything, we had huge problems with our hosting company and their service called Portable IP addresses. It seems that switching DNS is much more reliable that using Portable IPs that took hours to switch (and were supposed to take seconds to switch)! DNS switching time was 15 minutes.</p> <p>We learned a lot from the situation. Hardware upgrade postponed from really long time needs to be done quite quickly. We need more servers, to see which element breaks. For example if database server has high load, we know we need to tune database settings. If we have all on one massive server and one brick on it crashes, it usually causes all the server overloaded and this causes other bricks to crash as well, so it's hard too tell what the real problem is.</p> <p>Another thing is that we see our users want information on what happens. It's bad when Wikidot crashes, but it's even worse, when it crashes and they have no information about this.</p> <p>So, the next time a similar disaster happens we'll update on each technical detail possible, to let you know, that we know it's broken and we work hard to fix it. Other thing is, we plan having more fail-over servers in case something dies.</p> <p>Thank you all for using Wikidot, it's a great pleasure working (and fixing things) for you!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:yaml-and-php</guid>
				<title>YAML and PHP</title>
				<link>http://piotr.gabryjeluk.pl/dev:yaml-and-php</link>
				<description>

&lt;p&gt;There are 3 main YAML implementations for PHP:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Syck (native C library bindings to PHP)&lt;/li&gt;
&lt;li&gt;Symphony YAML (pure PHP)&lt;/li&gt;
&lt;li&gt;Spyc (pure PHP again)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the comparison.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 30 Jul 2009 19:03:36 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>There are 3 main YAML implementations for PHP:</p> <ul> <li>Syck (native C library bindings to PHP)</li> <li>Symphony YAML (pure PHP)</li> <li>Spyc (pure PHP again)</li> </ul> <p>This is the comparison.</p> <div class="content-separator" style="display: none:"></div> <h1><span>What the hell is YAML</span></h1> <p>Have you heard about XML or JSON? YAML is similarly to JSON and XML a way to store (read and write) structured data like arrays (a.k.a. lists), dictionaries (a.k.a. hash maps) and atomic values like strings and numbers. The structures can be nested, to form a definition of near-real-life objects, for example:</p> <div class="code"> <pre> <code>--- Piotr Gabryjeluk: company: Wikidot Inc. university: Nicolaus Copernicus University, Toruń, Poland lives_in: Toruń, Poland hobbies: - basketball - playing the guitar</code> </pre></div> <p>Which translates to PHP:</p> <div class="code"> <div class="hl-main"> <pre> <span class="hl-inlinetags">&lt;?php</span><span class="hl-code"> </span><span class="hl-var">$data</span><span class="hl-code"> = </span><span class="hl-reserved">array</span><span class="hl-brackets">(</span><span class="hl-quotes">'</span><span class="hl-string">Piotr Gabryjeluk</span><span class="hl-quotes">'</span><span class="hl-code"> =&gt; </span><span class="hl-reserved">array</span><span class="hl-brackets">(</span><span class="hl-code"> </span><span class="hl-quotes">'</span><span class="hl-string">company</span><span class="hl-quotes">'</span><span class="hl-code"> =&gt; </span><span class="hl-quotes">'</span><span class="hl-string">Wikidot Inc.</span><span class="hl-quotes">'</span><span class="hl-code">, </span><span class="hl-quotes">'</span><span class="hl-string">university</span><span class="hl-quotes">'</span><span class="hl-code"> =&gt; </span><span class="hl-quotes">'</span><span class="hl-string">Nicolaus Copernicus University, Toruń, Poland</span><span class="hl-quotes">'</span><span class="hl-code">, </span><span class="hl-quotes">'</span><span class="hl-string">lives_in</span><span class="hl-quotes">'</span><span class="hl-code"> =&gt; </span><span class="hl-quotes">'</span><span class="hl-string">Toruń, Poland</span><span class="hl-quotes">'</span><span class="hl-code">, </span><span class="hl-quotes">'</span><span class="hl-string">hobbies</span><span class="hl-quotes">'</span><span class="hl-code"> =&gt; </span><span class="hl-reserved">array</span><span class="hl-brackets">(</span><span class="hl-quotes">'</span><span class="hl-string">basketball</span><span class="hl-quotes">'</span><span class="hl-code">, </span><span class="hl-quotes">'</span><span class="hl-string">playing the guitar</span><span class="hl-quotes">'</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">))</span><span class="hl-code">;</span> </pre></div> </div> <p>So you see YAML is quite nice even when you need to write it yourself.</p> <p>YAML has its specification (see <a href="http://yaml.org">http://yaml.org</a>), so once we have standard YAML parser and standard YAML dumper we can send arrays from one machine to another and the result should be the same array as was sent.</p> <h1><span>PHP</span></h1> <p>So let's see what are the choices if you want to play with YAML in PHP.</p> <h2><span>Syck</span></h2> <p>This is the fastest and the most complete YAML dumper and loader library available. This is binding to C library and this is available in PEAR. It is also available as regular package in Ubuntu repository, so install it by simple:</p> <div class="code"> <pre> <code>aptitude install php5-syck</code> </pre></div> <p>In some shared hosting environment this could be a problem, so you need a pure PHP solution.</p> <h2><span>Spyc</span></h2> <p>This was the first PHP YAML implementation I saw. It is both dumper and loader and it seemed to work fine, but then I found some bugs, that stopped me from using it as the base and only YAML loader and dumper for Wikidot.</p> <p>This one has really nice thing, which is nice when you want your users to enter YAML to define things (like we do for forms). It is quite forgiving when it comes to the syntax and ignores things that don't fit and still parses the rest.</p> <p>Unfortunately as I stated before Spyc dumper so, when you first dump an array and then load it with Spyc you get something different (for example multiple new-lines are treated as one). Not good. Also as a loader it does not fully understand the full YAML specification (which is quite huge BTW).</p> <h2><span>Symphony YAML</span></h2> <p>This one is pure-PHP as well, so you don't need special rights, to use it on a PHP-enabled machine.</p> <p>It's loader does not understand full YAML specification, so for example you can't load documents dumped by Syck. Dumper is good.</p> <h1><span>Summary</span></h1> <table class="wiki-content-table"> <tr> <th></th> <th>Syck</th> <th>Spyc</th> <th>Symphony YAML</th> </tr> <tr> <td>type of library</td> <td>PHP extension</td> <td><strong>pure PHP library</strong></td> <td><strong>pure PHP library</strong></td> </tr> <tr> <td>speed</td> <td><strong>fast</strong></td> <td>slow</td> <td>slow</td> </tr> <tr> <td>loader: YAML support</td> <td><strong>full</strong></td> <td>bad</td> <td>not bad</td> </tr> <tr> <td>loader: if YAML is corrupted</td> <td>exception</td> <td><strong>tries to do its best to load the rest</strong></td> <td>exception</td> </tr> <tr> <td>dumper: YAML human-readable</td> <td>more-or-less</td> <td><strong>yes</strong></td> <td>more-or-less if set properly</td> </tr> <tr> <td>dumper: YAML conforms to spec</td> <td><strong>yes</strong></td> <td>no</td> <td><strong>yes</strong></td> </tr> <tr> <td>loads Syck's dumper output correctly</td> <td><strong>yes</strong></td> <td>no</td> <td>no</td> </tr> <tr> <td>loads Symphony's dumper output correctly</td> <td><strong>yes</strong></td> <td>no</td> <td><strong>yes</strong></td> </tr> </table> <h2><span>Verdict: loader</span></h2> <p>Syck is the winner in loading YAML. If you cannot use Syck, use Symphony YAML. If you need to parse user input (which should be human readable/writable similar to YAML), use Spyc.</p> <p>Actually, this is nice combination for loading:</p> <div class="code"> <div class="hl-main"> <pre> <span class="hl-inlinetags">&lt;?php</span><span class="hl-code"> </span><span class="hl-reserved">try</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// if syck is available use it</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">extension_loaded</span><span class="hl-brackets">(</span><span class="hl-quotes">'</span><span class="hl-string">syck</span><span class="hl-quotes">'</span><span class="hl-brackets">))</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">syck_load</span><span class="hl-brackets">(</span><span class="hl-var">$string</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// if not, use the symfony YAML parser</span><span class="hl-code"> </span><span class="hl-var">$yaml</span><span class="hl-code"> = </span><span class="hl-reserved">new</span><span class="hl-code"> </span><span class="hl-identifier">sfYamlParser</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-var">$yaml</span><span class="hl-code">-&gt;</span><span class="hl-identifier">parse</span><span class="hl-brackets">(</span><span class="hl-var">$string</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">catch</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Exception</span><span class="hl-code"> </span><span class="hl-var">$e</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// if YAML document is not correct,</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">Spyc</span><span class="hl-code">::</span><span class="hl-identifier">YAMLLoadString</span><span class="hl-brackets">(</span><span class="hl-var">$string</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span> </pre></div> </div> <p>This way, you have the fastest library used if possible, then the best pure-PHP, and if it fails in a way, that document was badly written (by human being for example), you fall-back to Spyc.</p> <h2><span>Verdict: dumper</span></h2> <p>In my opinion Symphony YAML dumper is the best from the three in terms of usability, portability and interoperability, because its output can be read by both itself and Spyc.</p> <p>However, if you dump YAML often, use (hell faster) Syck for both loading and dumping. The generated YAML won't be readable by Symphony YAML or Spyc, but this is because they don't follow the specification (so not Syck's problem in fact).</p> <p>Also note, that any valid JSON dumper output is readable by standard YAML 1.2 loaders, because JSON is a subset of YAML 1.2. So if using for data exchange (and not for talking to human) any fast JSON dumper can be used.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:july-news</guid>
				<title>July News</title>
				<link>http://piotr.gabryjeluk.pl/dev:july-news</link>
				<description>

&lt;p&gt;Some of you may be more used to me posting more often, than in last time.&lt;br /&gt;
Some of you may wonder why I stopped blogging.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 28 Jul 2009 15:41:03 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Some of you may be more used to me posting more often, than in last time.<br /> Some of you may wonder why I stopped blogging.</p> <div class="content-separator" style="display: none:"></div> <h2><span>Brussels</span></h2> <p>Last month was full of adventures. It started 1st of July with me going to Brussels meat our friend to talk about wikipedia-like site about art. We're going to help this man build the most complete site about art using Wikidot software!</p> <p>BTW, this was my first flight in lifetime. Quite a strange feeling, but generally fine.</p> <h2><span>Forms</span></h2> <p>I was working on some nice technical and UI improvements to Wikidot, that is crucial for the art site (but really really nice for Wikidot as well, like forms for editing, entering and viewing structured data to wiki pages).</p> <h2><span>Search issues</span></h2> <p>That week was also spent on some massive Wikidot.com search engine tweaks. A stupid one-line bug, which was <strong>not</strong> exporting proper LC_ALL environmental variable in indexing script, caused many sites that used Asian or East-European languages to be not indexed (most notably the great <a href="http://istorijska-biblioteka.wikidot.com/">ИСТОРИЈСКА БИБЛИОТЕКА</a>). At first we though that we can re-index the broken sites, but our re-indexing mechanism was way too slow (would last for weeks for all broken sites).</p> <p>Pieter then challenged me. He said he can index whole Wikidot in 6 hours. I thought it's not even possible, but then I started to work on that and I managed to index the whole Wikidot in less than 2 hours without indexing tags at first. Then with tags, it took 2 hours and 10 minutes or so. That was damn fast!</p> <p>Inspired by this and an accident of disk full error on /var partition of our webserver (but this is why we keep user-uploaded files and other important things on separate disks), I also rewrote the incremental indexer, to work in similar way to the whole-Wikidot-re-indexer.</p> <h2><span><tt>search-api reindex</tt></span></h2> <p>If you care about some technical details:</p> <ul> <li>all search operations are issued with use of search-api, a separate program that can: <ul> <li>re-index whole Wikidot</li> <li>queue indexing page/thread</li> <li>queue deleting page/thread</li> <li>queue re-indexing site</li> <li>flush queue</li> </ul> </li> <li>search-api is written in Python</li> <li>search-api uses PyLucene - a native Java Lucene library binded to CPython objects with PyJCC. Compiled with GNU Java Compiler to native code (like C programs), this binding has improved performance over using Lucene with Sun's Java.</li> <li>before rewriting it to only-Python, search-api was written in BASH and was a wrapper to: <ul> <li><tt>java -jar searchApiHelper.jar search "phrase-to-search"</tt></li> <li><tt>php search-api-helper.php flush</tt></li> </ul> </li> <li>search-api also takes care of file locking to assure that <ul> <li>only one process tries to modify the index</li> <li>items are added to queue one-after-another</li> <li>when doing some big index modification (read: full re-index) queue is not flushed (so that after the re-index all changes are applied to new index)</li> <li>when flushing queue takes more time, and cron tries to run more flushing processes, they simply end (so only one process flushes the queue at a time)</li> </ul> </li> </ul> <h2><span>Union of Rock Festival</span></h2> <p>Just after week spent in Brussels in nice hotel I went to Węgorzewo, Mazury (Poland biggest lakes distinct) to have fun on rock music festival. Unfortunately, the music level was not very impressive, so I mainly enjoyed the atmosphere on the camping area.</p> <p>The weather was not great. It was wet everywhere, the ground was covered in 20 centimeters of mud and it was hard to walk around without getting dirty. But during the first day of being there, I learned to do that.</p> <h2><span>Improved workflow at Wikidot</span></h2> <p>Some of you noticed, that recently we started to work more efficiently, but this is not quite true. In fact we work as efficiently as before, but we are better organized, and have better priorities on tasks. Also we keep track of what we do, so we can then tell what we've done. So for us, this is a little more work of "documenting" our work (so maybe we work even less efficiently than before?), but for the outside world, we make more noise (in a positive meaning) around that. So basically, people know what we do, what we are going to do, when they can expect changes and most importantly, they understand why some feature request is being postponed. This is (and was) because we have more important things to do, but before they couldn't tell it.</p> <p><span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/squark" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/160/160264/a16.png" alt="Squark" style="background-image:url(http://www.wikidot.com/userkarma.php?u=160264)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/160/160264/a16.png" alt="Squark" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=160264,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/squark" >Squark</a></span> turned into a professional project manager, that manages our time. <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/pieterh" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/0/99/a16.png" alt="pieterh" style="background-image:url(http://www.wikidot.com/userkarma.php?u=99)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/0/99/a16.png" alt="pieterh" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=99,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/pieterh" >pieterh</a></span> decided to <a href="http://blog.wikidot.com/">talk</a> to the Community and listen to their complaints (he reads or at least skims every post on Community forums). He tells Łukasz what needs to be done, Łukasz knows when we will have time to do this. This way communication inside Wikidot improved. Also we (<span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/michal-frackowiak" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/0/1/a16.png" alt="michal frackowiak" style="background-image:url(http://www.wikidot.com/userkarma.php?u=1)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/0/1/a16.png" alt="michal frackowiak" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=1,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/michal-frackowiak" >michal frackowiak</a></span> and me) no longer look on Community forums (some of you may regret), but this allows us to concentrate on our work.</p> <h2><span>The work continues</span></h2> <p>As I mentioned before, we want to introduce a great feature to Wikidot, which is <em>forms</em>. But the implementation now concentrates on the <a href="http://www.wikidot.org/">open source version of Wikidot software</a> (once it's ready, working and tested we'll copy the feature to the Wikidot.com service).</p> <h2><span><tt>aptitude install wikidot</tt></span></h2> <p>As forms is a huge change, I started to prepare a good ground for it and closed most important bugs in Wikidot open source and I'm about to start making Ubuntu packages for it to allow even-simpler installation on Debian-based systems. Now the installation involves only <a href="http://www.wikidot.org/installation-guide">6 child-easy steps</a> and in fact can be done by copying&amp;pasting a few commands.</p> <h2><span>Yesterday's party</span></h2> <p>Yesterday I went to met some old-school-times friends in the heart of the city. It was meant to be a meeting for "a beer or two" but evolved into beer and dancing till morning. That was first time I get a morning bus (not even the first) to my home just after partying.</p> <p>It was such a great fun and great folks I met.</p> <h2><span>Summary</span></h2> <p>I hope with this long blog post (but divided into friendly sections ;) ) I recompensed long period of not-posting anything here.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:lucene-replaces-local-searches</guid>
				<title>Lucene Replaces Local Searches</title>
				<link>http://piotr.gabryjeluk.pl/dev:lucene-replaces-local-searches</link>
				<description>

&lt;p&gt;As some of you know, some time ago I worked on a new &lt;a href=&quot;http://piotr.gabryjeluk.pl/dev:wikidot-search-launched&quot;&gt;Wikidot search&lt;/a&gt;. It is used for the &lt;a href=&quot;http://www.wikidot.com/search:all&quot;&gt;Search all sites&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;This work was done, because searching the whole Wikidot database was far from being fast. At first we used Google Custom Search Engine to solve the problem, but we wanted to be independent from it. Also we wanted to include search results that are accessible by the person that searches but not by search engines (like private sites).&lt;/p&gt;
&lt;p&gt;This worked really good. The only problem was long time spent to display the results. It was like 20 seconds, which was far better than when using previous search, but much worse than Google. This was strange, because from previous tests, we calculated the average search time should be about a second or two.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 23 Jun 2009 18:25:58 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>As some of you know, some time ago I worked on a new <a href="http://piotr.gabryjeluk.pl/dev:wikidot-search-launched">Wikidot search</a>. It is used for the <a href="http://www.wikidot.com/search:all">Search all sites</a> page.</p> <p>This work was done, because searching the whole Wikidot database was far from being fast. At first we used Google Custom Search Engine to solve the problem, but we wanted to be independent from it. Also we wanted to include search results that are accessible by the person that searches but not by search engines (like private sites).</p> <p>This worked really good. The only problem was long time spent to display the results. It was like 20 seconds, which was far better than when using previous search, but much worse than Google. This was strange, because from previous tests, we calculated the average search time should be about a second or two.</p> <div class="content-separator" style="display: none:"></div> <p>It started to be clear, when we noticed that only 20-30 searches per day are performed. The index is quite big, and it needs to be cached in RAM to work with sufficient performance.</p> <p>Today I did more tests under heavy load and it seams Lucene can handle big number of queries. When users search often, the index is partially or even fully cached by the filesystem and searches are really quick!</p> <p>But our main problem to solve today was slow local search a.k.a. "search this site". Moreover many concurrent queries were degrading database performance (not only for searching), so we decided to enable Lucene for local searches as well.</p> <p>I must say it works really nice, fast and has a nice set of syntax tricks you can do with it, for example you can search for pages with something in tags. Just search for <em>youtube tags:embed</em>. This would search for pages matching <em>youtube</em> (in tags, title or content) and with <em>embed</em> in tags. If no such pages are found, partial matches are also returned, like: pages matching <em>youtube</em> (but with no <em>embed</em> in tags) or pages with <em>embed</em> in tags, (but not matching <em>youtube</em>).</p> <p>To sum up, new search is faster, gives more accurate results, saves the database performance (which was the main goal) and allows nicer syntax than the old one.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:php-as-fastcgi-backend</guid>
				<title>PHP as FastCGI backend and Lighttpd</title>
				<link>http://piotr.gabryjeluk.pl/dev:php-as-fastcgi-backend</link>
				<description>

&lt;h1&gt;&lt;span&gt;Wikidot + Lighttpd + PHP5&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;At Wikidot we use PHP5 as FastCGI backend to Lighttpd light-and-fast webserver. It works like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there are a few hundreds of php5-cgi processes (name is cgi, but they also support FastCGI mode) running and waiting to be used&lt;/li&gt;
&lt;li&gt;lighttpd (only one needed!) process manages the network connections to all the clients and once the request is ready serves a static file or forwards the request to one of PHP backends processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 15 Jun 2009 20:59:29 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>Wikidot + Lighttpd + PHP5</span></h1> <p>At Wikidot we use PHP5 as FastCGI backend to Lighttpd light-and-fast webserver. It works like this:</p> <ul> <li>there are a few hundreds of php5-cgi processes (name is cgi, but they also support FastCGI mode) running and waiting to be used</li> <li>lighttpd (only one needed!) process manages the network connections to all the clients and once the request is ready serves a static file or forwards the request to one of PHP backends processes.</li> </ul> <div class="content-separator" style="display: none:"></div> <p>We used to use internal Lighttpd FastCGI process manager, meaning the lighttpd processes actually used to start the PHPs.</p> <h1><span>Problems</span></h1> <p>We encountered some known problems of 500 (server side) errors appearing after some random time, especially under a high traffic. The typical message appearing at the Lighttpd's error.log was:</p> <div class="code"> <pre> <code>&lt;some date&gt;: (mod_fastcgi.c.2494) unexpected end-of-file (perhaps the fastcgi process died): pid: ...</code> </pre></div> <p>There are plenty of reports on this in both <a href="http://forum.lighttpd.net/topic/93190">Lighttpd</a>'s and <a href="http://bugs.php.net/bug.php?id=43295">PHP</a>'s <a href="http://forum.lighttpd.net/topic/93190">forums</a>, <a href="http://pecl.php.net/bugs/bug.php?id=12608">bug</a> <a href="http://redmine.lighttpd.net/issues/1466">trackers</a> and even <a href="http://www.rooftopsolutions.nl/article/201">some</a> <a href="http://jcj.net/?p=24">blogs</a>.</p> <h1><span>Workarounds</span></h1> <p>We managed to write some hacky scripts that detected the situation and restarted the backends when needed. The reaction was so quick, that almost no-one noticed the error, but damn, this is not how WE solve problems.</p> <h1><span>A blind try</span></h1> <p>We decided to give spawn-fcgi a shot. What is it? It is a program that spawns FastCGI backends (independently from Lighttpd server). Why trying it? I've read somewhere, that it works more reliably than the internal Lighttpd spawner. What's interesting is that this program comes from lighttpd package, so we're in family anyway. It's mainly intended to run the FastCGI backends from different user than the webserver user or to run them on different machine(s) than the webserver machine. This can be used naturally for some smart load-balancing.</p> <p>The only problem of this solution we encountered was internal limit of number of processes to spawn by a single process which was 256 (hardcoded, fixed in next versions). But at the same time, we decided to build a few FastCGI bridges (each spawning ~200 PHPs) anyway so that was no longer a problem for us.</p> <p>What was quite surprising (but honestly, I deeply believed in this), our problems with 500 server errors and PHP disappeared. This configuration works for about 2 weeks now with absolutely no hacky scripts involved and no restarting needed. Cool.</p> <h1><span>Why I wrote this</span></h1> <p>I wrote this short note just for the record and to let other people know, that using spawn-fcgi instead of the internal Lighttpd's FastCGI spawner might solve their problems with PHP (FastCGI) and 500 internal server errors.</p> <p>Hope this helps someone.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:announcing-pymalist</guid>
				<title>Announcing pymalist</title>
				<link>http://piotr.gabryjeluk.pl/dev:announcing-pymalist</link>
				<description>

&lt;p&gt;I would like to announce &lt;a href=&quot;http://piotr.gabryjeluk.pl/pymalist&quot;&gt;pymalist&lt;/a&gt; project. It is a stupid and simple highly-modular Pythonic mail list server, that uses basic concepts to do the job well.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sat, 23 May 2009 12:24:04 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>I would like to announce <a href="http://piotr.gabryjeluk.pl/pymalist">pymalist</a> project. It is a stupid and simple highly-modular Pythonic mail list server, that uses basic concepts to do the job well.</p> <div class="content-separator" style="display: none:"></div> <p>Read more on <a href="http://piotr.gabryjeluk.pl/pymalist">pymalist</a> page, browse the source on <a href="http://github.com/gabrys/pymalist">GitHub project page</a>.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:wikidot-org-refresh</guid>
				<title>Refreshing www.wikidot.org</title>
				<link>http://piotr.gabryjeluk.pl/dev:wikidot-org-refresh</link>
				<description>

&lt;p&gt;Yesterday I started refreshing &lt;a href=&quot;http://www.wikidot.org/&quot;&gt;www.wikidot.org&lt;/a&gt; website — the home of Wikidot open source software.&lt;/p&gt;
&lt;p&gt;Also we decided to move from managing our code in SVN to Git — more precisely to www.github.com. Our project page at GitHub is the following: &lt;a href=&quot;http://github.com/gabrys/wikidot&quot;&gt;http://github.com/gabrys/wikidot&lt;/a&gt;. Feel free to just follow it or even fork!&lt;/p&gt;
&lt;p&gt;Last times, I made Wikidot insanely easy to install (check out the &lt;a href=&quot;http://www.wikidot.org/installation-guide&quot;&gt;installation guide&lt;/a&gt;). It totally rocks (you can install Wikidot within 10 commands and no file editing).&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 21 May 2009 18:11:17 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Yesterday I started refreshing <a href="http://www.wikidot.org/">www.wikidot.org</a> website — the home of Wikidot open source software.</p> <p>Also we decided to move from managing our code in SVN to Git — more precisely to www.github.com. Our project page at GitHub is the following: <a href="http://github.com/gabrys/wikidot">http://github.com/gabrys/wikidot</a>. Feel free to just follow it or even fork!</p> <p>Last times, I made Wikidot insanely easy to install (check out the <a href="http://www.wikidot.org/installation-guide">installation guide</a>). It totally rocks (you can install Wikidot within 10 commands and no file editing).</p> <div class="content-separator" style="display: none:"></div> <p>I want to state, that this post is the last about the Wikidot open source software on this blog, as I'm running a new blog just about the Wikidot software at <a href="http://www.wikidot.org/blog">wikidot.org</a>. This will be more practical to filter the posts and will push some life into that site.</p> <p>At the end I want to invite you to the <a href="http://www.wikidot.org/irc-channel">Wikidot IRC channel</a> #wikidot at irc.freenode.org. That would be probably the easiest way to contact the Wikidot team without much formalism.</p> <p>(As many posts here were about Wikidot software, this blog will be less regularly updated, but if really care about Wikidot software news, just follow the new one and you won't be spammed about posts about Python, BASH or other things. I hope that blog-split will really help everyone.)</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:ubuntu-and-intel-agn-wireless-card</guid>
				<title>Ubuntu And Intel AGN Wireless Card</title>
				<link>http://piotr.gabryjeluk.pl/dev:ubuntu-and-intel-agn-wireless-card</link>
				<description>

&lt;p&gt;I &lt;strong&gt;finally&lt;/strong&gt; managed to get a version of kernel/modules that work nice on my wireless card:&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 19 May 2009 15:20:42 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>I <strong>finally</strong> managed to get a version of kernel/modules that work nice on my wireless card:</p> <div class="content-separator" style="display: none:"></div> <div class="code"> <pre> <code>06:00.0 Network controller: Intel Corporation PRO/Wireless 4965 AG or AGN [Kedron] Network Connection (rev 61)</code> </pre></div> <p>I had problems since late Hardy or Intrepid, using different kernel from that time with no luck. The worst case was connecting to WPA2 Enterprise (user/password) secured network. In worst subcase, I had the connection for 5-20 minutes and after that the network card or driver hanged and then only computer reboot used to help.</p> <p>I was used to see <tt>iwlist scan</tt> errors like "Resource temporarily unavailable" or "Busy". <tt>dmesg</tt> showed different things on different kernels.</p> <p>My frustration was great, when the problem started (after some upgrades that meant to improve things) to appear even on unsecured network. The connection was broken and I had to notoriously reconnect with my NetworkManager.</p> <p>Finally I found a version of kernel that plays well.</p> <p>It's from jaunty-proposed repository (enable it in your Synaptic or other package manager). Don't use jaunty-backports (this one was only broken). The package that solves things is: <strong>linux-backports-modules-jaunty</strong> version <strong>2.6.28.12.16</strong>. The install should also update the linux-image to 2.6.18-12 (update your grub.conf as usual to include the changes).</p> <p>After rebooting, run uname, to verify the kernel version:</p> <div class="code"> <pre> <code># uname -a Linux vaio 2.6.28-12-generic #43-Ubuntu SMP Fri May 1 19:31:32 UTC 2009 x86_64 GNU/Linux</code> </pre></div> <p>This is crucial here: <strong>2.6.28-12-generic</strong>. The original Ubuntu kernel is 2.6.28-11 not -12.</p> <p>Hope this helps someone (with VAIO SZ or any other Intel AGN-enabled notebook with Ubuntu).</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:wikidot-opensource-changes-in-trunk</guid>
				<title>Wikidot OpenSource Changes In Trunk</title>
				<link>http://piotr.gabryjeluk.pl/dev:wikidot-opensource-changes-in-trunk</link>
				<description>

&lt;p&gt;I decided to wait no more and share what I&#039;ve kept at my local disk. I committed the changes &lt;a href=&quot;http://piotr.gabryjeluk.pl/dev:wikidot-opensource-direction&quot;&gt;I&#039;ve been working on&lt;/a&gt; to the repository.&lt;/p&gt;
&lt;p&gt;The biggest change is Wikidot is now single wiki engine by default (you need additional configuration for the wiki farm mode).&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sat, 16 May 2009 11:38:27 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>I decided to wait no more and share what I've kept at my local disk. I committed the changes <a href="http://piotr.gabryjeluk.pl/dev:wikidot-opensource-direction">I've been working on</a> to the repository.</p> <p>The biggest change is Wikidot is now single wiki engine by default (you need additional configuration for the wiki farm mode).</p> <div class="content-separator" style="display: none:"></div> <p>What demonstrates the change is the simplified installation process.</p> <p>Now it's as simple as installing any other software or service:</p> <ol> <li>install the dependencies <ul> <li>for Ubuntu: <tt>sudo aptitude install lighttpd php5 php5-cli tetex-bin tetex-extra gs-gpl imagemagick libmagic1 subversion postgresql-8.3 php5-tidy php5-pgsql php5-gd zip make</tt></li> </ul> </li> <li>get the code <ul> <li><tt>svn co <a href="http://svn.wikidot.org/repos/wikidot1/trunk/">http://svn.wikidot.org/repos/wikidot1/trunk/</a> wikidot-install-dir</tt></li> <li><tt>cd wikidot-install-dir</tt></li> </ul> </li> <li>if on Ubuntu: configure conf/wikidot.ini and set user at [db] section to your login name</li> <li>prepare database (create role and database) <ul> <li>as user postgres: <tt>make prepare_db</tt></li> </ul> </li> <li>build Wikidot: configure files and make initial dabatase <ul> <li>as you: <tt>make</tt></li> <li>this prints a secret URL at the end, you'll use it to configure Admin password</li> </ul> </li> <li>start Wikidot <ul> <li>./wikidotctl start</li> </ul> </li> <li>set Admin password <ul> <li>use your browser and navigate to the secret URL make prints at the end</li> </ul> </li> </ol> <p>We would like to finally create Debian/Ubuntu packages for that (once it gets more stable and have some bug fixed). So keep tuned!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:wikidot-opensource-direction</guid>
				<title>Wikidot OpenSource Development Direction</title>
				<link>http://piotr.gabryjeluk.pl/dev:wikidot-opensource-direction</link>
				<description>

&lt;p&gt;Today we though that we might completely change the direction for Wikidot OpenSource. This would deprecate the previous &lt;a href=&quot;http://piotr.gabryjeluk.pl/dev:wikidot-1-0-roadmap&quot;&gt;Wikidot 1.0 Roadmap&lt;/a&gt;. Also this solution deprecates &lt;a href=&quot;http://piotr.gabryjeluk.pl/dev:wdlite-ready-for-testing&quot;&gt;wdLite&lt;/a&gt; (which was a big dirty hack).&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 11 May 2009 20:01:25 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Today we though that we might completely change the direction for Wikidot OpenSource. This would deprecate the previous <a href="http://piotr.gabryjeluk.pl/dev:wikidot-1-0-roadmap">Wikidot 1.0 Roadmap</a>. Also this solution deprecates <a href="http://piotr.gabryjeluk.pl/dev:wdlite-ready-for-testing">wdLite</a> (which was a big dirty hack).</p> <div class="content-separator" style="display: none:"></div> <h1><span>Single wiki mode</span></h1> <p>The main difference is introducing a new Wikidot installation mode: "sigle wiki mode" and setting it as the default instead of "wiki farm mode".</p> <p>What does it mean?</p> <ul> <li>you don't have to bother about: <ul> <li>domains (DNS)</li> <li>mail configuration</li> <li>root access (you can start Wikidot on custom port — default 8080)</li> </ul> </li> <li>you get direct administration panel for managing users</li> </ul> <p>But:</p> <ul> <li>you get only ONE single wiki</li> <li>you cannot create more wikis</li> <li>every IP/DNS address pointing to the started server (and corresponding port) displays THE wiki</li> <li>no mail invitations or password recovery</li> <li>no HTTPS</li> </ul> <h1><span>Lighttpd</span></h1> <p>Our main webserver is now <a href="http://lighttpd.net/">Lighttpd</a>, we forget about Apache. We can do this, because now you can run your regular Apache server on :80 port and Lighttpd serving Wikidot on :8080 (or other). They don't overlap, so there's no need to keep the compatibility.</p> <p>Wikidot is a full web service with its dedicated web server running on separate port. This is the philosophy.</p> <h1><span>Scripts</span></h1> <p>To make Wikidot easily installable, we're going to create a script (possibly a Makefile) that will simplify the whole process. It would be 4 steps:</p> <ul> <li>install dependencies</li> <li>setup the database</li> <li>edit ini file (supply database credentials)</li> <li>make</li> </ul> <p>Dependencies are Lighttpd, PHP, PostgreSQL, ImageMagick and maybe a few other things. Each of them can be installed without root access, but with root access it would be just as easy as</p> <ul> <li>aptitude install lighttpd php5-cgi postgresql-8.3 ImageMagick</li> </ul> <p>(or so)</p> <p>Running/stopping Wikidot:</p> <ul> <li>./wikidotctl start</li> <li>./wikidotctl stop</li> </ul> <h1><span>Next steps</span></h1> <p>Once all this is done (which isn't too much work BTW), we could make a *.deb package, that would take care of dependencies, database, creating a special user for wikidot, setting the root directory by convention and installing init script in proper place. Then you would just:</p> <ul> <li>/etc/init.d/wikidot start/stop/restart</li> </ul> <p>This should kill all Wikidot-is-to-hard-to-install issues.</p> <h1><span>… upgrade to wiki farm</span></h1> <p>Once you have your single-wiki Wikidot up and running you may want to upgrade to full featured wiki farm solution. The one-wiki would become the main wiki (as is www.wikidot.com for the Wikidot.com service), other wikis would be able to be created. On the other hand, as your getting more powerful, you need to configure your DNS (have a domain-class just for your wiki farm), mail service and move the service to the main HTTP port (80).</p> <h1><span>The hardest thing</span></h1> <p>Q: What is the hardest thing to do in that plan?<br /> A: The hardest thing is to create a module to manage wiki users.</p> <p>This should not be too hard, so in the overall, this should work!</p> <p>I'm waiting for your opinions.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:api-save</guid>
				<title>API page.save</title>
				<link>http://piotr.gabryjeluk.pl/dev:api-save</link>
				<description>

&lt;p&gt;The &lt;a href=&quot;http://www.wikidot.com/doc:api&quot;&gt;Wikidot API&lt;/a&gt; &lt;strong&gt;page.save&lt;/strong&gt; method is one of the things we&#039;ve been holding over for weeks, just because there were so many other important things to do.&lt;/p&gt;
&lt;p&gt;But the API is also important, because it can enable developers (which are big fraction of our users) to create applications that use Wikidot.com as a service. This can be useful in many ways, the best example I can think of is creating an application, that get some photos from your disk, uploads them to Wikidot, creates a page for each of them and saves some metadata from them into it.&lt;/p&gt;
&lt;p&gt;Today, I finally managed to start working on one of the most important API method which is page.save. This method obviously is for saving a page. The page saving mechanism in Wikidot is quite complex, because of advanced site permissions, various performance optimizations, caches and many scenarios of updating a page.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 07 May 2009 20:32:23 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>The <a href="http://www.wikidot.com/doc:api">Wikidot API</a> <strong>page.save</strong> method is one of the things we've been holding over for weeks, just because there were so many other important things to do.</p> <p>But the API is also important, because it can enable developers (which are big fraction of our users) to create applications that use Wikidot.com as a service. This can be useful in many ways, the best example I can think of is creating an application, that get some photos from your disk, uploads them to Wikidot, creates a page for each of them and saves some metadata from them into it.</p> <p>Today, I finally managed to start working on one of the most important API method which is page.save. This method obviously is for saving a page. The page saving mechanism in Wikidot is quite complex, because of advanced site permissions, various performance optimizations, caches and many scenarios of updating a page.</p> <div class="content-separator" style="display: none:"></div> <p>Also, we wanted to make page.save to be one method to update and create pages, which involves other permission checking, because in some cases you have permission to create a page, but not to edit. This is especially true if you have a site category with everybody-can-create-but-only-author-can-edit policy.</p> <p>I started from dealing with updating already existing pages, so for now page.save can't yet create pages. We have more or less working page.save for the following activities:</p> <table class="wiki-content-table"> <tr> <th></th> <th>code in Python syntax</th> </tr> <tr> <th>set title</th> <td><strong>page.save</strong>({ 'site': 'quake', 'page': 'dev:api-save', <strong>'title': 'Other title'</strong> })</td> </tr> <tr> <th>update page source</th> <td><strong>page.save</strong>({ 'site': 'quake', 'page': 'dev:api-save', <strong>'source': '+ Come back later'</strong> })</td> </tr> <tr> <th>set tags</th> <td><strong>page.save</strong>({ 'site': 'quake', 'page': 'dev:api-save', <strong>'tags': ['wikidot', 'api']</strong> })</td> </tr> <tr> <th>all at once</th> <td><strong>page.save</strong>({ 'site': 'quake', 'page': 'dev:api-save', <strong>'title': 'Other title', 'source': '+ Come back later', 'tags': ['wikidot', 'api']</strong> })</td> </tr> </table> <p>Notes:</p> <ul> <li>the API service is XML-RPC based, so the syntax of calls is syntax of the programming language you use (Python used as an example syntax)</li> <li>site: quake and page: dev:api-save means http://<strong>quake</strong>.wikidot.com/<strong>dev:api-save</strong> page</li> <li>setting tags as a single string (comma- or space-separated) is also supported</li> <li>setting of parent page is also supported in similar way to setting the title</li> </ul> <p>Now we're going to review the code, ensure there is no more caching mechanism I forgot about and make final polishing. This includes generating notifications to be send to page-watchers and saving revision comments.</p> <p>This method is crucial for the API. Once it gets fully working (including creating pages), applications will be able to:</p> <ul> <li>create pages</li> <li>read pages</li> <li>modify pages</li> </ul> <p>and this the <strong>core functionality we want to provide with the Wikidot API</strong>.</p> <p>Check out the <a href="http://www.wikidot.com/doc:api-methods">API methods</a> to see if page.save is already out.</p> <p>If you want to test the (beta) API, drop a comment at the <a href="http://www.wikidot.com/doc:api">Wikidot API page</a>.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:iphone-vs-android</guid>
				<title>Apple&#039;s iPhone VS Google&#039;s Android</title>
				<link>http://piotr.gabryjeluk.pl/dev:iphone-vs-android</link>
				<description>

&lt;p&gt;Do you wonder what are the main differences between iPhone and Android?&lt;/p&gt;
&lt;p&gt;I prepared a quick and highly subjective comparison.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sun, 03 May 2009 17:45:50 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Do you wonder what are the main differences between iPhone and Android?</p> <p>I prepared a quick and highly subjective comparison.</p> <div class="content-separator" style="display: none:"></div> <p>First in what ares the two phones are comparable:</p> <ul> <li>they are phones</li> <li>they look and behave similarly (users can easily migrate from one to another)</li> </ul> <p>OK, now what is different:</p> <table class="wiki-content-table"> <tr> <th>area</th> <th>iPhone</th> <th>Android</th> </tr> <tr> <th>GUI</th> <td>eye-candy and polished to single detail</td> <td>eye-candy but not perfect</td> </tr> <tr> <th>applications you can install</th> <td>only from AppStore (application from different repositories after jail-breaking)</td> <td>any application from start</td> </tr> <tr> <th>hardware</th> <td>phone, music player</td> <td>many small devices (including netbooks)</td> </tr> <tr> <th>integration with Desktop</th> <td>Mac/Windows</td> <td>Mac/Windows/Linux</td> </tr> <tr> <th>software developing platform</th> <td>closed (need to enroll first), special IDE for Mac</td> <td>open, runs also on Linux, includes Eclipse plug-in integrated with device Emulator</td> </tr> <tr> <th>hacking</th> <td>jail-break and install many open-source applications</td> <td>get root access on your G1 with specially designed SD-card (gold-card), or install Android on other device (like FreeRunner)</td> </tr> </table> <p>I just need to say, that developing application for Android is just easy as</p> <ul> <li>downloading and unpacking SDK (software development kit)</li> <li>running Android Debug/Development Bridge from the SDK:</li> </ul> <div class="code"> <pre> <code>$ cd SDK $ tools/adb start-server</code> </pre></div> <ul> <li>installing an Eclipse plug-in (via the Eclipse update dialog)</li> <li>creating virtual Android device (emulator)</li> </ul> <div class="code"> <pre> <code># cd SDK # tools/android create avd -t 1 -n "androidemulator"</code> </pre></div> <ul> <li>running emulator</li> </ul> <div class="code"> <pre> <code># cd SDK # tools/emulator -avd "androidemulator"</code> </pre></div> <ul> <li>and then, for every application you want to write: <ul> <li>creating new Android project with Eclipse</li> <li>developing application in Java and Visual-Editors (mainly for GUI development)</li> <li>testing on emulator (by clicking on Run in Eclipse)</li> </ul> </li> </ul> <p>The .apk files (which are fully-enclosed applications) are created automatically. You can then sign them with your key and sell on Android Application Market. This requires you to register for $50. 1/3 of your income is taken by Google.</p> <p>I really need to say, this impresses me much, because the SDK (including emulator) and Eclipse plug-in are so well integrated, that I didn't need any Android-specific knowledge to start working on Android application. I would really, really like to develop something for iPhone, just for comparison, but I would need to have Mac and then enroll to the developer platform. Also I would need to learn Objective C, a language that is hardly used outside of areas influenced by Apple.</p> <p>So to cut long story short from the developer point of view it seems that Android is much much better. Also the fact that your application will run on more devices in the future without any modifications is really nice.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:to-i-owo</guid>
				<title>To i owo</title>
				<link>http://piotr.gabryjeluk.pl/dev:to-i-owo</link>
				<description>

&lt;p&gt;Tracę już powoli pomysły na sensowne tytuły notek, ale pisać coś trzeba. Zatem dzisiaj krótkie podsumowanie ostatnich kilku dni.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Wed, 29 Apr 2009 20:34:10 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Tracę już powoli pomysły na sensowne tytuły notek, ale pisać coś trzeba. Zatem dzisiaj krótkie podsumowanie ostatnich kilku dni.</p> <div class="content-separator" style="display: none:"></div> <h1><span>CUDA</span></h1> <p>Zaczynając od początku czyli od wczoraj: rano działy się niezłe CUDA. Konkretnie, chodzi o technologię firmy nVidia stosowaną w kartach graficznych, która nazywa się CUDA. Ogólny zamysł polega na wykorzystaniu jednostki GPU (procesor karty graficznej) do wykonywania napisanych w języku podobnym do C (i odpowiednio skompilowanych).</p> <p>Procesory GPU różnią się znacznie od naszych CPU (centralnych procesorów). W procesorze CPU mamy zwykle 1, 2 lub 4 rdzenie. W procesorach graficznych kart nVidii mamy dziesiątki, czy nawet setki multiprocesorów (bliski procesorowi wielordzeniowemu).</p> <p>Ogólnie, możemy uruchomić około 1000 i więcej "wątków" na jednej karcie graficznej. Daje to spore możliwości wykorzystania takiej architektury w obliczeniach równoległych. Przedtem taka architektura była tylko wykorzystywana przez zamknięte sterowniki karty graficznej w celu renderowania skomplikowanej grafiki 3D w czasie rzeczywistym (i nie tylko). Teraz moc obliczeniowa stoi przed zdolnymi programistami i czeka na wykorzystanie.</p> <p>Oprócz niewątpliwej zalety jaką jest wysoka współbieżność, mamy również garść bardzo szybkiej pamięci do dyspozycji (tak szybka jak rejestry procesora). Po krótkiej analizie jednak okazało się, że konieczność współdzielenia jej przez wiele wątków redukuje nam średnią ilość tej szybkiej pamięci do kilku bajtów na wątek. Należy więc jednak trochę z tym uważać.</p> <p>Odwołania do pamięci RAM karty graficznej są wolne, jednak procesor potrafi w czasie "czekania" na dane z RAM-u wykonywać inny wątek. Jest to również stosowane w zwykłych procesorach. Znane jest pod pojęciem wywłaszczenia procesu (np., gdy proces oczekuje na operację I/O na wolnym dysku na jego miejsce "wskakuje" inny proces). Jednak w odróżnieniu od zwykłego procesora, gdzie wywłaszczenie inicjuje system operacyjny, a samo przełączenie kontekstu jest dość kosztowne, w CUDA-ch wywłaszczenie jest częścią logiki realizowanej sprzętowo i jest bardzo bardzo szybkie. Zatem odpowiednio oprogramowując używanie pamięci globalnej, możemy uzyskać wykonywanie programu (złożonego z wielu — np. 256 — wątków) bez przerw w wykorzystywaniu mocy obliczeniowej GPU.</p> <p>Oprócz zalet mamy oczywiście też wady. Jedną z nich (choć można to uznać za zaletę) jest brak mechanizmów cache'owania i stronicowania, co powoduje konieczność bardzo skrupulatnego zarządzania pamięcią.</p> <p>Np. na moim laptopie nie udało się uruchomić prawie żadnej przykładowej aplikacji dołączonej do SDK CUDA-ów. Powód: za mało pamięci. Pamięci karty graficznej oczywiście. W przypadku tradycyjnego procesora pamięć zwykle nie stanowi problemu, ponieważ tak naprawdę nigdy nią nie zarządzamy. Jako podstawę mamy pamięć RAM. Gdy komórka pamięci jest często używana trafia (automatycznie) do keszu, który jest dużo szybszy. Z drugiej strony, jeśli pamięci brakuje, następuje przeniesienie nieużywanej pamięci do swapa, czyli na dysk, który z kolei jest bardzo wolny w porównaniu do pamięci RAM.</p> <p>Efekt jest taki, że programując nie przejmujemy się gdzie będzie przechowywana zmienna. Na start trafia ona do RAM-u, ale może zostać skopiowana do szybkiego kesza, lub przeniesiona na wolny dysk. W CUDA-ch musimy sami zdecydować gdzie będą przechowywane poszczególne zmienne. Problem, który się pojawia, to nieznana ilość dostępnej pamięci. Każda karta ma inną dostępną pamięć każdego rodzaju, co więcej uruchomienie aplikacji graficznych (np. Compiz) zmniejsza ilość dostępnej pamięci.</p> <p>Są zatem dwa wyjścia — albo programujemy na konkretną kartę grafiki i wymagamy, aby tylko nasz program był na niej uruchomiony (a nie np. jeszcze Quake ;) ), albo inwestujemy w jakiś system zarządzania pamięcią karty graficznej (czyli robimy kawałek systemu operacyjnego, tyle, że na GPU). Oczywiście druga rzecz jest trudna i zmniejsza wydajność całego systemu.</p> <p>W praktyce pozostaje jeszcze jedno rozwiązanie. Zakupienie karty nVidia SPECJALNIE do obliczeń w technologii CUDA. Firma nVidia nie pozostawia nas w trudnej sytuacji i daje nam do dyspozycji kartę Tesla, która ma ze 4&nbsp;GB RAM-u, dużo wszystkiego (wątków, multiprocesorów itd), za to w zasadzie ciężko ją nazwać graficzną, bo nie ma wyjścia wideo. Przewrotnie, co?</p> <h1><span>Android</span></h1> <p>Następny dzień, następna prezentacja. Dzisiaj dotyczyła ona systemu Android opierającego się na jądrze Linux przystosowanego dla telefonów komórkowych (takich jak HTC G1) i innych małych urządzeń. System, pomimo jądra Linuksa jest czymś zupełnie innym niż znane nam z desktopów Ubuntu, a nawet projekty uruchamiania Linuksa na telefonach (np. OpenMoko). Różnica polega na tym, że warstwa narzędzi GNU (shell, podstawowe programy, zarządzanie użytkownikami) zostaje zastąpiona przez warstwę bibliotek w C i w Javie.</p> <p>Należy sobie jednak zdać sprawę z tego, że Java w Androidzie, to rzecz nieco inna niż Java używana na PC-tach, czy nawet w komórkach. Androidowa Java ma swoją implementację maszyny wirtualnej. Jest to DalvikVM, zoptymalizowany na maszyny o małej ilości RAM-u (64MB dla całego systemu) i wolnych procesorach (200-500&nbsp;MHz i ARM). Z tej optymalizacji bierze się inny bytecode, który optymalizuje użycie CPU i RAM. Również w celu zwiększenia wydajności pracy z pakietami zmianie ulega sposób wewnętrznej organizacji pakietu.</p> <p>Jednak wciąż pozostaje to Java i to dość niedaleko leżąca od tej Sunowej. W praktyce bowiem programowanie wygląda tak:</p> <ul> <li>programujemy w Javie (jednak mamy do dyspozycji mniej funkcji bibliotecznych)</li> <li>kompilujemy programy do bytecodu Sunowego (pliki class)</li> <li>tworzymy paczkę JAR</li> <li>korzystając z narzędzia dx konwertujemy plik JAR do pliku JAR, który zawiera bytecode przystosowany do maszyny DalvikVM. Otrzymany JAR jest zwykle ponad 2 razy mniejszy!</li> </ul> <p>Poza zmianą formatu bytecode'u, czeka nas również zmiana w działaniu wszystkich istotnych części systemu. Odpowiednie klasy dostarczone w bibliotece Androida pozwalają nam na tworzenie "okien", zadań działających w tle, komunikowanie się z innymi procesami, dostęp do danych zapewnianych przez inne programy (np. książkę adresową), ustawień telefonu i sprzętu. Wszystko to jest opakowane przez zarządcę uprawnień, który przyznaje danej aplikacji prawa do różnych części systemu po uprzednim uzgodnieniu tego z użytkownikiem telefonu ;). W praktyce, wygląda to tak:</p> <ul> <li>twórca aplikacji definiuje w "opisie" aplikacji (plik AndroidManifest.xml) jakich uprawnień potrzebuje aplikacja (np. uprawnienie do wybierania numeru, uprawnienie do czytania z GPS-u, uprawnienie do uruchamiania aplikacji)</li> <li>użytkownik instalując aplikację przyznaje jej uprawnienia, o które aplikacja prosi. W przeciwnym razie instalacja nie dokonuje się</li> <li>aplikacja może robić cokolwiek zostało jej dozwolone, każde użycie niedozwolonej funkcji kończy się wyjątkiem</li> </ul> <p>Dostęp do ograniczanych zasobów odbywa się przez specjalne Androidowe API. Dostęp w inny sposób nie jest możliwy, ponieważ każda aplikacja jest uruchamiana z innym numerem użytkownika i w zupełnym odizolowaniu (pewnie coś podobnego do chroota) od innych aplikacji.</p> <p>Dostępna jest również komunikacja między aplikacjami. Ogólnie mówiąc jest to koncepcja podobna do D-BUS, jednak implementacja jest nieco inna.</p> <p>Inną ciekawą funkcją aplikacji pracujących w systemie Android jest ich gotowość do bycia zabitym w każdym momencie. Z racji ograniczenia ilości pamięci dostępnej dla systemu i mimo wszystko (pomimo sporego postępu względem Javy Suna) wysokiego zużycia pamięci przez aplikacje Javove, system zawiera mechanizm zabijania procesów w przypadku, gdy zaczyna brakować zasobów (CPU lub RAM).</p> <p>Każda aplikacja jednak może się przygotować na taką sytuację, ponieważ w momencie przykrycia aplikacji przez inną (kiedy to możliwe jest jej zabicie) wywołana jest metoda onStop (lub onPause w przypadku częściowego zakrycia), która umożliwia zrzut stanu aplikacji do systemowej bazy danych. Gdy użytkownik wraca do aplikacji (pomimo, że została w tle zabita, użytkownik wcale tego nie widzi), system ponownie uruchamia aplikację przywracając jej poprzedni stan. To jak aplikacja chce reprezentować swój stan zależy od samej aplikacji.</p> <p>Problemem maszyny Dalvik, podobnie jak i maszyny wirtualnej Javy Suna, to długi czas uruchamiania się (mniej niż sekunda na Twoim Pentium4? Aparat G1 jest ~10 razy wolniejszy). DalvikVM na telefonie HTC G1 uruchamia się około 5 sekund. Jest to problem tym bardziej, że każda aplikacja uruchamiana jest w swojej własnej instancji tej maszyny (w celach zapewnienia wymaganej izolacji).</p> <p>Tutaj do akcji wkracza specjalna usługa systemowa zygote, która jak tylko może przygotowuje proces maszyny wirtualnej Dalvik, który, gdy jest potrzeba zostaje oddany jakiemuś procesowi do natychmiastowego wykorzystania. Przez następne 5 sekund zygote znowu przygotowuje (kolejną) maszynę Dalvik i czeka aż jakiś proces o nią poprosi. Widać, że o ile nie uruchamiamy aplikacji częściej niż co pięć sekund, otrzymujemy złudzenie natychmiastowego uruchamiania aplikacji. Niezły trik.</p> <p>Widać, że inżynierzy pracujący nad systemem Android stawali na głowie, żeby wszystko było naprawdę dopracowane. Nie inaczej jest z dopieszczeniem programistów. Każdy, kto chce napisać swoją aplikację dla systemu Android może sobie ściągnąć Android-SDK, które zawiera emulator (oparty o QEmu) i narzędzia potrzebne do budowania (dx, apkbuild), i debugowania aplikacji.</p> <p>Dostępna jest również wtyczka do Eclipse'a o nazwie ADT (Android Developer Toolkit). Pozwala ona stworzyć aplikację dla Androida jednym kliknięciem, zbudować ją drugim, a uruchomić na wcześniej uruchomionym emulatorze trzecim :). Widziałem to w akcji i wygląda to naprawdę bardzo przyjemnie.</p> <p>Minusem Androida są trudności jakie niesie przeportowanie istniejących nie-androidowych aplikacji:</p> <ul> <li>aplikacje w C trzeba dolinkować do Google'owego bionic — lekka wersja biblioteki standardowej C — coś w stylu uclibc</li> <li>aplikacje w Javie mogą nie działać, bo używają rzeczy niezaimplementowanych w bibliotece Javy Dalvika</li> <li>GUI Androida jest zupełnie inne od każdego innego (oparte o pliki XML - zatem może trochę podobne do XUL-a). Zatem GUI w przypadku każdej aplikacji trzeba przepisać</li> </ul> <p>Naturalnym pytaniem, które się rodzi w czasie rozważań nad Androidem jest jego przyszłość. Choćby w porównaniu z iPhonem, którego w pierwszym miesiącu sprzedaży sprzedało się więcej niż wszystkich telefonów z Androidem na pokładzie. Zatem, co może przeważyć szalę zwycięstwa na stronę Androida? iPhone to jeden telefon (no konkretnie to dwa modele) i jeden software (no konkretnie to chyba trzy wersje). Android to otwarta platforma, która będzie stosowana nie tylko w telefonach firmowanych przez Google (póki co HTC G1 i G2), ale również przez inne telefony, takie jak FreeRunner (port i to całkiem dobrze działąjący na to urządzenie już dawno dostępny).</p> <p>Czołowi producenci tacy jak Samsung, czy Motorola planują wprowadzić do swojej oferty po 2-3 modele z Androidem. Do tego dochodzą netbooki, samochody (ktoś już portuje Androida na samochód), również odtwarzacze MP3, a być może i komputery stacjonarne. Możliwość uruchomienia aplikacji Androidowej na komputerze pracującym pod kontrolą Linuksa byłaby bardzo miła (zwłaszcza, że nie ma żadnych technicznych przeszkód). No i najważniejsze, ogromny wkład w rozwój Androida na platformach innych niż wspierane komercyjnie ma rosnąca społeczność deweloperów i testerów.</p> <p>Wiwat społeczność!</p> <h1><span>Podsumowanie</span></h1> <p>Ostatnie dwa dni w moim życiu pozwoliły na poszerzenie mojej wiedzy na temat technologii, które mogą w ciągu najbliższych 2 lat odmienić życie ludzi na naszej planecie. Oczywiście może się tak nie stać, ale samo poznawanie rzeczy, o których można powiedzieć "dobrze przemyślane, dopracowane, świeże, potrzebne i pomysłowe" przyprawia mnie o dreszczyk emocji.</p> <p>Muszę powiedzieć, że choć początkowo byłem sceptyczny wobec Androida, myślę teraz, że ma spore szanse za szybki rozwój i wielki (również komercyjny) sukces. Jak pamiętamy na samym spodzie architektury, zaraz nad sprzętem znajduje się jądro Linuksa. Niektóre jego modyfikacje dokonane na potrzeby telefonu trafiły do głównej (lub testowej) gałęzi kernela, co świadczy o dobrej jakości tych zmian.</p> <p>Pokazuje również, że praca nad tym systemem powoduje również szereg innych pożytecznych (choć pobocznych) zjawisk, co jest oczywiście bardzo budujące (i zupełnie nie występuje w przypadku zamkniętego oprogramowania).</p> <h1><span>Stop Cenzurze</span></h1> <p>Cały entuzjazm i moją ogólną radość mącą plany UE dotyczące ograniczenia dostępu do Internetu przez zmianę rozporządzeń europejskich, które są lobbowane przez duże firmy telekomunikacyjne, które prawdopodobnie mogą zwiększyć w ten sposób swoje zyski ze świadczenia usług internetowych i wykończyć małych providerów. Już raz Unia Europejska pokazała, że w sprawie informatycznej niezależności, potrafi się zachować w sposób rozsądny odrzucając propozycję wprowadzenia patentów na oprogramowanie. Miejmy nadzieję, że i tym razem nie zostanie ograniczona konkurencja w tym sektorze i kontrowersyjne rozwiązanie zostanie wyśmiane i zapomniane.</p> <p>O szczegółach można przeczytać na specjalnej stronie poświęconej temu zagadnieniu: <a href="http://stopcenzurze.wikidot.com/">http://stopcenzurze.wikidot.com/</a> . Można tam również okazać swoje poparcie dla akcji przez wirtualne podpisanie petycji.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:ewolucja-przegladarek</guid>
				<title>Ewolucja przeglądarek, czyli o tabach</title>
				<link>http://piotr.gabryjeluk.pl/dev:ewolucja-przegladarek</link>
				<description>

&lt;p&gt;Nie będzie dzisiaj o żadnych standardach, wsparciach dla CSS-ów i innych bzdurach, tylko o rzeczy najważniejszej dla użytkownika. Czyli o interfejsie użytkownika. Konkretnie o kartach (tabach). W reszcie posta będę używać słowa tab.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sat, 14 Mar 2009 10:30:57 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Nie będzie dzisiaj o żadnych standardach, wsparciach dla CSS-ów i innych bzdurach, tylko o rzeczy najważniejszej dla użytkownika. Czyli o interfejsie użytkownika. Konkretnie o kartach (tabach). W reszcie posta będę używać słowa tab.</p> <div class="content-separator" style="display: none:"></div> <h1><span>Historia</span></h1> <p>Kiedyś nie było kart. Przeglądarki miały swoje okno, w którym wyświetlały jedną stronę. Jeśli chciałeś otworzyć dwie strony, musiałeś otworzyć dwie przeglądarki. (Nie będę się zagłębiał w różnice pomiędzy dwoma <strong>oknami</strong> przeglądarki, a dwoma przeglądarkami.)</p> <p>Miało to swoje plusy. Można było otworzyć sobie dwie strony "side by side", czyli jedna obok drugiej i np. porównywać dwa teksty.</p> <p>Potem, pewnego magicznego dnia, pojawiła się przeglądarka, która otwierała strony w tabach! Zaraz pod wszystkimi paskami narzędziowymi, a nad wyrenderowaną treścią strony pojawił się (czasami znikający) <strong>pasek tabów</strong>. Niczym klon paska okienek z systemu okienkowego, pokazywał otworzone w przeglądarce strony, pomiędzy którymi można się przełączać.</p> <p>Technologia wnosi takie ciekawe i ważne opcje jak:</p> <ul> <li>otwieranie linków w nowym tabie zamiast w nowym oknie</li> <li>otwieranie nowych tabów w tle — niech się załadują, zanim skończę czytać aktualną stronę — bardzo sprytne i przydatne</li> <li>minimalizuje chaos na pasku zadań — po co osobny kwadracik dla każdej otworzonej strony</li> </ul> <h1><span>Teraźniejszość</span></h1> <p>Każda ważna przeglądarka posiada taby. Nawet Internet Explorer w wersji 7 ma taby. Ale ewolucja tabów idzie krok naprzód. Przeglądarki Chrome i Safari 4 posiadają taby na samej górze okna:</p> <p style="text-align: center;"><img src="http://piotr.gabryjeluk.pl/local--files/dev:ewolucja-przegladarek/google-chrome-tabs.png" alt="google-chrome-tabs.png" class="image" /></p> <div style="text-align: right;"> <p><a href="http://www.phoboslab.org/files/images/google-chrome-tabs.png">źródło obrazka</a></p> </div> <p>Czy powinno to kogoś dziwić? Według mnie nie, ponieważ taby zmieniają nie tylko wyświetlaną stronę, ale również adres wyświetlany w pasku adresu (URL) oraz znaczenie przycisków wstecz i dalej (bo cofamy się tylko w danym tabie). Oznacza to, że taby przełączają nam kontekst prawie całej przeglądarki.</p> <h1><span>Przyszłość</span></h1> <p>Skoro taby przełączają kontekst całej przeglądarki, to co byśmy powiedzieli na wyodrębnienie tabów do osobnych okien?</p> <p>Ups! Przecież od tego wyszliśmy i myśleliśmy, że taby to krok naprzód, więc dlaczego mielibyśmy się cofać?</p> <p>Taby odegrały ważną (jeśli nie bardzo ważną) rolę w systemach operacyjnych. Dziś znajdujemy je nie tylko w przeglądarkach, ale również w edytorach tekstu (GEdit, Kate), narzędziach developerskich (Eclipse), terminalach (Konsole, Gnome Terminal) oraz w innych programach.</p> <p>Czy rola tabów w różnych aplikacjach jest inna? Według mnie nie. Zawsze chodzi o to, żeby (podobnie jak w przeglądarkach):</p> <ul> <li>grupować podobne zadania</li> <li>oszczędzić chaosu na pasku aplikacji</li> <li>pozwolić na szybkie przełączanie pomiędzy tabami (w odróżnieniu od przełączania między aplikacjami)</li> <li>pozwolić na otwieranie czegoś w tle (bez otwierania wkurzających okienek)</li> </ul> <p>Tylko dlaczego w każdej z wymienionych przeze mnie aplikacji taby realizowane są inaczej? Firefox ma taby nad stroną, Chrome zupełnie na górze, Safari jako fragment paska tytułowego aplikacji, Konsole na dole strony, Kate jako listę plików po lewej stronie…</p> <p>Proponuję, aby istotnie przenieść ideę taba na poziom zarządzania okienkami, a nie poszczególnych aplikacji.</p> <p>Moje postulaty dla menedżerów okien:</p> <ul> <li>wyróżnienie aplikacji (czyli to, co teraz jest oknem) i jej okien (czyli to co teraz jest tabem)</li> <li>mechanizm otwierania nowego okna w tle</li> <li>możliwość prostego przełączania się między oknami jednej aplikacji w odróżnieniu od przełączania się między aplikacjami</li> </ul> <p>W tym miejscu przychodzi mi do głowy jeszcze jedna funkcjonalność tabów, która bywa różnie implementowana w różnych aplikacjach: możliwość odrywania tabów do osobnych okien i późniejszego ich łączenia.</p> <p>W Firefoksie jeśli dobrze się rozeznałem takiej możliwości nie ma, w Konsole możemy odrywać taby, ale nie możemy ich z powrotem łączyć, czyli nie ma pełnego mechanizmu przenoszenia tabów pomiędzy oknami danego typu.</p> <p>Zamiast kazać się wysilać twórcom aplikacji, zróbmy to raz a dobrze! Dodajmy do listy postulatów dla menedżerów okien następujący punkt:</p> <ul> <li>swobodne przenoszenie okien pomiędzy aplikacjami tego samego typu</li> </ul> <h1><span>Implementacja</span></h1> <p>Z ciekawością obserwowałem możliwość grupowania okien i późniejszego przełączania się między nimi na zasadzie tabów w <a href="http://en.wikipedia.org/wiki/Compiz" >Compizie</a>.</p> <p style="text-align: center;"><object width="320" height="265"><param name="movie" value="http://www.youtube.com/v/1nK4_cH5sbM&amp;hl=pl&amp;fs=1" /> <param name="allowFullScreen" value="true" /> <param name="allowscriptaccess" value="always" /> <embed src="http://www.youtube.com/v/1nK4_cH5sbM&amp;hl=pl&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="320" height="265" /></object></p> <p>Brakuje natomiast jednej bardzo istotnej rzeczy, która pozwala <strong>zastąpić</strong> poszczególne mechanizmy tabów w aplikacjach jednym mechanizmem z Compiza wyjętym.</p> <p>Otworzenie nowego okna przez okno z danej grupy okien powinno powodować automatyczne przejęcie tego okna przez tę grupę. Musimy sami "doklejać" nowo-otwarte okna do grupy. I to jest główny ból tego rozwiązania. Ciekaw jestem jak z punktu widzenia protokołów i bibliotek można zaimplementować moje postulaty oraz jak daleko posunięte zmiany musiałyby nastąpić, żeby to dało się zrealizować.</p> <h1><span>Czy warto?</span></h1> <p>No i pozostaje pytanie, czy warto. Zacznę od wad:</p> <ul> <li>prawdopodobnie potrzeba przebudowy (lub rozbudowy) kilku elementów systemu okienkowego (takich jak menedżery okien i API do nich)</li> <li>konieczność aktualizacji aplikacji, żeby korzystały z natywnych tabów, zamiast ze swoich implementacji</li> <li>być może utrata pewnych możliwości (takich jak "podczepienie" swojego menu do zakładek) przez aplikacje</li> </ul> <p>Zalety:</p> <ul> <li>zunifikowanie tabów w całym systemie (!)</li> <li>możliwość przebudowy mechanizmu "grupowania podobnych zadań" na pasku zadań — teraz wiemy dokładnie jakie zadania są powiązane — być może będzie ktoś odważny, kto wprowadzi dwupoziomowy pasek aplikacji</li> <li>znaczne uproszczenie kodu aplikacji</li> <li>łatwiejsze pisanie nowych aplikacji (mechanizm tabów mamy od razu za darmo, nie musimy się tym przejmować, ani zastanawiać się czy będzie kiedyś potrzebny)</li> <li>możliwość połączenia zmiany okien danej aplikacji z pewnym efektem graficznym</li> <li>możliwość prezentowania wszystkich okien danej aplikacji naraz tak jak to robi Safari dla tabów (tylko, że u nas to będzie za friko i dla każdej aplikacji):</li> </ul> <p style="text-align: center;"><img src="http://piotr.gabryjeluk.pl/local--files/dev:ewolucja-przegladarek/safari4.png" alt="safari4.png" class="image" /></p> <div style="text-align: right;"> <p><a href="http://afinedram.files.wordpress.com/2009/02/safari4.png?w=300&amp;h=271">źródło obrazka</a></p> </div> <p>Czekam na Wasze opinie!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:running-things-in-parallel-in-bash</guid>
				<title>Running things in parallel in BASH</title>
				<link>http://piotr.gabryjeluk.pl/dev:running-things-in-parallel-in-bash</link>
				<description>

&lt;p&gt;Suppose you have a nice script that does its job pretty well, but you figured out, that running certain parts of scripts in parallel would speed things up.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 09 Mar 2009 00:21:22 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Suppose you have a nice script that does its job pretty well, but you figured out, that running certain parts of scripts in parallel would speed things up.</p> <div class="content-separator" style="display: none:"></div> <p>This can be the option, when you send a bunch of files to an Internet service, that is generally fast, but the connection sequence is quite slow, so uploading 100 files one after one causes the script to wait 100 times to quickly upload a file.</p> <p>Other situation could be when you have multi-core machine, for example you have eight processing units, but use only one in your script, and you have a bunch of files to compile or to process in some CPU-expensive manner.</p> <p>We'll use only BASH to smartly parallelize the tasks and speed up the slow part of your script.</p> <p>First of all you need to know how many jobs in parallel you want (if you have 8 cores and CPU-expensive part of script, having more than 8 jobs does not help, probably a number between 4 and 8 will do best in this case).</p> <div class="code"> <pre> <code>#!/bin/bash PROC_NUM=4</code> </pre></div> <p>Generally, we'll ensure, than no more than <tt>PROC_NUM</tt> processes are forked into background and run another task. If there are <tt>PROC_NUM</tt> processes running in the background we'll wait a (fraction of) second and check again.</p> <div class="code"> <pre> <code>#!/bin/bash PROC_NUM=4 function run_task() { # task to run # can be more than one-line # can take parameters $1, $2, ... } function run_parallel() { while [ `jobs | grep Running | wc -l` -ge $PROC_NUM ]; do sleep 0.25 done run_task "$@" &amp; }</code> </pre></div> <p><tt>run_task "$@"</tt> passes all the parameters passed to run_parallel to run_task. You can use <tt>"$@"</tt> in run_task to pass all the parameters to external command! The <tt>"$@"</tt> is the best choice when you have spaces, dollars and other special characters in parameters. It doesn't transform anything, it's completely safe (probably the only short way to pass all the parameters).</p> <p>There are only two things left: invoking the <tt>run_parallel</tt> and synchronizing the tasks — you need to know when ALL the tasks ended, right?</p> <div class="code"> <pre> <code>#!/bin/bash PROC_NUM=4 function run_task() { # task to run # can be more than one-line # can take parameters $1, $2, ... } function run_parallel() { while [ `jobs | grep Running | wc -l` -ge $PROC_NUM ]; do sleep 0.25 done run_task "$@" &amp; } function end_parallel() { while [ `jobs | grep Running | wc -l` -gt 0 ]; do sleep 0.25 done } # script content cd /some/where/you/want # now the parallel operations # for example in some while find | while read file; do run_parallel "$file" done # now you want to continue when ALL parallel tasks ended end_parallel # the linear script code again cd /some/where/else make something</code> </pre></div> <p>That's all! Though, there is a different approach to this:</p> <div class="code"> <pre> <code>#!/bin/bash function parallel() { local PROC_NUM="$1" local SLEEP_TIME="$2" shift; shift while [ `jobs | grep Running | wc -l` -ge $PROC_NUM ]; do sleep $SLEEP_TIME done "$@" &amp; }</code> </pre></div> <p>This function acts as a wrapper to a non-parallel command and runs it in the background assuring that no more than <tt>PROC_NUM</tt> processes run at once. If there are <tt>PROC_NUM</tt> processes running in the background, the wrapper waits <tt>SLEEP_TIME</tt> to re-check the number of background jobs.</p> <p>Invoking:</p> <div class="code"> <pre> <code>parallel PROC_NUM SLEEP_TIME /usr/bin/some-command arguments ...</code> </pre></div> <p>so</p> <div class="code"> <pre> <code>parallel 4 0.5 ls -R /tmp</code> </pre></div> <p>means: run <tt>ls -R /tmp</tt> in the background if there is no more than 3 processes already run in the background. Otherwise wait 0.5 seconds and try again. Then run <tt>ls -R /tmp</tt> if there is no more than 3 processes already run in the background. Otherwise wait 0.5 seconds and try again. Then run <tt>ls -R /tmp</tt> if …</p> <p>Quite nice, isn't it?</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:nice-bash-random-implementation</guid>
				<title>Nice BASH Random Implementation</title>
				<link>http://piotr.gabryjeluk.pl/dev:nice-bash-random-implementation</link>
				<description>

&lt;p&gt;Today I wrote something like this in BASH:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;pre&gt;
&lt;code&gt;echo $(($(printf &#039;%d&#039; &quot;&#039;`head -c 1 /dev/urandom | base64 | tr A D`&quot;)%4))&lt;/code&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 05 Mar 2009 20:20:28 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Today I wrote something like this in BASH:</p> <div class="code"> <pre> <code>echo $(($(printf '%d' "'`head -c 1 /dev/urandom | base64 | tr A D`")%4))</code> </pre></div> <div class="content-separator" style="display: none:"></div> <p>This prints a random number from 0 to 3. How it works?</p> <p>Firstly the command below prints one random byte using special Unix /dev/urandom device:</p> <div class="code"> <pre> <code>head -c 1 /dev/urandom</code> </pre></div> <p>Piping this to <tt>| base64</tt> gives us base64 representation of the byte.</p> <p><a href="http://en.wikipedia.org/wiki/Base64" >Base64</a> is a method of encoding 8-bit data as human-readable strings using only 64 visible characters (letters, digits and some !@ and stuff). Whitespace is always ignored when reading base64 string. The standard is really nice, because you can for example read a base64-encoded file with a phone or send a printout of it with a classic mail. Because all the characters are human-readable, one can enter them and decode the original message. The format is used widely for sending emails.</p> <p>It is important for us, that running base64 on a random byte gives us 2 bytes that are only "normal" characters, ie don't have any special meaning in any context (like some white characters may have).</p> <p>Now comes the tricky part:</p> <div class="code"> <pre> <code>printf '%d' "'`head -c 1 /dev/urandom | base64`"</code> </pre></div> <p><tt>printf '%d' "'a"</tt> would give us 97 — this is the ASCII code of letter a. Remember we get two bytes of data after base64? No problem, printf cares in this case only about the first character.</p> <p>As you may notice the output of this is one of these:</p> <ul> <li>43</li> <li>a number between 47 and 57</li> <li>a number between 65 and 90</li> <li>a number between 97 and 122</li> </ul> <p>This gives us 64 possibilities. Great! 6 bits is just the same as 64.</p> <p>This is when we get to BASH arithmetics. This is how it works:</p> <div class="code"> <pre> <code>echo $((7*12))</code> </pre></div> <p>This should print 84 obviously. Knowing that enclosing a string in <tt>$(</tt> and <tt>)</tt> causes BASH to run the enclosed command and return its results (just like using the backticks: `command`) this is everything.</p> <p>The % means modulo in BASH arithmetic (just like in C, Java, Python, PHP, …), so this:</p> <div class="code"> <pre> <code>echo $(($(printf '%d' "'`head -c 1 /dev/urandom | base64`")%4))</code> </pre></div> <p>prints the number we generated (one of 43, 47…57, 65…90, 97…122) modulo 4. This needs to be 0, 1, 2 or 3.</p> <p>Let's now check what the probability of receiving each of the digits.</p> <p>Suppose /dev/urandom prints every possible byte with equal probability. We'll now analyze each byte (possibly) generated by /dev/urandom, it's base64 representation, ASCII code of first byte of the representation and the modulo 4 of it. We'll use a table for it:</p> <table class="wiki-content-table"> <tr> <th>/dev/urandom byte</th> <th>base64</th> <th>code of the first letter of base64</th> <th>the same modulo 4</th> </tr> <tr> <td>(code 0)</td> <td>AA==</td> <td>65</td> <td>1</td> </tr> <tr> <td>(code 1)</td> <td>AQ==</td> <td>65</td> <td>1</td> </tr> <tr> <td>(code 2)</td> <td>Ag==</td> <td>65</td> <td>1</td> </tr> <tr> <td>(code 3)</td> <td>Aw==</td> <td>65</td> <td>1</td> </tr> <tr> <td>(code 4)</td> <td>BA==</td> <td>66</td> <td>2</td> </tr> <tr> <td>(code 5)</td> <td>BQ==</td> <td>66</td> <td>2</td> </tr> <tr> <td>(code 6)</td> <td>Bg==</td> <td>66</td> <td>2</td> </tr> <tr> <td>(code 7)</td> <td>Bw==</td> <td>66</td> <td>2</td> </tr> <tr> <td>(code 8)</td> <td>CA==</td> <td>67</td> <td>3</td> </tr> <tr> <td>(code 9)</td> <td>CQ==</td> <td>67</td> <td>3</td> </tr> <tr> <td>(code 10)</td> <td>Cg==</td> <td>67</td> <td>3</td> </tr> <tr> <td>(code 11)</td> <td>Cw==</td> <td>67</td> <td>3</td> </tr> <tr> <td>(code 12)</td> <td>DA==</td> <td>68</td> <td>0</td> </tr> <tr> <td>(code 13)</td> <td>DQ==</td> <td>68</td> <td>0</td> </tr> <tr> <td>(␌⎺␍␊ 14)</td> <td>Dg==</td> <td>68</td> <td>0</td> </tr> <tr> <td>(code 15)</td> <td>Dw==</td> <td>68</td> <td>0</td> </tr> <tr> <td>(code 16)</td> <td>EA==</td> <td>69</td> <td>1</td> </tr> <tr> <td>(code 17)</td> <td>EQ==</td> <td>69</td> <td>1</td> </tr> <tr> <td>(code 18)</td> <td>Eg==</td> <td>69</td> <td>1</td> </tr> <tr> <td>(code 19)</td> <td>Ew==</td> <td>69</td> <td>1</td> </tr> <tr> <td>(code 20)</td> <td>FA==</td> <td>70</td> <td>2</td> </tr> <tr> <td>(code 21)</td> <td>FQ==</td> <td>70</td> <td>2</td> </tr> <tr> <td>(code 22)</td> <td>Fg==</td> <td>70</td> <td>2</td> </tr> <tr> <td>(code 23)</td> <td>Fw==</td> <td>70</td> <td>2</td> </tr> <tr> <td>(code 24)</td> <td>GA==</td> <td>71</td> <td>3</td> </tr> <tr> <td>(code 25)</td> <td>GQ==</td> <td>71</td> <td>3</td> </tr> <tr> <td>(code 26)</td> <td>Gg==</td> <td>71</td> <td>3</td> </tr> <tr> <td>(code 27)</td> <td>Gw==</td> <td>71</td> <td>3</td> </tr> <tr> <td>(code 28)</td> <td>HA==</td> <td>72</td> <td>0</td> </tr> <tr> <td>(code 29)</td> <td>HQ==</td> <td>72</td> <td>0</td> </tr> <tr> <td>(code 30)</td> <td>Hg==</td> <td>72</td> <td>0</td> </tr> <tr> <td>(code 31)</td> <td>Hw==</td> <td>72</td> <td>0</td> </tr> <tr> <td>(code 32)</td> <td>IA==</td> <td>73</td> <td>1</td> </tr> <tr> <td>! (code 33)</td> <td>IQ==</td> <td>73</td> <td>1</td> </tr> <tr> <td>" (code 34)</td> <td>Ig==</td> <td>73</td> <td>1</td> </tr> <tr> <td># (code 35)</td> <td>Iw==</td> <td>73</td> <td>1</td> </tr> <tr> <td>$ (code 36)</td> <td>JA==</td> <td>74</td> <td>2</td> </tr> <tr> <td>% (code 37)</td> <td>JQ==</td> <td>74</td> <td>2</td> </tr> <tr> <td>&amp; (code 38)</td> <td>Jg==</td> <td>74</td> <td>2</td> </tr> <tr> <td>' (code 39)</td> <td>Jw==</td> <td>74</td> <td>2</td> </tr> <tr> <td>( (code 40)</td> <td>KA==</td> <td>75</td> <td>3</td> </tr> <tr> <td>) (code 41)</td> <td>KQ==</td> <td>75</td> <td>3</td> </tr> <tr> <td>* (code 42)</td> <td>Kg==</td> <td>75</td> <td>3</td> </tr> <tr> <td>+ (code 43)</td> <td>Kw==</td> <td>75</td> <td>3</td> </tr> <tr> <td>, (code 44)</td> <td>LA==</td> <td>76</td> <td>0</td> </tr> <tr> <td>- (code 45)</td> <td>LQ==</td> <td>76</td> <td>0</td> </tr> <tr> <td>. (code 46)</td> <td>Lg==</td> <td>76</td> <td>0</td> </tr> <tr> <td>/ (code 47)</td> <td>Lw==</td> <td>76</td> <td>0</td> </tr> <tr> <td>0 (code 48)</td> <td>MA==</td> <td>77</td> <td>1</td> </tr> <tr> <td>1 (code 49)</td> <td>MQ==</td> <td>77</td> <td>1</td> </tr> <tr> <td>2 (code 50)</td> <td>Mg==</td> <td>77</td> <td>1</td> </tr> <tr> <td>3 (code 51)</td> <td>Mw==</td> <td>77</td> <td>1</td> </tr> <tr> <td>4 (code 52)</td> <td>NA==</td> <td>78</td> <td>2</td> </tr> <tr> <td>5 (code 53)</td> <td>NQ==</td> <td>78</td> <td>2</td> </tr> <tr> <td>6 (code 54)</td> <td>Ng==</td> <td>78</td> <td>2</td> </tr> <tr> <td>7 (code 55)</td> <td>Nw==</td> <td>78</td> <td>2</td> </tr> <tr> <td>8 (code 56)</td> <td>OA==</td> <td>79</td> <td>3</td> </tr> <tr> <td>9 (code 57)</td> <td>OQ==</td> <td>79</td> <td>3</td> </tr> <tr> <td>: (code 58)</td> <td>Og==</td> <td>79</td> <td>3</td> </tr> <tr> <td>; (code 59)</td> <td>Ow==</td> <td>79</td> <td>3</td> </tr> <tr> <td>&lt; (code 60)</td> <td>PA==</td> <td>80</td> <td>0</td> </tr> <tr> <td>= (code 61)</td> <td>PQ==</td> <td>80</td> <td>0</td> </tr> <tr> <td>&gt; (code 62)</td> <td>Pg==</td> <td>80</td> <td>0</td> </tr> <tr> <td>? (code 63)</td> <td>Pw==</td> <td>80</td> <td>0</td> </tr> <tr> <td>@ (code 64)</td> <td>QA==</td> <td>81</td> <td>1</td> </tr> <tr> <td>A (code 65)</td> <td>QQ==</td> <td>81</td> <td>1</td> </tr> <tr> <td>B (code 66)</td> <td>Qg==</td> <td>81</td> <td>1</td> </tr> <tr> <td>C (code 67)</td> <td>Qw==</td> <td>81</td> <td>1</td> </tr> <tr> <td>D (code 68)</td> <td>RA==</td> <td>82</td> <td>2</td> </tr> <tr> <td>E (code 69)</td> <td>RQ==</td> <td>82</td> <td>2</td> </tr> <tr> <td>F (code 70)</td> <td>Rg==</td> <td>82</td> <td>2</td> </tr> <tr> <td>G (code 71)</td> <td>Rw==</td> <td>82</td> <td>2</td> </tr> <tr> <td>H (code 72)</td> <td>SA==</td> <td>83</td> <td>3</td> </tr> <tr> <td>I (code 73)</td> <td>SQ==</td> <td>83</td> <td>3</td> </tr> <tr> <td>J (code 74)</td> <td>Sg==</td> <td>83</td> <td>3</td> </tr> <tr> <td>K (code 75)</td> <td>Sw==</td> <td>83</td> <td>3</td> </tr> <tr> <td>L (code 76)</td> <td>TA==</td> <td>84</td> <td>0</td> </tr> <tr> <td>M (code 77)</td> <td>TQ==</td> <td>84</td> <td>0</td> </tr> <tr> <td>N (code 78)</td> <td>Tg==</td> <td>84</td> <td>0</td> </tr> <tr> <td>O (code 79)</td> <td>Tw==</td> <td>84</td> <td>0</td> </tr> <tr> <td>P (code 80)</td> <td>UA==</td> <td>85</td> <td>1</td> </tr> <tr> <td>Q (code 81)</td> <td>UQ==</td> <td>85</td> <td>1</td> </tr> <tr> <td>R (code 82)</td> <td>Ug==</td> <td>85</td> <td>1</td> </tr> <tr> <td>S (code 83)</td> <td>Uw==</td> <td>85</td> <td>1</td> </tr> <tr> <td>T (code 84)</td> <td>VA==</td> <td>86</td> <td>2</td> </tr> <tr> <td>U (code 85)</td> <td>VQ==</td> <td>86</td> <td>2</td> </tr> <tr> <td>V (code 86)</td> <td>Vg==</td> <td>86</td> <td>2</td> </tr> <tr> <td>W (code 87)</td> <td>Vw==</td> <td>86</td> <td>2</td> </tr> <tr> <td>X (code 88)</td> <td>WA==</td> <td>87</td> <td>3</td> </tr> <tr> <td>Y (code 89)</td> <td>WQ==</td> <td>87</td> <td>3</td> </tr> <tr> <td>Z (code 90)</td> <td>Wg==</td> <td>87</td> <td>3</td> </tr> <tr> <td>[ (code 91)</td> <td>Ww==</td> <td>87</td> <td>3</td> </tr> <tr> <td>\ (code 92)</td> <td>XA==</td> <td>88</td> <td>0</td> </tr> <tr> <td>] (code 93)</td> <td>XQ==</td> <td>88</td> <td>0</td> </tr> <tr> <td>^ (code 94)</td> <td>Xg==</td> <td>88</td> <td>0</td> </tr> <tr> <td>_ (code 95)</td> <td>Xw==</td> <td>88</td> <td>0</td> </tr> <tr> <td>` (code 96)</td> <td>YA==</td> <td>89</td> <td>1</td> </tr> <tr> <td>a (code 97)</td> <td>YQ==</td> <td>89</td> <td>1</td> </tr> <tr> <td>b (code 98)</td> <td>Yg==</td> <td>89</td> <td>1</td> </tr> <tr> <td>c (code 99)</td> <td>Yw==</td> <td>89</td> <td>1</td> </tr> <tr> <td>d (code 100)</td> <td>ZA==</td> <td>90</td> <td>2</td> </tr> <tr> <td>e (code 101)</td> <td>ZQ==</td> <td>90</td> <td>2</td> </tr> <tr> <td>f (code 102)</td> <td>Zg==</td> <td>90</td> <td>2</td> </tr> <tr> <td>g (code 103)</td> <td>Zw==</td> <td>90</td> <td>2</td> </tr> <tr> <td>h (code 104)</td> <td>aA==</td> <td>97</td> <td>1</td> </tr> <tr> <td>i (code 105)</td> <td>aQ==</td> <td>97</td> <td>1</td> </tr> <tr> <td>j (code 106)</td> <td>ag==</td> <td>97</td> <td>1</td> </tr> <tr> <td>k (code 107)</td> <td>aw==</td> <td>97</td> <td>1</td> </tr> <tr> <td>l (code 108)</td> <td>bA==</td> <td>98</td> <td>2</td> </tr> <tr> <td>m (code 109)</td> <td>bQ==</td> <td>98</td> <td>2</td> </tr> <tr> <td>n (code 110)</td> <td>bg==</td> <td>98</td> <td>2</td> </tr> <tr> <td>o (code 111)</td> <td>bw==</td> <td>98</td> <td>2</td> </tr> <tr> <td>p (code 112)</td> <td>cA==</td> <td>99</td> <td>3</td> </tr> <tr> <td>q (code 113)</td> <td>cQ==</td> <td>99</td> <td>3</td> </tr> <tr> <td>r (code 114)</td> <td>cg==</td> <td>99</td> <td>3</td> </tr> <tr> <td>s (code 115)</td> <td>cw==</td> <td>99</td> <td>3</td> </tr> <tr> <td>t (code 116)</td> <td>dA==</td> <td>100</td> <td>0</td> </tr> <tr> <td>u (code 117)</td> <td>dQ==</td> <td>100</td> <td>0</td> </tr> <tr> <td>v (code 118)</td> <td>dg==</td> <td>100</td> <td>0</td> </tr> <tr> <td>w (code 119)</td> <td>dw==</td> <td>100</td> <td>0</td> </tr> <tr> <td>x (code 120)</td> <td>eA==</td> <td>101</td> <td>1</td> </tr> <tr> <td>y (code 121)</td> <td>eQ==</td> <td>101</td> <td>1</td> </tr> <tr> <td>z (code 122)</td> <td>eg==</td> <td>101</td> <td>1</td> </tr> <tr> <td>{ (code 123)</td> <td>ew==</td> <td>101</td> <td>1</td> </tr> <tr> <td>| (code 124)</td> <td>fA==</td> <td>102</td> <td>2</td> </tr> <tr> <td>} (code 125)</td> <td>fQ==</td> <td>102</td> <td>2</td> </tr> <tr> <td>~ (code 126)</td> <td>fg==</td> <td>102</td> <td>2</td> </tr> <tr> <td> (code 127)</td> <td>fw==</td> <td>102</td> <td>2</td> </tr> <tr> <td>� (code 128)</td> <td>gA==</td> <td>103</td> <td>3</td> </tr> <tr> <td>� (code 129)</td> <td>gQ==</td> <td>103</td> <td>3</td> </tr> <tr> <td>� (code 130)</td> <td>gg==</td> <td>103</td> <td>3</td> </tr> <tr> <td>� (code 131)</td> <td>gw==</td> <td>103</td> <td>3</td> </tr> <tr> <td>� (code 132)</td> <td>hA==</td> <td>104</td> <td>0</td> </tr> <tr> <td>� (code 133)</td> <td>hQ==</td> <td>104</td> <td>0</td> </tr> <tr> <td>� (code 134)</td> <td>hg==</td> <td>104</td> <td>0</td> </tr> <tr> <td>� (code 135)</td> <td>hw==</td> <td>104</td> <td>0</td> </tr> <tr> <td>� (code 136)</td> <td>iA==</td> <td>105</td> <td>1</td> </tr> <tr> <td>� (code 137)</td> <td>iQ==</td> <td>105</td> <td>1</td> </tr> <tr> <td>� (code 138)</td> <td>ig==</td> <td>105</td> <td>1</td> </tr> <tr> <td>� (code 139)</td> <td>iw==</td> <td>105</td> <td>1</td> </tr> <tr> <td>� (code 140)</td> <td>jA==</td> <td>106</td> <td>2</td> </tr> <tr> <td>� (code 141)</td> <td>jQ==</td> <td>106</td> <td>2</td> </tr> <tr> <td>(code 142)</td> <td>jg==</td> <td>106</td> <td>2</td> </tr> <tr> <td>(code 143)</td> <td>jw==</td> <td>106</td> <td>2</td> </tr> <tr> <td>� (code 144)</td> <td>kA==</td> <td>107</td> <td>3</td> </tr> <tr> <td>� (code 145)</td> <td>kQ==</td> <td>107</td> <td>3</td> </tr> <tr> <td>� (code 146)</td> <td>kg==</td> <td>107</td> <td>3</td> </tr> <tr> <td>� (code 147)</td> <td>kw==</td> <td>107</td> <td>3</td> </tr> <tr> <td>� (code 148)</td> <td>lA==</td> <td>108</td> <td>0</td> </tr> <tr> <td>� (code 149)</td> <td>lQ==</td> <td>108</td> <td>0</td> </tr> <tr> <td>� (code 150)</td> <td>lg==</td> <td>108</td> <td>0</td> </tr> <tr> <td>� (code 151)</td> <td>lw==</td> <td>108</td> <td>0</td> </tr> <tr> <td>� (code 152)</td> <td>mA==</td> <td>109</td> <td>1</td> </tr> <tr> <td>� (code 153)</td> <td>mQ==</td> <td>109</td> <td>1</td> </tr> <tr> <td>� (code 154)</td> <td>mg==</td> <td>109</td> <td>1</td> </tr> <tr> <td>� (code 155)</td> <td>mw==</td> <td>109</td> <td>1</td> </tr> <tr> <td>� (code 156)</td> <td>nA==</td> <td>110</td> <td>2</td> </tr> <tr> <td>� (code 157)</td> <td>nQ==</td> <td>110</td> <td>2</td> </tr> <tr> <td>� (code 158)</td> <td>ng==</td> <td>110</td> <td>2</td> </tr> <tr> <td>� (code 159)</td> <td>nw==</td> <td>110</td> <td>2</td> </tr> <tr> <td>� (code 160)</td> <td>oA==</td> <td>111</td> <td>3</td> </tr> <tr> <td>� (code 161)</td> <td>oQ==</td> <td>111</td> <td>3</td> </tr> <tr> <td>� (code 162)</td> <td>og==</td> <td>111</td> <td>3</td> </tr> <tr> <td>� (code 163)</td> <td>ow==</td> <td>111</td> <td>3</td> </tr> <tr> <td>� (code 164)</td> <td>pA==</td> <td>112</td> <td>0</td> </tr> <tr> <td>� (code 165)</td> <td>pQ==</td> <td>112</td> <td>0</td> </tr> <tr> <td>� (code 166)</td> <td>pg==</td> <td>112</td> <td>0</td> </tr> <tr> <td>� (code 167)</td> <td>pw==</td> <td>112</td> <td>0</td> </tr> <tr> <td>� (code 168)</td> <td>qA==</td> <td>113</td> <td>1</td> </tr> <tr> <td>� (code 169)</td> <td>qQ==</td> <td>113</td> <td>1</td> </tr> <tr> <td>� (code 170)</td> <td>qg==</td> <td>113</td> <td>1</td> </tr> <tr> <td>� (code 171)</td> <td>qw==</td> <td>113</td> <td>1</td> </tr> <tr> <td>� (code 172)</td> <td>rA==</td> <td>114</td> <td>2</td> </tr> <tr> <td>� (code 173)</td> <td>rQ==</td> <td>114</td> <td>2</td> </tr> <tr> <td>� (code 174)</td> <td>rg==</td> <td>114</td> <td>2</td> </tr> <tr> <td>� (code 175)</td> <td>rw==</td> <td>114</td> <td>2</td> </tr> <tr> <td>� (code 176)</td> <td>sA==</td> <td>115</td> <td>3</td> </tr> <tr> <td>� (code 177)</td> <td>sQ==</td> <td>115</td> <td>3</td> </tr> <tr> <td>� (code 178)</td> <td>sg==</td> <td>115</td> <td>3</td> </tr> <tr> <td>� (code 179)</td> <td>sw==</td> <td>115</td> <td>3</td> </tr> <tr> <td>� (code 180)</td> <td>tA==</td> <td>116</td> <td>0</td> </tr> <tr> <td>� (code 181)</td> <td>tQ==</td> <td>116</td> <td>0</td> </tr> <tr> <td>� (code 182)</td> <td>tg==</td> <td>116</td> <td>0</td> </tr> <tr> <td>� (code 183)</td> <td>tw==</td> <td>116</td> <td>0</td> </tr> <tr> <td>� (code 184)</td> <td>uA==</td> <td>117</td> <td>1</td> </tr> <tr> <td>� (code 185)</td> <td>uQ==</td> <td>117</td> <td>1</td> </tr> <tr> <td>� (code 186)</td> <td>ug==</td> <td>117</td> <td>1</td> </tr> <tr> <td>� (code 187)</td> <td>uw==</td> <td>117</td> <td>1</td> </tr> <tr> <td>� (code 188)</td> <td>vA==</td> <td>118</td> <td>2</td> </tr> <tr> <td>� (code 189)</td> <td>vQ==</td> <td>118</td> <td>2</td> </tr> <tr> <td>� (code 190)</td> <td>vg==</td> <td>118</td> <td>2</td> </tr> <tr> <td>� (code 191)</td> <td>vw==</td> <td>118</td> <td>2</td> </tr> <tr> <td>� (code 192)</td> <td>wA==</td> <td>119</td> <td>3</td> </tr> <tr> <td>� (code 193)</td> <td>wQ==</td> <td>119</td> <td>3</td> </tr> <tr> <td>� (code 194)</td> <td>wg==</td> <td>119</td> <td>3</td> </tr> <tr> <td>� (code 195)</td> <td>ww==</td> <td>119</td> <td>3</td> </tr> <tr> <td>� (code 196)</td> <td>xA==</td> <td>120</td> <td>0</td> </tr> <tr> <td>� (code 197)</td> <td>xQ==</td> <td>120</td> <td>0</td> </tr> <tr> <td>� (code 198)</td> <td>xg==</td> <td>120</td> <td>0</td> </tr> <tr> <td>� (code 199)</td> <td>xw==</td> <td>120</td> <td>0</td> </tr> <tr> <td>� (code 200)</td> <td>yA==</td> <td>121</td> <td>1</td> </tr> <tr> <td>� (code 201)</td> <td>yQ==</td> <td>121</td> <td>1</td> </tr> <tr> <td>� (code 202)</td> <td>yg==</td> <td>121</td> <td>1</td> </tr> <tr> <td>� (code 203)</td> <td>yw==</td> <td>121</td> <td>1</td> </tr> <tr> <td>� (code 204)</td> <td>zA==</td> <td>122</td> <td>2</td> </tr> <tr> <td>� (code 205)</td> <td>zQ==</td> <td>122</td> <td>2</td> </tr> <tr> <td>� (code 206)</td> <td>zg==</td> <td>122</td> <td>2</td> </tr> <tr> <td>� (code 207)</td> <td>zw==</td> <td>122</td> <td>2</td> </tr> <tr> <td>� (code 208)</td> <td>0A==</td> <td>48</td> <td>0</td> </tr> <tr> <td>� (code 209)</td> <td>0Q==</td> <td>48</td> <td>0</td> </tr> <tr> <td>� (code 210)</td> <td>0g==</td> <td>48</td> <td>0</td> </tr> <tr> <td>� (code 211)</td> <td>0w==</td> <td>48</td> <td>0</td> </tr> <tr> <td>� (code 212)</td> <td>1A==</td> <td>49</td> <td>1</td> </tr> <tr> <td>� (code 213)</td> <td>1Q==</td> <td>49</td> <td>1</td> </tr> <tr> <td>� (code 214)</td> <td>1g==</td> <td>49</td> <td>1</td> </tr> <tr> <td>� (code 215)</td> <td>1w==</td> <td>49</td> <td>1</td> </tr> <tr> <td>� (code 216)</td> <td>2A==</td> <td>50</td> <td>2</td> </tr> <tr> <td>� (code 217)</td> <td>2Q==</td> <td>50</td> <td>2</td> </tr> <tr> <td>� (code 218)</td> <td>2g==</td> <td>50</td> <td>2</td> </tr> <tr> <td>� (code 219)</td> <td>2w==</td> <td>50</td> <td>2</td> </tr> <tr> <td>� (code 220)</td> <td>3A==</td> <td>51</td> <td>3</td> </tr> <tr> <td>� (code 221)</td> <td>3Q==</td> <td>51</td> <td>3</td> </tr> <tr> <td>� (code 222)</td> <td>3g==</td> <td>51</td> <td>3</td> </tr> <tr> <td>� (code 223)</td> <td>3w==</td> <td>51</td> <td>3</td> </tr> <tr> <td>� (code 224)</td> <td>4A==</td> <td>52</td> <td>0</td> </tr> <tr> <td>� (code 225)</td> <td>4Q==</td> <td>52</td> <td>0</td> </tr> <tr> <td>� (code 226)</td> <td>4g==</td> <td>52</td> <td>0</td> </tr> <tr> <td>� (code 227)</td> <td>4w==</td> <td>52</td> <td>0</td> </tr> <tr> <td>� (code 228)</td> <td>5A==</td> <td>53</td> <td>1</td> </tr> <tr> <td>� (code 229)</td> <td>5Q==</td> <td>53</td> <td>1</td> </tr> <tr> <td>� (code 230)</td> <td>5g==</td> <td>53</td> <td>1</td> </tr> <tr> <td>� (code 231)</td> <td>5w==</td> <td>53</td> <td>1</td> </tr> <tr> <td>� (code 232)</td> <td>6A==</td> <td>54</td> <td>2</td> </tr> <tr> <td>� (code 233)</td> <td>6Q==</td> <td>54</td> <td>2</td> </tr> <tr> <td>� (code 234)</td> <td>6g==</td> <td>54</td> <td>2</td> </tr> <tr> <td>� (code 235)</td> <td>6w==</td> <td>54</td> <td>2</td> </tr> <tr> <td>� (code 236)</td> <td>7A==</td> <td>55</td> <td>3</td> </tr> <tr> <td>� (code 237)</td> <td>7Q==</td> <td>55</td> <td>3</td> </tr> <tr> <td>� (code 238)</td> <td>7g==</td> <td>55</td> <td>3</td> </tr> <tr> <td>� (code 239)</td> <td>7w==</td> <td>55</td> <td>3</td> </tr> <tr> <td>� (code 240)</td> <td>8A==</td> <td>56</td> <td>0</td> </tr> <tr> <td>� (code 241)</td> <td>8Q==</td> <td>56</td> <td>0</td> </tr> <tr> <td>� (code 242)</td> <td>8g==</td> <td>56</td> <td>0</td> </tr> <tr> <td>� (code 243)</td> <td>8w==</td> <td>56</td> <td>0</td> </tr> <tr> <td>� (code 244)</td> <td>9A==</td> <td>57</td> <td>1</td> </tr> <tr> <td>� (code 245)</td> <td>9Q==</td> <td>57</td> <td>1</td> </tr> <tr> <td>� (code 246)</td> <td>9g==</td> <td>57</td> <td>1</td> </tr> <tr> <td>� (code 247)</td> <td>9w==</td> <td>57</td> <td>1</td> </tr> <tr> <td>� (code 248)</td> <td>+A==</td> <td>43</td> <td>3</td> </tr> <tr> <td>� (code 249)</td> <td>+Q==</td> <td>43</td> <td>3</td> </tr> <tr> <td>� (code 250)</td> <td>+g==</td> <td>43</td> <td>3</td> </tr> <tr> <td>� (code 251)</td> <td>+w==</td> <td>43</td> <td>3</td> </tr> <tr> <td>� (code 252)</td> <td>/A==</td> <td>47</td> <td>3</td> </tr> <tr> <td>� (code 253)</td> <td>/Q==</td> <td>47</td> <td>3</td> </tr> <tr> <td>� (code 254)</td> <td>/g==</td> <td>47</td> <td>3</td> </tr> <tr> <td>� (code 255)</td> <td>/w==</td> <td>47</td> <td>3</td> </tr> </table> <p>(Some of the symbols in the first column may appear not visible or otherwise look strange. This is normal, there are many characters in ASCII that has (or had) some special meaning.)</p> <p>Let's count have many 0s, 1s, 2s and 3s did we get:</p> <table class="wiki-content-table"> <tr> <td>0</td> <td>60</td> </tr> <tr> <td>1</td> <td>68</td> </tr> <tr> <td>2</td> <td>64</td> </tr> <tr> <td>3</td> <td>64</td> </tr> </table> <p>This is not ideal, because you get statistically slightly more 1s than 0s, but if you don't care too much, this is all!</p> <p>If you care however, here is some solution. We need to get 4 results that give us 1 and convert them to some that gives 0:</p> <div class="code"> <pre> <code>echo $(($(printf '%d' "'`head -c 1 /dev/urandom | base64 | tr A D`")%4))</code> </pre></div> <p>Notice the <tt>tr A D</tt>. This changes each A to D in the base64 output. Thus four rows of table above (for 0 to 4 codes) should behave like the 12 to 15 codes, thus giving 0 at the end instead of 1.</p> <p>We're done. Let's enclose the procedure in function clause and create a sample code that tells us how many 0s, 1s, 2s and 3s where hit within 100 shots.</p> <div class="code"> <pre> <code>#!/bin/bash function random() { echo $(($(printf '%d' "'`head -c 1 /dev/urandom | base64 | tr A D`")%4)) } ret=`i=0; time while [ $i -lt 100 ]; do random ; i=$((i+1)); done` echo -n '0: ' ; echo "$ret" | grep 0 | wc -l echo -n '1: ' ; echo "$ret" | grep 1 | wc -l echo -n '2: ' ; echo "$ret" | grep 2 | wc -l echo -n '3: ' ; echo "$ret" | grep 3 | wc -l</code> </pre></div> <p>The program also shows how much time did it take to generate the numbers. Adjust the 100 to your needs ;).</p> <p><strong>UPDATE:</strong> having this article posted on <a href="http://www.reddit.com/r/programming/comments/82gd1/nice_bash_random_implementation/">reddit programming</a> gave me actually much much better ways of doing a random function (see in <a href="#comments">comments</a>). Thank you for your replies!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:wikidot-search-launched</guid>
				<title>Wikidot Search Launched</title>
				<link>http://piotr.gabryjeluk.pl/dev:wikidot-search-launched</link>
				<description>

&lt;p&gt;After about three months of indexing (because &lt;a href=&quot;http://piotr.gabryjeluk.pl/dev:wikidot-is-big&quot;&gt;Wikidot is BIG&lt;/a&gt;) all content that&#039;s hosted on Wikidot, the time came to launch the new search system.&lt;/p&gt;
&lt;p&gt;I described the system extensively in blog post titled &lt;a href=&quot;http://piotr.gabryjeluk.pl/dev:new-search-for-wikidot-s-gonna-rock&quot;&gt;New search for Wikidot&#039;s gonna rock&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.wikidot.com/search:all&quot;&gt;The new search system&lt;/a&gt; replaced the old Google-powered one.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 02 Mar 2009 21:28:30 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>After about three months of indexing (because <a href="http://piotr.gabryjeluk.pl/dev:wikidot-is-big">Wikidot is BIG</a>) all content that's hosted on Wikidot, the time came to launch the new search system.</p> <p>I described the system extensively in blog post titled <a href="http://piotr.gabryjeluk.pl/dev:new-search-for-wikidot-s-gonna-rock">New search for Wikidot's gonna rock</a>.</p> <p><a href="http://www.wikidot.com/search:all">The new search system</a> replaced the old Google-powered one.</p> <div class="content-separator" style="display: none:"></div> <p>The main advantages over Google Search Engine that has been used till now are:</p> <ul> <li>search in public sites + those you are a member of</li> <li>semantic search: tags, title are more important when searching</li> <li>simple to sophisticated queries <ul> <li>"blog site:community" — search for anything with "blog" in it but only on site community.wikidot.com</li> <li>"tags:wikidot site:quake" — search for pages tagged "wikidot" on my site (quake.wikidot.com)</li> </ul> </li> <li>poor quality content filtered</li> </ul> <p>Things left to do:</p> <div class="image-container floatright"><img src="http://piotr.gabryjeluk.pl/local--thumbnail/small.jpg" alt="small.jpg" class="image" /></div> <ul> <li>add site preview (thumbnail) like the one on right to the search results</li> <li>explain in simple words the <a href="http://piotr.gabryjeluk.pl/search-syntax">search syntax</a></li> <li>promote good and/or active sites (give them higher rank)</li> <li>decrease delay from editing to updating search index (should be max 5 minutes)</li> <li>create custom search module searching in a bunch of (related) sites — this would be nice if you keep separate sites for different areas of a project like private site for project members and public one for project users. The search is intelligent enough to filter restricted items from search results if someone is not member of the private site the items come from.</li> </ul> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://piotr.gabryjeluk.pl/dev:web-services-hosting</guid>
				<title>Web services hosting</title>
				<link>http://piotr.gabryjeluk.pl/dev:web-services-hosting</link>
				<description>

&lt;p&gt;There are many features you expect from a high volume web service (like Wikidot):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;low response time&lt;/li&gt;
&lt;li&gt;works for (hundreds of) thousands clients at once&lt;/li&gt;
&lt;li&gt;makes use of a client cache (i.e. send &lt;tt&gt;Not Modified&lt;/tt&gt; header instead of the full page if page was not modified from last time)&lt;/li&gt;
&lt;li&gt;fault tolerance (if something crashes, this does not affect the rest of system)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;&lt;!--[if gte IE 7]&gt;&lt;!--&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common--images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=2462)&quot; /&gt;&lt;!--&lt;![endif]--&gt;&lt;!--[if lt IE 7]&gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/common&amp;#45;&amp;#45;images/avatars/2/2462/a16.png&quot; alt=&quot;Gabrys&quot; style=&quot;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod=&#039;scale&#039;)&quot;/&gt;&lt;![endif]--&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/gabrys&quot;  &gt;Gabrys&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sun, 01 Mar 2009 00:01:14 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>There are many features you expect from a high volume web service (like Wikidot):</p> <ul> <li>low response time</li> <li>works for (hundreds of) thousands clients at once</li> <li>makes use of a client cache (i.e. send <tt>Not Modified</tt> header instead of the full page if page was not modified from last time)</li> <li>fault tolerance (if something crashes, this does not affect the rest of system)</li> </ul> <div class="content-separator" style="display: none:"></div> <p>After talking with Michał, reading some documents about HTTP, browsing <a href="http://djangoproject.org/">Django</a> documentation and testing tools and programs like <a href="http://lighttpd.net/">Lighttpd</a>, <a href="http://nginx.net/">Nginx</a>, <a href="http://varnish.projects.linpro.no/">Varnish</a> I thought of a architecture pattern for hosting Django applications, that can be easily adapted to other frameworks and languages (including PHP).</p> <h1><span>The architecture</span></h1> <p>I'll describe the whole architecture from the application to the user's browser.</p> <ul> <li>[backend] Application served with WSGI, SGI or FastCGI</li> <li>[gateway] Web server talking to WSGI, SGI or FastCGI service</li> <li>[frontend] Cache proxy</li> </ul> <p>The main reason for separating those things is different nature of particular levels</p> <ul> <li>Application may introduce big memory overhead (if it is a PHP or Python application for example)</li> <li>Web server may introduce big lags (if server-client connection is slow)</li> <li>Separate cache server may be precisely configurable depending on Cookies and other factors</li> </ul> <p>Why joining backend and gateway (for example using mod_python or mod_php) is bad? The perfect example of this, is having a small and fast script (like echo "hello";) and extremely slow and unreliable client (may be some sort of attacker as well). The script should be run in milliseconds, but sending the data to client may take a few seconds. Having backend and gateway in one process makes the high memory consumption of PHP/Python interpreter last until the client receives the last byte of data.</p> <p>Keeping backend and gateway separate solves this problem. The client requests a page from gateway, the gateway uses backend to generate the content, the backend generates it in milliseconds and the gateway then frees the backend and remembers the data (using much less memory than the backend process) until it is completely sent to the client. During (possibly) slow sending the response to client some other gateway process may use the backend that was just freed.</p> <h1><span>HTTP Cache</span></h1> <p>Application should at least set ETag headers (may be computed as simple as md5 from the whole response to the client). In Django, this can be done automatically using the cache system bundled with Django. Setting of this header makes client receive only the headers which greatly limits the amount of data sent to a browser (if the site is visited more often than it is modified).</p> <p>Using a separate cache system you can easily deal so-called "Digg load peak". This is a situation when a huge amount of people visits a page after it is mentioned on a popular website like Digg. You can get as much as 1000 or 2000 requests per second. If you would ask the application to generate the content for every of the request, request could simply time out.</p> <p>There are two or three answers to this: a simple and a precise one at least. The simple one is adjusting the frontend to cache response to each URL for a minute or two for every user not having a Cookie (so not logged in). The downside is that for non-logged user, there can be a delay before actually having the updated page. The upside is that 90% (or so) of traffic does not hit your application (and is server from cache).</p> <p>The precise solution, would be setting the application to first check the ETag of the content and returning ONLY Not Modified header without actually running ANY heavy operations. This would work well, because after the first visitor asked for the page, the cache frontend saved the ETag for the page and would attach it to the consecutive requests for the same URL (even for another user). This however can be non-trivial to implement in some particular cases. Also, this method does not eliminate the use of application level, only minimize the time spent in it. On the good side, this should be completely safe to use and help the performance even in non-peak load. This means the system is worth implementing it!</p> <p>Between the precise and simple solution, there is a variation of simple one, that would require more work to be done by the caching server. It would be applying the cache-for-one-minute rule after reaching some threshold request rate (like 20 reqs/s) for single URL, or for all system. This should quite good comprise between serving the most accurate data and actually being fast. I mean, if trying to be the most accurate you can, results in high load and not serving anything, it would be better to temporarily serve less accurate data.</p> <p>The actual caching rules may be a combination of the ones depending on client location, referrer, cookies settings and so.</p> <h1><span>Notes on application level</span></h1> <p>In order to have your application behave well under high load, you'll need to concern the following designs:</p> <ul> <li>database reads caching (ideally on database-access library level)</li> <li>exporting non-realtime operations (like cronjobs) to different machine</li> <li>exporting non-database tasks (like making thumbnails of images) to other machine</li> <li>exporting static files serving to other machine (you'll need a separate domain and an IP address)</li> <li>exporting other expensive tasks (searching, summing big sets of data) to other machine</li> <li>finally database sharding (dividing the database schema or data into a bunch of machines)</li> </ul> <p>Using <a href="http://www.danga.com/memcached/">Memcached</a> for caching is really a good choice, because it was designed to support many caching hosts from the start. This means, that adding more "cache power" is as easy as adding a new machine to the caching cluster.</p> <h1><span>Bottom line</span></h1> <p>The nice thing about this design is that it works like a stack, each layer adding some features to the system. This means, that you can start with the simplest mod_python and with the growth of the service add a smart cache server, then change the mod_pyothn to WSGI and Nginx for example.</p> <p>Also, the design allows to run two or three levels on one computer and then moving some services out. This gives much flexibility from the start, which means you don't have to worry about problems you'll encounter some day, because now you know, that you will be able to solve them. Just not having to, you can now concentrate on real thing — the application and care about the maintenance later.</p> <h1><span>My experiment</span></h1> <p>I would like to test <a href="http://nginx.net/">Nginx</a> (gateway) and its experimental <strong>mod_wsgi</strong> module to host sample <a href="http://djangoproject.org/">Django</a> application (backend) in WSGI mode using <a href="http://varnish.projects.linpro.no/">Varnish</a> (frontent) as the cache proxy. The nice thing about Nginx is that it uses really small amount of system resources to talk with each client. The WSGI application makes a "pool" of backend processes that Nginx can use. Also I would like to try caching options of Nginx instead of setting up varnishd (but I assume the varnish would be better).</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/gabrys" ><!--[if gte IE 7]><!--><img class="small" src="http://www.wikidot.com/common--images/avatars/2/2462/a16.png" alt="Gabrys" style="background-image:url(http://www.wikidot.com/userkarma.php?u=2462)" /><!--<![endif]--><!--[if lt IE 7]><img class="small" src="http://www.wikidot.com/common&#45;&#45;images/avatars/2/2462/a16.png" alt="Gabrys" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=http://www.wikidot.com/userkarma.php?u=2462,sizingMethod='scale')"/><![endif]--></a><a href="http://www.wikidot.com/user:info/gabrys" >Gabrys</a></span></p> 
				 	]]>
				</content:encoded>							</item>
				</channel>
</rss>