Local time for mutt email display

I use mutt as my email client. Something that has recently been bugging me is that when reading a message it displays the original Date: header with the sender's timezone. Since I work with people in several different zones I am constantly having to do timezone math when looking at these. So I decided to fix that with a bit of python.

One of mutt's features is that you can feed every email you view through a filter by using the display_filter setting. So I created a small python app using the email module to parse the message, grab the original date and add a new header named X-Date: that has my local time in it. It looks like this:

Changes to the Webpage and Blog

As you may have noticed my webpages have changed a bit today. After about 6 years I decided it was time to revamp things a bit. The old site was all written in plain html with an attempt at reference link usage. I decided to rewrite in Restructured Text using Python and some kind of template language. I decided to do some searching before writing my own and found the Pelican project which fit what I wanted to do perfectly.

Nice %changelog entries

When updating a rpm package it is nice to include a summary of the changes made since the last time. anaconda does this with a nifty script written by dcantrell called makebumpver which also enforces some RHEL rules and handles changing the version. I only needed the changelog part of this so I modified the script a bit to remove the extras:

#!/usr/bin/python
#
# git-changelog - Output a rpm changelog
#
# Copyright (C) 2009-2010  Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: David Cantrell <dcantrell@redhat.com>
# Author: Brian C. Lane <bcl@redhat.com>

import os
import re
import subprocess
import sys
import textwrap
from optparse import OptionParser

class ChangeLog:
    def __init__(self, tag):
        self.tag = tag
        self.ignore = None

    def _getCommitDetail(self, commit, field):
        proc = subprocess.Popen(['git', 'log', '-1',
                                 "--pretty=format:%s" % field, commit],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE).communicate()

        ret = proc[0].strip('\n').split('\n')

        if len(ret) == 1 and ret[0].find('@') != -1:
            ret = ret[0].split('@')[0]
        elif len(ret) == 1:
            ret = ret[0]
        else:
            ret = filter(lambda x: x != '', ret)

        return ret

    def getLog(self):
        range = "%s.." % (self.tag)
        proc = subprocess.Popen(['git', 'log', '--pretty=oneline', range],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE).communicate()
        lines = filter(lambda x: x.find('l10n: ') != 41 and \
                                 x.find('Merge commit') != 41 and \
                                 x.find('Merge branch') != 41,
                       proc[0].strip('\n').split('\n'))

        if self.ignore and self.ignore != '':
            for commit in self.ignore.split(','):
                lines = filter(lambda x: not x.startswith(commit), lines)

        log = []
        for line in lines:
            fields = line.split(' ')
            commit = fields[0]

            summary = self._getCommitDetail(commit, "%s")
            long = self._getCommitDetail(commit, "%b")
            author = self._getCommitDetail(commit, "%aE")

            log.append(("%s (%s)" % (summary.strip(), author)))

        return log

    def formatLog(self):
        s = ""
        for msg in self.getLog():
            sublines = textwrap.wrap(msg, 77)
            s = s + "- %s\n" % sublines[0]

            if len(sublines) > 1:
                for subline in sublines[1:]:
                    s = s + "  %s\n" % subline

        return s

def main():
    parser = OptionParser()
    parser.add_option("-t", "--tag", dest="tag",
                      help="Last tag, changelog is commits after this tag")
    (options, args) = parser.parse_args()

    cl = ChangeLog(options.tag)
    print cl.formatLog()

if __name__ == "__main__":
    main()

I've copied my version of this here for easy downloading. Run it like this - git-changelog -t livecd-tools-15.0 and the output is:

AIS feed is up again

A few weeks back my ancient Compaq laptop stopped booting (two LOUD beeps, no display, no drive noises). This system has been used in the garage to act as a serial to WiFi bridge for my AIS receiver, and to log temperatures for the garage and freezer temps you see at digitemp.com. The AIS data feeds the Live AIS view of Puget Sound.

The laptop was exiled to the garage after its power connector broke for the 3rd time and I had to hard-wire it by soldering it directly to the motherboard. Its battery hadn't been holding a charge all that well either. I installed Fedora 11 or 12 on it, choosing to encrypt the whole drive. This ended up being a bit of a mistake, after power outages I would have to try to remember the passphrase, and after it finally failed I pulled the drive to read it with a USB to IDE adapter and was stumped until I looked at my password list and realized I had written it down.

Using RAID to Escape Disaster

Failed hard drives are inevitable. Especially when the drive in question was manufactured on November 27, 2001. You know the time has come to replace it when your log files start filling up with errors like this:

Oct 28 03:53:05 cat kernel:         res 51/40:00:fc:33:4e/00:00:00:00:00/e0 Emask 0x9 (media error)
Oct 29 16:06:46 cat smartd[24427]: Device: /dev/sdb [SAT], FAILED SMART self-check. BACK UP DATA NOW!

Failure is inescapable. Everything fails eventually, computers, people, electronics. This is the only constant in life. It is only a question of when. In my case this 40GB drive had served me well in multiple computers and as part of a RAID5 array for my Linux Journal article. In its final installation it was part of a 2 disk RAID1 in cat, my webserver. cat runs Fedora 13 and a minimal set of software for serving up my webpages, including this blog. cat was built using spare parts, its job isn't hard and space requirements aren't large. Good logging and reporting are important, they help you anticipate the impending doom. On my systems I am running the smartd daemon to monitor drive health as well as epylog to parse all my logfiles and email me nightly results.

Kindle on Linux using Wine

I have a few books I've bought for reading using the Kindle app on my iPhone. I'd like to be able to read them on my Fedora Linux based MacBook as well, but Amazon hasn't released a version for Linux yet, which is ironic given that the Kindle's OS is Linux based. All is not lost, there is an excellent project called Wine that enables you to run many Windows applications on your Linux system. My experience with it has been mixed, with some applications working fine and others not making it past the installer stage.

tidy_html plugin for rawdog

Requires python-tiny package on Fedora. Cleans up the HTML, preventing broken elements from spilling over into adjacent postings. Code was lifted from feedparser.py and dropped into a plugin for rawdog since I couldn't find an easy way to get mx.Tiny installed.

# rawdog plugin to tidy up html output using python-tidy module
# Brian C. Lane <bcl@brianlane.com>
#
from tidy import parseString
import rawdoglib.plugins, re

def tidy_html(config, box, baseurl, inline):
    data = box.value
    utf8 = type(data) == type(u'')
    if utf8:
        data = data.encode('utf-8')

    data = str(parseString(data, output_xhtml=1, numeric_entities=1, wrap=0))

    if utf8:
        data = unicode(data, 'utf-8')
    if data.count('<body'):
        data = data.split('<body', 1)[1]
        if data.count('>'):
            data = data.split('>', 1)[1]
    if data.count('</body'):
        data = data.split('</body', 1)[0]

    box.value = data.strip()

rawdoglib.plugins.attach_hook("clean_html", tidy_html)

Sharing Music on the LAN

No, not on the lam. On the LAN. I have a fairly large collection of music. Years ago I used iTunes to rip the CD's to AAC format. Recently I've been using Amazon.com for more of my downloads so I have converted the library to high quality VBR mp3 files instead. I like being able to play the music no matter which system I am using, and the iTunes sharing works well for that. I wanted to centralize storage of the files, and setup sharing from one of my Fedora 13 systems so I went in search of a solution. There are several out there, but I think I hit on the simplest of them.