Tuesday, October 23, 2012

All Hallow's Eve

Growing up, I was raised with the understanding that Halloween was a "pagan holiday" that was to not be celebrated at all. Our Halloween "tradition" was to put blankets over every window that could possibly leak light out of them on Halloween night, all cuddle around a single, solitary lamp in the back of the house, and maybe play a board game or two just to keep busy -- never anything that was loud of course, and just wait out Halloween night in seclusion. Of course, this wasn't always the case. When my father was a pastor in Needles, we had a "Reformation Day" costume party on October 31st, and even before that, I distinctly remember trick-or-treating as a little kid dressed in... I believe a homemade Cheshire Cat costume when I was 5 or so. But my Dad had read some information about Halloween's origins, and decided that we were not going to support a pagan, anti-Christian holiday.

Now, as an adult, married, and with a family of my own, I have tackled the Halloween question anew... and as much as I respect my father, and I appreciate how blessed I was being raised in a Christian family, I will have to question his position on Halloween. Is Halloween a pagan holiday? Well, curiously enough, there was a historic pagan holiday celebrated on October 31st called Samhain. It is a harvest festival, to celebrate the start of the "darker half of the year", another year's harvest, and a time when spirits of the dead were supposed to be able to enter our world. Ok, by all accounts, this is a pagan holiday that has nothing to do with Christianity.

However, another Holiday, originally celebrated in April but eventually migrated to November 1st came into being -- All Saints Day, which was celebrated by the Christian church only a handful of centuries after Christ's Ascension. Eventually, there was an influence of the popular purgatory heresy that affected the night before All Saint's Day, called All Saint's Eve (or All Hallow's Eve), which would be celebrated for the people supposedly in purgatory as a chance for those trapped in purgatory to be able to right whatever wrongs they did that keeps them bound in purgatory and out of heaven -- essentially the spirits of the dead being able to enter our world. Ok, so this also really isn't Christian, it is a heretical teaching that was infecting Christianity at the time (and still does in Roman Catholicism and Eastern Orthodoxy).

At some point, these two holidays began to influence each other (as there are obvious similarities, and it is likely that "All Hallow's Eve" was originally influenced by Samhain in the Celtic world and eventually spread throughout all of the Christian world at the time). As time progressed, the religious influences were washed out from the combined holiday, now called Halloween, and it became more strongly influenced by the secular world than either religious origin. About a hundred to a hundred and fifty years ago, the modern rebirth of paganism (neopaganism & Wicca) began to want to take claim over the holiday as their own -- with some justification, as they claim to be the continuation of the ancient pagan world that created the Samhain festival, yet the holiday really has nothing much to do with the old ways any more. It has become less involved with the seasons, and more involved with secular family fun (trick-or-treating, costume parties, decorations) and sudden shocks/startles from surprises/scares.

To make the claim that Halloween is a pagan holiday seems a bit of a stretch. It has pagan & heretical origins, that is true, but those origins really have no influence on the holiday as it is celebrated today. What does the Bible say about things like this? Well, there is a similar situation covered by Paul in 1 Corinthians 8 (ESV):
Food Offered to Idols
Now concerning food offered to idols: we know that “all of us possess knowledge.” This “knowledge” puffs up, but love builds up. If anyone imagines that he knows something, he does not yet know as he ought to know. But if anyone loves God, he is known by God.

Therefore, as to the eating of food offered to idols, we know that “an idol has no real existence,” and that “there is no God but one.” For although there may be so-called gods in heaven or on earth—as indeed there are many “gods” and many “lords”— yet for us there is one God, the Father, from whom are all things and for whom we exist, and one Lord, Jesus Christ, through whom are all things and through whom we exist.
However, not all possess this knowledge. But some, through former association with idols, eat food as really offered to an idol, and their conscience, being weak, is defiled. Food will not commend us to God. We are no worse off if we do not eat, and no better off if we do. But take care that this right of yours does not somehow become a stumbling block to the weak. 10 For if anyone sees you who have knowledge eating in an idol’s temple, will he not be encouraged, if his conscience is weak, to eat food offered to idols? 11 And so by your knowledge this weak person is destroyed, the brother for whom Christ died. 12 Thus, sinning against your brothers and wounding their conscience when it is weak, you sin against Christ. 13 Therefore, if food makes my brother stumble, I will never eat meat, lest I make my brother stumble.
I believe this has a practical application to Halloween. This particular passage dealt with a problem that Greek Christians were having to deal with -- meat that was being sold in the marketplace which was being offered to pagan gods. The meat was consecrated and sacrificed to these gods, which were obviously anti-Christian, and the Greek Christians were concerned that eating this food offered to other gods would be wrong. Paul points out that these other "gods" aren't real, and the food is still just food, regardless of whatever people may have done beforehand. Armed with the knowledge that these other "gods" aren't real, it becomes a non-issue, and Paul states that it is perfectly fine to eat the food from the marketplaces that were sacrificed in this manner without it being a sin or wrong.

However he also says that there are some that are "weaker" -- that is, it defiles their conscience to eat this meat -- Christians who still feel that it is wrong to eat the meat sacrificed to other "gods", and it would be wrong to force these Christians with a weakness in their faith (that is, they don't fully trust that there is only one God, that the other "gods" don't exist, or that God might get upset if they eat it) to eat the food sacrificed to false "gods", because it can make them stumble -- such a thing could shatter their weak faith and make them lose their faith all together and no longer believe in Christ and what he did for them. Making anyone lose their faith is a very bad thing.

How does this have application to Halloween? Well, the celebration of Halloween has roots in pagan and heretical beliefs and practices, but those aspects simply don't apply in the modern, common celebration of Halloween -- the holiday itself takes the place of the "meat sacrificed to idols" that Paul wrote about. The purgatory heresy and the pagan gods & beliefs are all false, and don't do anything to a Christian -- you aren't "guilty by association" by celebrating a holiday that may have once been a pagan festival, but no longer has that pagan element in the holiday. It is sterilized (as the commercial/secular world likes to do with anything religious -- purge all religious overtones completely from it, for reference, look at what has happened to Christmas) from the pagan roots, and no longer is connected to it. A Christian doesn't need to fear Halloween.

What about this verse in the bible: "whatever is true, whatever is honorable, whatever is just, whatever is pure, whatever is lovely, whatever is commendable, if there is any excellence, if there is anything worthy of praise, think about these things."? Obviously this says to stay away from Halloween, because Halloween isn't pure, lovely, commendable, etc. right? Well, this verse comes from Philippians 4:8... and it is often taken way out of context and applied to things it has no business being applied to. Let's look at Philippians 4:2-9 (ESV):

Exhortation, Encouragement, and Prayer
I entreat Euodia and I entreat Syntyche to agree in the Lord. Yes, I ask you also, true companion, help these women, who have labored side by side with me in the gospel together with Clement and the rest of my fellow workers, whose names are in the book of life.

Rejoice in the Lord always; again I will say, rejoice. Let your reasonableness be known to everyone. The Lord is at hand; do not be anxious about anything, but in everything by prayer and supplication with thanksgiving let your requests be made known to God. And the peace of God, which surpasses all understanding, will guard your hearts and your minds in Christ Jesus.
Finally, brothers, whatever is true, whatever is honorable, whatever is just, whatever is pure, whatever is lovely, whatever is commendable, if there is any excellence, if there is anything worthy of praise, think about these things. What you have learned and received and heard and seen in me—practice these things, and the God of peace will be with you.

This passage is dealing with issues directly in the congregation at Philippi. First dealing with some squabble going on between two women in that church, and then encouraging the Philippians to be peaceful and to pray about good things (to not get caught up in whatever fight was going on between the afore-mentioned ladies). Verse 8 isn't a rule for everyone to yank out of this context and then apply to everything in life no matter what. It is a nice sentiment, but really it has to do with church practice and prayer, based on the context, not a rule to measure things against to determine if Christians should or shouldn't be involved with it. It has no application to Halloween whatsoever.

Friday, April 6, 2012

Music Playlist Generator, Part 2

Part 1

Now that I had a XML file representing everything in my playlist, I needed to be able to actually use it within my Python script... so I could then, in turn, generate my daily playlist. I'm using PyPy, which is a rather unique variation of Python that can drastically speed up the performance of Python... so I figured why not? Anyway, this does pose some limitations on how I can proceed, as a number of Python XML parsing libraries are built in C with Python hooks, something PyPy doesn't handle gracefully (yet). So I decided to stick with what I had in the Python Standard Library, which included the ever so convenient ElementTree that quickly and easily allows for me to process an XML file.

Of course, I needed to make a Python object to load the tracks into, so first off, I built my Track object:

class Track:
        __slots__ = ('disc','discTotal','track','trackTotal','title','album','year','artist','albumArtist','genre','flags','filename','fileDate','length','size','channels','sampleRate','bitRate','fileType','fileTag','added','played')
        def __init__(self):
                self.disc = 0
                self.discTotal = 0
                self.track = 0
                self.trackTotal = 0
                self.title = ""
                self.album = ""
                self.year = ""
                self.artist = ""
                self.albumArtist = ""
                self.genre = ""
                self.flags = []
                self.filename = ""
                self.fileDate = ""
                self.length = 0
                self.size = 0
                self.channels = ""
                self.sampleRate = ""
                self.bitRate = ""
                self.fileType = ""
                self.fileTag = ""
                self.added = datetime.today()
                self.played = []

        def toTuple(self):
                dat = (
                        self.disc, self.discTotal, self.track, self.trackTotal,
                        self.title, self.album, self.year, self.artist,
                        self.albumArtist, self.genre, self.flags, self.filename,
                        self.fileDate, self.length, self.size, self.channels,
                        self.sampleRate, self.bitRate, self.fileType,
                        self.fileTag
                )

                return dat

        def __eq__(self, other):
                if 'toTuple' not in dir(other):
                        return False
                return self.toTuple().__eq__(other.toTuple())

        def __ne__(self, other):
                if 'toTuple' not in dir(other):
                        return False
                return self.toTuple().__ne__(other.toTuple())

        def __lt__(self, other):
                if 'toTuple' not in dir(other):
                        return False
                return self.toTuple().__lt__(other.toTuple())

        def __le__(self, other):
                if 'toTuple' not in dir(other):
                        return False
                return self.toTuple().__le__(other.toTuple())
        def __gt__(self, other):
                if 'toTuple' not in dir(other):
                        return False
                return self.toTuple().__gt__(other.toTuple())

        def __ge__(self, other):
                if 'toTuple' not in dir(other):
                        return False
                return self.toTuple().__ge__(other.toTuple())

        def checkSum(self):
                return hashlib.sha512(str(self.toTuple())).hexdigest()

        def copy(self, other):
                log.info("Changing \"%s\":", self.filename)

                logLine = "\t%s (%s -> %s)"
                logLineQ = "\t%s (\"%s\" -> \"%s\")"

                if self.disc != other.disc:
                        log.info(logLine, "Disc", str(self.disc), str(other.disc))
                        self.disc = other.disc

                if self.discTotal != other.discTotal:
                        log.info(logLine, "Disc Total", str(self.discTotal), str(other.discTotal))
                        self.discTotal = other.discTotal

                if self.track != other.track:
                        log.info(logLine, "Track", str(self.track), str(other.track))
                        self.track = other.track

                if self.trackTotal != other.trackTotal:
                        log.info(logLine, "Track Total", str(self.trackTotal), str(other.trackTotal))
                        self.trackTotal = other.trackTotal

                if self.title != other.title:
                        log.info(logLineQ, "Title", str(self.title), str(other.title))
                        self.title = other.title

                if self.album != other.album:
                        log.info(logLineQ, "Album", str(self.album), str(other.album))
                        self.album = other.album

                if self.year != other.year:
                        log.info(logLine, "Year", str(self.year), str(other.year))
                        self.year = other.year

                if self.artist != other.artist:
                        log.info(logLineQ, "Artist", str(self.artist), str(other.artist))
                        self.artist = other.artist

                if self.albumArtist != other.albumArtist:
                        log.info(logLineQ, "Album Artist", str(self.albumArtist), str(other.albumArtist))
                        self.albumArtist = other.albumArtist

                if self.genre != other.genre:
                        log.info(logLineQ, "Genre", str(self.genre), str(other.genre))
                        self.genre = other.genre

                if self.flags != other.flags:
                        log.info(logLineQ, "Flags", str(self.flags), str(other.flags))
                        self.flags = other.flags

                if self.filename != other.filename:
                        log.info(logLineQ, "Filename", str(self.filename), str(other.filename))
                        self.filename = other.filename

                if self.fileDate != other.fileDate:
                        log.info(logLine, "File Date", str(self.fileDate), str(other.fileDate))
                        self.fileDate = other.fileDate

                if self.length != other.length:
                        log.info(logLine, "Length", str(timedelta(0, self.length)), str(timedelta(0, other.length)))
                        self.length = other.length

                if self.size != other.size:
                        log.info(logLine, "Size", str(self.size), str(other.size))
                        self.size = other.size

                if self.channels != other.channels:
                        log.info(logLineQ, "Channels", str(self.channels), str(other.channels))
                        self.channels = other.channels

                if self.sampleRate != other.sampleRate:
                        log.info(logLineQ, "Sample Rate", str(self.sampleRate), str(other.sampleRate))
                        self.sampleRate = other.sampleRate

                if self.bitRate != other.bitRate:
                        log.info(logLineQ, "Bit Rate", str(self.bitRate), str(other.bitRate))
                        self.bitRate = other.bitRate

                if self.fileType != other.fileType:
                        log.info(logLineQ, "File Type", str(self.fileType), str(other.fileType))
                        self.fileType = other.fileType

                if self.fileTag != other.fileTag:
                        log.info(logLineQ, "File Tag", str(self.fileTag), str(other.fileTag))
                        self.fileTag = other.fileTag


This basically is an object that just holds onto the various XML sub-elements for the XML track into a single object for me to work with. It has a couple of other pieces, namely it some some overriding for comparison operators, has a way to copy a track from another track, and has a way to produce a tuple representation of the object that I use with the difflib for figuring out when two tracks are most likely the same (explained later).

Then, there is the actual import function:

def importData():
        """
        Imports a new set of data from the XML file.
        """
        listIter = ET.iterparse(XML_FILENAME)

        tracks = {}
        track = Track()
        trackCat = None

        for listItem in listIter:
                trackElem = listItem[1]
                if trackElem.tag == "track":
                        if trackCat not in tracks:
                                tracks[trackCat] = []
                        tracks[trackCat].append(track)
                        track = Track()
                elif trackElem.tag == "discnumber":
                        track.disc = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "totaldiscs":
                        track.discTotal = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "tracknumber":
                        track.track = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "totaltracks":
                        track.trackTotal = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "title":
                        if trackElem.text == None:
                                track.title = u''
                        else:
                                track.title = trackElem.text

                elif trackElem.tag == "album":
                        if trackElem.text == None:
                                track.album = u''
                        else:
                                track.album = trackElem.text

                elif trackElem.tag == "year":
                        if trackElem.text == None:
                                track.year = u''
                        else:
                                track.year = trackElem.text

                elif trackElem.tag == "artist":
                        if trackElem.text == None:
                                track.artist = u''
                        else:
                                track.artist = trackElem.text

                elif trackElem.tag == "albumartist":
                        if trackElem.text == None:
                                track.albumArtist = u''
                        else:
                                track.albumArtist = trackElem.text

                elif trackElem.tag == "genre":
                        if trackElem.text == None:
                                track.genre = u''
                        else:
                                track.genre = trackElem.text

                elif trackElem.tag == "category":
                        if trackElem.text == None:
                                tracvCat = u''
                        else:
                                trackCat = trackElem.text

                elif trackElem.tag == "flags":
                        if trackElem.text == None:
                                track.flags = []
                        else:
                                track.flags = trackElem.text.split(",")

                elif trackElem.tag == "filename":
                        if trackElem.text == None:
                                track.filename = u''
                        else:
                                track.filename = trackElem.text

                elif trackElem.tag == "filedate":
                        track.fileDate = datetime.strptime(trackElem.text, '%m/%d/%Y %I:%M:%S %p') if trackElem.text != None else None

                elif trackElem.tag == "length":
                        track.length = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "size":
                        track.size = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "channels":
                        if trackElem.text == None:
                                track.channels = u''
                        else:
                                track.channels = trackElem.text

                elif trackElem.tag == "samplerate":
                        track.sampleRate = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "bitrate":
                        track.bitRate = int(trackElem.text) if trackElem.text != None else None

                elif trackElem.tag == "type":
                        if trackElem.text == None:
                                track.fileType = u''
                        else:
                                track.fileType = trackElem.text

                elif trackElem.tag == "tag":
                        if trackElem.text == None:
                                track.fileTag = u''
                        else:
                                track.fileTag = trackElem.text

        for trackList in tracks.values():
                random.shuffle(trackList)

                for track in trackList:
                        track.added = datetime.today()

        return tracks
This does two things, it rapidly loads every track from the XML file, and then... because I have had to rebuild my datafile a number of times and don't want to keep playing the same set of tracks (through the LRA process explained later), I shuffle the tracks and assign the added date in random order.

Once this is completed, I am able to use the tracks in Python using the tracks object I built. I partition my music list along "category" lines, which I use the "categorygroup" or other equivalent tags within my music to identify what goes to which category. Everything about my playlist works from this set of categories -- no song exists in two categories, so it really does a complete partitioning of my music, which means I can process each category completely independent of each other... something very useful later in the code.

Wednesday, April 4, 2012

Music Playlist Generator... or how I like to make things complicated...


Well, more or less. See, I have this collection of music I've transferred from a whole lot of CD's I bought over the years onto my computer. I had an iPhone which I had used to play that music by using iTunes to create a dynamic playlist, and load that playlist onto my phone. Well... it worked, other than the fact that I can't use iTunes on Linux and such. So... I decided to go with an alternate plan -- stream the music and use a music player to listen to it. My problem was, how to make my music playlist with the same functionality that iTunes provided me.

First, I used Firefly Media Server... which became quite a hassle to work with, so I switched to Logitech's SqueezeBox Server (with an external MySQL database backend). I then built some stored procedures to generate the playlist directly into the database.... which really didn't work very well. So I built a Google AppEngine web app that would do most of the processing... which worked pretty well but was a pain to keep up. And... SqueezeBox Server died on me, and I didn't want to mess with it any more. After some searching, I settled on something simple -- MusicPlayer Daemon, with its own internal HTTP streaming output. Using this, I could once again access my music as a stream -- a continuous stream that works all the time. And, it is driven by a playlist I can control and manipulate with Python (thanks to python-mpd2). After wiring everything up to provide an encrypted channel with login credentials being required to reach the stream (thanks to squid3 acting as a reverse proxy and upgrading http to https traffic automatically), I finally had a solution. I'd just write my playlist generator as a python script.

In comes TagScanner -- a beautiful program (if a little cumbersome to learn how it works at first) which lets me clean up and organize my music & all of the music tags easily. It also provides a way for me to export my list of music to XML... which I use as the starting point for my python script. TagScanner lets me build the XML file with the following:
# Tagscanner export script

$file_name TrackList.xml
$file_notes XML file with full information
$file_encoding utf-8
$file_writebom 1
$file_ishtml 1
$file_relativepaths 1

$document_open

<tracklist>
$select %_index%,0
 <track>
  <discnumber>%disc%</discnumber>
  <totaldiscs>%totaldiscs%</totaldiscs>
  <tracknumber>%track%</tracknumber>
  <totaltracks>%totaltracks%</totaltracks>
  <title>%title%</title>
  <album>%album%</album>
  <year>%year%</year>
  <artist>%artist%</artist>
  <albumartist>%albumartist%</albumartist>
  <genre>%genre%</genre>
  <category>%contentgroup%</category>
  <flags>%comment%</flags>
  <filename>%fullfilenameext%</filename>
  <filedate>%_filedate%</filedate>
  <length>%_length_sec%</length>
  <size>%_filesize_bytes%</size>
  <channels>%_channels%</channels>
  <samplerate>%_samplerate%</samplerate>
  <bitrate>%_bitrate%</bitrate>
  <type>%_codec%</type>
  <tag>%_tagtype%</tag>
 </track>
$endselect
</tracklist>
$document_close
I'm not going to go into details as to what all of this means, but basically it generates the entire list of tracks into an XML file that I can parse. And thus my journey of building my playlist begins.

The next step was to be able to easily update my music list. Which comes down to a nice program called QtdSync, which uses the tried and true rsync algorithm for incremental updates to provide what I need. I simply sync my music folders on my portable hard drive with a directory on my server at home, and it does the nasty work of figuring out how to only update what has changed (because I don't want to have to re-upload my entire music library every time I make a change). Conveniently, this makes me have the same directory structure on my portable drive as the server, which makes the XML file above provide the same relative paths to my files.

On Part 2, I will explain the process I use for importing the XML file into something usable in my python script...

Thursday, March 29, 2012

Squid 3.1, mod_python, and how I have fixed my reverse proxy redirects...

Ahh squid, how I love to hate to love you... most of the time, you work great for me. But sometimes... grr. Anyway...

So here's my problem -- have squid (3.1.18) being used in both a transparent and reverse proxy configuration (transparent proxy for everything inside my network going out to the internet, reverse proxy to redirect certain CNAME virtual hosts to my various other servers on my network). However, I do not want to permit http traffic to go through the reverse proxy peers, I want everything https. No problem, right? Simple configuration directive for just one of my cache peers:

cache_peer 10.18.75.1 parent 80 0 no-query originserver login=PASS name=xlorep
acl sites_xlorep url_regex ^https://xlorep\.darkhelm\.org
cache_peer_access xlorep allow sites_xlorep
http_access allow sites_xlorep
acl http_xlorep url_regex ^http://xlorep\.darkhelm\.org
http_access deny http_xlorep
deny_info https://xlorep.darkhelm.org/ http_xlorep 

Basically, it uses a couple regular expressions to figure out when someone is accessing the xlorep.darkhelm.org host. If it is https, it connects to that server. If it is http, if denies access, and sends a HTTP redirect command to send the person to the https equivalent.

Perfectly fine configuration, unless the person wants to, let's say, go to http://xlorep.darkhelm.org/some/other/path.html. Rather than going to https://xlorep.darkhelm.org/some/other/path.html, the person is sent to https://xlorep.darkhelm.org/. Because squid 3.1 is too dumb to do anything better. For almost every request I use, this is *perfectly fine* -- most of my redirects go to sites that all handle themselves in a friendly way to this kind of scenario.

Enter mpd's client175, and suddenly this causes a problem. For some reason, client175 likes to send links to the unsecured version of the URL (http rather than https), which gets hosed because of the deny_info redirecting going on in my squid configuration. So... client175 becomes only partway functional. I needed to find some way to properly change the URL to the exact https equivalent of the http url.

Well, I found out that squid 3.2 did exactly that. Oh, other than it being beta software and completely crippling my configuration from the ground up preventing any http to https redirects from happening, even those from the transparent proxy side of things... so... 3.2 didn't work.

Solution #2 came to me rather simply -- I write a simple redirect script that could do the work for me, and put the URL for that in my deny_info directive above. So... after installing mod_python into my in-house apache server (why mod_python? because I like python -- same thing for client175, it is a python-based mpd client). So... get mod_python configured with a simple addition to the default site for apache:

<Directory /var/www/py>
        AddHandler mod_python .py
        PythonHandler mod_python.publisher
        PythonDebug On
</Directory>

And then write a simple python script to solve my problem:

from mod_python import apache, util

import re, urllib

def redir(req):
        req.log_error('handler')
        reqURI = req.unparsed_uri

        patt = re.compile(r"^\/py\/redirect\.py\/redir\?uri=")

        reSearch = re.search(patt, reqURI)

        oldURL = urllib.unquote(reqURI[reSearch.end():])

        newURL = re.sub(r"^http:\/\/","https:\/\/",oldURL)

#       req.content_type = 'text/html'
#       req.send_http_header()
#       req.write('')
#       req.write('' + reqURI + '<br />')
#       req.write('' + newURL + '<br />')
#       req.write('')
#       return apache.OK
        return util.redirect(req, newURL)

I configure this to be handled through redirect.darkhelm.org, and it basically translates a given URL parameter from http to https. I can then go back to that squid configuration, and change it as follows:

cache_peer 10.18.75.1 parent 80 0 no-query originserver login=PASS name=xlorep
acl sites_xlorep url_regex ^https://xlorep\.darkhelm\.org
cache_peer_access xlorep allow sites_xlorep
http_access allow sites_xlorep
acl http_xlorep url_regex ^http://xlorep\.darkhelm\.org
http_access deny http_xlorep
deny_info https://redirect.darkhelm.org/py/redirect.py/redir?uri=%s http_xlorep 

And now, if someone goes to http://xlorep.darkhelm.org/some/other/path.html they get redirected to https://xlorep.darkhelm.org/some/other/path.html -- exactly what I want. And it makes client175 work perfectly! As we used to say in the Army, HOOAH! problem solved.

Monday, March 19, 2012

Jonah, Jonah, did not obey God immediately...

Something that has been bothering me for a little bit, and I've talked with my loving wife about, is the lyrics to a song that my little girl likes, called "Jonah", by a published Christian children's music artist from Bob Jones called "Patch the Pirate". The song has a catchy tune, derived from the Sailor's Hornpipe with a chorus that starts off with "Jonah, Jonah, did not obey Got immediately; Jonah, Jonah, down in the depths of the deep blue sea." The lyrics go about talking about how Jonah didn't do what God told him to do, so he got punished, with strong emphasis that the listener should do what God wants or else [God might punish you like he did to Jonah].

My problem with this is: first, it seems like an incomplete telling of the story of Jonah (there is more to the story than Jonah disobeying God and getting swallowed by a big fish for 3 days and then getting spit back out after). Second, it seems like a complete confusion of law and gospel. Third, it misses the point as to what the entire story of Jonah was about, and even why Jonah was swallowed by a big fish.

Jonah is an interesting book in the Bible, and it is an incredible story that can be hard to accept with human reason, and requires the reader to accept it because it is in the Bible, and because Jesus acknowledged it as real historical fact himself (Matthew 12:38-42, Luke 11:29-36). It is an incredible story about one of the prophets of the Old Testament (Jonah), who was sent on a mission by God to warn the people of the large city of Nineveh that their destruction is at hand unless they repent (turn away from) their sins and evil ways. Something akin to being told in the modern day to go to Las Vegas and tell the people there that they have to turn from their sinful ways and repent, or else God will destroy them.

Of course, Jonah does what any normal, rational person would do in the event of being told to do something so insane -- he runs from it. God punishes him with raising a storm, and eventually having him swallowed by a big fish (the Bible doesn't state it was a whale and it is best to take it as a literal fish, not inject human reason into the story and claim it is a whale, like God somehow can't make a fish big enough to swallow a man).  Three days later, after he repents of his willful disobedience toward God (something that a prophet should have known better than doing int he first place), he is spit out from the fish, and goes to Nineveh -- where he proceeds to do as God told him to do -- tells the people of their impending doom unless they repent.

Surprisingly, rather than being laughed out of town, or executed for his insolence, the people believed what Jonah said, and repented of their sins -- thus they avoided the destruction that awaited them, God forgave the people of Nineveh, just as he had forgiven Jonah when he repented of (turned away from) his sin.

Which brings us to the crux of the matter -- was the point of the book of Jonah to show us that we must do what God says or else He will punish us? Or was the purpose for this story to show us another example of God's ceaseless mercy and grace? Is it a story about following the rules (Law), or was it a story about forgiveness (Gospel)? I will make the claim that it is a story about forgiveness. It is how God spared Jonah & Ninevah from their respective dooms when they repented of their sins. It isn't a story about how we should do what God wants for us or else we'll be punished.

The song has an obvious confusion between law and gospel -- and the writer should review Galatians 3 for reference as to what the distinction should be, and what place the Law really has.

For all who rely on works of the law are under a curse; for it is written, “Cursed be everyone who does not abide by all things written in the Book of the Law, and do them.” Now it is evident that no one is justified before God by the law, for “The righteous shall live by faith.” But the law is not of faith, rather “The one who does them shall live by them.” Gal. 3:10-12
The Law shows us how much we fail to live up to it, and just how much we need God's forgiveness (given to us through Jesus' suffering, death, and resurrection). Making the story be purely reduced to "follow God's law or else!" strips the refreshing story of forgiveness within the passage. So, why is this story in the Bible? Well, basically, because it is a footnote in Christ's life. He referenced the book, specifically the Sign of Jonah, and used it as the only sign he would give, that just as Jonah spent three days and nights in the belly of the fish, so too will Christ spend three days and nights in the belly of the earth. It is a sign that points directly to the cross, pointed out by none other than Christ himself.

So, the entire point of the Book of Jonah could be summarized as being a marker that points directly to Christ's work -- his death and resurrection, the very act that triumphed over death and sin, and promises true salvation from both to those that believe. Hardly the point that had been derived into the lyrics of that song.

Tuesday, November 30, 2010

Maven meanderings...

So... as my understanding of how to get my little programming projects rolled out matures, I have discovered Apache Maven.... and it really is quite nice for getting everything I was doing manually for the projects, done automatically. Building, distributing, constructing a project reporting site, running tests (finally have some testing built into my apps), etc. All in all, a very useful all-inclusive tool. It has taken a little work to make everything run the way I was hoping it would run, but little by little, I'm getting there.

So far, I've been using the gwt-syntaxhighlighter project as my testbed for maven, with plans on rolling out the concepts/methods I've used there to also be applied to the gwt-fontface project, and finally the ShowSort app.

Since I decided to go with Google Code for where I have the project hosted, and picked Mercurial as my source control of choice, it has proven to produce some interesting hurdles along the way. First and foremost, getting maven configured to be able to generate a GWT module was interesting, as the "Maven Way" is to keep source code and compiled code separate in two different packaged jar files, but the GWT way is to have them together, so the GWT compiler can do its thing with the source code to generate the JavaScript code for GWT. This proved to be rather simple, with the Maven GWT Plugin as it can handle having the source code in a separate file from the compiled code, and make it work with GWT. However I still needed to build something that would work for non-maven people... so the Maven Assembly Plugin comes to the rescue, making an additional jar file with the necessary source files added into the compiled file. The GWT plugin also makes it so I can separate the "resource" files (the CSS, JavaScript, and .gwt.xml files needed for the final product) separate from the Java files, making it a little cleaner and easier to locate the java files I need to work on without a polluted directory structure.

Tying it all together with Maven's SCM configuration, I was then able to have maven automatically push the new, updated source files to the source code repository whenever I release a new version. I use the SonaType OSS Maven artifact repository to release the jars into the Maven world, which is a nice convenient way for Maven users to be able to locate and use the gwt-syntaxhighlighter project and have it included into their own apps. Finally, I was able to rely on the Maven GWU Plugin to have it automatically upload the released jars up to the Google Code downloads. This means in one simple command, I can completely deploy the gwt-syntaxhighlighter project in an efficient and effective manner, getting the source code checked-in, and releasing the built jars into both the Maven central repository and into the Google Code downloads section for the project.

Finally, I tied in the Maven site generation system, which constructs a nice, simple website for the project. I have two sets of Java API documentation, the change log for the project, and a few other details all integrated into it, and have it all rigged up to be updated when I release a new version of the code.

Another thing Maven allowed for me to do, was to have a simple way to construct and run tests on the gwt-syntaxhighlighter module, checking to ensure that it works how I think it should be working. These tests end up being run automatically whenever I build the project, giving me a nice, comfortable feeling that the project will be running the way I hope it will be. Right now, the tests are probably not completely adequate, but I will be working over time to make them as robust as I can.

Since everything is tied directly into Maven, it should, theoretically, be possible for me to use any development environment (that supports Maven), so I'll no longer need to worry about how the directory structure and set-up is for my project in Eclipse and then need to switch to NetBeans at some point or another... I can use either one at my leisure.

Thursday, November 11, 2010

More about the SyntaxHighlighter

A lot has happened to this little sub-project of mine. Originally simply a part of the ShowSort app, after breaking it out into its own project (see previous entry), I eventually moved it over to Google Code, completely segregating it from the ShowSort app into a project of its own.

I then transitioned it over into a maven project, hopefully separating it from the NetBeans-specific structure to something portable (in case I switch to a different IDE or something). This didn't go off without a few bumps along the way, but it is looking like it is getting closer to the mark finally. Maven lets me do some real nice things, like finally having some version management, and setting up a simple repository (which eventually, I'll get this into the main maven repositories).

I also updated the project to use the GWT 2.1.0 release, which finally removes my previous dependence on the aging gwt-incubator, in favor of the new Logging API presented in 2.1.0, which is far more comfortable to work with.