Search Results: "costela"

1 July 2016

Leo 'costela' Antunes: Yet another letsencrypt (ACME) client

Well, I apparently joined the hordes of people writing ACME (the Protocol behind Let s Encrypt) clients. Like the fairy tale Goldilocks, I couldn t find a client in the right spot between minimalistic and full-featured for my needs: acme-tiny was too bare-bones; the official letsencrypt client (now called certbot) too huge; and simp_le came very close, but it s support for pluggable certificate formats made it just a bit too big for me. So, wile (named after another famous user of ACME products) was born. Maybe it will fill someone else s very subjective needs.

28 March 2015

Leo 'costela' Antunes: Go linear programming library

After a way too long hiatus, I finally got back to working on some side-projects and wrote a small go library for solving linear programming problems. Say hi to golp! Since I m no LP expert, golp makes use of GLPK to do the actual weight-lifting. Unfortunately, GLPK currently isn t reentrant, so it can t really be used with go s great goroutines. Still, works well enough to be used for a next little project. Now, if only I could get back to working on Debian

30 April 2013

Leo 'costela' Antunes: Deprecation of $(ARCH)-geomirror.debian.net

After the announcement of http.debian.net some months back I imagined the few people using my older $(ARCH)-geomirror.debian.net DNS redirector would relatively quickly jump ship to the newer solution, it being superior in basically every aspect. However it seems I had highly underrated the usage of my little hack. According to the server logs there still are a sensible number of genuine-looking queries being made (around 600 unique IPs in the last 3 days), and even if a sizable fraction of them are being generated by bots, this still leaves a pretty big number of potential users out there. So I guess it s only common courtesy to let these potential users know in a slightly more public place that I plan on pulling the plug till the end of the year. If you re one of the people making use of the service, please migrate to http.debian.net. Note however that this has nothing to do with cdn.debian.net, besides being based on a similar idea.

6 December 2012

Leo 'costela' Antunes: Silly Wal(l Cloc)k 2.0

So I finally got the first laser-cut version of the Silly Wal(l Cloc)k done: The back plate was cut from a 1mm plexiglass sheet using this SVG. The black parts were spray painted with matte black and inserted back into the hollowed out sections (I should have made pictures of the pieces before assembling; too lazy to do it now). This proved out to be somewhat problematic, since keeping the small pieces (the chin and the white part of the suitcase handle) in place isn t really easy without gluing everything together, which I wanted to avoid because I m not sure what effect the glue might have on plexiglass in the long term. Another problem was the fact that the plexiglass sheet is white, but slightly translucent. Since the back piece of the clock is black, this caused the white parts to look dirty . As a workaround for both problems I ended up using a thin white cardboard sheet as additional background, where the small hollowed out pieces could be affixed and the translucency of the plexiglass could be hidden. On the other hand, the 1mm thickness for the plexiglass turned out to be perfect for the clock hands (or feet). I think it would still have worked with 2mm, but that might be too heavy for the puny clock torque and they might start snagging on each other. Careful eyes will also note that I made a stupid mistake when writing down the dimensions for the back plate and it ended up being slightly smaller than the available space. Not my brightest moment, but it worked out alright with the additional cardboard background. The next iteration will probably be made with a different two-color plexiglass sheet that can be engraved. Let s see how that works out. Related: Silly Wal(l Cloc)k

28 October 2012

Leo 'costela' Antunes: Silly Wal(l Cloc)k

Inspired by this clock, which made the rounds a few weeks back, and fueled by a bet to see who could make it first (hi Fabs!), ladies and gentleman, I m honored to introduce to you, the Silly Wal(l Cloc)k:

The finished clock This is a prototype version, done without any fancy precision tools and whatnot, so you can easily see that it s not the most high quality product you ll ever get your hands on. If anyone wants to reproduce this at home, here s how it was done: First get a hold of some cheap wall clock like this one: The original cheap clock Some things to think about when buying this beauty *cough*:

Disassembling this specific cheap model was pretty straightforward and didn t take more than a minute to get to juicy parts:

Clock parts: actual clock, cover tack and hands As a base image I used this and traced it to produce this SVG with the legs in different layers and a marker for the holes. For this prototype, it would probably have been ok to use the original photo directly, but having the vector version ready means it s easy to use a 3d printer and laser-cutter to make the next version of the hands and face, which will definitely look and behave a lot better. Next step is printing the three layers body and two legs. Since this specific clock is wider than an A4 sheet, I had to use some white cardboard as background (the clock s own box hurray for resourcefulness) and painstakingly cut the printed body s outline, to avoid the slight tone difference between cardboard and paper. If you use the provided SVG and a laser-cutter, you d hopefully avoid this sort of issue. I didn t have any black cardboard lying around, so I had to print the legs, cut the outline again and strengthen them with some thin cardboard from tea boxes (taken directly from the trash hurray for resourcefulness again). Again something that 3d printing will hopefully side-step. It s important to use light materials for the legs, since most clock mechanisms don t have enough force to move heavy hands, which would cause the clock to quickly loose precision. Depending on the width of the clock s axis, a paper hole puncher might be the ideal tool to make holes in the legs. Since the prototype was largely made with paper, there was no point in going for precision when making the holes, as there s no way to fit the legs tightly to the clock s axis. For this, the original hands were cut in length and used as a base on which to super-glue the legs, so the original holes could be used. After these few easy steps, it s only a matter of reassembling the clock and voil !

Silly Wal(l Clock)k

19 June 2011

Leo 'costela' Antunes: Importing an Outlook PST into IMAP

Well, every once in a while we re forced to do something that isn t particularly interesting or pleasant. Last week it happened again: I had to import a few pretty big PSTs (most 2Gig, one 10Gig, with about 100.000 emails) into our dovecot IMAP. Doing this with Outlook itself was out of the question: it took way too long, even on the local network (many hours for a 1G file) and was prone to hanging and crashes, which were obviously a pain to debug and start over.
Thunderbird was unfortunately not much better, since at least in our tests it didn t import the read status of the emails (they were all marked as unread) and also wasn t particularly good at handling folders with strange names, containing . , / or some more obscure characters. We had used it before for smaller files, where manually dealing with the problems was acceptable, but this time it required something a bit more elaborate, if we were to keep our sanity. Enter libpst. It includes the handy readpst utility which dumps all emails in usable formats in a directory tree, one directory per folder. Unfortunately the Debian version is somewhat outdated and doesn t support the newer Outlook formats, so I did some packaging and even a little bit of patching. It seems Thunderbird also uses this library, which would explain why it didn t handle the Read-Status (haven t confirmed this though; just read it somewhere). The last step was this not-so-little script, which uses the dumped directories from readpst and imports them in IMAP. It would have probably been a bit more elegant to use libpst directly, but I unfortunately didn t have the time to mess around with that. I did have to mess around a lot with encodings though, ergo the unholy chaos with unicode()s and str.encode()s thrown around like rice at a wedding (I could never really wrap my head around charset problems; the subject boggles my mind to this very day).
#!/usr/bin/env python2.7
#-*- encoding: utf-8 -*-
 
import os, sys, time
import re
 
from argparse import ArgumentParser
from getpass import getpass
 
import imaplib
from email.header import decode_header
from mailbox import mbox
 
from twisted.mail import imap4 # for their imap4-utf7 implementation
 
parser = ArgumentParser(description="""Recursively import mbox files in a directory 
                                        structure to an IMAP server.\n
                                        The expected structure is that generated by 
                                        'readpst -r'.""")
parser.add_argument('-s', dest='imap_server', default='localhost', help='IMAP server to import emails to')
parser.add_argument('-u', dest='imap_user', required=True, help='user for logging in to IMAP')
parser.add_argument('-p', dest='imap_passwd', help="will be prompted for if not provided")
parser.add_argument('-c', dest='charset', default='utf8', help='charset in which the folders are stored (for versions older than 2003)')
parser.add_argument('-f', dest='force', action='store_true', help='import mail even if we think it might be a duplicate')
parser.add_argument('-m', dest='mappings', help='a JSON file with mappings between folder names and mailbox names (no slashes or dots)')
parser.add_argument('folder', nargs='+', help="the base folders to import")
 
args = parser.parse_args()
 
if not args.imap_passwd:
    args.imap_passwd = getpass()
 
 
if args.mappings:
    import json
    folderToMailbox = json.load(open(args.mappings,'r'))
else:
    folderToMailbox =   
 
 
 
def mailboxFromPath(path):
    paths = []
    for p in path.split(os.path.sep):
        p = folderToMailbox.get(p, p)
 
        # only other invalid char besides '/', which can't be created by readpst anyway
        p = p.replace('.','')
 
        paths.append(p)
 
    return '.'.join(paths)
 
def imapFlagsFromMbox(flags):
    # libpst only sets R and O
    f = []
    if 'R' in flags or 'O' in flags:
        f.append(r'\Seen')
    if 'D' in flags:
        f.append(r'\Deleted')
    if 'A' in flags:
        f.append(r'\Answered')
    if 'F' in flags:
        f.append(r'\Flagged')
    return '('+' '.join(f)+')'
 
def utf7encode(s):
    return imap4.encoder(s)[0]
 
def headerToUnicode(s):
    h = decode_header(s)[0]
    try:
        if h[1]: # charset != None
            try:
                return unicode(*h)
            except LookupError:
                return unicode(h[0],'utf8','replace')
        else:
            return unicode(h[0], 'utf8')
    except UnicodeDecodeError:
        try:
            return unicode(h[0], 'cp1252') # the usual culprits for malformed headers
        except UnicodeDecodeError:
            pass
 
        try:
            return unicode(h[0], 'latin1') # the usual culprits for malformed headers
        except UnicodeDecodeError:
            pass
 
        return unicode(h[0], 'ascii', 'ignore') # give up...
 
def main():
    imap = imaplib.IMAP4_SSL(args.imap_server)
    imap.login(args.imap_user, args.imap_passwd)
 
    imap.select()
    for base in args.folder:
        print "importing folder "+base
        for root, dirs, files in os.walk(base):
            if 'mbox' in files:
                folder = unicode(os.path.relpath(root, base), args.charset)
                mailbox = mailboxFromPath(folder)
                print u'importing mbox in  0  to  1 '.format(folder, mailbox)
                mailbox_encoded = utf7encode(mailbox)
                m = mbox(os.path.join(root, 'mbox'), create=False)
                for msg in m:
                    if imap.select(mailbox_encoded)[0] != 'OK':
                        print "creating mailbox "+mailbox
                        r = imap.create(mailbox_encoded)
                        if r[0] != 'OK':
                            sys.stderr.write("Could not create mailbox: "+str(r))
                            continue
                        imap.subscribe(mailbox_encoded)
                        imap.select(mailbox_encoded)
 
 
                    # skip possibly duplicated msgs
                    query = 'FROM " 0 " SUBJECT " 1 "'.format(
                                utf7encode(headerToUnicode(msg['from']).replace('"','')),
                                utf7encode(headerToUnicode(msg['subject']).replace('"',r'\"'))
                            )
                    if msg.has_key('date'):
                        query += ' HEADER DATE " 0 "'.format(utf7encode(msg['date']))
                    if msg.has_key('message-id') and msg['message-id']:
                        query += ' HEADER MESSAGE-ID " 0 "'.format(utf7encode(msg['message-id']))
 
                    r = imap.search(None, '( 0 )'.format(query))
                    if r[1][0] and not args.force:
                        print "skipping "+mailbox+": '"+headerToUnicode(msg['subject'])[:20]+"' (mid: "+str(msg['message-id'])+")"
                        continue
 
                    r = imap.append(mailbox_encoded, '', imaplib.Time2Internaldate(time.time()), str(msg))
                    if r[0] != 'OK':
                        sys.stderr.write("failed to import  0  ( 1 ):  2 ".format(msg['message-id'], msg['date'], r[1]))
                        continue
                    num = re.sub(r'.*APPENDUID \d+ (\d+).*', r'\1', r[1][0])
                    r = imap.uid('STORE', str(num), "FLAGS", imapFlagsFromMbox(msg.get_flags()))
 
                    if r[0] != 'OK':
                        sys.stderr.write("failed to set flags for msg  0  in  1 ".format(num, mailbox))
 
    imap.logout()
 
if __name__ == '__main__':
    main()
Another useful automation is using the mappings file, which looks something like
 
"Deleted items": "Trash",
"Sent items": "Sent"
 
to automatically import in the right destinations, in case you have a standardized structure.

20 January 2011

Leo 'costela' Antunes: I really shouldn t, but

I'm going to FOSDEM, the Free and Open Source Software Developers' European Meeting See you there!

1 December 2010

Leo 'costela' Antunes: Screenshots on unrooted Android

A few weeks ago I noticed an image on my Froyo phone which appeared to be a screenshot. This came as a surprise, since I don t have any screenshot applications installed and am generally economic when it comes to the amount of fluff on my phone. I did a few searches and there doesn t seem to be any visible mention of this feature out there, only mentions of how to do it with the SDK or with specific apps. Since today I finally managed to discover how to actually do it again (holding the Back button and pressing Home), I took these screenshots using different launchers to be sure it wasn t some feature of ADW.Laucher. I still haven t beat my laziness to actually reset to factory settings and make sure it s not some other random app (which would be a bit unsettling, actually) and it could also be a Samsung specific feature, implemented as one of those pesky unremovable apps. Can anybody out there confirm this (not particularly useful) feature? Did I miss it being published somewhere? [UPDATE]: I found a couple of hidden apps that probably have something to do with this (through Settings Applications Manage applications All). The first one is an obvious suspect, but since it isn t named after Samsung (which admittedly doesn t mean much) I figured it might be activated by the second one, which in my overly-creative and not-really-investigative mind could be a sort of hotkey-daemon. [UPDATE 2: well, that's ignoring the obvious fact that SEC stands for "Samsung Electronics Corporation". D'oh!]
I don t think the feature is important or interesting enough for me to investigate much further, but still I thought I might leave some pointers if people want to get this on their ROMs.

26 November 2010

Leo 'costela' Antunes: My kingdom for a VGA cable

So you have two geeks in a university room after a relatively late and (at least for one of them) unproductive learning session. It s just natural that they decide to kick back and watch some mind-numbingly stupid geek series, which in this case happened to be Stargate SG-1 (so absurdly shitty its actually very entertaining).
The first lazy geek instinct is to just watch it on the laptop that has the file, which with its 11 display and shitty speakers doesn t turn out to be a great idea. The next try involves the other laptop, but a 13 screen isn t that big of an improvement.
Since the room our intrepid heroes are in happens to have a pretty decent built-in projector and a couple of small but still a lot better than a laptop s Bose speakers, the obvious next step would be using it. The only problem is the lack of a VGA cable. Inspired by the brief sight of MacGyver on the 11 screen, one particularly enterprising geek comes up with the challenge of making a VGA cable out of the only material available at the time: one horribly yellow cat5 ethernet cable.
Being the helpful little extra-dimensional entity that it is, the internet happily provided all the needed information and after some slight problems trying to appropriately deprive the cat5 of its connectors (no scissors and no blades of any kind in sight) and some annoying and manual sticking-cable-to-socket action what did you mother tell you about sticking things in sockets? our reluctant hackers get it right: it's ALIIIIIVEEEE!!! The final solution looked like this: nothing like cable salad for dinner And if you re wondering where those white wires came from, one final touch of ber-hackerdom: notepads have never been more useful This might seem like overkill, but after a nice nice 4 hour movies and series marathon, we can safely say it was totally worth it (but no, we didn t stand 4 hours of Stargate; even geeks have their limits). Just in case the Instructables page gets hosed at some point, here s the invaluable connection diagram, originally scraped off of a since dead Geocities page.

22 October 2010

Sandro Tosi: URL shortcuts in Chromium

If you like the Firefox feature to create special bookmarks that are actual templates for specific urls, well, here's how I got the same for Chromium.

That's actually how WordReference is suggesting, so it's defining new search engines: right click on the URL bar, Edit seach engine, and then Add; now you can define what you like, f.e. :


and so now typing "pts package<package>" in the URL bar you'll get to the package PTS page.</package>

That's one of the core points I had to get fixed in order to migrate from FF to chromium, one step closer then.

12 October 2010

Leo 'costela' Antunes: Calendar Server Woes

Another one of these technical reminder posts.
Calendar Server needs extented attributes on the filesystem it uses to store calendars. This fact has been very helpfully documented by the Debian maintainer, but since there are apparently still a couple consistency checks missing in the server, I ve spent the last couple of hours trying to find out why Sunbird (actually Lightning) would simply loop like crazy between PROPFINDs and OPTIONs, going nowhere.
It turns out the first time I started caldavd, the underlying filesystem didn t have xattr support, which meant some files and/or directories in the spool directory tree didn t get the needed attributes and after a restart the server would just gladly run as if nothing were amiss, driving the client slightly bonkers. I honestly wish I had the time right now to debug it a bit better and provide some patches upstream, but for now this post will have to suffice as help for any other poor soul out there facing this issue.

26 September 2010

Leo 'costela' Antunes: Slight esmtp weirdness

Just so I don t forget it for a second time: esmtp can be pretty obscure when it comes to error messages.
When it encounters an .authenticate/ca.pem file with public permissions it connects to the server and only after seeing the extensions list does it tell you StartTLS extension not supported by MTA . Not exactly straightforward.

1 July 2010

Leo 'costela' Antunes: Quantum Bogosort

Sometimes Wikipedia shows it even has a somewhat humorous side. My finding it funny may be a product of late night learning sessions and semi-random clicking-sprees, but still, worthy of a chuckle for those with the right (wrong?) inclinations.
Quantum computing could be used to effectively implement a bogosort algorithm with a time complexity of O(n). It uses true quantum randomness to randomly permute the list. By the many-worlds interpretation of quantum physics, the quantum randomization spawns 2^N (where N is the number of random bits) universes and one of these will be such that this single shuffle had produced the list in sorted order. The list is then tested for sortedness (requiring n-1 comparisons); should it be out of order, the computer destroys the universe implementation of this step being left as an exercise for the reader. The only observers will then be in the surviving universes and will see that the randomization worked the first time and that the list is in sorted order. Note, however, that while this algorithm is O(n) in time, permuting the list requires O(n log n) bits of quantum randomness. It also assumes that destroying the universe is O(1) in operation.
Found here.

27 June 2010

Neil Williams: Switching from iceweasel to chromium

I've grown tired of the firefox/iceweasel memory hog for a couple of reasons:

Chromium is just faster, everywhere. A nice touch is the new tab behaviour too - previews of the most recently viewed pages is far more useful than the usual homepage. Chromium is also faster than epiphany/webkit.

I'll still be using two different browsers, because neither iceweasel nor chromium can do the clever smart bookmarks thing of epiphany. I have come to utterly rely on bookmark input boxes for direct access to specific Debian bug numbers, individual PTS pages, Google searches, dwww searches, specific manpages, buildd reports and various other pages where a bookmark really needs to be an input box, not a label, and the bookmark itself sits on a toolbar and can then use http://url/%s which makes life so much easier.
Update
I wondered if epiphany smart bookmarks would be understood but apparently not. So:

The smart bookmarks are BTS, Google, PTS, buildd and man. At work, I've got a wider screen and I add several more for internal bug tracker numbers and similar.
Chromium and iceweasel/FF have nothing like this. Entering a bug number is utterly trivial - it even works with middle mouse button paste which, cleverly, opens the page in a new tab too. Iceweasel/FF can do this ONCE but only once, via an extension. The key points with epiphany are:

So, yes, Leo, the way epiphany does this is massively more effective for a "work-type" browser situation where there is such a need to access a specific page immediately, with no editing or keyboard usage.

Leo 'costela' Antunes: Re: Firefox/Iceweasel/Chromium smart-bookmarks

Neil Williams recently commented on the lack of smart-bookmarks in Firefox/Iceweasel/Chromium and since the post doesn t accept comments, counter-post FTW.
Maybe I didn t understand exactly what was meant, but I m personally trying to see the advantage of having a smart-bookmark sit on the toolbar as opposed to just being a used via a label. Both FF/IW and Chrome can do the label thing, where you bookmark something like http://bugs.debian.org/%s with a shortcut like bugs and can then simply Ctrl-L to the address bar and type bugs 999999 . Done.
Can the way Epiphany does this be more effective? (actually this is the way Galeon did it way back then and I used to love it before I found out I could be way quicker with the keyboard+shortcut thingy. Not to mention having less clutter in the toolbar.) And granted: Chromium s interface doesn t allow the editing of this shortcut and they only work when imported from FF/IW, but I expect this to be fixed eventually. Doesn t make much sense to have such a hidden feature. [UPDATE: nevermind. As handily pointed out by Chris Butler, you can edit the shortcuts under Options Basics Default Search Manage. It might not be the most intuitive of places to put it, but it's there.] As for the rest of the reasons for switching mentioned in the original post, I can certainly see where they re coming from. No real solid counter-arguments there.

20 February 2010

Martin F. Krafft: Charge advertisers for the last mile

ISPs fight a raging war over net neutrality because their infrastructure cannot keep up with the increasing demand (or rather supply) of content. Therefore, ISPs want to charge the users premiums if they wish to use certain services on the Net. For instance, since videos are usually large in size, one would have to purchase e.g. the platinum package to be able to access video hosting sites. It would be a serious loss of freedom if they won, and the Internet would never be the same. Let s turn that idea around: since sites that use advertising make money off every visitor, they are really the ones that should pay the ISPs so that they can improve their infrastructure. The same applies to sites that make money off visitors in other ways. At the moment, users pay to access the network (which is like paying a taxi to get to the market), so that they can visit sites where advertisers make money showing ads to the visitor, which might actually let them to pay a manufacturer for a product the end user pays twice, and the advertisers take in money, leeching off the ISPs investing into their infrastructure. I think that the advertiser and not the consumer should pay the ISP to keep the infrastructure afloat improve it even. The manufacturer should then pay the advertisers for displaying the ad, and the user consumes if s/he chooses to and everyone only pays once, for services they want. This will help improve competition among providers, which should always be the goal. If my ISP would start to record the volume of HTTP traffic I produce for each target site, charge the targets appropriately (they could start with a couple at first), and I d get free connectivity in turn, I d be quite happy. The ISP wouldn t have to look at the contents at all for that. I don t yet know what to do if the target sites choose not to pay up. ISPs could block them, or throttle or deprioritise traffic, but either of those might simply lead to an exodus of users, just like premiums would. As usual, this just needs to be done by many ISPs in concert. Are you listening?

3 February 2010

Leo 'costela' Antunes: Yeah, about that

I was trying not to complain about it, but now that the number of people asking me about it is getting bigger, my frustration got the best of me. So unfortunately I won t see you all there.

29 December 2009

Leo 'costela' Antunes: Re: Making pbuilder just that little bit faster

Absolutely, but there are at least two workarounds:
  1. Adding
    BINDMOUNTS="/var/cache/apt/archives"
    APTCACHE=""
    to your .pbuilderrc, in case you don t need any special separation of local and pbuilder caches (that s my case).
  2. Using apt-proxy or the like, which has its overhead, but also its other advantages.
None of them seem all that bad to me, considering the sensible speed improvements to pbuilder, but the ultimate decision probably depends on the amount of disk-access the packages in question need.

8 December 2009

Leo 'costela' Antunes: Notes on the Google Chrome Debian package

Just some quick superficial observations on the Debian/Ubuntu package distributed by Google:
  • Most files are installed in /opt/google/.
  • It attempts to patch /usr/share/gnome-control-center/gnome-default-applications.xml on postinst (maybe legacy compatibility? Someone with more gnome-fu than me care to explain?).
  • The postinst also automatically adds a souce for updates to /etc/apt/sources.list.d and an archive key (this is IMHO the worst part)
  • It includes a daily cronjob that at least at first glance tries to do the same things the postinst did (new apt source, archive key, etc) and some further archive configuration. The cron script is called at the end of postinst.
  • A casual look at objdump suggests it s statically linked to libv8
  • On a slightly more positive note, it at least seems to successfully undo most of the changes once removed, with the exception of the added archive key and the above mentioned patch to gnome s default apps list (that is: if there s any situation it actually gets applied).
I understand it might be too much hassle doing it the right way (from the corporate POV), but then why not simply cooperate a bit more with the community? Hopefully they ll accept some criticism and suggestions.
Or even better: they could simply reuse all the work being done to officially package Chromium.

12 November 2009

Leo 'costela' Antunes: Learning a bit more about ACPI

Ever since my misadventures into ACPI-land with my old laptop I ve been quite curious to better understand how it s all implemented under Linux. I skimmed the ACPI spec and that may have given me some insight on how to hack together a temporary fix to the problem I had then, but it doesn t really count as real understanding. Since I don t currently have the necessary time, I wrote it off as just another one of my many dead-end interests, but I nevertheless remained subscribed to the linux-acpi mailing list and the curiosity was still there, so it was a nice surprise to read a couple of posts by Mathew Garret on the subject, elucidating some bits of technicality, and I just decided to show some appreciation by posting about it! (I figure there s not enough appreciation out there, generally speaking ) Who knows, maybe someday I ll get my act together and be able to contribute some code?

Next.