Merb exception email notifier. May 13

1. Handle internal_server_error in your exceptions controller. Deliver email before render.

In app/controllers/exceptions.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Exceptions < Merb::Controller

  ...

  cattr_accessor :email_addresses

  def internal_server_error
    @exception = self.params[:exception]
    @exception_name = @exception.name.split("_").map {|x| x.capitalize}.join(" ")
    notify_emails if Merb.env?(:production)
    render
  end

private

  def notify_emails
    begin      
      return if self.class.email_addresses.blank?

      subject = "[#{@exception.class::STATUS}] #{@exception_name}: #{@exception.message}"
      mail_body = render(:template => "exceptions/error_email.txt")

      email = Merb::Mailer.new({ 
        :to => self.class.email_addresses.join(", "), 
        :from => "MyApp", 
        :subject => subject, 
        :text => mail_body })

      email.deliver!

    rescue Error => e
      Merb.logger.error("Error sending error email: #{e}")
    end
  end


end

2. Define your email template.

In app/views/exceptions/error_email.txt.rb:

1
2
3
4
5
6
7
8
<%= @exception_name %> (<%= @exception.class::STATUS %>): <%= @exception.message %>

URL: <%= "#{request.protocol}#{request.env["HTTP_HOST"]}#{request.uri}" %>
Parameters: <%= params[:original_params].inspect %>
                        
<% @exception.backtrace.each_with_index do |line, index| %>
  <%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %>:<%=line%>
<% end %>

3. Configure the mailer. This example uses sendmail.

In config/init.rb:

1
2
3
4
5
6
7
8
9
10
11
dependency "merb-mailer"

Merb::BootLoader.after_app_loads do

  ...

  # Mailer configuration
  Merb::Mailer.config = {:sendmail_path => "/usr/sbin/sendmail"}
  Merb::Mailer.delivery_method = :sendmail
  Exceptions.email_addresses = ["my@email.com"]
end

If you need something more complex, there is a merb_exceptions plugin from newbamboo.

Fix for XCode unit test error (exited abnormally with code 127). Apr 23

If you are getting the:


Test host '/path/to/App exited abnormally with code 127 (it may have crashed).'

error in XCode 3.1 (beta) when you try to run your unit tests then you need to:

  1. Get Info for the Unit Tests target.
  2. Choose the Build tab.
  3. Set the Architectures value to Native Architecture of Build Machine.

Freezing gems with Gem.use_path. Apr 09

Edge rails now has gem dependency support built in, with tasks to freeze in vendor/gems.

I have tried a bunch of different methods, and things like setting $GEM_HOME or using gemsonrails work well. But lately I have been using the Gem.use_paths method.

If you put this in the config/environments rb:

1
2
3
  Gem.use_paths(nil, [ "#{RAILS_ROOT}/vendor/rubygems" ])
  # Gem.path is now:
  # ["/Users/ghandford/Projects/my_rails_proj/vendor/rubygems", "/Library/Ruby/Gems/1.8"]

The vendor/rubygems directory is the same structure as system gems folder:

1
2
3
4
  vendor/rubygems/cache
  vendor/rubygems/doc
  vendor/rubygems/gems
  vendor/rubygems/specifications

The convention I use is any non-native gems get frozen with the project, and then all the native gems go in the system gem path, which gets you pretty close to clone/checkout and rake setup.

As far as unpacking and install gems, you just use gem install with GEM_HOME of your local project:


GEM_HOME=`pwd`/vendor/rubygems gem install capitate

A benefit of the use_paths method is that you can create scripts in your Rails project that use gems frozen in your project (and not necessarily have to require rails or environment.rb to get at them).

./script/my_non_rails_script
1
2
  require 'rubygems'
  Gem.use_paths(nil, [ File.dirname(__FILE__) + "/../vendor/rubygems" ])

Has anyone else tried this before?

Memory usage during file upload in AIR. Mar 28

Uploading files in AIR is a little bit problematic these days.

Here is a HTML control with a file form post, of a 700MB file and the memory usage before and after it starts going insane:

:O

To prove I am not doing anything weird, here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" showStatusBar="false" 
  initialize="onInitialize(event)">  

  <mx:Script>
    <![CDATA[
          
      import com.rwnage.s3.service.S3PostOptions;
      import flash.events.Event;
      
      public function onInitialize(event:Event):void {
        var accessKey:String = "0RXZ3R7Y034PA8VGNWR2";
        var bucketName:String = "rwnage_test";
        var objectName:String = "test_file";
        var contentType:String = "application/octet-stream";
        var secretAccessKey:String = "[PUT YOUR SECRET ACCESS KEY HERE]";      
        
        var postOptions:S3PostOptions = new S3PostOptions(bucketName, objectName, accessKey, 
          { contentType: contentType });

        var policy:String = postOptions.getPolicy();
        var signature:String = postOptions.getSignature(secretAccessKey, policy);

        html.htmlText = '<html><body> \
          <form name="upload" action="http://' + bucketName + '.s3.amazonaws.com/" method="post" enctype="multipart/form-data"> \
            Key to upload: <input type="input" name="key" value="' + objectName + '" /><br /> \
            Content-Type: <input type="input" name="Content-Type" value="' + contentType + '" /><br /> \
            <input type="hidden" name="AWSAccessKeyId" value="' + accessKey + '" /> \
            <input type="hidden" name="Policy" value="' + policy + '" /> \
            <input type="hidden" name="Signature" value="' + signature + '" /> \
            File: <input type="file" name="file" /> <br /> \
            <!-- The elements after this will be ignored --> \
            <input type="submit" name="submit" value="Upload to Amazon S3" /> \
          </form></body></html>';                                                     
      }
              
    ]]>
  </mx:Script>

  <mx:HTML id="html" width="100%" height="100%" paddingLeft="20" paddingTop="20"/>

</mx:WindowedApplication>

No I don't normally use mx:script tags, its a test case.

Eventually it malloc fails and crashes. This issue is Mac only and also occurs during a FileReference (or File) upload.

Actionscript splat (argument unpacking). Feb 14

In actionscript there is no splat operator or argument unpacking. In the case where you want to proxy calls to vararg methods you have to use the Function#apply() method.

1
2
3
4
var array:Array = [];
var stuffToAdd:Array = [ 1, 2, 3, 4 ];

array.push.apply(array, stuffToAdd);

It would be nice to have a splat/unpack operator:

1
2
3
array.push(*stuffToAdd);
// Or
array.push(... stuffToAdd);

The ecma mailing list discussed this use of super.apply(this, arguments). And specifically regarding argument unpacking in AS3:

We dropped this from AS3 for lack of evidence for its need.

I think any language with (... args) to Array needs the Array to (... args) if not for anything other than completeness. Does anyone know if this is planned for ES4 at least?

Socket output progress in AIR. Jan 17

Currently when using the flash.net.Socket api, there is no way to determine that buffered data has been written to the socket. (The flush method is non-blocking and applies only to the actionscript socket buffer).

This is a follow-up to a previous post.

This issue does not arise when using the new File api (and writing to files), since an event has been included in flash.filesystem.FileStream: OutputProgressEvent.OUTPUT_PROGRESS

If this (or a similar) event could be added to the Socket api, it would allow us to:

  • Send large amounts of data on a socket (without causing large memory usage as data buffers in memory).
  • Measure the current progress of data being sent on the socket.

We came upon this limitation from using our AS3 http client library, and trying to send large files as part of a multipart upload. However this issue would arise when sending any significant amounts of data on a socket. For example, an FTP or bittorrent client would be impossible.

This issue has been discussed in other places, but is probably more pressing now with the AIR releases.

require. Jan 14

Here are some different ways to require stuff in ruby. The funny ones courtesy of why the lucky stiff.

1
2
3
require "rubygems"
require "open-uri"
require "yaml"

[ "rubygems", "open-uri", "yaml" ].each { |s| require s }

%w[rubygems open-uri yaml].map(&method(:require))

File.dirname(__FILE__)

What got me looking at require was how annoying it is to use the File.dirname(__FILE__) syntax all the time in init.rb's, like:

1
2
3
4
5
require File.dirname(__FILE__) + "/parser/black_cat"
require File.dirname(__FILE__) + "/parser/dar"
require File.dirname(__FILE__) + "/parser/dc_nine"
require File.dirname(__FILE__) + "/parser/iota"
require File.dirname(__FILE__) + "/parser/jammin_java"

I am always just verbose with requires, I don't know why. It gets even less readable if you start using File.join(...). I was poking around in the rails source to see how they do requires and they use $::

1
2
3
4
5
6
7
$:.unshift(File.dirname(__FILE__)) unless
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

require 'action_controller/base'
require 'action_controller/request'
require 'action_controller/rescue'
...

$:

What is $:?

1
2
3
4
5
irb(main):001:0> $:
=> ["/usr/local/lib/ruby/site_ruby/1.8", "/usr/local/lib/ruby/site_ruby/universal-darwin8.0", 
"/usr/local/lib/ruby/site_ruby/1.8/universal-darwin8.0", "/usr/local/lib/ruby/site_ruby", 
"/usr/local/lib/ruby/1.8", "/usr/local/lib/ruby/1.8/universal-darwin8.0", 
"/usr/local/lib/ruby/1.8/universal-darwin8.0", "."]

Its the load path. It turns out $LOAD_PATH is a synonym. So that would take me:

1
2
3
4
5
6
7
$LOAD_PATH.unshift(File.dirname(__FILE__))

require "parser/black_cat"
require "parser/dar"
require "parser/dc_nine"
require "parser/iota"
require "parser/jammin_java"

But is this a bad idea? What if the load path gets large? Do we care?

politweets. Jan 09

We built another twitter app, this time tracking candidates names. Its called politweets.

One #1 referrer today was from 100shiki. I've always been big in Japan.

Hopefully this one is a little more relevant than tracking curse words. But I still think the bitch balls'er is awesome.

nginx conf with alternate cache dir. Jan 07

Its a good idea to use an alternate cache directory in rails (in your environment.rb):


config.action_controller.page_cache_directory = RAILS_ROOT + "/public/cache/"

This makes it easier to sweep. To setup nginx to pick up from a rails alternate cache directory like public/cache, I used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if ($http_user_agent ~* "(iPhone|iPod)") {
  rewrite ^/$ /iphone break;
  proxy_pass http://mongrel-pt;
  break;
}

if (-f $request_filename) {
  break;
}

if (-f $document_root/cache/$uri/index.html) {
  rewrite (.*) /cache/$1/index.html break;
}

if (-f $document_root/cache/$uri.html) {
  rewrite (.*) /cache/$1.html break;
}

if (-f $document_root/cache/$uri) {
  rewrite (.*) /cache/$1 break;
}

if (!-f $request_filename) {
  proxy_pass http://mongrel-pt;
  break;
}

It was a little tricky to figure out to use the $uri variable. The first rewrite is used for iphone requests so it doesn't get the non-iphone cached page.

On duck typing and interfaces. Jan 01

I was looking at different type systems (apparently there are 4 dimensions) in different languages and ran across this use of duck typing in C# :

For example, the C#'s foreach operator already uses duck typing. This might be surprising to some, but to support foreach in C# you don't need to implement IEnumerable! All you have to do is:

Provide a public method GetEnumerator that takes no parameters and returns a type that has two members: a) a method MoveMext that takes no parameters and return a Boolean, and b) a property Current with a getter that returns an Object.

Do you have multiple fine-grained interfaces (like IEnumerable and IEnumerator and ISupportsAdd) or a single interface with all the methods and maybe they throw UnsupportedOperationExceptions if they don't actually support a particular call (like in a mutable versus immutable collection)?

The closer you get to more fine grained interfaces like ISupportsAdd<T> or Closeable, defining a contract for a single method Add(T obj) or Close is that you might as well just ask the class if it has that method (if it "quacks") instead of whether it implemented a single method interface. Better yet, just assume it and let it throw an error at runtime. Whatever the compiler doesn't give you, some decent test coverage will.

But what strikes me as particularly great is that foreach is obviously such a powerful thing to restrict to a single interface, and so they chose "Duck Notation", because in this case it is worth it.

This reminded me of why I like ecma 4 (and its derivatives like actionscript). You have freedom to use static and dynamic typing at will.

If you check out my take on an AS3 HTTP client library, HttpRequest class and allowing the request body to be of any type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class HttpRequest {
  
  // Request method. For example, "GET"
  protected var _method:String;
  
  // Request header
  protected var _header:HttpHeader;
  
  // Request body
  protected var _body:*;
  
  /**
   * Create request.
   *  
   * The request body can be anything but should respond to:
   *  - readBytes(bytes:ByteArray, offset:uint, length:uint)
   *  - length
   *  - bytesAvailable
   *  - close
   *  
   * @param method HTTP Method, for example 'GET'
   * @param header HTTP request header (or null)
   * @param body HTTP request body (or null)
   *  
   */
  public function HttpRequest(method:String, header:HttpHeader = null, body:* = null) {
    _method = method;
    _header = header;      
    _body = body;

 ...

I think this is a good choice because:

  1. The IDataInput interface (that ByteArray already implements) doesn't have a length property and we need to know the request body size (Content-Length) before we send.
  2. The IDataInput interface wants you to implement a ton of read methods, which would make it annoying to force people to implement.
  3. ByteArray has these methods and also implements the length property already. It quacks.
  4. If we created our own interface with these methods, we can't add this interface retroactively to ByteArray.
  5. I wrote it. Heh.

The other option is to subclass ByteArray and have that implement the new interface, but it just seems awkward (in this case) having any empty subclass solely for the point of enforcing an interface.

Another place where this principle applied for me is in HttpSocket. We need to support both Socket (for http) and TLSSocket (for https). TLSSocket has all the same (mostly) methods as Socket (but does not extend Socket; yay for composition over inheritance). Creating a proxy to delegate to different socket implementations based on the http scheme is an option but to me wasn't worth it. And using a design pattern to overcome the lack of an appropriate and existing interface just seems to make things worse.

Closures. Dec 27

There is alot of debate going on between the CICE and BGGA proposals for Java closure support.

I thought I would try to experiment with the different proposals and compare to languages I am using right now. I picked something simple to try out, like summing an array of integers:

Also check out good closures, bad closures.

Actionscript 3

Assume you have a reduce function, for example:

1
2
3
4
public function reduce(array:Array, f:Function, index:int = 0):* {
  if (index == array.length - 1) return array[index];
  return f(array[index], reduce(array, f, index + 1));
}

Then the syntax is:

1
2
var array:Array = [ 1, 2, 3 ];
reduce(array, function(x1:Number, x2:Number):Number { return x1 + x2; });

Ruby

Sorry I have to.

1
2
array = [ 1, 2, 3 ];
array.inject { |sum, n| sum + n }

or with Symbol#to_proc:

1
2
array = [ 1, 2, 3 ];
array.inject(&:+)

Java (CICE)

Assume you have a "Reducer" interface (and Collections#reduce):

1
2
3
interface Reducer<T> {
  T reduce(T t1, T t2);
}
1
2
3
4
List<Integer> = Arrays.asList([ 1, 2, 3 ]);
Integer sum = Collections.reduce(list, Reducer<Integer>(Integer x1, Integer x2) {
  return x1 + x2;
});

I think this is right, help? So you might have to declare new interfaces if the API doesn't have them. It would probably have common ones though.

Java (BGGA)

Assume you have Collections#reduce:

1
2
List<Integer> = Arrays.asList([ 1, 2, 3 ]);
Integer sum = Collections.reduce(list, { Integer x, Integer y => x+y });

The other powerful thing with the BGGA proposal is the control invocations, like:

1
2
3
4
5
6
7
8
9
10
Lock lock = new Lock();

Lock.withLock(lock) {
  doSomething();
}

// Automatically close stream when done
with(FileInputStream f : exp) {
  doSomething();
}

Like in Ruby:

1
2
3
4
5
6
7
8
mutex = Mutex.new
mutex.synchronize do
  doSomething
end

File.open("foo.txt", "w") do |file|
  doSomething
end

Since I've been doing Ruby lately, I know which Java proposal I prefer. The CICE doesn't address the problem that within an anonymous inner instance return, and this and any anonymous instance methods are scoped to that anonymous instance and not the enclosing method.

I guess CICE is more about reducing the verbosity of the anonymous instance declaration and allowing mutability on public variables, and its not a real closure in the strict definition. I know people refer to anonymous inner classes as the poor man's closure, but I don't really think of it as a closure, I think of it as, you know, an anonymous inner class.

Flush. Dec 27

So I decided to try to overcome the Leopard File.upload() bug by rolling my own http client: as3httpclientlib which even supports https with the TLS (Socket) support from as3crypto (which is great). The HTTP protocol is pretty simple and I figured it would be a good way to learn the internals and look smart reading ancient RFC's. All you need is a Socket class and nice, flash api has one at flash.net.Socket

I got everything major working like GET, PUT and POST with multipart/form-data and reading chunked encoding and all that stuff. Not too hard since there are open source http libraries for every major language. I looked at ruby Net::HTTP (stole some regex's) and Apache Httpclient (stole your multipart boundary).

I got to the point where I was testing uploading larger files, like in:

1
2
3
4
var client:HttpClient = new HttpClient();
var uri:URI = new URI("http://mybucket.s3.amazonaws.com/big.mp3");
var testFile:File = new File("app:/test/assets/big.mp3");
client.upload(uri, testFile); 

It turns out that when you call _socket.flush() (in HttpSocket) it doesn't actually block, which you might expect. After thinking on it, this isn't totally shocking since the whole threading model behind actionscript, you don't really ever block (like there is no such thing as a modal dialog in flash). The problem is without blocking or an event to notify of a flush, the socket just gets filled with the request body all at once (all 800MB's or whatever file size).

It turns out other people ran into this as well. If you look at (FlexFTP) source, the UploadInv.as file, you will see block of code commented out and replaced by a setInterval(.. , 300). Nice. I guess that upload is capped at ~12kb/sec.

Any of the links in the comments off this blog post are useful too.

If you look at FileStream object you will see it has an outputProgressEvent so that would be useful, you know, if Socket had that too.

I don't know how you get around this issue, other than trying to guess how fast the socket can transmit and try to stay under that. So now I'm not sure what to do, other than abandon it and come back to it when its fixed, which hopefully will be soon. Thankfully, I can go back to ruby and maybe play with some RubyCocoa or Shoes framework.

Update: Feel free to chime in at Adobe Air Forum or Feature Request/Bug Report Form

Twittertale now with iPhone. Dec 20

Thanks to Min we now have iPhone support.

twittertale.com. Dec 17

Some friends and I did a twitter thing where we catch tweets with curse words and put them on twittertale.com. Its kind of funny. The backend twitter side is xmpp4r and xmpp4r-simple straight up ruby. And the web side is fresh rails 2.0 happiness.

On a side note, if you are doing ruby sans rails with mysql gem, you need to take care of the character encoding on the connection, which you can do by sending SET NAMES UTF8; after connect or I guess by setting the default on connection settings in my.cnf

View paths in Rails 2.0. Dec 17

New in rails 2.0: ActionController::Base.append_view_path

This is useful for loading views from a plugin or gem:


ActionController::Base.append_view_path(File.dirname(__FILE__) + "/views")

There is also ActionController::Base.prepend_path.

Contracts. Dec 04

There is no reason you couldn't define a Ruby DSL or library (or even test harness) to assert or enforce "contracts", method signatures, responds, whatever. All I could find so far, are these:

  • handshake: Handshake is an informal design-by-contract system written in pure Ruby. It‘s intended to allow Ruby developers to apply simple, clear constraints to their methods and classes.
  • ruby-contract: Represents a contract between Objects as a collection of test cases. Objects are said to fulfill a contract if all test cases suceed.

I don't think I have an opinion yet. When would you really need something like this?

Protocol. Dec 02

The similarity of this approach to protocols was clear to users of OOP languages long ago. Smalltalk and ObjectiveC, both dynamic OOP languages, have long used the term Protocol to refer to this concept.

The Protocol concept is certainly useful, if only to give a name to a particular set of messages.

-- Duck Typing and Protocols vs. Inheritance

It would be nice to know ahead of time what methods a Ruby object accepts. It would be nice to talk about a bunch of classes as all defining read, write and size methods as IO classes. Even if you could use some kind of marker interface (or marker module) that says, "Hey, you probably don't care but my object might respond to these methods..", ruby doesn't itself protect or enforce it anyway.

popen'ing part 2. Nov 09

I guess I should rtfm before blogging about something. This is my new IO.popen block which allows you to run a process which wants input, like the adt (Adobe Debug Tool) package task (which asks for your certificate password):

1
2
3
4
5
6
7
IO.popen(@cmd) do |f|                        
  while s = f.read(1)
    printf s
    STDOUT.flush
  end
end
@process = $?

View the airake/runner.rb. The IO.read call if not given args will block until EOF. This means we can't output anything until the process ends which makes it appear hung if its asking for input. The waitpid call in the previous version just picks up the Process::Status since the read was the blocking call, but you can get that from $? after the popen block returns. I needed to add the STDOUT flush since printf isn't guaranteed to (and wasn't) outputing on the adt password input since I think it waits for a newline. Also this totally works on windows, although the whole read char + printf + flush makes it feel like a typewriter. This might be solved by using the IO.read_nonblock method. I'll need to look into that.

Also, I updated the blog css, which I was told was depressing. Now its a total ripoff of coudal.com instead of danwebb.net

popen'ing . Nov 07

I updated airake to work on windows (thanks Todd). Instead of using system (or %x), I am trying IO.popen:

1
2
3
4
IO.popen(@cmd) do |f|
  @output = f.read
  @process = Process.waitpid2(f.pid)[1]
end

and in windows you'll need to cmd.exe /c it:


@cmd = RUBY_PLATFORM =~ /win32/ ? "cmd.exe /c #{cmd}" : cmd

This thread was helpful and I popened a new airake/runner.rb version. The FCSH daemon still doesn't work in windows yet.

Update: Google Reader AIR app. Oct 31

Download GReader-0_1_2.air

I updated the app to work in Leopard. I have no idea why but using the url, http://www.google.com/reader/view, instead of http://reader.google.com fixed it. I think that might mean there is a redirect issue? I changed the dock icon unread count to just show the number in the top right. Also I added an option to enable the hicksdesign google reader theme. I had to mess with it since apparently AIR webkit doesn't support the css data uri scheme, which is annoying.

I apply the style through the javascript bridge:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    [Embed(source="/assets/styles/greader/greader.css", mimeType="application/octet-stream")]
    private var cssClass:Class;

    private var html:HTML;
    ...

    public function applyStyle(e:Event):void {
      var cssByteArray:ByteArrayAsset = ByteArrayAsset(new cssClass());
      var css:String = cssByteArray.readUTFBytes(cssByteArray.length);
      
      var document:JavaScriptObject = html.htmlControl.window.document;
      
      var head:JavaScriptObject = document.getElementsByTagName("head")[0];
      var node:JavaScriptObject = document.createElement("style");
      node.type = "text/css";
      node.appendChild(document.createTextNode(css));
      head.appendChild(node); 
    }

Google Reader AIR app. Oct 29

Download GReader-0_1.air

I was playing around with the actionscript to javascript bridge and built this Google Reader AIR app:

I find the unread count from 'reading-list-unread-count':

1
2
3
var html:HTML = ...
var window:JavaScriptObject = html.htmlControl.window;
var unread:String = window.document.getElementById("reading-list-unread-count").innerHTML.toString();

Then I generate a dock icon (ignore all the hard coded pixel math):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[Embed(source="/assets/app_icons/greader/icon_128.png")]
private var iconClass:Class;

[Embed(source="/assets/fonts/Verdana.ttf", fontName="GReaderDockIconFont", mimeType="application/x-font-truetype")]
private var fontClass:Class;
....

private function draw():void {  
  var icon:BitmapAsset = BitmapAsset(new iconClass()); 
      
  var shape:Shape = new Shape();
  shape.graphics.beginBitmapFill(icon.bitmapData, new Matrix(), true, false);
  shape.graphics.drawRect(0, 0, dimensions, dimensions);
  shape.graphics.endFill();
  
  shape.graphics.beginFill(0xDF0013);
  shape.graphics.drawCircle(32, dimensions - 32, 32);
  shape.graphics.endFill();
  addChild(shape);
  
  if (unreadCount > 0) addChild(buildText());      
}

public function buildText():Bitmap {
  var countText:TextField = new TextField();
  countText.embedFonts = true;

  var format:TextFormat = new TextFormat();
  format.font = new fontClass().fontName;
  format.color = 0xFFFFFF;      
  format.size = 26;
  format.align = TextFormatAlign.CENTER;
  
  countText.defaultTextFormat = format;
  countText.selectable = false;      
  countText.text = unreadCount.toString();
  countText.height = 32;
  countText.width = 54;
  
  var bitmapData:BitmapData = new BitmapData(64, 32, true, 0x00000000);
  bitmapData.draw(countText);      
  var bitmap:Bitmap = new Bitmap(bitmapData);
  bitmap.smoothing = true;
  bitmap.y = 128 - 52;
  bitmap.x = 4;
  return bitmap;
}

Set the dock icon with the generated bitmap data:

1
2
if (Shell.shell.icon is InteractiveIcon) 
  InteractiveIcon(Shell.shell.icon).bitmaps = icons;

For more info on dynamic dock icons, see Generating Dynamic Dock and System Tray Icons in AIR

I think the next step is taking a bunch of greasemonkey js scripts and adding them in. This might be how you would do that:

1
2
3
4
5
6
7
8
var html:HTML = ...
var window:JavaScriptObject = html.htmlControl.window;      
var head:JavaScriptObject = window.document.getElementsByTagName("head")[0];         
var script:JavaScriptObject = window.document.createElement('script');
script.type = 'text/javascript';
//script.src = 'http://lolcats.com/script.js';
script.innerHTML = "alert('HAI.. KTHXBYE');";
head.appendChild(script);

Update: I've seen it tack the CPU when interacting in a couple places so I will try to figure out if its AIR webkit being buggy or me. Also will fix the external link lameness at some point.

Update: Oops. Looks like it doesn't work in Leopard.

browsair - Letting sites live in your dock. Oct 13

I wrote an AIR project generator for airake called browsair. It will build you a AIR webkit browser app targeted at a web site, so you can let it live in you dock (or tray whatever), instead of that 4th tab on the 2nd firefox window.

Theoretically someone could make a packaged browser + greasemonkey + stylish AIR app for particular web sites that growl notifies and lives in the dock and be more in your face; but this scaffold right now is pretty basic.

I should also mention there is a website called airifier which does this as well, except I think they give you a packaged app only, without the source.

I also got a bunch of fixes into this version of airake; packaging is fixed, and there is a task for creating certificates, among other things. I just deployed version 0.2.4 to rubyforge so it should be up by the time you read this.

The usage:

browsair GReader http://reader.google.com path/to/rss_icon_128x128.png

Creates an AIR project for a google reader AIR app. In the GReader project, run:

rake air:package CERTIFICATE=path/to/cert.pfx

If you need to generate a certificate:

rake air:certificate CERTIFICATE=../ducktyper.pfx

I built a couple already if you want to bypass all that compiling:

(If you don't have the AIR runtime installed, get it here)

Parsing bash.org quotes for eggdrop bottalk.tcl. Oct 10

Here is a script to scrape quotes from bash.org, and create a bottalker data file (source'd tcl script) for our eggdrop. If you want to chat with it, come to #oaktoncc on freenode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
require 'rubygems'
require 'open-uri'
require 'hpricot'
require 'cgi'

count = 10
last_page = 407

lines = []

count.times do |i|
  page = last_page - i
  uri = "http://bash.org/?browse&p=#{page}"
  puts "#{uri}"
  doc = Hpricot(open(uri))
    
  doc.search("//p[@class='qt']").each do |element|
    
    quote = element.inner_html

    quote.split(/\n/).each do |line|
      line = CGI::unescapeHTML(line)
      line.gsub!(/<br\s*\/>/, "")
      line.gsub!("&nbsp;", " ")
      
      # Different formats (otherwise ignore):
      # <name> blah
      # (@name:#channel) blah
      # name: blah
      # [name] blah
      
      if line =~ /^\s*<.*?>(.*)/ or line =~ /^\s*\(.*?\)(.*)/ or line =~ /^\s*.+?\:(.*)/ or line =~ /^\s*\[.+?\](.*)/ 
        lines << $1.strip
      end
      
    end
    
  end
end

File.open("BotTalker_data_bash_org.tcl", "w") do |f|
  f.puts("# Bot Talker Data file.")
  f.puts("set TalkzStrArray {")    
  lines.each do |line| 
    next if line =~ /[\{\}\\]/ # Ignore lines with {}\ chars since it screws up the tcl source
    line.gsub!(/\[/, "(")
    line.gsub!(/\]/, ")")
    f.puts(" {#{line}}") 
  end
  f.puts("}")
end

Updated airake to support AIR/Flex Beta 2. Oct 05

I updated airake to support new AIR/Flex beta 2 build.

sudo gem update airake

I changed the fcsh tasks to:

rake fcsh:start
rake fcsh:stop
rake fcsh:restart

airake - Rake tasks and generators for adobe AIR apps. Sep 10

airake gives you tasks for compiling, debugging, testing and packaging Adobe AIR applications. It also has a basic project generator/scaffold. All this was made possible because of the prolific newgem and rubigen gems.

To get started, checkout airake.rubyforge.org

The tasks and project might be a little Flex specific (cause of the particular project we are working on), so feel free to poke around or contribute back. Also I only tested it on MacOSX.

Some of the FCSH (flex compiler shell) daemon and wrappers are from the Sprout project, so be sure to check them out.

Contact

My name is Gabriel Handford.
I work in Washington DC.
I am a Ruby "professional".

gabr@gmail.com

ducktyper.com

Projects

  • capitate: Capistrano recipes, plugins and templates (2008 / Ruby / MIT)
  • as3httpclientlib: AS3 http client library (2008 / AS3|AIR / MIT)
  • politweets.com: Twitter candidates tracker (2008 / Ruby|Rails)
  • twittertale.com: Twitter curse word tracker (2007 / Ruby|Rails)
  • airake: Rake tasks and generators for Adobe AIR apps (2007 / Ruby / GPL)
  • dclicio.us: Aggregates event info for Washington DC concert venues and provides feeds and ical/gcal links
  • Slickr: Flickr OpenGL Library and Screensaver (2005 / C# / Windows / GPL)
  • RandomWeb: Screensaver displays random digital camera photos through google image search (2004 / Cocoa / OS X / GPL)

Stuff you should read

Lab NotesRaganwaldigvita

This blog uses mephisto, neoprint-m319 font and coderay.