<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-01-25T11:45:44+01:00</updated><id>/feed.xml</id><title type="html">The Cattle Grid</title><subtitle>Splashes of digital ink by Michele Beltrame</subtitle><entry><title type="html">Printing banners on continuous paper (dot matrix printer) from modern Linux</title><link href="/2022/12/05/printing-banners-on-continuous-paper-dot-matrix-printer-from-modern-inux.html" rel="alternate" type="text/html" title="Printing banners on continuous paper (dot matrix printer) from modern Linux" /><published>2022-12-05T21:58:00+01:00</published><updated>2022-12-05T21:58:00+01:00</updated><id>/2022/12/05/printing-banners-on-continuous-paper-dot-matrix-printer-from-modern-inux</id><content type="html" xml:base="/2022/12/05/printing-banners-on-continuous-paper-dot-matrix-printer-from-modern-inux.html"><![CDATA[<p>I’ve always loved dot matrix printers. Today it’s possible to easily use them on Linux, even if they are very old.
Recently I became interested in printing multi-page banners, a popular task back in the days: I do that nowadays
with an Amiga, but using a modern Linux gives access to more fonts and to all the clip-art you can get on the Internet.</p>

<p>If you are interested in printing banners, or just curious, read ahead.</p>

<p>It’s also worth checking
<a href="https://www.youtube.com/watch?v=3yVInvpebUk">this <em>Veronica Explains</em> video about printing dot matrix nowadays</a>.</p>

<h2 id="dot-matrix-printers">Dot Matrix printers</h2>

<div class="pimg-right-v lightbox">
    <a href="/assets/pimages/printing_banners/banner_printed.jpg" title="2-page banner printed on a NEC Pinwriter P20">
        <img src="/assets/pimages/printing_banners/banner_printed.jpg" alt="2-page banner printed on a NEC Pinwriter P20" />
    </a>
</div>

<p>Dot matrix printers are just incredible. Granted, they are slow and noisy, and quality of print is nowhere near the one of
a laser, but they have their own good points.</p>

<p>It’s fascinating to see how printing actually happens, with the rows which appear one after another: taking a look at
the printing head is also very instructive, as you understand how the ink is transferred to paper. They’re not a closed
box like modern lasers or inkjets.</p>

<p>New dot matrix printers are sold even now, as they are still used in some business environments. However, maybe shelling
out 500-700 Euros can be a bit steep, so why not use an old printer? They still work fine after 30 years, and using old
hardware can be sustainable choice.</p>

<p>For this article I’m using a <strong>NEC Pinwriter P20</strong>, a printer I’ve owned since when it came out in 1992.
It’s a parallel printer of course, but a <em>USB-to-Centronics</em> cable
<a href="https://electropapa.com/en/catalogsearch/result/?q=usb+centronics">costs just 10-15 Euros</a>
and does its job nicely. Also, it’s very easy to find
<a href="https://electropapa.com/en/catalogsearch/result/?q=nec+ribbon">ink ribbons for such printers</a>.</p>

<p>It works surprisingly well with Linux (<a href="https://pop.system76.com/">Pop!Os 22.04</a>):
<a href="https://openprinting.github.io/cups/">CUPS</a> automatically sees it once connected, even though you have to choose the
driver manually: there is the PPD a driver for NEC PinWriters, so once chosen from the list the printer <em>just works</em>,
and you can print anything as if it were a laser. Awesome, if not <strong><em>unbelievable</em></strong>, for a 30-year-old printer.</p>

<p>Printing a banner on multiple sheets of continuous paper, however, is another matter, as it’s non-obvious to have it
come out as expected.</p>

<h2 id="paper-sizes">Paper sizes</h2>

<div class="pimg-left-h lightbox">
    <a href="/assets/pimages/printing_banners/lodraw_page_settings.png" title="LibreOffice Draw page settings">
        <img src="/assets/pimages/printing_banners/lodraw_page_settings.png" alt="LibreOffice Draw page settings" />
    </a>
</div>

<p>If you are in Europe, like I am, you may only know of <em>A4</em> as paper size for printing on a computer, which is the
format 99% of people use nowadays. However, it has not always been that way.</p>

<p>Until mid-90s, when dot matrix printers began to go out of fashion, <strong>US Letter</strong> (8.5” x 11”) was widespread across
Europe. Why? Because continuous computer paper was available in that size, at least for standard 80-columns printers.</p>

<p>The continuous paper is actually 9.5” x 11”, because it has the side strips for the tractor. In Europe the format is
better known as 24 x 11”, where 24 is the width in centimeters (including side strips) and 11 the height in inches.
A <strong><em>bizarre</em></strong> side note: the width is expressed in <em>centimenters</em> and the height in <em>inches</em>.</p>

<p>If you are interested in more information about <em>US Letter</em> paper size,
<a href="https://www.youtube.com/watch?v=bWMGfFHr1ww" target="_blank" rel="noopener noreferrer">take a look at this
video</a>.</p>

<h2 id="creating-a-banner">Creating a banner</h2>

<div class="pimg-right-h lightbox">
    <a href="/assets/pimages/printing_banners/lodraw_document.png" title="2-page banner in LibreOffice Draw">
        <img src="/assets/pimages/printing_banners/lodraw_document.png" alt="2-page banner in LibreOffice Draw" />
    </a>
</div>

<p>I’m using LibreOffice Draw these days, which does its work well.</p>

<p>First off, we have to set the page size, creating a user-defined one. This depends on how many sheets you want
the banner to occupy: the longer, the nicer… but it also takes longer to print and more ribbon ink. 😉</p>

<p>A good idea is to set the page in <em>Horizontal</em> (landscape) orientation, so reverse width and height, so that you can
easily see what you are writing, and you get a better idea of the final result.</p>

<table>
  <thead>
    <tr>
      <th><strong>Format</strong></th>
      <th><strong>Width (mm)</strong></th>
      <th><strong>Height (mm)</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1 sheet (US Letter - Horizontal)</td>
      <td>279</td>
      <td>210</td>
    </tr>
    <tr>
      <td>2 sheet (US Letter - Horizontal)</td>
      <td>558</td>
      <td>210</td>
    </tr>
    <tr>
      <td>3 sheet (US Letter - Horizontal)</td>
      <td>837</td>
      <td>210</td>
    </tr>
    <tr>
      <td>4 sheet (US Letter - Horizontal)</td>
      <td>1116</td>
      <td>210</td>
    </tr>
  </tbody>
</table>

<p> </p>

<p>I recommend to set all margins to 0: d<strong>ot-matrix printers can print right to the edges of the paper</strong>:
that’s really cool, isn’t it?</p>

<p>You can download this banner here: in <a href="/assets/pimages/printing_banners/dotmatrix.odg" download="">ODG format</a>
or <a href="/assets/pimages/printing_banners/dotmatrix.pdf" download="">PDF format</a>.</p>

<p>The font comes from a fantastic web site with loads of fancy fonts, like the ones we used in the 90s… and they’re
even free for personal use! Here’s the link to this awesomeness: <a href="https://www.1001freefonts.com/">1001 Free Fonts</a>.</p>

<h2 id="properly-printing-the-banner">Properly printing the banner</h2>

<p>I found many suggestions on the web, which basically were down to “<em>select the LO Draw option which allows to print
across multiple sheets</em>”. These methods <em>do not work</em>, because - even when I managed to not print a complete mess - form
feeds are put between one page and the other, which make no sense when printing banners and create empty space, thus
breaking the banner.</p>

<p>What you need to do is to <strong>create a custom page size for printing</strong>, which is as big as the banner: exactly the same
thing we did earlier for the page in LibreOffice Draw: now it has to be done in the printing dialog as well.</p>

<p>Now the problem is that LibreOffice Draw uses itss custom printing dialog, which is a bit limited compared to the
system one, i.e. it doesn’t allow you to enter a custom paper size in the printer configuration.</p>

<p>So, just <strong>save the banner in PDF format</strong>.</p>

<div class="lightbox pimg-left-h">
    <a href="/assets/pimages/printing_banners/popos_printing_dialog.png" title="2-page banner in LibreOffice Draw">
        <img src="/assets/pimages/printing_banners/popos_printing_dialog.png" alt="2-page banner in LibreOffice Draw" />
    </a>
</div>

<p>Open the saved PDF with your viewer. At present I’m using <em>Evince Document Viewer</em>, which is the default in <em>Pop!OS</em>,
and choose the <em>print</em> function: this will open the printing dialog.</p>

<p>Just like LibreOffice, some viewers have their own printing dialog, which may not offer all the
options of the system one. In my case it didn’t offer the option to set a custom page size, but luckily there was
a button called <em>Use system printing dialog</em> which opened the full-featured one. LibreOffice Calc lacks this button,
which makes it necessary to go through a PDF conversion.</p>

<div class="lightbox pimg-right-h">
    <a href="/assets/pimages/printing_banners/popos_printing_dialog_custom_page_size.png" title="2-page banner in LibreOffice Draw">
        <img src="/assets/pimages/printing_banners/popos_printing_dialog_custom_page_size.png" alt="2-page banner in LibreOffice Draw" />
    </a>
</div>

<p>The important thing here is to set the page size: in the <em>Page settings</em> tab of the system printing dialog, look at
the <em>Paper size</em> selector: inside it you can find a <em>Manage custom sizes</em> option, which opens another dialog. In this
dialog you can add the size you need (the screenshots are in Italian language, sorry).</p>

<p>As before, I’d set all margins to 0.</p>

<p>The good thing is that of the above process is that you only need to add page sizes once: the system will then remember
them. The bad thing is that LibreOffice custom dialog won’t see them anyway, so you still need to use PDFs.</p>

<p><em>Ça va sans dire</em>, you need to select <em>Horizontal</em> (landscape) orientation. Printing begins from the end (like in my photo).
I actually found out that (in some cases I am still investigating) this setting adds unwanted white lines before
printing something, so I recommend <em>Reverse horizontal</em>, which prints from the beginning instead (basically, the 
banner is rotated 180 degrees before being sent to the printer).</p>

<p><strong>Happy banner printing!</strong></p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve always loved dot matrix printers. Today it’s possible to easily use them on Linux, even if they are very old. Recently I became interested in printing multi-page banners, a popular task back in the days: I do that nowadays with an Amiga, but using a modern Linux gives access to more fonts and to all the clip-art you can get on the Internet.]]></summary></entry><entry><title type="html">Two-factor authentication (2FA) in Perl with TOTP algorithm (Microsoft or Google Authenticator or others)</title><link href="/2022/11/08/two-factor-2fa-authentication-in-perl-with-totp-algorithm-microsoft-google-authenticator-others.html" rel="alternate" type="text/html" title="Two-factor authentication (2FA) in Perl with TOTP algorithm (Microsoft or Google Authenticator or others)" /><published>2022-11-08T15:22:00+01:00</published><updated>2022-11-08T15:22:00+01:00</updated><id>/2022/11/08/two-factor-2fa-authentication-in-perl-with-totp-algorithm-microsoft-google-authenticator-others</id><content type="html" xml:base="/2022/11/08/two-factor-2fa-authentication-in-perl-with-totp-algorithm-microsoft-google-authenticator-others.html"><![CDATA[<p>Two-factor authentication is now essential for a lot of web sites, as it ensures that - even if someone gets your access
credentials - he/she cannot login, because a token from your mobile phone is required. Therefor, the hacker should
also have access to your phone and to the code to unlock it, which is highly unlikely.</p>

<h2 id="how-does-2fa-work">How does 2FA work?</h2>

<p>Two-factor authentication (TFA) is just a generic concept, which means you need to provide two sets of security
credentials (factors). These are usually <em>username</em>, <em>password</em> and a code (usually 6-digits long). This one
may be sent you via text (SMS) by the website, or generated by an app on your phone.</p>

<p>The second system is now widely used. Putting it simple: based on a secret key provided at activation, the app
generates a different code every 30 seconds, which you provide at login and the service will check if it matches.</p>

<p>Many people use <em>Google Authenticator</em>, which is the most famous app for token generation. However, most folks do
not know that, even though the web site or service might ask you to install <em>Google Authenticator</em>, it really
doesn’t matter what app you use: it just has to support the
<a href="https://en.wikipedia.org/wiki/Time-based_one-time_password">Time-based one-time password (TOTP) algorithm</a>.
I, for instance, use <a href="https://www.microsoft.com/it-it/security/mobile-authenticator-app">Microsoft Authenticator</a>,
which is still free of charge but provides better security and some backup options.</p>

<h2 id="2fa-in-perl">2FA in Perl</h2>

<p>There are a couple of Perl modules to perform 2FA:
<a href="https://metacpan.org/pod/Auth::GoogleAuth">Auth::GoogleAuth</a> and <a href="https://metacpan.org/pod/Authen::TOTP">Authen::TOTP</a>.
I chose the first one because it had more recent updates, however the name of the latter seems more appropriate
as this kind of authentication system is not tied to the Google app, or any other one.</p>

<p>First off, you need to create a ID and secret for the user when he activated the 2FA.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">my</span> <span class="nv">$gauth</span> <span class="o">=</span> <span class="nn">Auth::</span><span class="nv">GoogleAuth</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">({</span>
    <span class="s">issuer</span> <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">My Web Site Name</span><span class="p">',</span>  <span class="c1"># No high chars, stay in ASCII set</span>
    <span class="s">key_id</span> <span class="o">=&gt;</span> <span class="nv">$username</span><span class="p">,</span>
<span class="p">});</span></code></pre></figure>

<p>The <em>issuer</em> is a text string which identifies the service, so that the user can actually
choose the correct code (also know as <em>OTP</em>, <em>one-time password</em>) among the ones generated by his authentication app. I saw that this field must only be ASCII chars, but I don’t know if
it’s by definition or the problem lies in Google’s QR generation service or in my authentication
app or whatever.</p>

<p>The <em>key_id</em> is something related to the user itself, such as the username or the e-mail
address. This value must be provided also when veryfing the secret, so if it changes the
secret key will no longer work and a new one will have to be generated.</p>

<p>Now let’s compute a secret unique to the user. This is something random, which <em>Auth::GoogleAuth</em>
can generate for you (or you can pass it, see the module’s documentation for details).</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">my</span> <span class="nv">$secret32</span> <span class="o">=</span> <span class="nv">$gauth</span><span class="o">-&gt;</span><span class="nv">generate_secret32</span><span class="p">;</span>

<span class="c1"># Store the secret somewhere user-related, i.e. in it's profile</span>
<span class="nv">$db</span><span class="o">-&gt;</span><span class="nv">query</span><span class="p">(</span>
    <span class="p">'</span><span class="s1">update users set secret32 = ? where username = ?</span><span class="p">',</span>
    <span class="nv">$secret32</span><span class="p">,</span> <span class="nv">$username</span>
<span class="p">);</span></code></pre></figure>

<p>Now we can create an HTML which shows the QR and other information, which should appear as this (sorry, it’s in Italian language 😊):</p>

<p><img src="/assets/pimages/2fa_example.jpg" alt="Example web page to activate 2FA authentication" style="border: 1px solid #666;" /></p>

<p>The relevant part of the code is something like this:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;</span><span class="err">%</span> <span class="na">my</span> <span class="err">$</span><span class="na">qrurl = </span><span class="s">$gauth-</span><span class="nt">&gt;</span>qr_code =~ s/200x200/500x500/xsr; %&gt;
    <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"&lt;%= $qrurl %&gt;"</span> <span class="na">class=</span><span class="s">"img-fluid"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;br&gt;</span>
    <span class="nt">&lt;b&gt;&lt;</span><span class="err">%=</span> <span class="err">$</span><span class="na">gauth-</span><span class="nt">&gt;</span>secret32 %&gt;<span class="nt">&lt;/b&gt;</span>
<span class="nt">&lt;/div&gt;</span></code></pre></figure>

<p>This CPAN module supports generating QR Codes using Google’s service, but it always passes
<em>200x200</em> as dimension, so in the above code I tweaked the URL a bit. You can also use modules
such as <a href="https://metacpan.org/pod/Imager::QRCode">Imager::QRCode</a> to generate the QR code by
yourself. I may submit a patch in the near future to at least support passing the dimensions
to <code class="language-plaintext highlighter-rouge">qr_code()</code>.</p>

<p>Now the user can scan the QR with his app (or enter the code manually) and have it appear in his app.
He will then have to enter one code/OTP in order to go through verification and enable
the whole this: this process should be well known to most Internet users.</p>

<p>The verification process, usually when logging in, is something like this:</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">my</span> <span class="nv">$gauth</span> <span class="o">=</span> <span class="nn">Auth::</span><span class="nv">GoogleAuth</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">({</span>
    <span class="c1"># No high chars, stay in ASCII set</span>
    <span class="s">issuer</span> <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">My Web Site Name</span><span class="p">',</span> 
    <span class="s">key_id</span> <span class="o">=&gt;</span> <span class="nv">$username</span>
<span class="p">});</span>

<span class="c1"># Retrieve the secret</span>
<span class="k">my</span> <span class="nv">$secret32</span> <span class="o">=</span> <span class="nv">$db</span><span class="o">-&gt;</span><span class="nv">query</span><span class="p">(</span>
    <span class="p">'</span><span class="s1">select secret32 from where username = ?</span><span class="p">',</span>
    <span class="nv">$iduser</span>
<span class="p">)</span><span class="o">-&gt;</span><span class="nv">hash</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">secret32</span><span class="p">};</span>
<span class="nv">$gauth</span><span class="o">-&gt;</span><span class="nv">secret32</span><span class="p">(</span><span class="nv">$secret32</span><span class="p">);</span>

<span class="k">my</span> <span class="nv">$isvalid</span> <span class="o">=</span> <span class="nv">$gauth</span><span class="o">-&gt;</span><span class="nv">verify</span><span class="p">(</span>
    <span class="nv">$q</span><span class="o">-&gt;</span><span class="nv">param</span><span class="p">('</span><span class="s1">otp</span><span class="p">'),</span>     <span class="c1"># code</span>
    <span class="mi">1</span><span class="p">,</span>                    <span class="c1"># range</span>
<span class="p">);</span></code></pre></figure>

<p>The <em>range</em> can be a value of <em>0</em> or greater. This tells the verification method how many
“nearby” codes (OTPs) to accept as valid besides the exact one of this moment.
Since device clocks are not always perfectly synchronized, it’s possible that the client has
one code when the server already has another one: or that the user loses some time in typing
it in and it expires. A value of <em>1</em> is ideal, as it accepts also the code generated before
and the one generated after the current time of the machine which checks it.</p>

<p>And that’s it!</p>

<p>The module has a series of other interesting options which I encourage you to check.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Two-factor authentication is now essential for a lot of web sites, as it ensures that - even if someone gets your access credentials - he/she cannot login, because a token from your mobile phone is required. Therefor, the hacker should also have access to your phone and to the code to unlock it, which is highly unlikely.]]></summary></entry><entry><title type="html">Cleaner code with perl 5.36 and defer</title><link href="/2022/06/06/cleaner-code-with-perl-536-and-defer.html" rel="alternate" type="text/html" title="Cleaner code with perl 5.36 and defer" /><published>2022-06-06T23:22:00+02:00</published><updated>2022-06-06T23:22:00+02:00</updated><id>/2022/06/06/cleaner-code-with-perl-536-and-defer</id><content type="html" xml:base="/2022/06/06/cleaner-code-with-perl-536-and-defer.html"><![CDATA[<p><a href="https://metacpan.org/dist/perl/view/pod/perl.pod">Version 5.36 of perl</a> was recently released. Amid the new features
it contains, there is a very interesting, and maybe lesser-know one: the new <code class="language-plaintext highlighter-rouge">defer</code> keyword.</p>

<p>What does it do? It simply <em>defers</em> the execution of some code to the end of the enclosing block. It’s a very useful
construct, because (in my opinion) it allows to make code much cleaner.</p>

<p>I put down a simple example which renders a checkout page for an fictional e-commerce site. There are some
conditions which bring to different result, with instruction</p>

<p>The first version uses a classic <em>if</em>/<em>else</em> approach. Since there are functions to be called between evaluation
of conditions, the <em>if</em>/<em>elsif</em> approach on a single level does not fit, so a nesting of conditional statements is
needed.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">sub </span><span class="nf">checkout</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">my</span> <span class="nv">$tplargs</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">is_user_logged</span><span class="p">()</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">not_logged</span><span class="p">';</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">my</span> <span class="nv">$user</span> <span class="o">=</span> <span class="nv">get_user</span><span class="p">();</span>
        <span class="k">my</span> <span class="nv">$cart</span> <span class="o">=</span> <span class="nv">get_cart_contents</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">@$cart</span> <span class="p">)</span> <span class="p">{</span>
            <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">empty_cart</span><span class="p">';</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">my</span> <span class="nv">$shrate</span> <span class="o">=</span> <span class="nv">calc_shipping_rate</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="nv">$cart</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nb">defined</span> <span class="nv">$shrate</span> <span class="p">)</span> <span class="p">{</span>
                <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">cant_ship_there</span><span class="p">';</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="k">my</span> <span class="nv">$res</span> <span class="o">=</span> <span class="nv">finalize_order</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="nv">$cart</span><span class="p">,</span> <span class="nv">$shrate</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span> <span class="nv">$res</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">status</span><span class="p">}</span> <span class="ow">eq</span> <span class="p">'</span><span class="s1">ko</span><span class="p">'</span> <span class="p">)</span> <span class="p">{</span>
                    <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">processing_error</span><span class="p">';</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">success</span><span class="p">}</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>Not he best, uh?</p>

<p>The second attempt moves everything to a single level, using <em>return</em> statements. Without nesting, code is more
straightforward but longer, especially the <code class="language-plaintext highlighter-rouge">render_template('checkout.html', $tplargs);</code> instruction.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">sub </span><span class="nf">checkout</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">my</span> <span class="nv">$tplargs</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">is_user_logged</span><span class="p">()</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">not_logged</span><span class="p">';</span>
        <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">my</span> <span class="nv">$user</span> <span class="o">=</span> <span class="nv">get_user</span><span class="p">();</span>
    <span class="k">my</span> <span class="nv">$cart</span> <span class="o">=</span> <span class="nv">get_cart_contents</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">@$cart</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">empty_cart</span><span class="p">';</span>
        <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">my</span> <span class="nv">$shrate</span> <span class="o">=</span> <span class="nv">calc_shipping_rate</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="nv">$cart</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nb">defined</span> <span class="nv">$shrate</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">cant_ship_there</span><span class="p">';</span>
        <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">my</span> <span class="nv">$res</span> <span class="o">=</span> <span class="nv">finalize_order</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="nv">$cart</span><span class="p">,</span> <span class="nv">$shrate</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nv">$res</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">status</span><span class="p">}</span> <span class="ow">eq</span> <span class="p">'</span><span class="s1">ko</span><span class="p">'</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="p">'</span><span class="s1">processing_error</span><span class="p">';</span>
        <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">success</span><span class="p">}</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>Enter <code class="language-plaintext highlighter-rouge">defer</code>, which allows to define at the top (or somewhere else) in the sub something which must happen at
the end of the execution scope.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">sub </span><span class="nf">checkout</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">my</span> <span class="p">(</span><span class="nv">$error</span><span class="p">,</span> <span class="nv">$tplargs</span><span class="p">);</span>

    <span class="nv">defer</span> <span class="p">{</span>
        <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">error</span><span class="p">}</span> <span class="o">=</span> <span class="nv">$error</span><span class="p">;</span>
        <span class="nv">render_template</span><span class="p">('</span><span class="s1">checkout.html</span><span class="p">',</span> <span class="nv">$tplargs</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">is_user_logged</span><span class="p">()</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$error</span> <span class="o">=</span> <span class="p">'</span><span class="s1">not_logged</span><span class="p">';</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">my</span> <span class="nv">$user</span> <span class="o">=</span> <span class="nv">get_user</span><span class="p">();</span>
    <span class="k">my</span> <span class="nv">$cart</span> <span class="o">=</span> <span class="nv">get_cart_contents</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">@$cart</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$error</span> <span class="o">=</span> <span class="p">'</span><span class="s1">empty_cart</span><span class="p">';</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">my</span> <span class="nv">$shrate</span> <span class="o">=</span> <span class="nv">calc_shipping_rate</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="nv">$cart</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nb">defined</span> <span class="nv">$shrate</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$error</span> <span class="o">=</span> <span class="p">'</span><span class="s1">cant_ship_there</span><span class="p">';</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">my</span> <span class="nv">$res</span> <span class="o">=</span> <span class="nv">finalize_order</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="nv">$cart</span><span class="p">,</span> <span class="nv">$shrate</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nv">$res</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">status</span><span class="p">}</span> <span class="ow">eq</span> <span class="p">'</span><span class="s1">ko</span><span class="p">'</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$error</span> <span class="o">=</span> <span class="p">'</span><span class="s1">processing_error</span><span class="p">';</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="nv">$tplargs</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">success</span><span class="p">}</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>Cleaner and elegant, isn’t it? It is also informative, as reading the beginning of the <em>sub</em>, it’s immediately
obvious what happens at the end.</p>

<p>I think <code class="language-plaintext highlighter-rouge">defer</code> will become a very appreciated addition to the Perl language.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Version 5.36 of perl was recently released. Amid the new features it contains, there is a very interesting, and maybe lesser-know one: the new defer keyword.]]></summary></entry><entry><title type="html">Properly transferring away mail from Gmail via IMAP</title><link href="/2022/02/06/properly-migrating-away-from-gmail-via-imap.html" rel="alternate" type="text/html" title="Properly transferring away mail from Gmail via IMAP" /><published>2022-02-06T01:22:00+01:00</published><updated>2022-02-06T01:22:00+01:00</updated><id>/2022/02/06/properly-migrating-away-from-gmail-via-imap</id><content type="html" xml:base="/2022/02/06/properly-migrating-away-from-gmail-via-imap.html"><![CDATA[<p>I wanted to name this article <em>How to finally do away with Gmail</em>, but such a title wouldn’t have revealed the
technical content of the writing, which represents its most relevant part.</p>

<h3 id="why-would-i-want-to-leave-gmail">Why would I want to leave Gmail?</h3>

<p>There are plenty of reasons:</p>

<ul>
  <li><strong>Privacy</strong>: most major email providers have serious privacy issues, but at least stalking you isn’t their core
business, as it happens with Google.</li>
  <li><strong>Webmail</strong>: Gmail web interface was nothing short of revolutionary when it was launched, back in 2004, but it
evolved into a bloatware full of mostly useless - if not totally worthless - features.</li>
  <li><strong>Ads</strong>: unless you pay for a Google Workspace subscription, you get tons of ads in various shapes, some of which
are very creative: they vary from <em>suggestions</em> put between your messages (which is reasonable, a free service
needs to pull money in some way) to custom <em>sponsored content</em> which haunts you everywhere in your online experience.</li>
  <li><strong>IMAP support</strong>: folders - a bizarre IMAP representation of Google’s own <em>labels</em> concept - are clumsy, deceptive and
tend to leave you with tons of duplicated messages.</li>
  <li><strong><em>Biodiversity</em></strong>: Internet was born with great ideals of freedom and distributed governance: show that these principles
are still alive in you, don’t allow all e-mails in the world to be in the hands of a few companies (Google, Apple and Microsoft
in particular).</li>
</ul>

<h3 id="what-are-the-options">What are the options?</h3>

<p>I’m not recommending any particular provider, but just what I think are the rules to help choosing it:</p>

<ul>
  <li>Enter in a state of mind where you have to <strong>pay</strong> for good e-mail service. Be willing to shell out from 20 to 50
Euro per months, for a 8 to 25 Gb quality mailbox.</li>
  <li>Prefer a relatively small email service provider - it is less likely they monitor your data to sell it to someone
or to use it to target you with “content of your interest”.</li>
</ul>

<h3 id="properly-transferring-away-mail-from-gmail-via-imap">Properly transferring away mail from Gmail via IMAP</h3>

<p>Now to the important thing. How to migrate away all my email from Gmail to the new provider?</p>

<p>Any decent provider will offer a tool for migrations, which will basically connect via IMAP to your Gmail mailbox,
download all the messages, and transfer them to you new account. Many of these tools are quite sophisticated, and
they will map the IMAP folder hierarchy perfectly, so that you will experience a smooth transfer.</p>

<p>If the provider
does not provide a tool, look for another one. But there are also a lot of software packages
(the best ones are not free, though) which download
the e-mail from the old mailbox and upload it to the new one. Or, well, you could always do it by hand using
Thunderbird or Outlook, but this is not particularly reliable for large boxes, in my own experience.</p>

<p>Even though Gmail’s folders are actually labels, everything tends to work well, as (for example) messages with the
<code class="language-plaintext highlighter-rouge">Sent</code> label are mapped to a <code class="language-plaintext highlighter-rouge">Sent</code> folder and therefore correctly migrated.</p>

<p>There is, however, a problem in the <em><code class="language-plaintext highlighter-rouge">All Mail</code></em> folder, which contains (as the name suggest) <em>all your mail</em>: things
your archived (that’s it to say, they live in no other place), but <em>also everything else</em>: <code class="language-plaintext highlighter-rouge">Inbox</code>, <code class="language-plaintext highlighter-rouge">Sent</code>, messages in
other folders (=with other labels). The only messages which are spared are the ones in <code class="language-plaintext highlighter-rouge">Spam</code> and <code class="language-plaintext highlighter-rouge">Trash</code>.
If you think this is awkward, it’s not only that: it’s also bad implemented on the IMAP side, because it insists
putting every message into <code class="language-plaintext highlighter-rouge">All Mail</code>, this creating a lot of duplication, adding download time, and occupying more
space on the client.</p>

<p>While one may just shrug (albeit not before downing a couple of pints) for this problem on an e-mail client, it’s
impossible to avoid wanting to take care of it when migrating to another mailbox: if not for all the rest, at least
for the (paid) space wasted to duplicated messages.</p>

<p>The first thing to <strong>make sure is that <code class="language-plaintext highlighter-rouge">All Mail</code> does not get copied to the new mailbox</strong>. To do this, it’s enough
to remove it from the folders exported via IMAP by Gmail. Go to <em>Settings</em>, then <em>Labels</em> and remove the check from
<em>Show in IMAP</em> from <code class="language-plaintext highlighter-rouge">All Mail</code> label. Note that the check only appears if you enabled IMAP access for the mailbox.</p>

<p>This avoids the disaster, but has a big downside: all the messages you archived that have no other label would get lost,
because <code class="language-plaintext highlighter-rouge">All Mail</code> doesn’t get copied and it’s the only one containing them. So, you <strong>need to assign all your
archived messages a specific label</strong>, using Gmail’s web interface.</p>

<p>But, how can you single out these messages among all the shit in <code class="language-plaintext highlighter-rouge">All Mail</code>? 🤔 Here’s the solution, that is to
say the search string which finds all of them:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>has:nouserlabels -in:Sent -in:Chat -in:Draft -in:Inbox 
</code></pre></div></div>

<p>Once you have the results, you check to select them all and then click
<em>Select all conversations that match this search</em>. Assign them a labels (I use <code class="language-plaintext highlighter-rouge">GoogleMail Archive</code>) to the results:
this will translate to an IMAP folders which all the messages, which will be safely translated to their new home.</p>

<p>I hope this information will help some of those who want to say <em>Bye bye</em> to Gmail.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I wanted to name this article How to finally do away with Gmail, but such a title wouldn’t have revealed the technical content of the writing, which represents its most relevant part.]]></summary></entry><entry><title type="html">Quick and dirty Apache and mySQL “watchdog”</title><link href="/2021/12/21/quick-dirty-apache-mysql-watchdog.html" rel="alternate" type="text/html" title="Quick and dirty Apache and mySQL “watchdog”" /><published>2021-12-21T17:45:00+01:00</published><updated>2021-12-21T17:45:00+01:00</updated><id>/2021/12/21/quick-dirty-apache-mysql-watchdog</id><content type="html" xml:base="/2021/12/21/quick-dirty-apache-mysql-watchdog.html"><![CDATA[<p>Continuing the “quick&amp;dirty” series, here’s a solution I implemented on a server where sometimes (once a week or so)
mySQL just dies.</p>

<p>This has unknown (=<em>still to investigate</em>) reasons. The problems appears to happen when there is heavy load on the
Apache server: spikes are unusual and they’re made by malicious scanner probably. Still, the mySQL server shouldn’t
die but just respond slowly, as it’s under heavy load because it powers some web sites on that server.
This has to be investigated, of course.</p>

<p>I the meanwhile, mySQL server has to be up and Apache has to be responsive. Without implementing a “professional”
solution with all the bells and whistles, a thing that can be done is to create a simple <em>watchdog</em> script in
<a href="https://www.perl.org/">Perl</a>.</p>

<p>The following script is made for <a href="https://www.gentoo.org/">Gentoo Linux</a>, but it should trivial to adapt it to any
Unix variant.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">use</span> <span class="nn">Email::</span><span class="nv">Address</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Email::</span><span class="nv">MIME</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Email::Sender::Transport::</span><span class="nv">Sendmail</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Email::Sender::</span><span class="nv">Simple</span> <span class="sx">qw/sendmail/</span><span class="p">;</span>
<span class="k">use</span> <span class="nv">version</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Arthas::Defaults::</span><span class="nv">520</span><span class="p">;</span>

<span class="k">our</span> <span class="nv">$VERSION</span> <span class="o">=</span> <span class="nv">qv</span><span class="p">(</span><span class="mf">1.0</span><span class="p">);</span>
<span class="k">our</span> <span class="nv">$BASE</span> <span class="o">=</span> <span class="p">'</span><span class="s1">/root/bin</span><span class="p">';</span>
<span class="k">our</span> <span class="nv">$transport</span><span class="p">;</span>
<span class="k">our</span> <span class="nv">$config</span> <span class="o">=</span> <span class="p">{</span> <span class="p">};</span>

<span class="nv">lock_session</span><span class="p">();</span>
<span class="nv">configure</span><span class="p">();</span>
<span class="nv">chh</span><span class="p">();</span>
<span class="nv">unlock_session</span><span class="p">();</span>

<span class="k">sub </span><span class="nf">lock_session</span> <span class="p">{</span>
    <span class="nb">die</span> <span class="p">"</span><span class="s2">Existing lock file</span><span class="p">"</span> <span class="k">if</span> <span class="o">-</span><span class="nv">e</span> <span class="p">"</span><span class="si">$BASE</span><span class="s2">/checkhealth.lock</span><span class="p">";</span>
    <span class="nb">open</span> <span class="p">(</span><span class="k">my</span> <span class="nv">$lfh</span><span class="p">,</span> <span class="p">'</span><span class="s1">&gt;</span><span class="p">',</span> <span class="p">"</span><span class="si">$BASE</span><span class="s2">/checkhealth.lock</span><span class="p">")</span> <span class="ow">or</span> <span class="nb">die</span> <span class="p">"</span><span class="s2">Can't lock</span><span class="p">";</span>
    <span class="k">print</span> <span class="nv">$lfh</span> <span class="p">"</span><span class="s2">PID</span><span class="se">\n</span><span class="p">";</span>
    <span class="nb">close</span> <span class="nv">$lfh</span><span class="p">;</span>
<span class="p">}</span> 

<span class="k">sub </span><span class="nf">configure</span> <span class="p">{</span>
    <span class="nv">$transport</span> <span class="o">=</span> <span class="nn">Email::Sender::Transport::</span><span class="nv">Sendmail</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">sub </span><span class="nf">chh</span> <span class="p">{</span>
    <span class="c1"># Get all mySQL and Apache processes</span>
    <span class="k">my</span> <span class="nv">@processes</span> <span class="o">=</span> <span class="nb">split</span> <span class="sr">m/\n/xs</span><span class="p">,</span> <span class="p">`</span><span class="sb">/bin/ps ax</span><span class="p">`;</span>
    <span class="k">my</span> <span class="nv">@p_mysql</span> <span class="o">=</span> <span class="nb">grep</span> <span class="p">{</span> <span class="vg">$_</span> <span class="o">=~</span> <span class="sr">m/mysqld/xs</span> <span class="p">}</span> <span class="nv">@processes</span><span class="p">;</span>
    <span class="k">my</span> <span class="nv">@p_apache</span> <span class="o">=</span> <span class="nb">grep</span> <span class="p">{</span> <span class="vg">$_</span> <span class="o">=~</span> <span class="sr">m/apache/xs</span> <span class="p">}</span> <span class="nv">@processes</span><span class="p">;</span>
    
    <span class="c1"># Parse uptime command output, and get system load in last minute, last 5 minutes and last 15 minutes</span>
    <span class="k">my</span> <span class="nv">$uptime</span> <span class="o">=</span> <span class="p">`</span><span class="sb">/usr/bin/uptime</span><span class="p">`;</span>
    <span class="k">my</span> <span class="p">(</span><span class="nv">$ul1</span><span class="p">,</span> <span class="nv">$ul5</span><span class="p">,</span> <span class="nv">$ul15</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nv">$uptime</span> <span class="o">=~</span> <span class="sr">m/(\d+),\d+,\s*(\d+),\d+,\s*(\d+),\d+/xs</span> <span class="p">)</span> <span class="p">{</span>
        <span class="p">(</span><span class="nv">$ul1</span><span class="p">,</span> <span class="nv">$ul5</span><span class="p">,</span> <span class="nv">$ul15</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="err">$</span><span class="mi">1</span><span class="p">,</span> <span class="err">$</span><span class="mi">2</span><span class="p">,</span> <span class="err">$</span><span class="mi">3</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1"># Server name is used when reporting via e-mail, because if we run the same script on more than one</span>
    <span class="c1"># server we want to know which server had a problem.</span>
    <span class="k">my</span> <span class="nv">$uname</span> <span class="o">=</span> <span class="p">`</span><span class="sb">/usr/bin/uname -a</span><span class="p">`;</span>
    <span class="p">(</span><span class="nb">undef</span><span class="p">,</span> <span class="k">my</span> <span class="nv">$servername</span><span class="p">)</span> <span class="o">=</span> <span class="nb">split</span> <span class="sr">/\s+/</span><span class="p">,</span> <span class="nv">$uname</span><span class="p">;</span>

    <span class="k">my</span> <span class="nv">$body</span> <span class="o">=</span> <span class="p">'';</span>

    <span class="c1"># No mySQL process active? Restart!</span>
    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">@p_mysql</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$body</span> <span class="o">.=</span> <span class="p">"</span><span class="s2">MYSQL server not active, restarting...</span><span class="se">\n\n</span><span class="p">";</span>

        <span class="k">my</span> <span class="nv">$rsout</span> <span class="o">=</span> <span class="p">`</span><span class="sb">/etc/init.d/mysql restart</span><span class="p">`;</span>
	    <span class="nv">$body</span> <span class="o">.=</span> <span class="nv">$rsout</span><span class="o">.</span><span class="p">"</span><span class="se">\n\n</span><span class="p">";</span>
    <span class="p">}</span>

    <span class="c1"># No Apache process active? Restart! Unlikely, never happened, but I don't need to spare 5 lines of code</span>
    <span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nv">@p_apache</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$body</span> <span class="o">.=</span> <span class="p">"</span><span class="s2">Apache server not active, restarting...</span><span class="se">\n\n</span><span class="p">";</span>

        <span class="k">my</span> <span class="nv">$rsout</span> <span class="o">=</span> <span class="p">`</span><span class="sb">/etc/init.d/apache2 restart</span><span class="p">`;</span>
	    <span class="nv">$body</span> <span class="o">.=</span> <span class="nv">$rsout</span><span class="o">.</span><span class="p">"</span><span class="se">\n\n</span><span class="p">";</span>
    <span class="p">}</span>

    <span class="c1"># If load in that past minutes is over 6 (it should be 2 at most, it's a 2-core VPS)</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nv">$ul1</span> <span class="o">&gt;</span> <span class="mi">6</span> <span class="p">)</span> <span class="p">{</span>
        <span class="nv">$body</span> <span class="o">.=</span> <span class="p">"</span><span class="s2">Server under heavy load, restarting Apache...</span><span class="se">\n\n</span><span class="p">";</span>

        <span class="k">my</span> <span class="nv">$rsout</span> <span class="o">=</span> <span class="p">`</span><span class="sb">/etc/init.d/apache2 restart</span><span class="p">`;</span>
	    <span class="nv">$body</span> <span class="o">.=</span> <span class="nv">$rsout</span><span class="o">.</span><span class="p">"</span><span class="se">\n\n</span><span class="p">";</span>
    <span class="p">}</span>

    <span class="c1"># Notify, but only in case of a problem - we surely don't want to get an useless e-mail every 5 minutes or so</span>
    <span class="k">if</span> <span class="p">(</span> <span class="nv">$body</span> <span class="p">)</span> <span class="p">{</span>
        <span class="c1"># Add uname and uptime output, which can be useful</span>
        <span class="nv">$body</span> <span class="o">.=</span> <span class="p">"</span><span class="s2">SERVER DATA:</span><span class="se">\n</span><span class="p">"</span> <span class="o">.</span> <span class="nv">$uname</span> <span class="o">.</span> <span class="nv">$uptime</span> <span class="o">.</span> <span class="p">"</span><span class="se">\n</span><span class="p">";</span>

        <span class="k">my</span> <span class="nv">@parts</span> <span class="o">=</span> <span class="p">(</span><span class="nn">Email::</span><span class="nv">MIME</span><span class="o">-&gt;</span><span class="nv">create</span><span class="p">(</span>
            <span class="s">attributes</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="s">encoding</span>        <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">quoted-printable</span><span class="p">',</span>
                <span class="s">content_type</span>    <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">text/plain</span><span class="p">',</span>
                <span class="s">charset</span>         <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">UTF-8</span><span class="p">',</span>
            <span class="p">},</span>
            <span class="s">body_str</span> <span class="o">=&gt;</span> <span class="nv">$body</span><span class="p">,</span>
        <span class="p">));</span>

        <span class="k">my</span> <span class="nv">$msg</span> <span class="o">=</span> <span class="nn">Email::</span><span class="nv">MIME</span><span class="o">-&gt;</span><span class="nv">create</span><span class="p">(</span>
            <span class="s">header_str</span>  <span class="o">=&gt;</span> <span class="p">[</span>
                <span class="s">From</span>    <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">domain@cattlegrid.info</span><span class="p">',</span>
                <span class="s">To</span>      <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">mb@cattlegrid.info</span><span class="p">',</span>
                <span class="s">Subject</span> <span class="o">=&gt;</span> <span class="p">"</span><span class="si">$servername</span><span class="s2">: health notice</span><span class="p">",</span>
            <span class="p">],</span>
            <span class="s">parts</span>       <span class="o">=&gt;</span> <span class="o">\</span><span class="nv">@parts</span><span class="p">,</span>
            <span class="p">);</span>
        <span class="nv">sendmail</span><span class="p">(</span><span class="nv">$msg</span><span class="p">,</span> <span class="p">{</span> <span class="s">transport</span> <span class="o">=&gt;</span> <span class="nv">$transport</span> <span class="p">});</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">sub </span><span class="nf">unlock_session</span> <span class="p">{</span>
    <span class="nb">unlink</span> <span class="p">"</span><span class="si">$BASE</span><span class="s2">/checkhealth.lock</span><span class="p">"</span> <span class="ow">or</span> <span class="nb">die</span> <span class="p">"</span><span class="s2">Can't unlock</span><span class="p">";</span>
<span class="p">}</span></code></pre></figure>

<p>The script is designed to be run as a <em>cronjob</em>, because it’s the safest thing: there is no need to ensure the script
itself is always running, as it’s called every few minutes. The definition “few minutes” depend on how long you
want it to check the health of the system.</p>

<p>The locking system, basically just a simple lockfile, is needed to prevent the script being executed twice at the
same time. It’s actually an extremely unlikely event, which could happen only if you schedule the script to be run
very often, like every minute.</p>

<p>Here’s an example of the notification e-mails that are being sent in case of a problem.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MYSQL server not active, restarting...

* Checking mysqld configuration for mysql ... [ ok ]
* Stopping mysql ... [ ok ]
* Starting mysql ... [ ok ]

SERVER DATA:
Linux cattlegrid 5.14.17-x86_64-linode150 #1 SMP Thu Nov 11 13:17:05 EST 2021 x86_64 AMD EPYC 7601 32-Core Processor AuthenticAMD GNU/Linux
17:47:28 up 11 days, 18:06,  2 users,  load average: 0,09, 0,19, 0,23
</code></pre></div></div>

<p>The rest of the script is explained in the comments. It’s actually very simple, maybe even silly, but works like a
charm.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Continuing the “quick&amp;dirty” series, here’s a solution I implemented on a server where sometimes (once a week or so) mySQL just dies.]]></summary></entry><entry><title type="html">Quick and dirty Apache log analysis</title><link href="/2021/12/16/quick-dirty-apache-log-analysis.html" rel="alternate" type="text/html" title="Quick and dirty Apache log analysis" /><published>2021-12-16T17:15:00+01:00</published><updated>2021-12-16T17:15:00+01:00</updated><id>/2021/12/16/quick-dirty-apache-log-analysis</id><content type="html" xml:base="/2021/12/16/quick-dirty-apache-log-analysis.html"><![CDATA[<p>One hosting service I have contains several domains. In order to understand which of these web sites was the most
trafficked, I needed to analyze the logs a bit.
There are sever Apache log analysis solutions out there, but frankly I just needed some basic information, and didn’t
want a software bloated with a lot of feature 99% of which I didn’t need.</p>

<p><a href="https://www.perl.org/">Perl</a> came to the rescue, and in some 15 minutes I wrote a working script.</p>

<h2 id="what-i-needed-to-know">What I needed to know</h2>

<p>Basically, I wanted to know the number of hits and the bytes transferred for each of the web sites, in order
to make a ranking.</p>

<p>The Apache log is something that is a standard now, and is something like this:</p>

<figure class="highlight"><pre><code class="language-apache" data-lang="apache">75.137.96.53 - - [16/Dec/2021:00:01:16 -0500] "GET /assets/js/main.js?v=5.6.0 HTTP/1.1" 200 6696 "https://www.mydomain1.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0"</code></pre></figure>

<p>Being a shared Apache, my log also has an extra field at the beginning, with the various domain names:</p>

<figure class="highlight"><pre><code class="language-apache" data-lang="apache">www.mydomain1.com 75.137.96.53 - - [16/Dec/2021:00:01:16 -0500] "GET /assets/js/main.js?v=5.6.0 HTTP/1.1" 200 6696 "https://www.mydomain1.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0"</code></pre></figure>

<h2 id="the-script">The script</h2>

<p>The script is meant to be used feeding a log on its standard input, so for instance.</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># tail -1000 access_log | process_log.pl</span></code></pre></figure>

<p>Here goes the script, with some comments where needed.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">use</span> <span class="nn">Path::</span><span class="nv">Class</span> <span class="sx">qw/file/</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Number::Bytes::</span><span class="nv">Human</span> <span class="sx">qw/format_bytes/</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Text::</span><span class="nv">Table</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Arthas::Defaults::</span><span class="nv">520</span><span class="p">;</span>

<span class="k">my</span> <span class="nv">$sites</span><span class="p">;</span>
<span class="k">my</span> <span class="nv">$totbytes</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="k">while</span> <span class="p">(</span><span class="k">my</span> <span class="nv">$row</span> <span class="o">=</span> <span class="o">&lt;</span><span class="bp">STDIN</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">chomp</span> <span class="nv">$row</span><span class="p">;</span>

    <span class="c1"># Regex which parses a line of the Apache log.</span>
    <span class="k">my</span> <span class="p">(</span>
       <span class="nv">$sitename</span><span class="p">,</span> <span class="nv">$clientip</span><span class="p">,</span>  <span class="nv">$rfc1413</span><span class="p">,</span> <span class="nv">$username</span><span class="p">,</span> 
       <span class="nv">$when</span><span class="p">,</span>     <span class="nv">$reqstring</span><span class="p">,</span> <span class="nv">$status</span><span class="p">,</span>  <span class="nv">$bytesout</span><span class="p">,</span>
       <span class="nv">$referer</span><span class="p">,</span>  <span class="nv">$useragent</span>
    <span class="p">)</span> <span class="o">=</span> <span class="nv">$row</span> <span class="o">=~</span> <span class="sr">/^(\S+) (\S+) (\S+) (\S+) \[(.+)\] \"(.+)\" (\S+) (\S+) \"(.*)\" \"(.*)\"/o</span><span class="p">;</span>

    <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$sitename</span><span class="p">}</span> <span class="sr">//</span><span class="o">=</span> <span class="p">{</span>
       <span class="s">hits</span>    <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
       <span class="s">okhits</span>  <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
       <span class="s">bytes</span>   <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">};</span>
    <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$sitename</span><span class="p">}</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">hits</span><span class="p">}</span><span class="o">++</span><span class="p">;</span>

    <span class="c1"># Don't care about byte count on non-OK hits. Maybe we should add that as well, but... no.</span>
    <span class="k">next</span> <span class="k">if</span> <span class="nv">$status</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">;</span>

    <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$sitename</span><span class="p">}</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">okhits</span><span class="p">}</span><span class="o">++</span><span class="p">;</span>
    <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$sitename</span><span class="p">}</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">bytesout</span><span class="p">}</span> <span class="o">+=</span> <span class="nv">$bytesout</span><span class="p">;</span>
    <span class="nv">$totbytes</span> <span class="o">+=</span> <span class="nv">$bytesout</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1"># Create a nicely-formatted table</span>
<span class="k">my</span> <span class="nv">$tb</span> <span class="o">=</span> <span class="nn">Text::</span><span class="nv">Table</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">('</span><span class="s1">Site</span><span class="p">','</span><span class="s1">OK Hits</span><span class="p">','</span><span class="s1">Transfer Out</span><span class="p">');</span>

<span class="c1"># Only display the first 10 entries, sorted by number of 200 OK hits</span>
<span class="k">my</span> <span class="nv">$ii</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="k">my</span> <span class="nv">$sk</span><span class="p">(</span>
    <span class="nb">sort</span> <span class="p">{</span> <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$b</span><span class="p">}</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">okhits</span><span class="p">}</span> <span class="o">&lt;=&gt;</span> <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$a</span><span class="p">}</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">okhits</span><span class="p">}</span> <span class="p">}</span> <span class="nb">keys</span> <span class="nv">%$sites</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">my</span> <span class="nv">$sv</span> <span class="o">=</span> <span class="nv">$sites</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$sk</span><span class="p">};</span>
    <span class="k">my</span> <span class="nv">$tout</span> <span class="o">=</span> <span class="nv">format_bytes</span><span class="p">(</span><span class="nv">$sv</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">bytesout</span><span class="p">});</span>
    <span class="nv">$tb</span><span class="o">-&gt;</span><span class="nv">add</span><span class="p">(</span><span class="nv">$sk</span><span class="p">,</span> <span class="nv">$sv</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">okhits</span><span class="p">},</span> <span class="nv">$tout</span><span class="p">);</span>
    <span class="k">last</span> <span class="k">if</span> <span class="nv">$ii</span> <span class="o">==</span> <span class="mi">9</span><span class="p">;</span>
    <span class="nv">$ii</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>

<span class="nv">say</span> <span class="nv">$tb</span><span class="p">;</span>

<span class="c1"># We also want the totals, why not?</span>
<span class="k">my</span> <span class="nv">$totbytes_h</span> <span class="o">=</span> <span class="nv">format_bytes</span><span class="p">(</span><span class="nv">$totbytes</span><span class="p">);</span>
<span class="nv">say</span> <span class="p">"</span><span class="s2">TOTAL TRANSFER: </span><span class="si">$totbytes_h</span><span class="p">";</span></code></pre></figure>

<p>We use a couple of nice Perl modules here, which are <a href="https://metacpan.org/pod/Number::Bytes::Human">Number::Bytes::Human</a>, used to automatically convert bytes
in something easier to read, and especially <a href="https://metacpan.org/pod/Text::Table">Text::Table</a>, which produces a great nicely-formatted table on
the console.</p>

<p>Here’s the output:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">Site                             OK Hits Transfer Out
www.domain1.com                  128360  2.6G        
www.mydomain2.com                108854  3.5G        
www.thedomain3.it                98164   4.2G        
www.ourdomain4.com               59108   6.6G        
www.infodomain.info              35417   560M        
www.custdomain.com               29424   520M        
www.shopdomain.it                20547   623M        
www.bardomain.it                 12633   403M        
www.bdm.it                       11330   292M        
basehostx.com                    10030   1.4M   
TOTAL TRANSFER: 29G</code></pre></figure>

<p><em>Text::Table</em> also supports borders for cells and other niceties, which I didn’t use.</p>

<p>This quick and dirty solution is very easy to adapt and improve, for example to show the IPs which hit the server
most, grouped by web site.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[One hosting service I have contains several domains. In order to understand which of these web sites was the most trafficked, I needed to analyze the logs a bit. There are sever Apache log analysis solutions out there, but frankly I just needed some basic information, and didn’t want a software bloated with a lot of feature 99% of which I didn’t need.]]></summary></entry><entry><title type="html">Restoring and updating an Amiga 1200</title><link href="/2020/09/23/restoring-and-updating-an-amiga-1200.html" rel="alternate" type="text/html" title="Restoring and updating an Amiga 1200" /><published>2020-09-23T10:40:00+02:00</published><updated>2020-09-23T10:40:00+02:00</updated><id>/2020/09/23/restoring-and-updating-an-amiga-1200</id><content type="html" xml:base="/2020/09/23/restoring-and-updating-an-amiga-1200.html"><![CDATA[<div class="pimg-left-h lightbox"><a href="/assets/pimages/amiga1200/amigastore_box.jpg" title="Package from AmigaStore.Eu"><img src="/assets/pimages/amiga1200/amigastore_box.jpg" alt="Package from AmigaStore.Eu" /></a></div>
<p>I had been an Amiga user for a long time between the end of the 80s and the beginning on the 90s: these computers were
ahead of their time, and I loved them. I owned an Amiga 500 (then sold) and an Amiga 2000 (which I’m trying to repair),
both featuring kickstart 1.2.</p>

<p>I recently saw an <em>Amiga 1200</em> on sale in a town not far from me, so I decided to take it to see what I could do with
it. I then began jumping around online forums, and discovered a whole universe of Amiga enthusiasts. Shortly after, I
decided to try to work on this glorious computer, and this is what I’ve done (so far).</p>

<p>I purchased most of the add-ons and hardware described here on  <a href="https://www.amigastore.eu/">AmigaStore</a>.</p>

<h2 id="recap">Recap</h2>

<p>One of the problems with old computers are capacitors: after so many years, they may not work as expected and (in
the worst case scenario) also leak their fluids on the mainboard, causing various damage. So, I just switched the
Amiga on a couple of times before sending it to a very competent folk here in Italy, which did the recapping and
also checked that everything was in good working order.</p>

<h2 id="monitor">Monitor</h2>

<div class="pimg-right-h lightbox"><a href="/assets/pimages/amiga1200/workbench31.jpg" title="Wordkbench 3.1"><img src="/assets/pimages/amiga1200/workbench31.jpg" alt="Workbench 3.1" /></a></div>
<p>I indeed plan to get a Commodore or Philips CRT monitor, like the ones we used back in the days. In the meantime,
however, the best choice was a LCD television, which has the required SCART connector.</p>

<p>Moreover, while it lacks the <em>nostalgia</em> factor, the LCD provides much better picture quality, and also the
interlaced modes - which looked horrible on the CRT monitors - are very usable.</p>

<h2 id="psu-substitution">PSU substitution</h2>

<div class="pimg-left-h lightbox"><a href="/assets/pimages/amiga1200/psu.jpg" title="Original and new PSU"><img src="/assets/pimages/amiga1200/psu.jpg" alt="Original and new PSU" /></a></div>
<p>The original external Power Supply Unit by Commodore was glued and impossible to open, so recapping was a no-go.
It was also just 25 W, which wasn’t ideal with expansions, disk, etc.</p>

<p>Many current Amiga users stick with ATX PSUs, which are inexpensive and supply plenty of power. I wanted
something more <em>classic</em> - at least in the looks - so I bought a white Amiga-specific PSU: it’s smaller and way
lighter than the original one, and it supplies a decent (and enough for most <em>classic</em> uses) 50 W of power.</p>

<h2 id="kickstart-31">Kickstart 3.1</h2>

<div class="pimg-right-h lightbox"><a href="/assets/pimages/amiga1200/kickstart31.jpg" title="Kickstart 3.1 ROMs by Cloanto"><img src="/assets/pimages/amiga1200/kickstart31.jpg" alt="Kickstart 3.1 ROMs by Cloanto" /></a></div>
<p>Amiga 1200 usually came with kickstart 3.0, which is fine but could use some bugfixes and improvements. There are
two main improvement choices:</p>

<ul>
  <li>Kickstart 3.1.4 by <em><a href="https://www.hyperion-entertainment.com/">Hyperion Entertainment</a></em></li>
  <li>Kickstart 3.1 by <em><a href="https://cloanto.com/">Cloanto Corporation</a></em></li>
</ul>

<p>I was initially tempted by kickstart 3.1.4, but then I decided to stay as <em>traditional</em> as possible. So, the Cloanto
<em>3.x</em> series looked like the best choice, being faithful to the original ones while featuring some needed patches such
as the ability to use large disk partitions.</p>

<div class="pimg-left-h lightbox"><a href="/assets/pimages/amiga1200/kickstart31_boot.jpg" title="Welcome screen, kickstart 3.1"><img src="/assets/pimages/amiga1200/kickstart31_boot.jpg" alt="Welcome screen, kickstart 3.1" /></a></div>
<p>Installation was almost straightforward: with the help of a screwdriver I removed the old 3.0 ROMs and inserted
the new ones (after adjusting the alignment of the pins, which were a bit too <em>wide</em>).</p>

<p>Everything looks fine, except for the copyrights on the welcome screen which are (of course) not by Commodore_
anymore like in version 3.0, but by Cloanto: I’ll live with that. 😊</p>

<h2 id="ram-expansion-and-real-time-clock">RAM expansion and Real Time Clock</h2>

<div class="pimg-right-v lightbox"><a href="/assets/pimages/amiga1200/a1208.jpg" title="A1208 expansion card"><img src="/assets/pimages/amiga1200/a1208.jpg" alt="A1208 expansion card" /></a></div>
<p>Amiga 1200 comes with 2 Mb of chip RAM, which is actually more chip RAM than I ever was able to use in any
Amiga (my 500 and 2000 both had 512 Kb chip and another 512 Kb fast).</p>

<p>However, I feel Workbench 3.1, WHDLoad and other applications would benefit from having some more RAM, so I
installed an A1208 expansion card, which adds 8 Mb of fast RAM: it should be more than enough for my needs,
even for <a href="https://github.com/AmiBlitz/AmiBlitz3">AmiBlitz</a>, which I plan to play with.</p>

<p>The system reacted very well to the installation of this expansion card (which was easy enough, even though I
didn’t remember how hard you have to push expansions in the slots in those old computers 😆).
Before installation, upon booting Workbench, I only had about 1.3 Mb of free chip RAM. Now I almost have the full
2 Mb free, plus about 7.5 Mb of fast RAM.</p>

<p>The expansion card also includes a Real Time Clock (RTC), with battery. This was optional, but I really want a
computer that can remember date and time.</p>

<h2 id="hard-disk-and-workbench-31">Hard disk and Workbench 3.1</h2>

<div class="pimg-left-h lightbox"><a href="/assets/pimages/amiga1200/dh0.jpg" title="IDE Compact Flash reader"><img src="/assets/pimages/amiga1200/dh0.jpg" alt="IDE Compact Flash reader" /></a></div>
<p>The computer had a 480 Mb IDE hard drive installed: even though fast, it was noisy and sometimes it turned off
spontaneously: I though the problem was in the PSU, but even with the new power supply it didn’t work properly,
and in the end it died altogether.</p>

<p>I opted to replace it with a IDE-to-CF adapter, to which I added a pre-installed Cloanto’s Workbench 3.1 CompactFlash
card. As it happened with the kickstart, I was tempted to go with Hyperion’s Workbench 1.3.4 and all its nice coloured
icons, but then I really wanted to stick to the <em>Commodore era</em> as much as possible.</p>

<p>The adapater is really nice: very easy to install, it doesn’t move around - I just put a thin plastic layer under it,
to avoid the risk of contacts touching the metal. It doesn’t even need the power cable: it takes the (little) power
it needs from the IDE cable. No more hard drive noise, it’s a true bootable disk, Workbench loads blazingly
fast (less than 5 seconds!) and works like charm.</p>

<h2 id="what-now">What now?</h2>

<p>Now it’s time to transfer some applications and games into the Amiga. I will write about that in the next post!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I had been an Amiga user for a long time between the end of the 80s and the beginning on the 90s: these computers were ahead of their time, and I loved them. I owned an Amiga 500 (then sold) and an Amiga 2000 (which I’m trying to repair), both featuring kickstart 1.2.]]></summary></entry><entry><title type="html">Reconstructing photo albums from a Flickr data export</title><link href="/2020/05/25/reconstructing-photo-albums-from-flickr-data-export.html" rel="alternate" type="text/html" title="Reconstructing photo albums from a Flickr data export" /><published>2020-05-25T19:04:00+02:00</published><updated>2020-05-25T19:04:00+02:00</updated><id>/2020/05/25/reconstructing-photo-albums-from-flickr-data-export</id><content type="html" xml:base="/2020/05/25/reconstructing-photo-albums-from-flickr-data-export.html"><![CDATA[<p>As every <a href="https://www.flickr.com/">Flickr</a> user knows, a year ago or so the service became subscription-based.
Initially I decided to stay with them as it was an handy service, but then I found myself not using it that much.
Moreover, they recently increased the monthly fee, so I opted to download the photos and store them locally.</p>

<p>Flickr allows to download all your data fairly easily, dividing it in ZIP archives of 500 photos each, so I downloaded by
<strong>26000+</strong> photos. Problem is the pics are <em>not divided into albums</em>, so - especially if you have a lot of pics -
you get a huge mess which is very difficult to manage.</p>

<p>Luckily, in the downloadable data Flickr also provides all the information to programmatically reconstruct all the
structures. I was able to successfully move the files into different directories, divided by album name. I hope
sharing this experience will be useful to anyone who either is moving away from Flickr or just downloading the
data to keep a local copy.</p>

<p>In the downloaded data, there is an <em>albums.json</em> file, which has all album information in this structure:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
    </span><span class="nl">"albums"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"photo_count"</span><span class="p">:</span><span class="w"> </span><span class="s2">"11"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"72157696153620351"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.flickr.com/photos/arthas/albums/72157696153620351"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2018-06-09: Iconocluster - Profumo DiVino"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
            </span><span class="nl">"view_count"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"created"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1528666970"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"last_updated"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1528799288"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"cover_photo"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.flickr.com/photos/arthas/42670192932"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"photos"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                </span><span class="s2">"28846036158"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"42670194192"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"40908904860"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"40908904790"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"42670192932"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"42701822032"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"42751133711"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"28877442928"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"42701821872"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"27882010047"</span><span class="p">,</span><span class="w">
                </span><span class="s2">"42751133381"</span><span class="w">
            </span><span class="p">]</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>Ain’t this perfect? Well, <em>not exactly</em>. You can’t just use the codes in the ‘‘photos’’ array as they are to find
the files belonging to an album, because:</p>

<ul>
  <li>The extension (JPG, MOV, PNG, …) is not provided</li>
  <li>Files in the data export have names including the title, such as <em>dublin-castle_5303007241_o.jpg</em></li>
</ul>

<p>So, a little bit of programming is needed. Here’s my commented Perl script which does the job. This assumes that
you are in a directory containing:</p>

<ul>
  <li>All the downloaded photos/videos in a <em>photos</em> subdirectory</li>
  <li>An empty <em>albums</em> subdirectory, where albums directories will be created</li>
  <li>The script itself</li>
</ul>

<p>For the rest, it’s mostly self-explanatory but I provided comments here and there.</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="c1">#!/usr/bin/env perl</span>

<span class="k">use</span> <span class="nn">Mojo::</span><span class="nv">File</span> <span class="sx">qw/path/</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Mojo::</span><span class="nv">JSON</span> <span class="sx">qw/decode_json/</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">Arthas::Defaults::</span><span class="nv">520</span><span class="p">;</span>

<span class="k">my</span> <span class="nv">$jdata</span> <span class="o">=</span> <span class="nv">decode_json</span><span class="p">(</span> <span class="nv">path</span><span class="p">('</span><span class="s1">./albums.json</span><span class="p">')</span><span class="o">-&gt;</span><span class="nv">slurp</span> <span class="p">)</span> <span class="o">||</span> <span class="nb">die</span> <span class="p">'</span><span class="s1">No-albumdata</span><span class="p">';</span>
<span class="k">my</span> <span class="nv">$photos</span> <span class="o">=</span> <span class="nv">path</span><span class="p">('</span><span class="s1">./photos</span><span class="p">')</span><span class="o">-&gt;</span><span class="nv">list</span> <span class="o">||</span> <span class="nb">die</span> <span class="p">'</span><span class="s1">No-photos</span><span class="p">';</span>
<span class="k">my</span> <span class="nv">@missings</span><span class="p">;</span>

<span class="k">my</span> <span class="nv">$albums</span> <span class="o">=</span> <span class="nv">$jdata</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">albums</span><span class="p">};</span>
<span class="k">for</span> <span class="k">my</span> <span class="nv">$album</span> <span class="p">(</span><span class="nv">@$albums</span><span class="p">)</span> <span class="p">{</span>
    <span class="nv">say</span> <span class="p">"</span><span class="s2">==&gt; </span><span class="si">$album</span><span class="s2">-&gt;{title}</span><span class="p">";</span>

    <span class="c1"># Create album directory, using a cleaned-up version of its title</span>
    <span class="k">my</span> <span class="nv">$albumdir</span> <span class="o">=</span> <span class="nv">path</span> <span class="p">'</span><span class="s1">./albums/</span><span class="p">'</span><span class="o">.</span><span class="nv">clean_albumtitle</span><span class="p">(</span> <span class="nv">$album</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">title</span><span class="p">}</span> <span class="p">);</span>
    <span class="nv">$albumdir</span><span class="o">-&gt;</span><span class="nv">make_path</span><span class="p">();</span>

    <span class="k">for</span> <span class="k">my</span> <span class="nv">$photocode</span> <span class="p">(</span><span class="nv">@</span><span class="p">{</span> <span class="nv">$album</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">photos</span><span class="p">}</span> <span class="p">})</span> <span class="p">{</span>
        <span class="c1"># Photo filename may have bizarre name but they always contain the code</span>
        <span class="k">my</span> <span class="nv">$fphotos</span> <span class="o">=</span> <span class="nv">$photos</span><span class="o">-&gt;</span><span class="nb">grep</span><span class="p">(</span><span class="sx">qr/$photocode/</span><span class="p">);</span>
        <span class="k">my</span> <span class="nv">$fphoto</span> <span class="o">=</span> <span class="nb">shift</span> <span class="nv">@$fphotos</span><span class="p">;</span>
        <span class="k">print</span> <span class="p">"</span><span class="s2">    </span><span class="si">$photocode</span><span class="s2"> =&gt; </span><span class="si">$fphoto</span><span class="s2"> =&gt; </span><span class="p">";</span>

        <span class="c1"># Check if we actually found a match</span>
        <span class="k">if</span> <span class="p">(</span> <span class="o">!-</span><span class="nv">e</span> <span class="nv">$fphoto</span> <span class="p">)</span> <span class="p">{</span>
            <span class="nb">push</span> <span class="nv">@missings</span><span class="p">,</span> <span class="nv">$photocode</span><span class="o">.</span><span class="p">'';</span>
            <span class="nv">say</span> <span class="p">"</span><span class="s2">MISSING!</span><span class="p">";</span>
            <span class="k">next</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1"># Copy the pic in destination folder</span>
        <span class="nv">$fphoto</span><span class="o">-&gt;</span><span class="nv">copy_to</span><span class="p">(</span><span class="nv">$albumdir</span><span class="p">);</span>
        <span class="nv">say</span> <span class="p">'</span><span class="s1">copied!</span><span class="p">';</span>
        <span class="c1"># If you have a ton of photos, you may prefer to move them</span>
        <span class="c1"># Also, moving allows to easily find any leftovers just by looking at the</span>
        <span class="c1"># directory</span>
        <span class="c1"># And by the way: it's also way faster than copying!</span>
        <span class="c1"># $fphoto-&gt;move_to($albumdir);</span>
        <span class="c1"># say 'moved!';</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># Report any codes which we didn't find</span>
<span class="c1"># There's should be any, so reporting is useful for debugging (i.e. if you forgot to unpack one of the ZIP files)</span>
<span class="nv">say</span> <span class="p">"</span><span class="se">\n</span><span class="s2">MISSING CODES:</span><span class="p">";</span>
<span class="nv">say</span> <span class="nb">join</span> <span class="p">"</span><span class="se">\n</span><span class="p">",</span> <span class="nv">@missings</span><span class="p">;</span>
<span class="nv">say</span> <span class="p">"</span><span class="s2">OK</span><span class="p">";</span>

<span class="c1"># File systems (depending on OS/FS) don't like some special chars, so I'm cleaning slashes, ampersands and colons.</span>
<span class="c1"># Tune this to match your own needs.</span>
<span class="k">sub </span><span class="nf">clean_albumtitle</span><span class="p">($title) {</span>
    <span class="nv">$title</span> <span class="o">=~</span> <span class="sr">s/[\/\'\:]/-/gxs</span><span class="p">;</span>
    <span class="k">return</span> <span class="nv">$title</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>The code relies on <a href="https://metacpan.org/pod/Mojo::File">Mojo::File</a> and <a href="https://metacpan.org/pod/Mojo::JSON">Mojo::JSON</a>
to achieve a clean and elegant approach to the solution of the task.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[As every Flickr user knows, a year ago or so the service became subscription-based. Initially I decided to stay with them as it was an handy service, but then I found myself not using it that much. Moreover, they recently increased the monthly fee, so I opted to download the photos and store them locally.]]></summary></entry><entry><title type="html">Dealing with dates and times in software and databases</title><link href="/2019/08/18/date-time-software-databases.html" rel="alternate" type="text/html" title="Dealing with dates and times in software and databases" /><published>2019-08-18T23:10:00+02:00</published><updated>2019-08-18T23:10:00+02:00</updated><id>/2019/08/18/date-time-software-databases</id><content type="html" xml:base="/2019/08/18/date-time-software-databases.html"><![CDATA[<p>This post aims at pointing out some of the difficulties that arise when writing software and dealing with dates and times, and with storing them in databases.</p>

<h2 id="databases-and-time-zones">Databases and time zones</h2>

<p>One of the problems which usually arise when storing date+time in a database, is that it might then come out of it in a different time zone, which can be sort of a nightmare especially if you are writing software which schedules events or performs similar tasks.</p>

<p><em>mySQL</em> date and time type is <code class="language-plaintext highlighter-rouge">datetime</code>, which knows nothing about time zones. So, when a date+time is stored there,
it’s up to you to “remember” the the time zone you used. Let’s try an example on my computer (the database is <em>MariaDB</em>, but that’s the same), which is configured with the <em>Europe/Rome</em> (Olson) time zone (<em>CET/UTC+1</em> in the winter, <em>CEST/UTC+2</em> during summer):</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">MariaDB [dtest]&gt; select now(), utc_timestamp();
+---------------------+---------------------+
| now()               | utc_timestamp()     |
+---------------------+---------------------+
| 2019-08-18 17:43:34 | 2019-08-18 15:43:34 |
+---------------------+---------------------+
Query OK, 0 rows affected (0.000 sec)</code></pre></figure>

<p>As you can see, the default time zone used is the local one, that is the one of the machine on which the server is running. Let’s set the session to use UTC instead:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">MariaDB [dtest]&gt; set @@session.time_zone='+00:00';
Query OK, 0 rows affected (0.000 sec)

MariaDB [dtest]&gt; select now(), utc_timestamp();
+---------------------+---------------------+
| now()               | utc_timestamp()     |
+---------------------+---------------------+
| 2019-08-18 15:43:09 | 2019-08-18 15:43:09 |
+---------------------+---------------------+
1 row in set (0.000 sec)</code></pre></figure>

<p>All fine here, but let’s try <em>storing</em> a date/time into a table_</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">MariaDB [dtest]&gt; set @@session.time_zone='+02:00';
Query OK, 0 rows affected (0.000 sec)

MariaDB [dtest]&gt; create table tztest(sampletime datetime);
Query OK, 0 rows affected (0.020 sec);

MariaDB [dtest]&gt; insert into tztest(sampletime) values('2019-08-18 17:24:17');
Query OK, 1 row affected (0.006 sec)

MariaDB [dtest]&gt; select * from tztest;
+---------------------+
| sampletime          |
+---------------------+
| 2019-08-18 17:24:17 |
+---------------------+
1 row in set (0.001 sec)

MariaDB [dtest]&gt; set @@session.time_zone='+00:00';
Query OK, 0 rows affected (0.000 sec)

MariaDB [dtest]&gt; select * from tztest;
+---------------------+
| sampletime          |
+---------------------+
| 2019-08-18 17:24:17 |   &lt;==== BIG PROBLEM
+---------------------+
1 row in set (0.001 sec)</code></pre></figure>

<p>See the problem? When changing session time zones, you still get the same time when reading data. That is, if a client sets a different session TZ than another, <strong>the same date will not be actually the same</strong>! Let’s say a web application enters a business meeting at 17:24 using the local time zone, and the user from a mobile phone when reading it sees 15:24: the business meeting would fail.</p>

<p>The trick here is to <strong>always set the session time zone to <em>UTC</em> (<em>+00:00</em>)</strong>. This is still prone to difficult-to-find errors, of course.</p>

<p><em>PostgreSQL</em> is much better, as it offers a <code class="language-plaintext highlighter-rouge">timestamp with time zone</code> type, which should always be used. When storing time in it, you need to pass the time zone, and this gets stored in the server, so TZ information is always kept and carried around. <em>UTC</em> should always be preferred (for reasons we will soon see), but with <em>PostgreSQL</em> you have the guarantee that a stored time will always be that one, regardless on how the database is accessed, which is a huge plus.</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">dtest=&gt; select now();
              now              
-------------------------------
 2019-08-18 17:54:51.146696+02
(1 row)</code></pre></figure>

<h2 id="why-utc-should-always-be-preferred">Why UTC should always be preferred</h2>

<p><strong>Aside</strong>: we already introduced <em>UTC</em> as a name earlier. For the ones who don’t know about it, it’s <em>Universal Time Coordinated</em>. <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">The UTC Wikipedia page</a> has full explanations. In practice, the UK uses <em>UTC</em> (<em>GMT</em>, <em>Greenwich Mean Time</em>) in the winter, and <em>UTC+1</em> (<em>BST</em>, <em>British Summer Time</em>) in the summer.</p>

<p>Let’s say we have a web application which collects times at which a movie meeting begins on TV. We will make a few examples in <em>Perl</em>.</p>

<p>The user enters a movie which begins at <em>02:00</em> local time (<em>Europe/Rome</em>), which is almost always fine, but not really always:</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">use</span> <span class="nv">DateTime</span><span class="p">;</span>

<span class="k">my</span> <span class="nv">$dt</span> <span class="o">=</span> <span class="nv">DateTime</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">(</span>
    <span class="s">year</span> <span class="o">=&gt;</span> <span class="mi">2019</span><span class="p">,</span> <span class="s">month</span> <span class="o">=&gt;</span> <span class="mi">3</span><span class="p">,</span> <span class="s">day</span> <span class="o">=&gt;</span> <span class="mi">31</span><span class="p">,</span>
    <span class="s">hour</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span> <span class="s">minute</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span> <span class="s">second</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s">time_zone</span> <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">Europe/Rome</span><span class="p">'</span>
<span class="p">);</span></code></pre></figure>

<p>Ops, we get:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Invalid local time for date in time zone: Europe/Rome</code></pre></figure>

<p>This happens because this time <strong>does not exist</strong>: that night, after <em>01:59:59</em> there was directly <em>03:00:00</em>, because that night, in <em>Europe/Rome</em>, we changed time from <em>CET</em> (<em>UTC+1</em>, winter) to <em>CEST</em> (<em>UTC+2</em>, summer).</p>

<p>But there’s worse, try this:</p>

<figure class="highlight"><pre><code class="language-perl" data-lang="perl"><span class="k">my</span> <span class="nv">$dt</span> <span class="o">=</span> <span class="nv">DateTime</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">(</span>
    <span class="s">year</span> <span class="o">=&gt;</span> <span class="mi">2018</span><span class="p">,</span> <span class="s">month</span> <span class="o">=&gt;</span> <span class="mi">10</span><span class="p">,</span> <span class="s">day</span> <span class="o">=&gt;</span> <span class="mi">28</span><span class="p">,</span>
    <span class="s">hour</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span> <span class="s">minute</span> <span class="o">=&gt;</span> <span class="mi">30</span><span class="p">,</span> <span class="s">second</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s">time_zone</span> <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">UTC</span><span class="p">'</span>
<span class="p">);</span>
<span class="k">my</span> <span class="nv">$dt2</span> <span class="o">=</span> <span class="nv">DateTime</span><span class="o">-&gt;</span><span class="k">new</span><span class="p">(</span>
    <span class="s">year</span> <span class="o">=&gt;</span> <span class="mi">2018</span><span class="p">,</span> <span class="s">month</span> <span class="o">=&gt;</span> <span class="mi">10</span><span class="p">,</span> <span class="s">day</span> <span class="o">=&gt;</span> <span class="mi">28</span><span class="p">,</span>
    <span class="s">hour</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span> <span class="s">minute</span> <span class="o">=&gt;</span> <span class="mi">30</span><span class="p">,</span> <span class="s">second</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="s">time_zone</span> <span class="o">=&gt;</span> <span class="p">'</span><span class="s1">UTC</span><span class="p">'</span>
<span class="p">);</span>
<span class="nv">say</span> <span class="nv">$dt</span><span class="o">-&gt;</span><span class="nv">set_time_zone</span><span class="p">('</span><span class="s1">Europe/Rome</span><span class="p">');</span>
<span class="nv">say</span> <span class="nv">$dt2</span><span class="o">-&gt;</span><span class="nv">set_time_zone</span><span class="p">('</span><span class="s1">Europe/Rome</span><span class="p">');</span></code></pre></figure>

<p>This code doesn’t even throw an error, it just shows this scary awesomeness:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">2018-10-28T02:30:00
2018-10-28T02:30:00</code></pre></figure>

<p>😱😱😱 How the f–k could this happen?!? Pretty simple, actually. When going back to winter time, it’s not a matter of time which does not exist, but a matter of <strong>time existing twice</strong>: that night, after 02:59:59 the time in <em>Europe/Rome</em> went back to <em>02:00:00</em>, thus duplicating an entire hour. So, two different <em>UTC</em> times are the same <em>Europe/Rome</em> time.</p>

<h2 id="if-you-only-need-a-date-forget-about-the-time">If you only need a date, forget about the time</h2>

<p>It’s tempting for software developers to always stick to date+time types, even when having to deal with dates (such as birth dates). This is not recommended.</p>

<p>One typical approach for a birth date is to set it as:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">1977-11-04 00:00:00 UTC   &lt;==== SINCE WE DECIDED TO STICK WITH UTC</code></pre></figure>

<p>which would even be fine for us in Europe, as it would convert to 01:00 or 02:00 when converted to local time. However this would cause a change of date when converted, say, to <em>America/Montreal</em> zone, showing the birth happening one day before.</p>

<p>Also, one might incur in a <em>non-existing time</em> problem like the one mentioned earlier, if dealing with local times instead of <em>UTC</em>. Say for instance:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">1972-05-28 00:00:00 - Europe/Rome</code></pre></figure>

<p>In 1975 (in Italy at least) the change to Daylight Saving (summer) time happened in late May instead of March, and at midnight instead of 2am. I found about this by chance, but there may be a lot of other edge cases.</p>

<p>So the solution is just: use just dates when dealing with dates. Databases have the fields, and programming languages have that support in the libraries.</p>

<h2 id="time-in-the-future">Time in the future</h2>

<p>There is a lot of ongoing political talk regarding wether Daylight Saving (summer time) should continue to exist, and it might actually be dropped in Europe in the upcoming years.</p>

<p>Time zones themselves are at risk of extinction - because they just are not so handy - even though that won’t happen soon (should it ever happen). Different countries think in different ways: China, for instance, has a single time zone even being one of the biggest countries in the Word; on the other hand, the USA has some 6 different time zones.</p>

<p>There are also complications in sight. When humans will colonize space, there will be a series of new time zones: at least one per planet (being Mars the first one, of course), if not more. Conversions are not going to be as easy as adding or subtracting hours, because planet rotation (and therefore duration of a day) is different when you change planet. However, <em>UTC</em> will probably still be used as the reference: it’s <em>Universal</em>, after all.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[This post aims at pointing out some of the difficulties that arise when writing software and dealing with dates and times, and with storing them in databases.]]></summary></entry><entry><title type="html">The Undergarden, a text adventure in Rust: 2 - Objects</title><link href="/2018/10/04/the-undergarden-text-adventure-rust-2-objects.html" rel="alternate" type="text/html" title="The Undergarden, a text adventure in Rust: 2 - Objects" /><published>2018-10-04T06:51:00+02:00</published><updated>2018-10-04T06:51:00+02:00</updated><id>/2018/10/04/the-undergarden-text-adventure-rust-2-objects</id><content type="html" xml:base="/2018/10/04/the-undergarden-text-adventure-rust-2-objects.html"><![CDATA[<p>In this second blog post I’m showing how I implemented objects - things the player can interact with -
for a text adventure written in Rust. As usual, the full code
<a href="https://github.com/lordarthas/undergarden">is available on GitHub</a>.</p>

<p>The <em>unend</em> engine defines a trait which anything which the player can interact with (objects at
present time; people, animals etc in the future) must implement:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">pub</span> <span class="k">trait</span> <span class="n">Interagible</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">get_tag</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">get_name</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span><span class="p">;</span>
    <span class="k">fn</span> <span class="nf">interact</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">_iact</span><span class="p">:</span> <span class="n">Interaction</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">InteractionRes</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>Basically, the <code class="language-plaintext highlighter-rouge">interact()</code> method accepts an interaction as a parameter, and returns a result for that.
Among the “planned for the future” things, there’s method like this:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="c1">// Future development</span>
<span class="k">fn</span> <span class="n">interact_with</span><span class="o">&lt;</span><span class="n">T</span><span class="p">:</span> <span class="n">Interagible</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">_other</span><span class="p">:</span> <span class="n">T</span><span class="p">,</span> <span class="n">_iact</span><span class="p">:</span> <span class="n">Interaction</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">InteractionRes</span><span class="p">;</span></code></pre></figure>

<p>in order to allow interaction between objects, such as <em>use ink with paper</em>. It would also allow the <em>give</em>
interaction (as in <em>give book to Alice</em>) to exist. This is, however, absolutely too advanced for current
development status. 😆</p>

<p><code class="language-plaintext highlighter-rouge">Interaction</code> and <code class="language-plaintext highlighter-rouge">InteractionRes</code> are <em>enums</em> defined as follows:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="cd">/// A complete set of interactions (these come from Thimbleweed Park, with</span>
<span class="cd">/// just a few variations to keep them 1-word long)</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">Interaction</span> <span class="p">{</span>
    <span class="n">Open</span><span class="p">,</span>
    <span class="n">Close</span><span class="p">,</span>
    <span class="n">Give</span><span class="p">,</span>  <span class="c1">// Placeholder, needs `interact_with()` to exist</span>
    <span class="n">Take</span><span class="p">,</span>
    <span class="n">Look</span><span class="p">,</span>
    <span class="n">Talk</span><span class="p">,</span>
    <span class="n">Push</span><span class="p">,</span>
    <span class="n">Pull</span><span class="p">,</span>
    <span class="n">Use</span><span class="p">,</span>
<span class="p">}</span>

<span class="cd">/// Possible results for an interaction. Plan for this is to be a complete</span>
<span class="cd">/// set of possible results. For now there are one two basic (but useful) ones.</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">InteractionRes</span> <span class="p">{</span>
    <span class="nf">Info</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>
    <span class="nf">GotoSection</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>
<span class="p">}</span></code></pre></figure>

<p>The first object I wanted to implement was a very simple one, that would just
show a text message for each interaction: a description if it’s <em>looked at</em>, a
nice “no, thank you” message if the player attempted to <em>take</em> it, etc. So, leaving
aside <em>trait</em> instantiation and other boilerplate, all the action happens in this code:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">impl</span> <span class="n">Interagible</span> <span class="k">for</span> <span class="n">InfoObject</span> <span class="p">{</span>
    <span class="c1">// ... cut ...</span>
    <span class="k">fn</span> <span class="nf">interact</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">iact</span><span class="p">:</span> <span class="n">Interaction</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">InteractionRes</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span><span class="py">.av_interactions</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">iact</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">Some</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="n">s</span><span class="nf">.to_string</span><span class="p">()),</span>
            <span class="nb">None</span> <span class="k">=&gt;</span> <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="s">"That won't work"</span><span class="nf">.to_string</span><span class="p">())</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>The implementation of <code class="language-plaintext highlighter-rouge">Interagible</code>’s <code class="language-plaintext highlighter-rouge">interact()</code> method is just a few lines: the code looks into
an <code class="language-plaintext highlighter-rouge">HashMap</code> (contained in the <code class="language-plaintext highlighter-rouge">InfoObject</code> <em>struct</em>) to see if there is a result for the interaction
requested (we’ll see in a while how possible results are added when creating a new <code class="language-plaintext highlighter-rouge">InfoObject</code>):
if a match is found, we return an <code class="language-plaintext highlighter-rouge">Info</code> value of the <code class="language-plaintext highlighter-rouge">InteractionRes</code> <em>enum</em>, which contains a
simple string to show to the player; with no match, we return the same type, but with a default string.</p>

<p>Now to the main loop code:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">let</span> <span class="n">interaction_regex</span> <span class="o">=</span> <span class="nn">Regex</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">r"^(\w+)\s+(\w+)$"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">loop</span> <span class="p">{</span>
    <span class="c1">// ... cut ...</span>
    <span class="k">match</span> <span class="n">command</span><span class="nf">.as_str</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ... cut ...</span>
        <span class="n">irx</span> <span class="k">if</span> <span class="n">interaction_regex</span><span class="nf">.is_match</span><span class="p">(</span><span class="n">irx</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// ... cut: get target `obj` and `interaction` from captures and look them up ...</span>
            <span class="k">match</span> <span class="n">target</span> <span class="p">{</span>
                <span class="nn">UnendObject</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">match</span> <span class="n">obj</span><span class="nf">.interact</span><span class="p">(</span><span class="n">interaction</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="nf">.write_line</span><span class="p">(</span><span class="o">&amp;</span><span class="n">s</span><span class="p">),</span>
                    <span class="n">_</span> <span class="k">=&gt;</span> <span class="nd">panic!</span><span class="p">(</span><span class="s">"InfoObject shouldn't interact this way."</span><span class="p">),</span>
                <span class="p">},</span>
                <span class="c1">// ... cut ...</span>
            <span class="p">};</span>
        <span class="p">}</span>
    <span class="p">};</span>
<span class="p">};</span></code></pre></figure>

<p>The <em>parser</em> is just a regular expression at the moment. Once it matches the type of the target
(the <em>enum</em> in which it’s contained was previously looked up via the object’s <em>tag</em>), the main loop
invokes its <code class="language-plaintext highlighter-rouge">interact()</code> method, passing the (previously looked up) <code class="language-plaintext highlighter-rouge">Interaction</code>.</p>

<p>We use <em>match</em> in order to <em>unwrap</em> the <code class="language-plaintext highlighter-rouge">InteractionRes</code> <em>enum</em>: if the value is
<code class="language-plaintext highlighter-rouge">Info</code> we show the contained string to the player; other result values are not supported for this
specific kind of object, so a <code class="language-plaintext highlighter-rouge">panic!()</code> is probably the best option (=forces somebody to debug
the thing).</p>

<p>Now that we have all of this setup, it is possible to modify the code - shown in previous article -
which was used to create the <em>kitchen</em>, so that is becomes:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">let</span> <span class="n">kitchen</span> <span class="o">=</span> <span class="nn">UnendSection</span><span class="p">::</span><span class="nf">Basic</span><span class="p">(</span><span class="nn">BasicSection</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
    <span class="nd">s!</span><span class="p">(</span><span class="s">"kitchen"</span><span class="p">),</span>
    <span class="nd">s!</span><span class="p">(</span><span class="s">"The grand kitchen"</span><span class="p">),</span>
    <span class="nd">s!</span><span class="p">(</span><span class="s">"You are at the center of the kitchen and dining room. The only exit is south. There's a *book* on the table."</span><span class="p">),</span>
    <span class="nd">hashmap!</span><span class="p">{</span>
        <span class="nn">ExitDir</span><span class="p">::</span><span class="n">South</span> <span class="k">=&gt;</span> <span class="nn">Exit</span><span class="p">::</span><span class="nf">Visitable</span><span class="p">(</span><span class="nd">s!</span><span class="p">(</span><span class="s">"hallway"</span><span class="p">)),</span>
        <span class="nn">ExitDir</span><span class="p">::</span><span class="n">East</span> <span class="k">=&gt;</span> <span class="nn">Exit</span><span class="p">::</span><span class="nf">Closed</span><span class="p">(</span><span class="nd">s!</span><span class="p">(</span><span class="s">"You can't exit through the window."</span><span class="p">)),</span>
    <span class="p">},</span>
    <span class="nd">hashmap!</span><span class="p">{</span><span class="nd">s!</span><span class="p">(</span><span class="s">"book"</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nn">UnendObject</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="nn">InfoObject</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
        <span class="nd">s!</span><span class="p">(</span><span class="s">"book"</span><span class="p">),</span>
        <span class="nd">s!</span><span class="p">(</span><span class="s">"Pink Book"</span><span class="p">),</span>
        <span class="nd">hashmap!</span><span class="p">{</span>
            <span class="nn">Interaction</span><span class="p">::</span><span class="n">Look</span> <span class="k">=&gt;</span>  <span class="nd">s!</span><span class="p">(</span><span class="s">"It is a strange pink book with a black sheep on the cover."</span><span class="p">),</span>
            <span class="nn">Interaction</span><span class="p">::</span><span class="n">Take</span> <span class="k">=&gt;</span>  <span class="nd">s!</span><span class="p">(</span><span class="s">"I don't need this book."</span><span class="p">),</span>
            <span class="nn">Interaction</span><span class="p">::</span><span class="n">Use</span>  <span class="k">=&gt;</span>  <span class="nd">s!</span><span class="p">(</span><span class="s">"Hmmm, I prefer to watch movies rather than read."</span><span class="p">),</span>
        <span class="p">},</span>
    <span class="p">))},</span>
<span class="p">));</span></code></pre></figure>

<p>The code should be self-explanatory enough. The only thing worth noting is that <code class="language-plaintext highlighter-rouge">InfoObject</code> is wrapped
into an <code class="language-plaintext highlighter-rouge">UnendObject::Info</code> <em>enum</em> value:  as we did for sections, this allows to have different object
(<code class="language-plaintext highlighter-rouge">Interagible</code>) types inside the same containing <code class="language-plaintext highlighter-rouge">HashMap</code>.</p>

<p>So, let’s see if this works somehow:</p>

<div style="width:100%;text-align:center;">
    <video width="80%" controls="" autoplay="" loop="">
        <source src="/assets/undergarden/objects.mp4" />
    </video>
    <br /><br />
</div>

<p>And now… a slightly different implementation: a <code class="language-plaintext highlighter-rouge">PortalObject</code>, which we define as an object which
transports the player to another section of the game when <em>used</em>.</p>

<p>Skipping the <em>struct</em> constructor and other service code (which you can find the in the repository),
the interesting part comes (as before) with the <code class="language-plaintext highlighter-rouge">interact()</code> method:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">fn</span> <span class="nf">interact</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">iact</span><span class="p">:</span> <span class="n">Interaction</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">InteractionRes</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">iact</span> <span class="p">{</span>
        <span class="nn">Interaction</span><span class="p">::</span><span class="n">Look</span> <span class="k">=&gt;</span> <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="k">self</span><span class="py">.dsc</span><span class="nf">.clone</span><span class="p">()),</span>
        <span class="nn">Interaction</span><span class="p">::</span><span class="n">Use</span> <span class="k">=&gt;</span> <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">GotoSection</span><span class="p">(</span><span class="k">self</span><span class="py">.destination</span><span class="nf">.clone</span><span class="p">()),</span>
        <span class="n">_</span> <span class="k">=&gt;</span> <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="s">"That won't work"</span><span class="nf">.to_string</span><span class="p">()),</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Only two interactions are supported by <code class="language-plaintext highlighter-rouge">PortalObject</code>: the player can only <em>look</em> at it or <em>use</em> it.
Everything else shows a default string. Compared to <code class="language-plaintext highlighter-rouge">InfoObject</code>, the difference here lies in how
the <code class="language-plaintext highlighter-rouge">Interaction::Use</code> is handled, that is by returning an <code class="language-plaintext highlighter-rouge">InteractionRes::GotoSection</code> containing
a string with the tag of the destination section. The main loop code will therefore know how
to use that tag:</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">loop</span> <span class="p">{</span>
    <span class="c1">// ... cut ...</span>
    <span class="k">match</span> <span class="n">command</span><span class="nf">.as_str</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ... cut ...</span>
        <span class="n">irx</span> <span class="k">if</span> <span class="n">interaction_regex</span><span class="nf">.is_match</span><span class="p">(</span><span class="n">irx</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// ... cut: get target `obj` and `interaction` from captures and look them up ...</span>
            <span class="k">match</span> <span class="n">target</span> <span class="p">{</span>
                <span class="c1">// ... cut ...</span>
                <span class="nn">UnendObject</span><span class="p">::</span><span class="nf">Portal</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">match</span> <span class="n">obj</span><span class="nf">.interact</span><span class="p">(</span><span class="n">interaction</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">Info</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="k">self</span><span class="nf">.write_line</span><span class="p">(</span><span class="o">&amp;</span><span class="n">s</span><span class="p">),</span>
                    <span class="nn">InteractionRes</span><span class="p">::</span><span class="nf">GotoSection</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                        <span class="k">self</span><span class="py">.current_section_tag</span> <span class="o">=</span> <span class="n">s</span><span class="p">;</span>
                        <span class="k">continue</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="p">},</span>
            <span class="p">};</span>
        <span class="p">}</span>
    <span class="p">};</span>
<span class="p">};</span></code></pre></figure>

<p>Dead easy: either show the description or change the section the player is in and skip to the
next loop cycle. Now we only need to actually create the portal object and put it inside a section/room.</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">let</span> <span class="n">hallway</span> <span class="o">=</span> <span class="nn">UnendSection</span><span class="p">::</span><span class="nf">Basic</span><span class="p">(</span><span class="nn">BasicSection</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
    <span class="nd">s!</span><span class="p">(</span><span class="s">"hallway"</span><span class="p">),</span>
    <span class="c1">// ... cut ...</span>
    <span class="nd">hashmap!</span><span class="p">{</span><span class="nd">s!</span><span class="p">(</span><span class="s">"fireplace"</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nn">UnendObject</span><span class="p">::</span><span class="nf">Portal</span><span class="p">(</span><span class="nn">PortalObject</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
        <span class="nd">s!</span><span class="p">(</span><span class="s">"fireplace"</span><span class="p">),</span>
        <span class="nd">s!</span><span class="p">(</span><span class="s">"A strange fireplace"</span><span class="p">),</span>
        <span class="nd">s!</span><span class="p">(</span><span class="s">"This fireplace glows like it's enchanted."</span><span class="p">),</span>
        <span class="nd">s!</span><span class="p">(</span><span class="s">"secretroom"</span><span class="p">),</span>
    <span class="p">))},</span>
<span class="p">));</span></code></pre></figure>

<p>A fireplace which happens to be a portal… how interesting! The <em>secretroom</em>, defined elsewhere,
is a section for which there is no access from any of the other sections; the <em>secretroom</em> itself,
however, has an exit which brings the player back to the <em>hallway</em>.</p>

<div style="width:100%;text-align:center;">
    <video width="80%" controls="" autoplay="" loop="">
        <source src="/assets/undergarden/portalobject.mp4" />
    </video>
    <br /><br />
</div>

<p>That’s all! See you next time with something else. I’ll likely try to implement
inventory, and therefore objects which can be <em>taken</em>. This poses some challenges: as of now,
everything in the game has an immutable state; using mutable states, besides the programming aspects
(which may be non-trivial for a Rust newbie like me), will also likely lead to some changes in how
<code class="language-plaintext highlighter-rouge">Visitable</code>s are structures: if an object is <em>taken</em>, it must disappear from the section, and the
section description (which is now a monolithic text block) should change. We’ll see…</p>]]></content><author><name></name></author><summary type="html"><![CDATA[In this second blog post I’m showing how I implemented objects - things the player can interact with - for a text adventure written in Rust. As usual, the full code is available on GitHub.]]></summary></entry></feed>