Archive for November, 2010

Wordle

Discovered wordle on someone’s twitter feed. I love this thing. Here’s the wordle of my post on Enyo:

wordle on enyo

Enyo

I missed webOS Developer Day, but I took a look at the presentation of the Enyo framework (video below). I like most of the bullet points: faster performance, hardware acceleration built-in, etc. However, one point that’s troubling me is the idea of using higher-level controls instead of divs. From the bit of code presented in the final demo, it looks like layout (and pretty much all UI stuff) will be done using JavaScript widgets, which are translated at some point into the appropriate HTML constructs. Obviously, widget-centered development isn’t a new concept (MFC, WinForms, Cocoa… sproutcore and such on the web) nor is it necessarily a bad one (getting something presentable up and running is easier), but there always seems to be a very real and very large loss of flexibility.

When a widget doesn’t look or function exactly how you need it to, and it becomes necessary to make a new one, development within the widget framework can range in difficulty from trivial to near-impossible. As I mentioned when I wrote about Adobe Air, HTML/CSS isn’t perfect, but it’s the most flexible layout and styling framework I’ve come across. Abstracting away that flexibility in favor of plug-and-play widgets makes me cringe… it’s a nice idea, it’s a very (object-oriented) developer-ish idea, but it usually comes with a pretty high cost.

From working with both HTML/CSS and WinForms extensively, I’d say the widget-centered framework model used for desktop apps shouldn’t be replicated for web development. In fact, it should be the other way around: the flexibility of a HTML/CSS-esque system should be brought to the desktop.

How this will play out for Enyo, I don’t know. I’m cautiously optimistic. Being a web framework, everything still boils down to HTML and CSS, but it remains to be seen what level of manipulation will be allowed or make sense (in terms of performance, coding difficultly, etc.) at that level.

dotspott 1.3.x for webOS

dotspott for webOS v1.3.0 should now be available in the webOS app catalog.

dotspott webOS screenshot 1 dotspott webOS screenshot 2 dotspott webOS screenshot 3

Primarily, there’s a new look compared to the older hotspotdot app. Not a whole lot of new features, but two noteworthy additions:

  • dotspott will show your current locations and, using the Google Map API, reverse geocode the latitude, longitude pair to tell you where you are.
  • Support for attaching photos to spotts

A smaller update (v1.3.1) should out in the coming week with some minor stylistic additions.

dotspott

I’ve been working on a fairly major update to hotspotdot, both the web client and the webOS mobile app.

First, a new name, accompanied by a new logo.
hotspotdot is now dotspott:

dotspott logo

This should (hopefully) put an end to any confusion that the app has anything to do with wi-fi hotspots. It also allows for a much nicer URL as I was able to get dotspott.com (much more elegant compared to the previous, my.hotspotdot.net).

There’s some minor style changes to the index page. Also, some additional code for some minor improves in user experience, such as auto-focus on input fields when a dialog comes up.

dotspott index page

The most significant changes are to the main interface. There’s a new look and you’ll notice support for uploading photos. Also, there’s now a 2-column layout; this avoid the issue of having to keep scrolling down the page to show a new location on the map (which was at the top of the page). The map and header areas are fixed (position: fixed), so as you scroll, only the list of locations on the left will move. I debated whether to do this or to contain the list of locations in a div with overflow:auto or overflow:scroll, so the list would have its own scrollbar, and you wouldn’t scroll the entire page to move up and down the list. I have somewhat of a dislike for both solutions, although one was necessary. I opted for the position:fixed approach because manipulating scrollbars within a page is impossible on a touchscreen device (perhaps we’re at the time where we’ll see the end of overflow:auto and overflow:scroll, along with :hover)

dotspott main interface

The updated webOS app is almost done. I’m just doing some last minute testing before I put it up in the app store, so I’ll have some info and screenshots up soon.

Edit: h/t to Shifting Pixel for the cool Smart Image Resizer; I really wasn’t feeling up to writing image resizing and caching routines, and this little library was a perfect solution.

Square, accept credit card payments anywhere

I don’t really have a use for it, but Square seems pretty damn cool…

Accept card payments with no contract, monthly fees, or hidden costs. Just plug a free card reader into your mobile device and start swiping.

square credit card payment

The transaction fees are reasonable (I think, I’m not really an expert here):

2.75% + 15¢ for swiped transactions

3.5% + 15¢ for keyed-in transactions

There are no activation, gateway, monthly, early termination, or hidden fees.

JavaScript MD5 hash for air.FileStream

This is the MD5 file hash used in progTools.

You can copy and paste the code below or get it via. bitbucket.

The code builds upon Paul Johnston’s MD5 implementation and you’ll need to include his code first.

<!-- Paul Johnston's MD5 implementation -->
<script type="text/javascript" src="tools/md5/md5.js"></script>

<!-- Additional MD5 functions for working on air.FileStream; requires Paj's md5.js -->
<script type="text/javascript" src="tools/md5/md5_file.js"></script>

core_md5_ex() is a modified/hacked version of the original core_md5() to allow for progressively processing chunks of data, instead of doing it all in one go.

// modified version of Paul Johnston's MD5 implementation
function core_md5_ex(x, len, abcd_start, append_padding, total_len)
{
/* append padding */
if (append_padding) {
    x[len >> 5] |= 0x80 << ((len) % 32);
    x[(((len + 64) >>> 9) << 4) + 14] = total_len*8;
}

var a = abcd_start[0];
var b = abcd_start[1];
var c = abcd_start[2];
var d = abcd_start[3];

for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;

a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);

a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}

return Array(a, b, c, d);
}

What’s changed is:

  • We pass in the starting values (which is the current hash on the data thus far, minus the necessary padding)
  • We use a variable to determine whether or not to append padding to the input; this is only done when processing the last block on data read from the stream.

hex_md5_stream() is the function which processes the stream: reading in chunks, transforming the bytes in inBytes into an array 4-byte WORDs, and calling core_md5_ex() with the necessary data.

function hex_md5_stream(inStream)
{
    
var abcd_start = new Array();
    abcd_start.push(1732584193);
    abcd_start.push(-271733879);
    abcd_start.push(-1732584194);
    abcd_start.push(271733878);
    
    
var inBytes = new air.ByteArray();
    
var appendPaddingToBlock = false;
    
var totalLen = 0;
    
    
while (inStream.bytesAvailable > 0)
    {
        inStream.readBytes(inBytes, 0, Math.min(8192, inStream.bytesAvailable));        
        
if(inBytes.length < 8192)
        {
            appendPaddingToBlock =
true;
        }
        
        totalLen += inBytes.length;
        
        bin =
new Array();
        
for(var i = 0; i < inBytes.length * 8; i+=8)
            bin[i>>5] |= (inBytes[i>>3] & 0xff) << (i%32);
        
        abcd_start = core_md5_ex(bin, inBytes.length*8, abcd_start, appendPaddingToBlock, totalLen);
        inBytes.clear();
    }
    
    
return binl2hex(abcd_start);    
}

Here’s the top-level view of how it works:

var inStream = new air.FileStream();
                
var file = new air.File();
file.url =
"file:///" + filename;                

inStream.open(file, air.FileMode.READ);
var md5result = hex_md5_stream(inStream);                 
inStream.close();