Tracking Dynamic IP

So it is nice to be able to remotely connect to your home computer. Many (included my) ISPs, give dynamic IP addresses, and although these don’t change often, they do change and are a pain to remember anyway. My solution is to have my computer e-mail me whenever it discovers it has a new IP address! You can easily configure exim4 to send emails from a linux machine through gmail, a great guide is here.

Once you have that you just need a little script, mine does this with python by asking http://icanhazip.com. and writing a temporary file with the old ip in /var/tmp. It only sends you a message when the IP changes, no need to spam your email box!

 

#!/usr/bin/python

''' A super simple script to email changes in a dynamic IP to
an email address. If you drop this in /etc/cron.* , it will periodically
poll a website to check if your iP has changed, and if it has it well
send you an e-mail with the new one!

Alex Knaust - 2/28/2013
'''

import os, smtplib, urllib2, re
from email.mime.text import MIMEText

# file to save the last IP address in
IPLOC = '/var/tmp/ip'

# site from which to get IP, if you change this you might need to alter get_live_ip
IPSITE = 'http://icanhazip.com'

# Message to send yourself with the IP
MAILSTR = '''Hi, My new IP address is {0}\n'''

# Address from which the email is being sent (this should probably match your SMTP setup
FROMADDR = 'you@host.com'

# Address to which to send the message
TOADDR = 'you@host.com'


#####################################################
############### CODE ################################
#####################################################
def save_ip(ipstr):
    '''Save the Ip to IPLOC, return true if successful, false otherwise'''
    try:
        with open(IPLOC, 'w') as f:
            f.write(ipstr)
    except:
        return False
    return True
    
def get_old_ip():
    '''Gets the old IP by reading IPLOC, returns None if it could not be read'''
    try:
        with open(IPLOC, 'r') as f:
            ip = f.read()
        return ip.strip()
    except IOError:
        return None
    
def get_live_ip():
    '''Return the IP address of this computer by polling IPSITE
    
    returns None if not successful
    '''
    try:
        f = urllib2.urlopen(IPSITE)
        return f.read().strip()
    except urllib2.URLError:
        return None

def send_mail(ipstr):
    '''Tries to send an email about the change of IP to the TOADDR
    
    returns true if successful, false otherwise
    '''
    msg = MIMEText(MAILSTR.format(ipstr))
    msg['Subject'] = 'IP Address change'
    msg['From'] = FROMADDR
    msg['To'] = TOADDR
    
    try:
        s = smtplib.SMTP('localhost')
        s.sendmail(FROMADDR, [TOADDR, ], msg.as_string())
        s.quit()
    except:
        return False
    return True

def main():
    oldip = get_old_ip()
    newip = get_live_ip()
    
    if newip is not None:
        if oldip is not None:
            if newip != oldip:
                send_mail(newip)
                save_ip(newip)
        else:
            send_mail(newip)
            save_ip(newip)

if __name__ == '__main__':
    main()

Building a Desk

My previous desk setup was pretty poor, the chair was one of those leather coated ones that look like they are from an era which never existed. Besides that it had lost 3 of its wheels, and the gas cylinder had kicked the bucket, and was replaced by a piece of PVC pipe. My desk, was actually a glass kitchen table, which went up to about knee height, and thus was standing on two flower pots. Getting the right work environment actually does make a big difference in comfort and ultimately productivity, especially considering if you are like me you spend almost your entire day at one desk or another. I noticed this at UTEP, where my office has some ergonomically designed desk and expensive Herman Miller chair. It is just easier to type. Building a new desk chair is a bit beyond me, plus I had fallen in love with the Mirra chair from work, so I received one of those for Christmas. The remaining task then is to obtain a new desk. Shopping for desks sucks. No one seriously wants a desk with drawers and 2 ft of leg room, yet somehow these seem to go for upwards of $200. Your other option is to buy a folding table, not exactly a dream arrangement either (at least they are cheap). My option is to build one :) Requirements

  • Height, my googling suggests that at rest the elbow angle should be slightly more than 90 degrees. This depends on the chair, and the length of your legs, for me ~25″
  • Width, desks are all way too narrow, I think having room for multiple objects on your desk is not unreasonable, two screens, speakers : mine is 5′
  • Finish, this was the most difficult part to decide, the finish should make the table waterproof/scratchproof but epoxy tops and glass can be very expensive
  • Depth, enough for the screens, and room to work on paper in front of them ~ 28″
  • Detachable Top, essentially if you glue everything together you will never be able to transport the table

Design I’ll discuss the general joinery. I looked up some other ideas on how to make tables/work benches online. There are tons of different ways to do this, one of the easiest is to make a skirt (horizontal beams connecting the legs) and use 4 legs. This is very sturdy and simple, but it means an awkward way to put your legs under the table. Another option is to have essentially two legs which are joined in the middle by only one beam, less support, but more leg room. I chose option c), a 3 sided skirt, with the one in the front missing. Easy to do, with plenty of leg space, you only make it structurally unstable to side-to-side motion. Materials Good wood is hard to find and very expensive, so compromises are made

  1. 2 sheets of 3/4″ oak veneer plywood (tabletop)
  2. 3/4″x 1 1/2″ red oak (trimming)
  3. 4×4″ redwood (legs)
  4. 2×6″ redwood (skirt)
  5. Cherry stain, Mineral spirits and Satin polyurethane

Photos!

The skirt legs are done with a half-open mortar tenon joint. I drilled the holes first with a forstner bit, and then go back with chisels.
The skirt legs are done with a half-open mortar tenon joint. I drilled the holes first with a forstner bit, and then go back with chisels.
Table top two pieces of plywood joined together, and trimmed with a router. I used a friend's table saw to cut this, the nice thing is with a trim-bit on your router you don't have to worry about matching the sizes exactly.
Table top two pieces of plywood joined together, and trimmed with a router. I used a friend’s table saw to cut this, the nice thing is with a trim-bit on your router you don’t have to worry about matching the sizes exactly. I used cross-beams to clamp them together, but alas you always need more clamps and there is a small gap in some places.
The trimmings are joined to the edges of the tabletop
The trimmings are joined to the edges of the tabletop. I cut off the extra with a miter saw, and then a chisel. I bought some pipes for my pipeclamps and used several ratcheting band-clamp like things to glue this together.
This is the assembled and glued leg fixture
This is the assembled and glued leg fixture. Used belts and extra wood to make sure the joints were square when clamping
The top will be attached with screws into a few oak blocks
The top will be attached with screws into a few oak blocks, lining them up is a pain. I glued the blocks to the skirt, because it seemed convenient, but they could have just been drilled to both the bottom and the top
A look at the bottom of the tabletop before staining. The trimmings have been planed to be flat with the surface, and then rounded with a router. I have the wholes predrilled on the bottom with screws so that I can stain/finish both sides simultaneously.
A look at the bottom of the tabletop before staining. The trimmings have been hand-planed to be flat with the surface, and then rounded with a router. I have the wholes pre-drilled on the bottom with screws so that I can stain/finish both sides simultaneously.
The legs, ready for staining. What we don't see is the hours of sanding I did on everything...
The legs, ready for staining. What we don’t see is the hours of sanding I did on everything…
After a few coats of wipe-on poly. There are 9 coats total. The staining wasn't done very well, and it became very uneven and mixed with the first two coats of poly
After a few coats of wipe-on poly. There are 9 coats total. The staining wasn’t done very well, and it became very uneven and mixed with the first two coats of poly
Of course the stain color is different on two diff. woods. I only sand every 3 coats. Unfortunately there is a lot of dust around
Of course the stain color is different on two diff. woods. I only sand every 3 coats. Unfortunately there is a lot of dust around
My final desk setup. There are dust bits in the last part of the finish, but it is very waterproof, and I think it looks pretty good. And needless to say the dimensions are perfect
My final desk setup. There are dust bits in the last part of the finish, but it is very waterproof, and I think it looks pretty good. And needless to say the dimensions are perfect

Tips/Mistakes

  • Unfortunately I have no pictures, but this involved a lot of very creative clamping because I just don’t have the right ones. Clamps can be extended by using leather belts, racheting tie downs can be used as band clamps, etc.
  • Let the stain dry completely before finishing. Finishing takes forever but you need to be patient
  • Had never actually used a hand-plane before, but it made quick work of the trimming. Unfortunately it also makes quick work of the veneer!

    Welded together when still in the table :(
  • Redwood has tons of knots, I filled mine with wood putty, but they are not very pretty
  • The screws were a total mess, the alignment somehow shifted and they made their own holes, and then I stripped one of them and ended up having to weld a nail onto it to remove it (don’t try this at home)

Yet another backup script

I’m publishing a simple script I wrote a year or so ago to do automatic mysql backups with 7zip on a linux server. It is inspired by the original mysqlbackup script, in that it can rotate the databases on a daily/weekly etc. basis, but it also provides stuff like exporting to an ftp server, or simpler backups. It isn’t completely polished, so any forks/tips are welcome!

I’ve made a permanent page here with a download link and some more about it.

#!/usr/bin/python

#################################
# created by Alex Knaust 06/2011
#################################

# mysql login credentials
MYSQL_USERNAME = ''
MYSQL_PASSWORD = ''
MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306

# directory that directories (daily, weekly, monthly, etc.) will be created in
BACKUP_DIR = '/backup/mysql'

# filename pattern to archive files as, will be formatted with datetime.strftime
FILENAME_PATTERN='%d-%m-%Y_database-dump.7z'

# arguments for the p7zip program
P7ZIP_ARGS = '-t7z -bd -m0=LZMA2 -mx=9'

#file to write the intermediate dump to
TMPFILE = '/var/tmp/dbdump.sql'

# arguments for the mysqldump command (specify which databases here)
MYSQLDUMP_ARGS = '--all-databases --force'

# octal permissions for the database dump
PERMS = 0o640



###############################################################################
# DO NOT EDIT BELOW HERE
###############################################################################
from datetime import datetime, timedelta
import shutil, os, subprocess
from ftplib import FTP
from syslog import syslog

def dumpmysqldb(user, password, port, host, args='--all-databases --force', 
            filename=TMPFILE):
    '''Runs mysqldump to backup all databases to filename, args are passed to mysqldump
        raises an Exception if the filename does not exist afterwards
    '''
    
    # set permissions of PERMS file to be written to
    with open(filename, 'w') as f:
        pass
    os.chmod(filename, PERMS)
    
    mysqldumpcommand = '''mysqldump --user="{user}" --password="{passwd}" --host={host} --port={port} {args} > {output}'''.format(
        user=user, passwd=password, output=filename, args= args, host=host, port=port)
    
    pipe = subprocess.Popen(mysqldumpcommand, shell=True, stdout=subprocess.PIPE)
    pipe.communicate()
    
    if not os.path.isfile(filename):
        syslog("{0} doesnt seem to exist although it should".format(filename))
        raise Exception("{0} doesnt seem to exist, although it should".format(filename))


def compress_7z(path, args='-bd', outfile=None):
    '''compresses a file with p7zip, outputting as .7z file, returns the name of the
        compressed file
    '''
    if not outfile:
        outfile = os.path.join(os.path.split(path)[0], 'dump.7z')
        
    p7zipcommand = '''7z a {args} "{outfile}" "{filename}"'''.format(
            args=args, filename=path, outfile=outfile)
    pipe2 = subprocess.Popen(p7zipcommand, shell=True, stdout=subprocess.PIPE)
    pipe2.communicate()
    
    if not os.path.isfile(outfile):
        syslog("{0} was not created correctly".format(outfile))
        raise Exception("{0} was not created correctly".format(outfile))
    else:
	os.chmod(outfile, PERMS)
        return outfile


class BackupUpdater:
    '''Class that handles deleting old files'''
    def __init__(self):
        raise NotImplementedError
    
    def update(self, time, file):
        '''This will be called when a new dump is created'''
        raise NotImplementedError

    
class BasicBackupUpdater(BackupUpdater):
    '''Just saves the files in the directory given'''
    def __init__(self, directory, fileformat='%d-%m-%Y_database-dump'):
        self.directory = directory
        self.fileformat = fileformat
        if not os.path.isdir(self.directory):
            os.makedirs(self.directory)
        
    def update(self, time, file):
        newfilename = time.strftime(self.fileformat)
        shutil.move(file, os.path.join(self.directory, newfilename))


class FTPBasicBackupUpdater(BackupUpdater):
    '''Saves the file to a directory on an FTP Server'''
    def __init__(self, directory, user, passwd, host, fileformat='%d-%m-%Y_database-dump'):
        self.fileformat = fileformat
        self.FTP = FTP(host=host, user=user, passwd=passwd)
        self.ftp.cwd(directory)
        
    def update(self, time, file):
        newfilename = time.strftime(self.fileformat)
        with open(newfilename, 'rb') as tfile:
            self.FTP.storbinary("STOR " + newfilename, tfile)
        ftp.quit()

    
class AdvancedBackupUpdater(BasicBackupUpdater):
    '''Works by rotating the backups into a folder hierarchy based on their
    creation time, i.e. after 1 week the daily folder will be emptied and the
    oldest file will be moved to weekly, and the newest file will now be in daily
    , does the same for weekly, monthly, yearly.
    '''
    folders = (
            ('daily', timedelta(weeks=1)),
            ('weekly', timedelta(weeks=5)),
            ('monthly', timedelta(weeks=52)),
            ('yearly', timedelta.max), #if this program works for more than 2 million years, we're in trouble
        )
        
            
    def __init__(self, directory, fileformat):
        BasicBackupUpdater.__init__(self, directory, fileformat)


    def _getAge(self, directory):
        '''Returns a datetime object representing the oldest creation date in a directory'''
        filelist = [os.path.join(directory, f) for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
        if filelist:
            filelist.sort(key= lambda f: os.path.getmtime(f))
            return datetime.fromtimestamp(os.path.getmtime((filelist[0]))), filelist[0]
        else: return datetime.now(), None


    def _updatePushing(self, time, filename, index=0):
            folder = os.path.join(self.directory, self.folders[index][0])
            delta = self.folders[index][1]
            
            if not os.path.isdir(folder):
                os.makedirs(folder)
            oldesttime, oldestfile = self._getAge(folder)
            
            #if its time to rotate, push them up with a recursive call
            if time - oldesttime > delta:
                self._updatePushing(oldesttime, os.path.join(folder, oldestfile), index+1)
                for file in [os.path.join(folder, f) for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]:
                    os.remove(file)
            try:
                shutil.move(filename, folder)
            except shutil.Error:
                syslog("Error : file {0} already exists".format(os.path.join(folder, filename)))
                print "Error : file {0} already exists".format(os.path.join(folder, filename))
                pass #probably already existed
    
    
    def update(self, time, filename):
        nf = os.path.join(os.path.split(filename)[0], 
            time.strftime(self.fileformat))
        shutil.move(filename, nf)
        self._updatePushing(time, nf, index=0)
        
        
if __name__=='__main__':
    print 'Dumping databases...'
    syslog('Dumping databases...')
    dumpmysqldb(user=MYSQL_USERNAME, password=MYSQL_PASSWORD,
             host=MYSQL_HOST, args=MYSQLDUMP_ARGS, port=MYSQL_PORT, filename=TMPFILE)
    
    print 'Compressing {0} with 7z...'.format(TMPFILE)
    syslog('Compressing {0} with 7z...'.format(TMPFILE))
    cmpfile = compress_7z(TMPFILE, args=P7ZIP_ARGS)
    
    print 'Updating backups with {0} in {1}'.format(cmpfile, BACKUP_DIR)
    syslog('Updating backups with {0} in {1}'.format(cmpfile, BACKUP_DIR))
    b = AdvancedBackupUpdater(BACKUP_DIR, FILENAME_PATTERN)
    b.update(datetime.now(), cmpfile)
    
    print'Success'
        

Cleaning Razer DeathAdd3r 3.5G

This is my second Razer mouse, and this one has lasted much better than the last (which was a diamondback). In the original, I had replaced all of the Green LEDs with red ones to match my case at the time, because I was young and into such things. The Deathadd3r has a similar layout on the inside. This mouse is already a few years old, and lately the wheel has been acting up. Sometimes it will scroll in the wrong direction, and performing a middle click is very difficult. I figured cleaning it might help remedy this situation.   To take it apart, there are three screws on the bottom, two under the top two teflon pads, which can be put back pretty safely.

DSC_0853DSC_0858

There are two boards inside, each is attached to the bottom/top half by means of two screws and some plastic locks which require you to tilt the boards to remove them. The bottom board also has a little plastic tab on the side holding it in place.DSC_0857

DSC_0852

Additonally there is an led stuck in the top, which makes the logo glow that you probably want to take out. Unlike the diamondback, you cannot separate the two boards, however I didn’t feel there was a good reason to clean the motherboard, as all of the switches seemed to be functioning fine.The top shell can be separated into two layers by pushing out a few plastic clear tabs on the inside of it. You need to slide it upwards to disengage the left/right mouse click assembly.

DSC_0854

My wheel was coated in hair and a thick layer of oil… (sebum?) as well as plenty of grime everywhere. I washed all the parts in a warm/water detergent solution with a toolbrush, except the reflector for the tracker (best not to touch that).

DSC_0855DSC_0860

In the end it is cleaner and actually works better. The middle mouse button still isn’t 100%, but it works much better than before, and the scrolling seems to have improved, although I suspect there is some slippage in the switch itself.

Reversing Chunks of a Linked List

One of my fellow researchers asked me this question, which he said was an interview question for amazon.

Write an algorithm to reverse blocks of length k- in a list. i.e. if k=3 and the list is originally \{0,1,2,3,4,5,6,7,8\} ; it should produce \{2,1,0,5,4,3,8,7,6\}.

There’s already a clever trick to reverse a linked list without requiring any extra space. The idea is that we just iterate through the list and reverse all the pointers. In java this would look something like this

	public static Node reverse(Node list){
		Node prev = null;
		while(list != null){
			Node next = list.next;
			list.next = prev;
			prev = list;
			list = next;
		}
		return prev;
	}

This will reverse the entire list (by going until the next link is null), so if we want to reverse blocks of length k instead, we should add an additional parameter and condition, to obtain below.

	public static Node reverse(Node list, int k){
		Node prev = null;
		while(list != null && k-- > 0){
			Node next = list.next;
			list.next = prev;
			prev = list;
			list = next;
		}
		return prev;
	}

So what happens when we call reverse on a list? Imagine we have the list 0 \rightarrow 1 \rightarrow 2 \rightarrow 3 \rightarrow 4\rightarrow \emptyset after a call to reverse with k=2 on the first node (0), the list will be \emptyset \leftarrow 0 \leftarrow 1\quad 2\rightarrow 3 \rightarrow 4, and the call returns a pointer to ‘1’. Now we see that the trick we need to overcome is losing our place in the list as we do the block-wise reversal. I did this by keeping track of basically three positions : the first element of the block you are going to reverse, the last element of this block (which is returned by the call to reverse) and the first element of the next block to be reversed.

This way, after we have reversed the first j-1 blocks, we only need to reverse block j, point the end of block j-1 to the new beginning of block j, and then update positions to go to block j+1. In code this looks like… (I handle the first block specially so we know how to return the start of the list)

	public static Node kreverse(Node list, int k){
		Node prev = list;
		int i = 0;

		//first block special case?
		while(list != null && i++ < k){
			list = list.next;
		}
		Node retval = reverse(prev, k);

		while(list != null){
			i = 0;
			Node me = list;
			while(list != null && i++ < k)
				list = list.next;
			Node end = reverse(me, k);
			prev.next = end;

			prev = me;
		}

		return retval;
	}

What is great is that we have only visited each element in the list about twice, (once in the inner while loop of kreverse, and once in reverse) and at most constant extra space. In fact, we can even avoid visiting each element twice if we are clever with the reverse method, and return multiple values (Python!!!!). So this method is O(n) in both time and complexity. One other consideration is what will happen if the length of the list is not divisible by k.