setTitle("Administrative Options"); $page->addSideBar( '

All Comments' ); $page->addSideBar( '

Post new' ); $page->addSideBar( '

Log out' ); $page->addSideBar( '


' ); } if ( isset($_GET['id']) ) { $page->addSideBar( "Back to Main" ); } $page->addSideBar( "

Recent posts

"); $posts = blogDb()->getAllPosts(); $now = strtotime( date( 'Y-m-d H:i:s' ) ); foreach ( $posts as $post ) { if ( $admin || $post->Shown && strtotime( $post->Date ) <= $now ) { $page->addSideBar( "
$post->Title
" ); } } $str = << function blog_go(id) { window.location.href = "$self?id=" + id; } EOF; $page->add( $str ); } function serve( $page ) { } } class AdminRequest extends Request { function serve( $page ) { global $_SERVER; $self = $_SERVER['PHP_SELF']; $page->setTitle("Login page!"); $form = << User Id
Password:
EOF; $page->add( $form ); } } class PostCommentRequest extends Request { function serve( $page ) { global $_POST; global $_SERVER; $mess1 = var_export( $_POST, true ); $mess2 = var_export( $_SERVER, true ); #mail('smhanov', "Comment", "$mess1\n$mess2"); $file = fopen("/home/smhanov/public_html/blog/comments.txt", 'a'); if ( $file ) { fwrite( $file, "---------------------------------------------------------\n"); fwrite( $file, $mess1 . "\n" . $mess2 ); fclose( $file ); } if ( !isset( $_POST['id'] ) ) { return; } $id = $_POST['id']; if ( !isset( $_POST['displayname'] ) ) { return; } $name = $_POST['displayname']; if ( $name === "hhfgdgdf" ) { // A prolific spammer. return; } $email = "none"; if ( !isset( $_POST['comment'] ) ) { return; } if ( isset( $_POST['email'] ) && $_POST['email'] != '' ) { // this is a fake field to weed out spammers. // email is not required. return; } $pos = strpos( $_POST['comment'], "http:" ); if ( !($pos === FALSE) ) { // Comments can't have this string. return; } $pos = strpos( $_POST['comment'], "https:" ); if ( !($pos === FALSE) ) { // Comments can't have this string. return; } $comment = htmlspecialchars($_POST['comment']); $comment = str_replace("\n", "

\n", $comment); blogDb()->postComment( $id, $name, $email, $comment ); $request = new ViewRequest( $_POST['id'] ); $request->serve( $page ); } } class DelCommentRequest extends Request { function __construct($id) { $this->id = $id; } function serve( $page ) { blogDb()->delComment( $this->id ); } } class AboutRequest extends IndexRequest { function serve( $page ) { global $_GET; $this->fillSideBar($page); $this->beginPost( $page ); $page->add(<< 1997 version

Contact

You can email me at steve.hanov@gmail.com.

Research

I have a Masters of Computer Science, part time, at the University of Waterloo. I had originally planned to write a thesis. But I also have a job. The likelihood of the thesis is inversely proportional to present income. In the end, I took an extra course to fullfill the requirements.

Here's some papers I wrote for classes:

  • A Lightweight Window Wrapper - This article was published in the August, 2000 issue of C/C++ User's Journal. It describes how to write the simplest possible object-oriented wrapper for Windows. The magazine paid me $200. In retrospect, I should have been more verbose.

Other pages

Pages of others

  • Matthias Wandel If you go to this web site, you will quickly become "stuck" on it and lose all track of time.

Current Interests

Here's a list of stuff that I'm working on:

  • Wavelets - Right now I'm obsessed with wavelets and their potential use in audio processing. See my web page on Wavelet sound Explorer

Music

I used to dream of being a composer of film scores. I enjoy the music of John Williams, and Danny Elfman, and Cliff Eidelman's work on Star Trek VI. Unfortunately I don't have the patience to learn to play an instrument, I got up to Grade 4 piano before I stopped. Here's some music that I composed using Cakewalk music software.

Comics

I drew a comic strip for Imprint, the University of Waterloo student newspaper. After I got a job at Research in Motion, I continued it there for a while. All strips are archived here

Budgie-ZILLA!

Budgie-Zilla! I made a web page once called Budgie-Zilla. I can't access it any more to change it, but here's a link to it. There are pictures of gigantic birds destroying landmarks. Apparently it's a part of budgie pop-culture now. The site is mentioned in a book about trademark law, and it has inspired a youtube video.

Work

  • Corel Corporation, Winter and Fall 1999
    On the Text Engines team, I fixed bugs in the text formatter, and added several new features. You can drag text on a path away from the path because of me.
  • Quack.com, Summer 2000, Winter 2001
    Quack.com (waybackmachine link) was making a phone portal, so you could call up a computer and it would tell you what movies were playing. I made the framework for a voice recognition server.
  • Soma Networks, Fall 2001
    Soma was making a box that would sit on your desk and give you your phone and internet connection wirelessly. I added the ability to play DTMF tones over the G729 voice codec.
  • Microsoft, Summer 2002
    As part of the Windows Networking team, I made a Layered Service Provider that overrides the winsock functions to make an IPv6 connection look like IPv4.
  • Research in Motion, Sept. 2003 to 2009
    In my first full time position, I created large chunks of the EDGE and UMA protocol stack. EOF ); $this->endPost( $page ); } } class IndexRequest extends Request { function serve( $page ) { global $_GET; if ( isset($_GET['before']) ) { $before = $_GET['before']; } else { $before = date('Y-m-d H:i:s'); } $this->fillSideBar( $page ); $posts = blogDb()->getPostsBefore($before, 10); $now = strtotime( date('Y-m-d H:i:s') ); foreach ( $posts as $post ) { if ( $admin || $post->Shown && strtotime( $post->Date ) <= $now ) { $this->addPost( $page, $post, true ); $this->endPost($page); $date = $post->Date; } } $page->add(<< More Posts

    EOF ); } function truncateEntry($entry) { // Look for . $pos = strpos( $entry, "" ); // If no break, look for

    if ( $pos == False ) { $pos = strpos($entry, "

    " ); } // If found, if ( $pos != False ) { // truncate the text at that point. $entry = substr($entry, 0, $pos); } return $entry; } function beginPost( $page ) { $page->add( <<

    EOF ); } function addPost( $page, $post, $summarize ) { global $admin; if ( !$summarize ) { $str = <<

Web Sequence Diagrams
Create sequence diagrams in seconds for free.
http://www.websequencediagrams.com

Why was this ad not blocked?
EOF; #

Make sure you click on the ads above to support this blog. I do, daily. #Don't make me add some adjoining/overlapping #content!

$page->add($str); } $this->beginPost( $page ); $page->add( "
$post->Title
"); $page->add( "
Posted on: $post->Date
"); $page->add( "
" ); if ( $admin ) { $page->add( "ID\">edit
"); } if ( $summarize ) { $page->add( $this->truncateEntry($post->Text) ); $page->add( "

ID\">More...\n" ); } else { $page->add( $post->Text ); } // This diff pushes the rest past any floats... if ( !$summarize) { $page->add( " subscribe to posts" ); } $page->add( "

" ); // endpost MUST be called... } function endPost($page) { $page->add( "
" ); $page->add("
"); $page->add( "" ); // blogText } function addComments( $page, $id ) { global $admin; if ( $admin ) { $str = << EOF; $page->add( $str ); } $comments = blogDb()->getComments( $id ); foreach ( $comments as $comment ) { $name = $comment->DisplayName; $date = $comment->Date; $text = $comment->Text; $cid = $comment->ID; $blogId = $comment->BlogID; $page->add( <<
EOF ); $page->add( "
\n" ); $page->add( "

$name

$date
\n" ); if ( $admin ) { $page->add( "delete". " | article
\n" ); } $page->add( stripslashes($text) ); $page->add( "
\n" ); $page->add( <<
EOF ); } } } class AtomRequest extends Request { function serve( $page ) { global $_SERVER; $queryString = "http://" . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; $page->setRaw(); header("Content-type: application/atom+xml"); $str = << Steve Hanov's Programming Blog Windows, C++ programming techniques, Mobile technology, neat tricks you can do with Computer Science algorithms. 2007-04-15T16:29:29Z tag:smhanov,2007:1 Copyright (c) 2007, Steve Hanov Example Toolkit EOF; $page->add( $str ); $posts = blogDb()->getAllPosts(); $now = strtotime( date('Y-m-d H:i:s') ); foreach ( $posts as $post ) { if ( $admin || $post->Shown && strtotime( $post->Date ) <= $now ) { $this->addPost( $page, $post ); } } $page->add( "" ); } function addPost( $page, $post ) { $date = substr($post->Date, 0, 10) . "T" . substr($post->Date, 11,8) . "-05:00"; $post->Text = str_replace("", "", $post->Text); $post->Text = str_replace("", "", $post->Text); $post->Text = htmlentities($post->Text, ENT_QUOTES, "UTF-8"); $str = << $post->Title tag:smhanovtechblog,2007:id$post->ID $date $date Steve Hanov steve.hanov@gmail.com steve.hanov@gmail.com $post->Text EOF; $page->add( $str ); } } class ViewRequest extends IndexRequest { function __construct( $id ) { $this->id = intval($id); } function serve( $page ) { global $BlogTitle; global $admin; $post = blogDb()->getPost( $this->id ); if ( !$post->Shown && !$admin ) { $request = new IndexRequest(); $request->serve( $page ); return; } $this->fillSideBar( $page ); $page->setTitle( $post->Title . " - $BlogTitle" ); $this->addPost( $page, $post, false ); $this->addCommentForm( $page ); $this->addComments( $page, $this->id ); $this->endPost($page); } function addCommentForm($page) { global $_SERVER; $self = $_SERVER['PHP_SELF']; $str = << function validate_field(field, alerttxt) { if ( field.value==null || field.value == "") { alert(alerttxt); return false; } return true; } function validateCommentForm(form) { if ( !validate_field(form.comment, "Please fill out all fields.") ) { return false; } if ( !validate_field(form.displayname, "Please fill out all fields.") ) { return false; } return true; }

Post comment

Real Name:
Your Email (Not displayed):
Text only. No HTML. If you write "http:" your message will be ignored.

EOF; $page->add( $str ); } } class EditRequest extends ViewRequest { function __construct( $id ) { $this->id = intval($id); } function serve( $page ) { $this->fillSideBar( $page ); $post = blogDb()->getPost( $this->id ); $page->setTitle( $post->Title . " - (Editing)" ); $this->makeEditForm($page, $post); $this->addPost( $page, $post, false ); $this->endPost($page); } function makeEditForm( $page, $post ) { global $_SERVER; $self = $_SERVER['PHP_SELF']; $text = htmlspecialchars($post->Text); if ( $post->Shown ) { $checked = "CHECKED"; } else { $checked = ""; } $str = << function onPreview() { text = document.getElementById("text").value; document.getElementById("blogText").innerHTML = text; text = document.getElementById("titleText").value; document.getElementById("blogTitle").innerHTML = text; }
Title:
Date:
Tags:
Shown

EOF; $page->add( $str ); } } class PostNewRequest extends EditRequest { function __construct() { parent::__construct(-1); } function serve( $page ) { // Create a new blog entry, then edit it. $this->id = blogDb()->postNew(); parent::serve( $page ); } } class SaveRequest extends Request { function serve( $page ) { global $_POST; $post = NULL; $post->Dat = $_POST['date']; if ( $_POST['shown'] === 'on' ) { $post->Shown = "1"; } else { $post->Shown = "0"; } $post->Date = $_POST['date']; if ( $_POST['shown'] === 'on' ) { $post->Shown = "1"; } else { $post->Shown = "0"; } $post->ID = $_POST['id']; $post->Text = stripslashes($_POST['text']); $post->Title = stripslashes($_POST['title']); $post->Tags = stripslashes($_POST['tags']); blogDb()->postEntry( $post ); $request = new ViewRequest( $_POST['id'] ); $request->serve( $page ); } } class AllCommentsRequest extends IndexRequest { function serve( $page ) { $this->fillSideBar( $page ); $this->addComments( $page, null ); } } class ComicsRequest extends IndexRequest { function serve( $page ) { $DATES = array( '20020919' => "Girls always assume I\'m trying to pick them up.", '20021004' => "", '20021018' => "", '20021101' => "", '20021115' => "", '20021129' => "", '20021213' => "This is based on Pat O'Neil when we were both in the lab taking CS 370 ". "one night.", '20021227' => "", '20030110' => "The math cafe is very small, but serves thousands each day.", '20030124' => "My most popular strip ever. I was in co-op through the tech bubble and ". "I saw a remarkable change when it burst.", '20030207' => "This was never published, because I accidentally drew the last panel ". "too small. Now, years later, it has been digitally remastered. (The ". "student is supposed to be Colin Bird)", '20030217' => "The previous week, the sex columnist had written a story on ". "'going commando', which is not wearing underwear.", '20031024' => "", '20031107' => "Programmers hate ambiguous specifications.", '20031205' => "Its fun to watch people in the RIM parking lot.", '20031219' => "", '20040116' => "", '20040213' => "", '20040227' => "I've been on dates where the girl just talked and talked and wouldn't ". "let me say anything.", '20040312' => "This is my girlfriend's wife's favourite.", '20040326' => "", '20040408' => "Without warning one week, all these walls sprung up around our cubes.", '20040506' => "This happens a lot at RIM.", '20040521' => "My experiment is a failure. 500 monkeys typing on typewriters for 50 ". "years have failed to produce anything close to Shakespeare. All we get ". "are specifications describing some kind of telecommunications system.", '20040604' => "", '20040618' => "People asked me to do a comic about one of the co-op students who had a ". "reputation for eating a lot.", '20040701' => "We had a canoe trip for a team-building event, but some of us couldn't ". "go because the deadline was the next day.", '20040716' => "", '20040730' => "", '20040813' => "I'm not proud of this one. In case you're wondering, the phone is ". "taking a picture of the inside of his pocket. Also, that day I did'nt ". "have my Sharpy marker, so I had to use a ball-point pen for the drawing.", '20041008' => "", '20041022' => "", '20041105' => "", '20041203' => "", '20041215' => "", '20050114' => "", '20050128' => "", '20050211' => "", '20050311' => "", '20050324' => "", '20050408' => "", '20050511' => "", '20050610' => "", '20050722' => "Cell phones have to go through lots of external testing.", '20090708' => "", '20090709' => "", '20090711' => "", '20090712' => "", ); krsort($DATES); $this->fillSideBar( $page ); $this->beginPost($page); $page->add( "

Hello, world

" ); if ( $_GET['comicdate'] ) { $comicdate = $_GET['comicdate']; } elseif ( $_SESSION['comicdate'] ) { $comicdate = $_SESSION['comicdate']; } if ( !isset($comicdate) || !isset($DATES[$comicdate]) ) { $keys = array_keys($DATES); $comicdate = $keys[0]; } foreach( $DATES as $date => $text ) { if ( $date == $comicdate ) { $prev = 1; $next = $temp; } elseif ( $prev == 1 ) { $prev = $date; } $temp = $date; } $page->add( <<Previous Next
EOF ); foreach( $DATES as $date => $text ) { } $this->endPost($page); } } class LoginRequest extends Request { function __construct( $user, $password ) { $this->user = $user; $this->password = $password; } function serve( $page ) { global $admin; global $_SESSION; if ( $this->user === 'steve' && $this->password === '1077sameasmypinnumber' ) { session_start(); $_SESSION['admin'] = true; $admin = true; } $request = new IndexRequest(); $request->serve( $page ); } } class LogoutRequest extends Request { function serve( $page ) { global $admin; global $_SESSION; $admin = false; $_SESSION['admin'] = false; $request = new IndexRequest(); $request->serve($page); } } class Page { private $content = ""; private $sideBar = ""; private $raw = false; function setRaw() { $this->raw = true; } function addSideBar( $str ) { $this->sideBar .= $str; } function add( $str ) { $this->content .= $str; } function write() { if ( $this->raw ) { echo $this->content; return; } echo << $this->title
$this->content
$this->sideBar
EOF; } private $title = ""; function setTitle( $title ) { $this->title = $title; } } function blogDb() { static $instance; if (!is_object($instance) ) { $instance = new BlogDb(); } return $instance; } class BlogDb { function __construct() { } private $comments = null; function getComments( $id ) { if ( $this->comments == null ) { // Text, Date, ID, Shown $db = $this->connectDb(); if ( $id == null ) { $sql = "SELECT * FROM Comments ORDER BY Date DESC;"; } else { $id = $db->escapeSimple( $id ); $sql = "SELECT * FROM Comments WHERE BlogID = \"$id\" ORDER BY Date;"; } $this->comments = array(); // look for record for given user name. $q = $db->query($sql); if ( DB::iserror($q)) { die($q->getMessage()); } for($i = 0;;$i++) { $row = $q->fetchRow(DB_FETCHMODE_OBJECT); if ( $row == null ) { break; } $this->comments[] = $row; } } return $this->comments; } private $posts = null; function getPost($id) { // Since we have to get everything anyway for the side bar, might as // well do it now. $posts = $this->getAllPosts(); foreach( $posts as $post ) { if ( $post->ID == $id ) { return $post; } } return null; } function postComment( $id, $name, $email, $comment ) { $db = $this->connectDb(); $row = NULL; $row->BlogID = $db->escapeSimple( $id ); $row->Date = date('Y-m-d H:i:s'); $row->DisplayName = $db->escapeSimple( htmlspecialchars( $name ) ); $row->Email = $db->escapeSimple( $email ); $row->Text = $db->escapeSimple( $comment ); $row->ID = $db->nextId("commentid"); $sql = <<ID", "$row->BlogID", "$row->Date", "$row->DisplayName", "$row->Email", "$row->Text"); EOF; $q = $db->query($sql); if (DB::iserror($q)) { echo "
$sql
"; die($q->getMessage()); } $db->commit(); } function postNew() { $row = NULL; // connect to database, $db = $this->connectDb(); $row->ID = $db->nextId("blogid"); $row->Title = "New Blog Entry"; $row->Shown = "0"; $row->Date = date('Y-m-d H:i:s'); $row->Keywords = ""; $row->Tags = ""; if ( PEAR::isError($id) ) { die($id->getMessage()); } $sql = <<ID", "$row->Title", "$row->Date", "", "$row->Shown", "$row->Keywords", "$row->Tags"); EOF; echo "
$sql
"; $q = $db->query($sql); if (DB::iserror($q)) { echo "
$sql
"; die($q->getMessage()); } $db->commit(); return $row->ID; } function postEntry( $post ) { // connect to database, $db = $this->connectDb(); $post->Title = $db->escapeSimple( $post->Title ); $post->Text = $db->escapeSimple( $post->Text ); $post->Date = $db->escapeSimple( $post->Date ); $post->Shown = $db->escapeSimple( $post->Shown ); $post->ID = $db->escapeSimple( $post->ID ); $post->Tags = $db->escapeSimple( $post->Tags ); $sql = "UPDATE Blogs SET Title=\"".$post->Title."\", Text=\"".$post->Text."\", Date=\"".$post->Date."\", Shown=\"".$post->Shown."\", Tags=\"".$post->Tags."\" WHERE ID=\"".$post->ID."\";"; $q = $db->query($sql); if (DB::iserror($q)) { echo "
$sql
"; die($q->getMessage()); } $db->commit(); } function delComment($id) { $db = $this->connectDb(); $id = $db->escapeSimple( $id ); $sql = <<query($sql); if (DB::iserror($q)) { echo "
$sql
"; die($q->getMessage()); } $db->commit(); } function getAllPosts() { if ( $this->posts == null ) { // Text, Date, ID, Shown $db = $this->connectDb(); $sql = "SELECT * FROM Blogs ORDER BY Date DESC;"; $this->posts = array(); // look for record for given user name. $q = $db->query($sql); if ( DB::iserror($q)) { die($q->getMessage()); } for($i = 0;;$i++) { $row = $q->fetchRow(DB_FETCHMODE_OBJECT); if ( $row == null ) { break; } $this->posts[] = $row; } } return $this->posts; } function getPostsBefore($date, $num) { global $admin; $posts = $this->getAllPosts(); $ret = array(); $date = strtotime( $date ); foreach ( $posts as $post ) { $postDate = strtotime($post->Date); if ( ($admin || $post->Shown) && $postDate < $date ) { $ret[] = $post; $n = count($ret); if ( count($ret) == $num ) break; } } return $ret; } function connectDb() { require_once('DB.php'); $db = DB::connect("mysql://root@localhost/Blog"); if ( DB::iserror($db)) { die("Message " . $db->getMessage()); } return $db; } } class Blog { function serve() { global $BlogTitle; $page = new Page(); $page->setTitle( $BlogTitle ); $request = createRequest(); $request->serve($page); $page->write(); } } $blog = new Blog(); $blog->serve(); ?>