#!/usr/bin/python

import hashlib
import io
import os
import tempfile

def files_differ(A, B):
    '''
    Return True if the content of the two files differ, False otherwise.
    '''
    if os.stat(A).st_size != os.stat(B).st_size: return True

    differ_status = False
    A_fi = B_fi = None
    sz = io.DEFAULT_BUFFER_SIZE
    try:
        A_fi, B_fi = open(A, 'rb'), open(B, 'rb')
        while True:
            A_buf, B_buf = A_fi.read(sz), B_fi.read(sz)
            if A_buf != B_buf:
                differ_status = True
                break
            if len(A_buf) < sz: break
    finally:
        if A_fi is not None: A_fi.close()
        if B_fi is not None: B_fi.close()

    return differ_status

def get_dir_name_from_hexdigest(digest):
    return digest[0:2]

def get_file_name_from_hexdigest(digest):
    return digest[2:]

def get_path_from_hexdigest(digest):
    return os.path.join(get_dir_name_from_hexdigest(digest),
        get_file_name_from_hexdigest(digest))

def add_file(addee, rootdir = ''):
    '''
    Add the given file to the given hash directory and return the hex digest
    thereof.  The addee can be given as either a file name or a file-like
    object.
    '''
    if rootdir and not os.path.exists(rootdir):
        raise ValueError('Directory not there: ' + rootdir)
    if rootdir and not os.path.isdir(rootdir):
        raise ValueError('Not directory: ' + rootdir)

    fi = fdo = None
    try:
        if type(addee) in (str, unicode):
            fi = open(addee, 'rb')
        else:
            fi = addee

        fdo, fdo_name = tempfile.mkstemp(dir = rootdir)

        sz = io.DEFAULT_BUFFER_SIZE
        fhash = hashlib.sha1()
        while True:
            buf = fi.read(sz)
            os.write(fdo, buf)
            fhash.update(buf)
            if len(buf) < sz: break
    finally:
        if fi is not None and id(fi) != id(addee): fi.close()
        if fdo is not None: os.close(fdo)

    digest = fhash.hexdigest()

    fdir = os.path.join(rootdir, get_dir_name_from_hexdigest(digest))
    if not os.path.exists(fdir): os.mkdir(fdir)

    targ = os.path.join(rootdir, get_path_from_hexdigest(digest))
    if os.path.exists(targ):
        if files_differ(fdo_name, targ):
            raise RuntimeError('Addee collided with extant file: %s, %s' % (
                fdo_name, targ))
        os.unlink(fdo_name)
    else:
        os.rename(fdo_name, targ)

    return digest

@@ -1013,17 +1013,34 @@
 tdir = '
-'
+.', rename = False
 ):%0A    '
@@ -1204,16 +1204,143 @@
  object.
+  If rename_file is True and addee is an already extant file,%0A    then it is moved to the new location instead of a copy of it.
 %0A    '''
@@ -1566,24 +1566,49 @@
  fdo = None%0A
+    rename_addee = False%0A
     try:%0A   
@@ -1673,32 +1673,66 @@
 en(addee, 'rb')%0A
+            rename_addee = rename%0A
         else:%0A  
@@ -1753,16 +1753,49 @@
  addee%0A%0A
+        if not rename_addee:%0A    
         
@@ -1837,24 +1837,67 @@
 r = rootdir)
+%0A        else:%0A            fdo_name = addee
 %0A%0A        sz
@@ -2010,24 +2010,45 @@
 %0A           
+ if not rename_addee:
  os.write(fd
@@ -2709,16 +2709,16 @@
  targ)%0A%0A
-
     retu
@@ -2719,16 +2719,686 @@
   return digest%0A
+%0Aclass HashDir(object):%0A    def __init__(self, rootdir = '.', rename = False):%0A        self.rootdir = rootdir%0A        self.rename = rename%0A%0A    def addFile(self, addee):%0A        '''Add addee to the hashdir and return the hex digest thereof.'''%0A        return add_file(addee, self.rootdir, self.rename)%0A%0A    def pathFromHexDigest(self, digest):%0A        '''Get the path to the file associated with the given digest.'''%0A        return os.path.join(self.rootdir, get_path_from_hexdigest(digest))%0A%0A    def openFileFromHexDigest(self, digest):%0A        '''Open the file associated with the given digest for reading.'''%0A        return open(self.pathFromHexDigest(digest), 'rb')%0A

Add the option to add_file() to rename the addee.