Showing posts with label Woot. Show all posts
Showing posts with label Woot. Show all posts

Tuesday, December 09, 2008

A Spry Woot-Off Tracker

Although Flex is a neat tool, good ol' Javascript has been around for quite a while, and I thought I'd try to build a Woot-Off tracker using Flex's Javascript cousin, Spry. Conveniently, there's a Woot-Off going on today.

Spry is an interesting Javascript toolkit since it focuses on data extraction and presentation and widgets. In this example, we're not using any widgets, but we are taking advantage of the Spry Dataset tools to grok the XML stream from Woot. Just like with the AIR version, we need to use a proxy to grab the Woot XML stream.

Spry's DataSet works by allowing the developer to query the data set. Since Woot is providing an RSSish feed, we use the XMLDataSet class. By using braces, the dataset's contents can be accessed using the name of the XML tag. So to grab the price, we use {woot:price}. It's relatively simple.

The challenging part is for data that's not quite perfectly formatted. In this case, the percentage needs to be multiplied by 100. We do that inside the Observer. The Observer can update the contents of the dataset. So you can simply create an observer function and change away. The description also needs to be cleaned up since its HTML entities do not provide the needed effect. We actually want to use the tags, so we unentify them.

The timer is actually easier than in Flex, since the timer comes automatically with the DataSet with the loadInterval option. The only thing we need to do is speed it up and slow it down at the appropriate time.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:spry="http://ns.adobe.com/spry">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Spry Woot Tracker</title>
<link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.6.0/build/reset-fonts-grids/reset-fonts-grids.css"/>
<link href="css/wootTracker.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="includes/xpath.js"></script>
<script type="text/javascript" src="includes/SpryData.js"></script>
<script type="text/javascript">
// we don't want to use cached data, and we need to reload every 30 seconds
var dsWootInfo = new Spry.Data.XMLDataSet("WootProxy", "rss/channel/item", {useCache: false, loadInterval: 30000});

var quickCheck = false; // Is the timer sped up

// Observer watches for when data changes and modifies for presentation
function wootObserver(notificationType, dataSet) {
if (notificationType == "onDataChanged") {
if (dataSet) {
var data = dataSet.getData();
var soldout = data[0]["woot:soldoutpercentage"];
data[0]["woot:soldoutpercentage"] = soldout*100;
var desc = data[0]["description"];
// Description contains HTML entities, fix them
// Something strange is going on with the formatter it's just desc not descdesc
desc = desc.replace(/&gt;/g, ">");
desc = desc.replace(/&lt;/g, "<");
desc = desc.replace(/&quot;/g, '"');
desc = desc.replace(/[\u201C\u201d]/g, '"');
data[0]["description"] = desc ;

// Determine if it's time to speed up or slow down
if (quickCheck) {
if (soldout < .95) {
dsWootInfo.stopLoadInterval();
dsWootInfo.startLoadInterval(30000);
quickCheck = false;
}
} else {
if (soldout > .95) {
dsWootInfo.stopLoadInterval();
dsWootInfo.startLoadInterval(1000);
quickCheck = true;
}
}
}
}
};

dsWootInfo.addObserver(wootObserver);

</script>
</head>
<body id="wootTracker">
<noscript>
<h1>This page requires JavaScript. Please enable JavaScript in your browser and reload this page.</h1>
</noscript>
<div id="doc" spry:region="dsWootInfo">
<h1 id="wootName"> {title} </h1>
<div style="float:left; padding-right: 10px">
<img src="{woot:thumbnailimage}" />
<h3 id="wootPrice">{woot:price} </h3>
<h4><a href="{woot:purchaseurl}" target="_blank">Buy This Woot</a></h4>
<h3 id="wootPercent">{woot:soldoutpercentage}% Sold Thus Far</h3>
</div>
<div id="description" >{description}</div>
</div>
</body>
</html>


And here's the CSS:

body {
background:#EDEDED none repeat scroll 0 0;
color:#191919;
margin:0;
padding:0;
}
h1 { font-size: 182%; margin-bottom: 0.5em}
h3 {font-size:138.5%; margin-bottom: 0.5em}
h4 {font-size:123.1%; margin-bottom: 0.5em}
li {list-style-type:disc; list-style-position:inside}
strong {font-weight:bold}
p {margin-top: 1em}


This application can be run inside Tomcat or wherever you have a proxy running.

Friday, November 21, 2008

An AIR based Woot-Off tracker

I've been going through some Flex training this week. It's an interesting tool, and pretty easy to make a quick application. Unfortunately, the training has been a bit robotic in terms of being very prescriptive on how to perform somewhat elementary programming. So it was time to take a break and actually try to attempt something that would be useful....or at least somewhat useful.

Since we had a Woot-Off yesterday, I decided to use Flex to write a Woot-Off tracker. A handy little AIR application to see when a new item appears. It's a simple single windowed application that polls Woot's API every 30 seconds, groks the RSSish feed, and displays information about the item being sold.

In addition, it provides handy information regarding the status of the sale in terms of percentage of items sold and a button to purchase the product, or manually check Woot for an update. If the percentage sold is above 94%, it ramps up the polling process to check Woot every second, since you never know when the BOC will appear.

The application is hardly complete. It lacks any style or substance in terms of look and feel. It also neglects the ability to run in the system tray (ala Twhirl or Tweetdeck) and update the user that an item might be selling out soon or that a new item is available. Right now it simply runs on the screen.

Of course, the trickiest part of this application is the need to run a Proxy service to hit an external URL. Due to Flash's security model, and the lack of a crossdomain.xml file at Woot, you need to have a local service running that will act as a proxy. A quick Java servlet and the very very lightweight Winstone servlet container. Ideally, you would launch this app with a little batch script that spun up your Servlet based proxy and then spun up the AIR app.

So let's walk through the source. That way, all of you out there who've actually done a lot of Flex and look at this and let me know what a BOC it is. :D First we'll look at the AIR app, and finally the Java based Proxy.


<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="init()" width="500" height="370" xmlns:utils="flash.utils.*">
<mx:Script>
<![CDATA[
import flash.net.navigateToURL;
import flash.utils.Timer;
import mx.formatters.Formatter;
import mx.formatters.NumberFormatter;
import mx.collections.ArrayCollection;
import mx.controls.Image;
import mx.rpc.events.ResultEvent;

private var START_QUICK_POLL_PERCENT:Number = 0.94;

// a 30 second and a 1 second Timer
private var wootPing:Timer = new Timer(30000, 1000000000);
private var wootEndPing:Timer = new Timer(1000, 1000000000);
private var checks:int = 0;

[Bindable] private var wootItemText:String = "No Woot Found...Yet";
[Bindable] private var wootItemPrice:String = "$10,000,000.00";
[Bindable] private var wootItemPercent:String = "0% Sold";
[Bindable] private var wootItemLink:String = "http://www.woot.com";
[Bindable] private var checkText:String= "Checked\n0 Times";
[Bindable] private var itemImgURL:String = "";

// Setup the Timers, and start the default timer
private function init():void {
wootPing.addEventListener(Event.ACTIVATE, wootHandler);
wootPing.addEventListener(TimerEvent.TIMER, wootHandler);
wootPing.start();
wootEndPing.addEventListener(TimerEvent.TIMER, wootHandler);
}

// Handles the purchase button to open your browser
private function openWootWindow(event:MouseEvent):void {
var u:URLRequest = new URLRequest(wootItemLink);
flash.net.navigateToURL(u, "_blank");
}

// Generally use the Event to handle updating the app
private function wootHandler(event:Event):void {
getWoot();
}

// Hits the API.
private function getWoot():void {
wootService.send();
checkCount.text = "Checked\n"+ ++checks + " times";
}

// Updates all of the items when the HTTPService completes
private function wootResultHandler(event:ResultEvent):void {
wootItemText = wootService.lastResult.rss.channel.item.title;
wootItemPrice = wootService.lastResult.rss.channel.item.price;
wootItemLink = wootService.lastResult.rss.channel.item.purchaseurl;
wootDesc.htmlText = wootService.lastResult.rss.channel.item.description;
wootImage.source = wootService.lastResult.rss.channel.item.thumbnailimage;
// Determine if we need to do percentage checking.
if (!(new Boolean(wootService.lastResult.rss.channel.item.wootoff))) {
wootItemPercent = "This is not a Woot-Off";
} else {
var percentNum:Number = new Number(wootService.lastResult.rss.channel.item.soldoutpercentage);
wootItemPercent = new String(percentNum/100 + "% Sold");
// Do we need to start checking more often?
if (!wootEndPing.running && percentNum > START_QUICK_POLL_PERCENT) {
wootEndPing.start();
} else if (wootEndPing.running && percentNum < START_QUICK_POLL_PERCENT) {
wootEndPing.stop();
}
}
}
]]>
</mx:Script>
<mx:HTTPService url="http://localhost:8080/WootProxy"
id="wootService" result="wootResultHandler(event)" />

<mx:VBox left="5" right="5" top="5" bottom="5">
<mx:Label id="itemText" text="{wootItemText}" fontSize="12" fontWeight="bold"/>
<mx:Canvas width="100%">
<mx:Button toolTip="Click to purchase" label="Purchase" click="openWootWindow(event)" y="152" x="0"/>
<mx:TextArea height="300" id="wootDesc" left="150" right="0" />
<mx:Image id="wootImage" width="142" height="116" left="0" top="0" >
<mx:source>http://upload.wikimedia.org/wikipedia/en/1/16/Wootlogo.png</mx:source>
</mx:Image>
<mx:Label id="checkCount" text="{checkText}" x="0" y="212" height="52" width="142"/>
<mx:Button toolTip="Click to load Woot" label="Check" click="getWoot()" y="182" />
<mx:Label id="itemPrice" text="{wootItemPrice}" y="124" fontStyle="italic" fontSize="12" x="0" width="142"/>
<mx:Label id="itemPercent" text="{wootItemPercent}" y="272" x="0" width="142"/>
</mx:Canvas>
</mx:VBox>
</mx:WindowedApplication>

And finally the Proxy:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.woot.tracker;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
*
* @author student
*/
public class WootProxy extends HttpServlet {

static final long serialVersionUID = 1L;

/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String contentObj = "http://www.woot.com/salerss.aspx";

URL content = null;

if (null == contentObj) {
throw new ServletException("The destination url must be specified for ProxyHttpService");
}

try {
content = new URL(contentObj);
} catch (MalformedURLException e) {
throw new ServletException(contentObj + " is a malformed url.");
}
HttpURLConnection contentCon = null;
try {
contentCon = (HttpURLConnection) content.openConnection();
} catch (IOException exception) {
throw new ServletException("Problem opening " + contentObj + ": " + exception.toString());
}

// Get the content type from the URLConnection and set it on the response.
String contentType = contentCon.getContentType();
response.setContentType(contentType);

// Get and read the input stream.
StringBuffer buffer = new StringBuffer();

BufferedReader din =
new BufferedReader(new InputStreamReader(contentCon.getInputStream()));

String s;
while ((s = din.readLine()) != null) {
buffer.append(s);
}
din.close();

// Now write the bytes out to the client.
byte[] contentBytes = buffer.toString().getBytes();
OutputStream out = response.getOutputStream();
out.write(contentBytes, 0, contentBytes.length);
out.flush();
out.close();
}

// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/**
* Handles the HTTP <code>POST</code> method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/**
* Returns a short description of the servlet.
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
}
There it is, enjoy :)

ShareThis