Outlook Macro : Move to Specified Folder & Mark As Read

  Tuesday, April 22 2008 @ 12:54 PM CDT

SoftwareAs a victim of Microsoft Outlook, I hear alot about using it in the GTD philosophy.  One of my favorite websites, LifeHacker, extolls it's virtues (GTD, not Outlook) on a daily basis.  Yesterday they posted an article about how to modify your toolbars to give you shortcut keys for "Copy to folder" and "Move to folder" actions.  I don't know why I never thought of that before.

I quickly set that up, and then realized I could add an additional shortcut for "Mark as Read" (our IT staff has disabled the ability to Mark as Read when Previews).  With those in place, I decided I might actually try another GTD point: an empty inbox.  The key to an empty inbox is to have a place to dump mail you've read and responded to, but don't really need anymore except for reference.  They call it an "Archived" or "Processed" folder.  While I could do this with the "Move to" shortcut, I had to select the folder everytime.  With a bit of googling, I was able to setup an Outlook VB Macro (Under the Tools Menu) to Mark the message as read and move it to my personal PST file, in a folder called "Ancient Archive".

For everyone else's benefit, the VB code is as follows:

Sub MoveToArchive()
On Error Resume Next
    Dim objFolder As Outlook.MAPIFolder
    Dim objNS As Outlook.NameSpace, objItem As Outlook.MailItem

    Set objNS = Application.GetNamespace("MAPI")
    Set objFolder = objNS.Folders("Personal Folders").Folders("Ancient Archive")
'Assume this is a mail folder
 
 
    If objFolder Is Nothing Then
        MsgBox "This folder doesn't exist!", vbOKOnly + vbExclamation, "INVALID FOLDER"
    End If
 
 
    If Application.ActiveExplorer.Selection.Count = 0 Then
        'Require that this procedure be called only when a message is selected
        Exit Sub
    End If
 
    For Each objItem In Application.ActiveExplorer.Selection
        If objFolder.DefaultItemType = olMailItem Then
            If objItem.Class = olMail Then
                objItem.UnRead = False
                objItem.Move objFolder
            End If
        End If
    Next
 
    Set objItem = Nothing
    Set objFolder = Nothing
    Set objNS = Nothing
End Sub

Technorati Tags: , , ,

Freezerburn - Screenshots in Action

  Wednesday, April 16 2008 @ 03:41 PM CDT

ProjectsSo, I've talked a good bit about the technical guts of Freezerburn, so I figure it's about time to show what it looks like.  Here is a screenshot of the Job Details page (in IE6 unfortunately, so the transparent PNG's are a big nasty). 


Up top you see some basic statistics about the server.  Right below that you see the progress of the entire job, and then the progress of all the frames.  Each little box indicates a frame.
  • Green box - Frame is completed
  • Striped box - It's a Mental Ray frame, and the three stripes indicate percentage complete of either Photon Emission (top bar), Final Gather (middle bar), or Rendering (bottom bar)
  • Spotted Box - some other unrecognized render stage
  • Red box (not shown) - Errored frame
  • Grey box - unassigned frame
Below that you see some basic information about the job (output frame, frame size), and i've expanded out the renderframe chart (Generated via Google Charts).  Below that are dropdowns for Server selection & individual frame controls.  It's a very clean & organized interface, largely thanks to some fine HTML & Graphics work by my co-worker Kevin

Comments?

Technorati Tags: , , ,

Laura is now an RHIT, CCS

  Monday, April 14 2008 @ 01:57 PM CDT

Just wanted to make a nice public announcement that now Laura is an RHIT, CCS!  She took her CCS Certification exam this weekend and passed with flying colors!  Congratulations Laura!

Technorati Tags: , ,

Aquaria

  Monday, March 31 2008 @ 09:51 AM CDT

GamesThis weekend I was digging through old bookmarks and dug up an old reference to a game called Aquaria.  I first heard of Aquaria when it won the IGF2007 Grand Prize, but it wasn't available for download at the time.  Now, they have a good 60M Demo of the game (good for 2 hours or so of playtime), and for $30 you can get the full 250M version.  I've been playing it all weekend, and after 6 hours of playtime (according to my savegame) I'm still finding new stuff and continually being astounded by the game.

The graphics are simply beautiful.  The entire game is a side-scroller basically, but with lots of little bells and whistles.  Seaweed and flowers flow as you swim by, matching your movements.  Schools of fish following true flocking behavior swimming around.  The environment changed from lush green landscapes, to barren castle hallways, to eerie organic fleshly tunnels, and all done seamlessly.  The music is even more impressive.  An eerie lonely tune that adds to the mystery of "Who am I?", with fast-paced music added for boss fights and action scenes.  The cut-scenes are great, and the british accent voiceovers are a perfect match.

At it's heart, Aquaria is a puzzle game with a little bit of combat.  Primarily you're searching for new "forms" (spells) to give you new abilities, but usually each form comes with solving a puzzle.  Boss fights are there, but are usually puzzle-based. Eg. The boss has only a single special weakness that you have to discover and exploit.

The game is a blast, and I'm catching myself humming the theme music constantly.  I look forward to seeing how the story plays out, and seeing what other fantastic environment there are for me to explore.

Technorati Tags: , ,

Webservers with Python - SSL & CAC Authentication

  Tuesday, March 25 2008 @ 01:11 PM CDT

Source CodeWorking in the DOD, there are a few things you just come to accept.  Webservers require security (SSL), and SSL requires Common Access Card Authentication.  I had hoped that when I implemented the HTTP Monitor for Freezerburn, I could ignore all the security aspects and simply say that "It's behind all the government firewalls" and "It requires an account on the fileserver".  I wasn't so lucky.

After alot of research from articles like:

I finally came up with a way to make it work, that required very little variation from Python's built-in HTTPServer object.  Starting with the ASPN Cookbook recipe for SecureHTTPServer, I came up with the following:
import socket, os
from SocketServer import BaseServer
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
from SocketServer import ThreadingMixIn
from OpenSSL import SSL
import sys

class SecureHTTPServer(HTTPServer):
    def __init__(self, server_address, HandlerClass,
                dodcerts, serverkey, servercert):
        BaseServer.__init__(self, server_address, HandlerClass)
        #  Based on online Documentation, the v23 actually enables TLS1 as well.
        ctx = SSL.Context(SSL.SSLv23_METHOD)
        #ctx = SSL.Context(SSL.TLSv1_METHOD)

        print "Loading Private Key from %s" % serverkey
        ctx.use_privatekey_file (serverkey)
        print "Loading Certificate from %s" % servercert
        ctx.use_certificate_file(servercert)
        print "Loading DOD Certifications from %s" % dodcerts
        ctx.set_verify_depth(2)
        ctx.load_client_ca(dodcerts)
        ctx.load_verify_locations(dodcerts)

        print "Creating SSL socket"
        callback = lambda conn,cert,errno,depth,retcode: retcode
        ctx.set_verify( SSL.VERIFY_FAIL_IF_NO_PEER_CERT | SSL.VERIFY_PEER, callback)
        ctx.set_session_id('Freezerburn')
        self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
                                                        self.socket_type))
        self.server_bind()
        self.server_activate()

class SecureHTTPRequestHandler(SimpleHTTPRequestHandler):
    def setup(self):
        self.connection = self.request
        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)

This creates a new object called the "SecureHTTPServer" that acts just like the regular HTTPServer, except it allows you to specify the location of the DOD Root Certificates, the Server Private Key, and the Server SSL Certificate.  The only real difference from the ASPN one is that it turns on Client Certificate Verification, which is the core of the CAC Authentication scheme.  With that little snippet of code, SSL & CAC were enabled in one fell swoop!

Technorati Tags: , ,

Family Pics

  Wednesday, March 19 2008 @ 03:55 PM CDT

Family

Laura made this for her MySpace page the other day, and I thought it was worth showing to the world here :) Enjoy.

Technorati Tags: ,

Webservers with Python - Caching

  Wednesday, March 19 2008 @ 03:32 PM CDT

Source CodeOne thing I decided very early on with FreezerBurn was the need for a Web-Based monitoring tool.  I didn't want to have to deploy a Python tool with a full user-interface to every desktop and then deal with all the Security implications of that.  With a web interface, I could simply let everyone use FireFox or IE or Safari and load it however they wanted.  At least, that was the initial idea.

Very quickly I came across Python's HTTPServer class which handles a good 90% of what I needed.  After some experiments, though, I found that it tended to die under heavy loads (eg, loading my page with external JS, CSS, and Images).  I needed it to be fairly reliable, and thusly discovered alot more then I ever intended to about HTTP Caching.

HTTP implements caching 2 ways: ETags & "Last-Modified" dates.  Etags are simply checksums where the Browser says "The version I have has this checksum", and the server checks it and tells you "That's right" or "That's old, here's a new one".  In my case, I chose to instead implement the "Last-modified" version where I could simply check the last modified date on the files.  The resulting code looks like this:

class MonitorServer(HTTPRequestHandler):
    def do_GET(self):
        global jobQueue
        global servers
        global mQueue
        global initTime
        # process self.path
        (scm, netloc, path, params, query, fragment) = urlparse.urlparse(self.path, 'http')
        if scm != 'http' or fragment:
            self.send_error(400, "bad url %s" % self.path)
            return
        print 'HTTP: Serving %s' % path
        # Write to self.wfilt
        if path == "/":
            # Raw Index Page
            self.wfile.write(""" blah blah blabh """)
        elif path == "/job":
            blah blah blah
        else:
            # Serve files from the system
            path = path.split('/')[-1]
            if os.path.exists(constants.MonitorPath + '\\' + path):
                (ctype, enc) = mimetypes.guess_type(path)
                info = os.stat(constants.MonitorPath + '\\' + path)
                lastmod = datetime.datetime.fromtimestamp(info[ST_MTIME])
                if self.headers.get('If-None-Match',''):
                    self.send_response(304)
                    return
                if self.headers.get('If-Modified-Since',''):
                    dt = self.headers.get('If-Modified-Since').split(';')[0]
                    try:
                        modsince = datetime.datetime.strptime(dt, "%a, %d %b %Y %H:%M:%S %Z")
                        if modsince >= lastmod:
                            print "HTTP: No new version of %s" % path
                            self.send_response(304)
                            return
                    except:
                        pass

                self.send_response(200)
                self.send_header('Cache-Control', 'max-age=864000')
                self.send_header('Expires', "Fri, 30 Jan 2010 12:00:00 GMT")
                self.send_header('Content-Length', info[ST_SIZE])
                self.send_header('Last-Modified', lastmod.strftime("%a, %d %b %Y %H:%M:%S GMT"))
                self.send_header('Content-Type', ctype)
                self.end_headers()
                self.copyfile(open(constants.MonitorPath + '\\' + path, 'rb'), self.wfile)
            else:
                print "MONITOR:Can't find %s" % (constants.MonitorPath + '\\' + path)
                self.send_error(404, "Unknown url %s" % self.path)

      
So, this piece of code creates a "MonitorServer" object  and defines the "GET" function.  In there it parses the requested URL, and if it's in one of a few selected forms then send the  dynamically generated content.  Otherwise, send the requested file directly to the User.  As a security precaution, I don't allow the user to specify a path (the path is stripped and the file must exist within 1 specific directory).  If the browser provides a "If-None-Match" header, then I return a 304 code which indicates the Cache is up-to-date.  If they provide a "If-Modified-Since", then I parse the date and compare it against the file, and returna 304 if appropriate.  One interesting thing I learned from this is that the Browser doesn't actually return the date of the file, but rather returns the "Last-Modified" you send it, in identical formatting. 

So far, this works great!  It significantly improved the response of the server and has greatly reduced load-times (Even though it's on a local-network).

Technorati Tags: , ,

Freezerburn : a Replacement for BackBurner

  Tuesday, March 11 2008 @ 10:47 AM CDT

WorkAnyone who's used Autodesk's 3D Studio Max for a while knows about BackBurner.  BackBurner is the Queue-controlling software they ship with Max to allow you to render jobs across multiple scenes in a naive way.  Rather than pooling resources to work on a single frame, it hands out individual frames of the sequence to each node in the backburner queue.  (You can pool resources using the "Strip" rendering, but it's pretty much the same thing).  However, the way we use BackBurner at work has proven to us that it's just a piece of crap.  Thus, I wrote my own queue manager that I've dubbed "Freezerburn".  Let me elaborate.. 

In our environment, we typically get a dataset of 1000 or so timesteps.  We extract 1000 isosurfaces (one from each timestep), and then need to render these 1000 files into 1000 frames of an animation (For those interested, we typically save the isosurfaces as Stanford PLY files and use the PLY import plugin I wrote, along with a MaxScript, to convert them all into MAX files).  So we create a simple setup scene with the context, lights, and camera.  We then use another custom MaxScript I developed to embed a callback into the Max File's "onLoad" event that will look at the current frame number, merge in the appropriate Max file, and apply whatever material properties we need.  That way we have a single MAX file that can render all 1000 frames of the animation.  Unfortunately, since it's in the onLoad, it means we need to load this Scene file from disk for each image.

With Backburner, you have no control over this.  With a large scene, it typically starts off assigning individual frames to nodes, which is exactly what we want.  After those complete, however, it starts assigning larger & larger batches of frames to each node (to reduce overhead spent loading the file & transmitting information).  This means that the onLoad callback is only triggered once but renders multiple frames, which isn't what we want.

The technical amongst you may be wondering "Why not just use preRenderFrame"? Well, there's a few reasons while the various preRender callbacks won't work like that:

  1. preRender - Only called once at the beginning of the entire render, basically no better than onLoad
  2. preRenderFrame - Called between every frame, but after geometry is cached.. Attempting to change geometry (eg. Merge in a new Max File) results in a SegFault
  3. beginRendering* - Same as preRenderFrame
  4. preRenderEval - I had high hopes for this when I first saw it, but it seems to operate identically to preRender (only once at the beginning of the entire render)
So, the only way to guarantee that backBurner will process the frames as individual jobs, is to submit each frame as an individual job.  Now, we have MaxScripts to do this for us with a single buttonclick, so it's not that bad.  However, Backburner simply can't take it.  BackBurner was designed to handle a handful of several-thousand-frame jobs, and does so beautifully.  However, hand it a few thousand single-frame jobs, and it dies.  It dies horribly.  It dies a slow excruciating death as you watch job submission times go from half a second to 3 or 4 minutes per.  It's truly sad and ridiculous.

So my solution was to use my new Python skills to write our own queue manager.  It's written entirely in Python, and offers the following features:
  • Queue & priority control
  • For Mental Ray scenes it gives you % complete on each of the 3 major stages of render (Preprocessing, Final Gather, Rendering)
  • Vastly improved "at-a-glance" status information
  • Controllable via a Web Interface (with SSL certificates and CAC authentication which is required where I work)
  • And much much more...
Over the next few weeks I'll post screenshots and articles of how it works, to share the knowledge I gleamed about Python, Windows Programming, HTTP servers, and 3d Studio Max.  So be sure to come back!
 

Technorati Tags: , ,

MAKO finally IPO's

  Monday, March 03 2008 @ 08:45 AM CST

WorkA friend of mine tipped me off to the fact that MAKO finally made their IPO last month.  I had completely missed it (which is probably a good thing).  So I hit up Google's Financial pages to see the trading charts..



While this may look good at a glance, a quick look shows nothing of note.  They opened just under $10, spiked to $12, and settled down around 10.50 for today.  What I find more interesting is the harsh commentary on it... Like the Wall Street Journal:

MAKO closed at $9.18 a share on the Nasdaq, down 8% from its IPO price of $10. It sold 5.1 million shares at the low end of a reduced $10 to $11 price range; it originally was expected to price between $14 to $16 through underwriters J.P. Morgan Chase & Co. and Morgan Stanley.
...
Meanwhile, Fort Lauderdale, Fla.-based MAKO specializes in making robotic instruments and implants for knee surgery. Its products, which were first cleared by the Food and Drug Administration, allow surgeons to avoid complete joint replacement for early-to-mid-stage osteoarthritic knee disease, and instead resurfaces damaged joints and places implants through a small incision.

MAKO has never been profitable and expects to continue losing money as it develops its business.
So, looks like noone will be selling their shares and buying a Yacht anytime soon.  Guess we'll just have to wait and see how it plays out.

Technorati Tags: ,

Project Management with Trac & SVN

  Wednesday, February 27 2008 @ 01:59 PM CST

SoftwareBack at Z-Kat, I remember we spent alot of time trying to get a web-based project management system up and running.  We were already using CVS for source control, and we had a Twiki advocate on staff who got Twiki up and running, and we eventually setup BugZilla for ticket tracking.  It all worked pretty well in it's individual pieces, when you wanted to start linking things together it got a big confusing.  We eventually wrote some code to allow us to reference ticket numbers from CVS commits, and put some stuff in the Wiki to let us reference things.  With CVSWeb operating, it was almost a project management system but always felt a bit kludgey.

Well, with Final Colony taking off and myself and a friend working on it, I thought we needed something similar.  Not wanting to rebuilt the previous solution, I dug around the net and eventually found Trac.  It does everything the previous solution I mentioned did and more.  It's got an integrated Wiki & Ticketing system, and links with Subversion for source control.  It all cross-links so that you can directly reference code & changesets in wiki edits & tickets, and vice versa.  They provide SVN post-commit-hooks to even allow you to commit code with a message that says "This fixes #45 and #46" and have those two tickets closed, with the SVN commit message added to the ticket.  It's a complete & powerful system that does everything I need.  It's written entirely in Python, and can be a bit of a Bear to install, but it's worth it.  And if you don't have access to Apache, they even provide a standalone python version you can run on your own machine to manage data. 

The one hurdle I had to overcome was converting my existing CVS repository to SVN. For this, I found a great tool called cvs2svn that converts your repository with full revision history, branches, and tags.  Since then I've fallen in love with SVN.  Life is so much easier than with CVS.  All your changesets are full transactions, so never again do I commit a bunch of files and have it die halfway through with a version mismatch.  Never again do I have to find cryptic date & timestamps to checkout a complete working version, just get "Version 738".  I remember Lou's masterful TCL scripts to attempt & cobble together data like the "Date" of a Tag.  Such things are trivial with SVN.

So, for those of you out there doing development that have not had the opportunity to use subversion & Trac, i highly recommend it.  It's a great solution to so many of the problems I spend my days complaining about.

Technorati Tags: , , ,