rate limit gnump3d with Apache 2.2 and mod_bw

While you can reencode MP3s fairly trivially with gnump3d, no limiting is applied. Whatever you've reencoded will be delivered to the client as quickly as it can accept the packets being sent. If your objective is to limit the stream to the neighborhood of the reencoded MP3's new bitrate, you will need some Apache reverse proxying magic.

First, you need mod_bw 0.7. Building is a simple process.

# /usr/bin/apxs2 -i -a -c mod_bw.c

If there's a build fail, you may need to edit mod_bw.c accordingly:

-/* Compatibility for ARP < 1 */
-#if (APR_MAJOR_VERSION < 1)
-    #define apr_atomic_inc32 apr_atomic_inc
-    #define apr_atomic_dec32 apr_atomic_dec
-    #define apr_atomic_add32 apr_atomic_add
-    #define apr_atomic_cas32 apr_atomic_cas
-    #define apr_atomic_set32 apr_atomic_set
-#endif

On Debian GNU/Linux, activating the module is straightforward.

# a2enmod bw
# a2enmod proxy_html proxy_http rewrite

Finally, a new virtual host is created.

<VirtualHost *:80>

ServerName mp3.example.com

DocumentRoot /var/www

ErrorLog /var/log/apache2/mp3.example.com_error.log
LogLevel warn
CustomLog /var/log/apache2/mp3.example.com_access.log combined
ServerSignature On

So far, the usual stuff.

ProxyRequests Off
<Proxy *>
Order allow,deny
Allow from all
</Proxy>

We need to ensure we can reverse proxy. By default on Debian all requests are denied.

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/mp3$
RewriteRule . /mp3/ [R=301,L]

Using mod_rewrite, the root URL is redirected to our proxy location.

BandWidthModule On
LargeFileLimit .mp3 250 16384

mod_bw is enabled and any file with an MP3 extension (not a MIME check) that is at least 250K is size is limited to 16KB/s, perfectly suitable for MP3s reencoded to 128Kbps.

ProxyPass /mp3/ http://10.10.1.1:8888/
<Location /mp3/>
ProxyPassReverse /
SetOutputFilter proxy-html
ProxyHTMLURLMap / /mp3/
</Location>

</VirtualHost>

Finally, the magic for the reverse proxy. All requests are proxied to our gnump3d server running on another system. The HTML sent back from GNUmp3d is filtered using the proxy-html filter to ensure all the links are rewritten. However, the m3u files will still be incorrect.

At least two options are available. The simplest way is to modify the existing gnump3d.conf so the m3u file uses the correct path, bouncing it through our reverse proxy.

use_client_host = 0
host_rewrite = mp3.edseek.com/mp3

The second option would be to filter m3u files and use an external tool, say, sed, to rewrite the URLs in the m3u file with Apache's ext_filter module.

Race for the Galaxy near Orlando, FL

I finally managed to secure a copy of Race for the Galaxy (R4TG). Of course, outside of Cool Stuff Games on Wednesday, I have no one to play with. If anyone happens upon this post and wants to play, let me know.

Recession Squeezing Customer Options

The current economic climate in the U.S. is causing downsizing of customer options in leiu of price increases. Recently, Subway nuked the small drink size option. Panera no longer allows a half sandwich order. It's a defacto price increase. Store hours are among the casualties, too.

CoolMax CN 350 NAS

I recently picked up the CoolMax CN-350 NAS for only $20 after a $30 mail-in rebate and a $10 Google Checkout signup bonus. So far I have been pleased with the device. It comes with the following noteworthy features.

* Fast Ethernet (100Mbps)
* USB2
* CIFS/SMB or FTP access (network)
* Attached storage (USB2)

If you are using the device over Ethernet, it will both offer an IP via DHCP and make DHCP lease requests. If a DHCP server answers, it will assume the assigned IP. So you can plug it into an isolated system and an IP will be offered to the attached computer.

The SMB performance is awful. The maximum sustained speed is around 4MB/s. If your primary usage mode is over Ethernet, buy something else. (You're also limited to about 30GB since the vfat implementation is weak, in additional to the under powered microprocessor.) The USB performance -- what the device was obviously intended for -- is great, at around 20MB/s.

I ultimately received my rebate quickly. For the money I paid, it's a nice device, if you can supply your own drive and don't care about the awful Ethernet performance.

Okay, I am done with RadioShack forever

I wander into my local RadioShack for a simple RCA to S-Video adapter (or SVHS). After looking around, I ask the lady and she finds one for me. It looks unimpressive enough. I ponder a cable to plugin to it, since the original one being replaced was male to female and this was female to female. She starts to grab one of those expensive monster cable sets, but I waive her off.

I proceed to buy the adapter, at which point she rings it up... It was almost $25! No kidding. I said no thanks. Visibly annoyed, she canceled the order and I walked out, never to return.

Years ago I recall I ordered cables with success from CCT. Sure enough, they have what I need for about $1.77. Yes, more than $20 less than RadioShack. Same part.

RadioShack is a failure.

gnump3d and downsampling mp3s

Recently I discovered gnump3d. It supports downsampling, which is a feature I especially need. However, the example reencoding command for handling MP3s doesn't seem to work. Instead, I am using a different variant.

 
downsample_medium_mp3 = \
  /usr/bin/sox -t mp3 $FILENAME -t wav - | /usr/bin/toolame  -b 128 - -
 

The original uses the lame encoder to both read the MP3 source file and then reencode it, but that seems to fail miserably. Fortunately, using sox seems to resolve it.

It's useful to note that this will not throttle the client connection, so if your goal was to limit the stream to, say, 128Kbps, it won't work. Instead, you need to rate limit some other way.

TigerDirect CompUSA spam

What a perfect marriage. I never liked CrapUSA. It seems TigerDirect has bought them out, and now I am getting weekly spam about the CrapUSA store re-openings. I honestly don't care. I unsubscribed -- supposedly -- after receiving the second email about them, but today I have yet another. Supposedly I am unsubscribed, again.

You have been unsubscribed from 'tiger_orlando'.

We shall see if that's the end of this nonsense, or not.

Rails STI resouce routes routing

After hours of Googling and an ill timed browser reload while typing this the first time, I finally happened upon RoR ticket 10454 which details the problem I am having with inferred routes and STI models. I modified it to supposedly support any level of model nesting. Seems to work now. I inserted it into my environment.rb presently.

module ActionController
  module PolymorphicRoutes
    def build_named_route_call(records, namespace, inflection, options = {})
      records = Array.new([extract_record(records)]) unless records.is_a?(Array)

      # No STI
      #base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"

      # Hack
      record = records.pop.class
      until (record.superclass == ::ActiveRecord::Base)
        record = record.superclass
      end

      base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"

      method_root = records.reverse.inject(base_segment) do |string, name|
      segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
      segment << string
    end

    action_prefix(options) + namespace + method_root + routing_type(options)
    end
  end
end

DokuWiki and HOWTO migration

Since I deployed DokuWiki last fall, I've found it to be an excellent tool for managing documentation. It enhances the quality of the final documentation product whilest not getting in the way of writing it. I rather enjoy it.

I am now migrating some of my existing HOWTO documents over to the wiki. Afterward, there's an chance I'll actually integrate suggestions and corrections into the documents; something I haven't done in two years. Messing with the Docbook XML has been too much of a pain to go back and perform any document maintanence.

The first document to be moved over, for no particular reason, is the Dirvish HOWTO.

has_many :through and with_scope protected

If you were following the excellent has_many :through blog, you may find you can't use with_scope in the methods defined on your association. Instead, just send :with_scope along.

has_many :active_campaigns, :through => :campaign_distributions do
  def <<(active_campaign)
    CampaignDistribution.send(:with_scope, :create => {}) {
      self.concat active_campaign
    }
  end
end

Hilarious craigslist job post for Web Freaks

I was flipping through craigslist today and came cross a job posting for Web Freaks worthy of a ticket to planet clue. Let's take a trip down the rabbit hole.

We are seeking a highly motivated Level 3 (Highest Level) Linux System Administrator to
maintain and administer approximately 500 LINUX servers in a Kissimmee, FL based Data
Center. $50K per year starting pay, no other benefits are provided as of now. Prior customer
service experience is a plus. Prior Linux experience is a must and cPanel experience is a very
large plus. Must be flexible with hours. On call required. We prefer that your life is not occupied
and you are willing to devote full attention to this company, our assets and our customers.

First, managing large clusters for only $50k a year without benefits? Laughable. Second, an unoccupied life? Sounds like code for indentured servitude. Web Freaks is clearly the kind of employer that doesn't engender a desire to give of oneself in the pursuit of greater profit. Here's a hint: treat someone like a valued, respected employee and he'll move mountains for you. If you instead treat him like a tool, you'll be stuck writing authoritarian nonsense like this job posting and be found wanton.

So, with that in mind, let us continue.

 - Capable of producing effective resolutions:
    we want you to think before you execute commands / respond to clients

Heh.

- INTEGRITY and HONESTY. IF YOU LACK INTEGRITY OR HONESTY DO NOT APPLY.
- Clean Appearance, well groomed, good manners are all necessary
- The ability to maintain a professional relationship with your employer,
    and maintain respectful manners at all times to your employer and fellow workers
- Good speaking skills. MUST SPEAK ENGLISH FLUENTLY - NO EXCEPTIONS
- Required to live within 15 minutes of the data center for rapid response.
    No Remote Technicians allowed
- AGE: A MATURE PERSON over 30 years old with life skills and experience
- EXTREMELY COMPETENT IN GENERAL
- TIME MANAGEMENT IS A REQUIREMENT. If you are not an expert in Time Management
     (ie: getting to work ON TIME, MAXIMIZING YOUR OFFICE TIME FOR THE COMPANY, etc)
     then you do not need to apply. We have a strict policy about being at work on time and etc.

Seeking a professional? Understandable. Not paying a professional size salary? Oops. And yes, requiring someone to live within 15 minutes of a data center is going to require more compensation.

Someone with life skills over 30 isn't going to work in central Florida for 50K, no benefits, within 15 minutes of a colo facility whilest being on-call 24/7, interacting with customers, and managing at least three other employees.

Yeah, wow.

 If you are not this person, a person who is wanting to strive for success and greatly
appreciate the company you work for, then please, do not apply.

In closing, please bow before the greatness of your employer. Work is life!

Sounds like someone needs to yank his ass out of his greatness and realize your employees are your partners in success and failure. Treat them as valued individuals and they'll serve you well. Treat them poorly and they certainly won't appreciate you or your company.

CL post, for as long as it lives.

Daniel Suarte, IT Services spammer

Today I received my third email from someone claiming to be a Daniel Suarte, writing about filing open IT positions. However, I've never heard of this person before and I have no company or positions to fill. Clearly, this lamer is a spammer. He's posting from a gmail account Daniel.Suarte@gmail.com. The email was not sent via gmail.

What a llamah.

Spammer is now using

Hi,

My name is Jennifer Florin and I am conducting my periodic check with you and your company to see if we can assist with any of your current IT/IS/Technical openings?

ed2k helper with Ruby

Nothing fancy. It works.

#!/usr/bin/ruby1.8

# ed2k link helper for Opera

host = '10.10.1.1'

link = ARGV.first

require 'net/telnet'
core = Net::Telnet::new(
        'Host' => host,
        'Port' => 4000
)

core.cmd("dllink #{link}")
core.cmd('q')
core.close

Moneydance, cross-platform money tracking

I've been investigating a switch from gnucash to Moneydance. There's no way to export data directly from gnucash. Fortunately, someone wrote a Java app that will parse the XML and produce a basic QIF export with your accounts and transactions.

Thus far, Moneydance has been able to handle downloading data using OFX for most of my accounts. For Chase, I had to enable OFX functionality through the Web site.

Update, October 18th. Moneydance has its own special bag of problems. Somehow I managed to get it to raise an exception when I try to save my datafile. That result is a corrupt file that cannot be opened. I created my accounts by using gnucashtoqif and then editing accordingly, so I may have started with some odd data. Only seems to happen when I import an OFX from my brokerage account, but only with this data set. It imports fine with a new Moneydance account set. Still, gives me a moment of pause.

The investment stuff isn't polished yet. If you track a bunch of investments, you'll find some things are missing and many things aren't immediately obvious.

I also found the liability account was kind of hit and miss. I prefer the way Gnucash handles it. I had to fight with Moneydance to automatically add all the prior transactions for my loan. The first time, it seemed to be willing to add some, not all, of the transactions. When I created the liability again, I could find no way of forcing it to create past loan payments. Seems like some kind of a bug that I had to do it by hand for two years of data.

I've filed a couple of these issues with Moneydance's Trac based ticket system.

For $30, you get what you pay for, I guess.

It's a positive enough experience to move forward with it and not enough to enslave myself to Intuit's Quicken and be forced to upgrade every N years to maintain access to online account syncing.

Plus, Moneydance actually offers QIF and XML export, so I can at least recover my data more easily than from Gnucash should I decide to move to something else. (What else...?)

piston saves!

I download a prerelease of the latest Engines earlier in the summer with piston. My tests were failing in strange and magical ways. I spent quite a bit of time trying different things, then finally resorted to a fresh Rails app that I slowly populated with relevant files. Quickly I discovered the problem was my Engines plugin. I really thought I had the full release, but clearly not.

I was really relieved when I retargeted piston at the actual release version of Engines and updated. A single file change and I was back in business.

jasonb@faith:~/src$ piston switch http://svn.rails-engines.org/plugins/engines \
  vendor/plugins/engines/
Processing 'vendor/plugins/engines/'...
  Fetching remote repository's latest revision and UUID
  Restoring remote repository to known state at r600
  Updating remote repository to http://svn.rails-engines.org/plugins/engines@611
  Processing adds/deletes
  Removing temporary files / folders
  Updating Piston properties
  Updated to r611 (4 changes)

As it happened, the problem was:

+++ vendor/plugins/engines/lib/engines/testing.rb
@@ -2,7 +2,6 @@
 # for more details.

 require 'test/unit'
+require 'test_help'

Sigh. It does help to import the correct branch of the code, yes?

Because I used piston, it was stupid easy to retarget the correct version of Engines, then update and see what it was I screwed up. Further, any local changes I made -- I had none for this plugin -- would be preserved and subject to the usual Subversion conflict resolution process (such as it is).

It's best not to modify plugins lest you find yourself maintaining even more code, but if you must, at least your changes won't be clobbered. And you don't have to run diff between plugin checkouts to try to deduce which of your changes are about to get lost.