Growl-Based Bonjour Chat with Channels in 150 Lines

Posted by kev Sun, 06 May 2007 10:58:00 GMT

One of the many awesome things about working at Powerset is the guys I get to hack with. Tonight, my buddies Tom Preston-Werner, Chris Van Pelt, and I were feeling whimsical. Full source, with quicksilver hook and startup scripts can be found here, but this is the meat:

growl_handler.rb
require 'rubygems'
require 'ruby-growl'

module Jakl
  class GrowlHandler
    def initialize
      if `which growlnotify` =~ /^no .+ in/
        @strategy = :ruby
        @growl = Growl.new("localhost", "jakl", ["jakl_message"])
      else
        @strategy = :command
      end
    end

    def notify(group, name, message)
      case @strategy
        when :command
          img_path = File.join(File.dirname(__FILE__), '../../assets/jakl.png')
          `growlnotify -n jakl --image #{img_path} -m '#{message}' '#{name} (#{group})'`
        when :ruby
          @growl.notify("jakl_message", "#{name} (#{group})", message)
        else
          raise StandardError.new('Invalid strategy')
      end
    end
  end
end
client.rb
require 'rubygems'
require 'net/dns/mdns-sd'
require 'base64'

module Jakl
  class Client
    DNSSD = Net::DNS::MDNSSD

    @@debug = false

    def self.debug=(value)
      @@debug = value
    end

    def self.debug
      @@debug
    end

    def initialize(options={})
      default_options = {
        :default_recv => "jakl", 
        :timeout => 2, 
        :login => ENV['USER']
      }
      @options = default_options.merge(options)
    end

    def send(message, recv=nil)
      recv ||= @options[:default_recv]
      recv = recv.split(',').collect {|g| g.strip }
      puts "Sending: '#{message}' to '#{recv.join(',')}'" if @@debug

      find_recipients = DNSSD.resolve('jakl', '_jakl._tcp') do |r|
        puts "Found jakl service at #{r.target}" if @@debug
        recvs = r.text_record['recvs'].split(',').collect {|g| g.strip }
        puts "  responds to: #{recvs.join(', ')}"  if @@debug

        if (succ_recvs = recvs & recv).any?
          puts "Sending to: #{r.target}:#{r.port}" if @@debug

          # B64 Encoded NAME;GROUP;MESSAGE
          data = [@options[:login], succ_recvs.first, message].map do |s| 
            Base64.encode64(s)
          end.join(';')
          TCPSocket.new(r.target, r.port).send(data, 0)
        end
      end

      sleep @options[:timeout]
      find_recipients.stop
    end
  end
end
server.rb
require 'rubygems'
require 'eventmachine'
require 'net/dns/mdns-sd'
require 'base64'

$:.unshift File.dirname(__FILE__)
require 'growl_handler'

module JaklEventServer
  def receive_data(data)
    # B64 Encoded NAME;GROUP;MESSAGE
    name, recv, message = data.split(';').map {|s| Base64.decode64(s) }
    Jakl::GrowlHandler.new.notify(recv, name, message)
    $stderr.puts "Name: #{name}, Recipient: #{recv}, Message: #{message}" if Jakl::Server.debug
  end
end

module Jakl
  class Server
    DNSSD = Net::DNS::MDNSSD

    @@debug = false

    def self.debug=(value)
      @@debug = value
    end

    def self.debug
      @@debug
    end

    def initialize(options={})
      default_options = {
        :recvs => "jakl", 
        :timeout => 5, 
        :login => ENV['USER']
      }
      @options = default_options.merge(options)

      validate_login!
    end

    def start
      DNSSD.register('jakl', '_jakl._tcp', 'local', 4180, 
                             {'recvs' => @options[:recvs], 'login' => @options[:login]})

      EventMachine::run {
        EventMachine::start_server "0.0.0.0", 4180, JaklEventServer
        puts "Listening for howls on 4180"
      }
    end

    def validate_login!
      # Yeah, I know there's a race condition here. So it goes.
      name_validation = DNSSD.resolve('jakl', '_jakl._tcp') do |r|
        if [r.text_record['login'], 
            r.text_record['recvs'].split(',')].flatten.include? @options[:login]
          puts "The name #{@options[:login]} is already taken. Sorry :\\"
          exit 1
        end
      end

      # Add username to recvs
      @options[:recvs] = [@options[:recvs].split(',') << @options[:login]].join(',')

      # Wait for resolv thread to come back
      sleep 3
      name_validation.stop
    end
  end
end

Posted in ,  | no comments

Snippet: Multi-Search and Replace

Posted by kev Mon, 16 Apr 2007 19:52:00 GMT

template = <<-EOT
%HEAD%
%SUBHEAD%

%BODY%

%FOOT%
EOT

replacements = {/%HEAD%/ => "A title", /%SUBHEAD%/ => "A subhead",
                /%BODY%/ => "Something about ruby perhaps", 
                /%FOOT%/ => "Creative Commons: BY-NC-SA"}

replacements.each do |search, replace|
  template.gsub! search, replace
end

puts template 
# =>
# A title
# A subhead
# 
# Something about ruby perhaps
# 
# Creative Commons: BY-NC-SA

Posted in  | no comments

On Twitter, Rails, and Community

Posted by kev Sun, 15 Apr 2007 20:13:00 GMT

An interview by a twitter developer has gotten a good deal of press lately. For many, it has reawakened the giant that is the “does Rails scale” debate, and others feel compelled to defend the framework.

The problem with Rails isn’t that Ruby is slow. It’s also not that Rails is slow, or big, or bloated, or doesn’t scale. The problem isn’t that Rails doesn’t support multiple databases or composite keys out of the box. The problem is that this is how the vocal minority in the community tends to respond to criticism.

So let’s all slow down and take a look at how we got here and what we can do to avoid it. That’s of course assuming we want to. Inflammatory posts certainly get lots of traffic, but I think Rails is in the spotlight enough we can actually talk like reasonable people and still get things done.

More after the jump.

Read more...

Posted in , ,  | 29 comments

Updates, Speaking, Writing

Posted by kev Fri, 06 Apr 2007 18:36:00 GMT

Hi guys. Gluttonous has been quiet lately, so I wanted to update you a bit on life in general.

First off, I haven’t yet talked about my work at Powerset. We’ve been quickly growing and I’m humbled by the type of people who join up on a daily basis. We’re about to hit 6 Ruby devs and 50 in the company, and for me it really feels like a dream.

That being said, I talk a bit more about what I’m actually doing at Powerset in an interview given over at the Working With Rails Blog in their series on Hackfest winners.

Additionally, I give an update on one of the reasons this blog has gone dry for a while at the end of the interview. You really shouldn’t miss this.

As far as speaking goes, I’ll be giving a talk on improving test coverage with Heckle at the Silicon Valley Ruby Conference in San Jose on April 21-22. The list of speakers is amazing, and I’m looking forward to the conf. If you’re there, come say hi.

Posted in , ,  | no comments

Link: Kathy Sierra Receives Death Threats

Posted by kev Mon, 26 Mar 2007 23:24:00 GMT

Death threats against bloggers are NOT “protected speech” (why I cancelled my ETech presentations)

I have cancelled all speaking engagements.

I am afraid to leave my yard.

I will never feel the same. I will never be the same.

– Kathy Sierra

So entirely not cool.

Posted in  | 2 comments

SD West Slides and Code

Posted by kev Thu, 22 Mar 2007 06:58:00 GMT

If you attended my talk at SD West today, here’s a zip with the slides and code we wrote in class. Thanks for coming out!

2 comments

Snippet: Shuffle an Array

Posted by kev Tue, 20 Mar 2007 07:47:00 GMT

class Array
  def shuffle
    sort { rand(3) - 1 }
  end
end

arr = (1..10).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr.shuffle
# => [1, 8, 6, 10, 9, 3, 7, 2, 5, 4]
arr.shuffle
# => [3, 7, 10, 4, 5, 8, 2, 6, 9, 1]

Posted in , ,  | 7 comments

Sorting Versions is Hard

Posted by kev Fri, 16 Mar 2007 17:58:00 GMT

class Package < ActiveRecord::Base
  def <=>(other)
    versions = self.version.split(".").push self.rel
    other_versions = other.version.split(".").push other.rel

    versions.size.times do |n|
      if versions[n] =~ /[^\d]/ || other_versions[n] =~ /[^\d]/
        comparison = versions[n] <=> other_versions[n]
      else
        comparison = versions[n].to_i <=> other_versions[n].to_i
      end
      return comparison unless comparison.zero?
    end

    return 0 
  end
end

[Package.new(:version => "0.0.8a"), 
 Package.new(:version => "0.0.8c"), 
 Package.new(:version => "0.0.8b")].sort.map(&:version)
# => ["0.0.8a", "0.0.8b", "0.0.8c"] 

[Package.new(:version => "0.0.8"), 
 Package.new(:version => "0.0.10"), 
 Package.new(:version => "0.0.9")].sort.map(&:version)
# => ["0.0.8", "0.0.9", "0.0.10"] 

Posted in ,  | 3 comments

When Config Files Go Bad

Posted by kev Thu, 08 Mar 2007 22:40:00 GMT

It is often necessary to check the content of a `configure.ac' file,
but parsing it yourself is extremely fragile and error-prone.  It is
suggested that you rely upon `--trace' to scan `configure.ac'.  For
instance, to find the list of variables that are substituted, use:


     $ autoconf -t AC_SUBST
     configure.ac:2:AC_SUBST:ECHO_C
     configure.ac:2:AC_SUBST:ECHO_N
     configure.ac:2:AC_SUBST:ECHO_T
     More traces deleted

-- info autoconf

Posted in  | no comments

Summer of Code Looking for Mentors

Posted by kev Thu, 08 Mar 2007 02:20:00 GMT

So it’s that time of year again. Google’s Summer of Code is just around the corner, and Pat Eyler is looking for mentors in the Ruby community.

Last year 10 Ruby projects were funded, but there can’t be projects without students and mentors.

If you’re interested in mentoring, send Pat an email at pat dot eyler at gmail dot com.

Posted in ,  | no comments

Older posts: 1 2 3 4 5 ... 17