def rotateFile(filepath, depth=5, suffix='.', verbosity=0): """ backup/rotate a file depth (-1==no limit) refers to num. of backups (rotations) to keep. Behavior: (1) x.txt (current) x.txt.1 (old) x.txt.2 (older) x.txt.3 (oldest) (2) all file stats preserved. Doesn't blow away original file. (3) if x.txt and x.txt.1 are identical (size or checksum), None is returned """ # check argument sanity (should really be down outside of this function) if not filepath or type(filepath) != type(''): raise ValueError("filepath '%s' is not a valid arguement" % filepath) if type(depth) != type(0) or depth < -1 \ or depth > sys.maxint-1 or depth == 0: raise ValueError("depth must fall within range " "[-1, 1...%s]" % (sys.maxint-1)) # force verbosity to be a numeric value verbosity = verbosity or 0 if type(verbosity) != type(0) or verbosity < -1 \ or verbosity > sys.maxint-1: raise ValueError('invalid verbosity value: %s' % (verbosity)) filepath = cleanupAbsPath(filepath) if not os.path.isfile(filepath): raise ValueError("filepath '%s' does not lead to a file" % filepath) pathNSuffix = filepath + suffix pathNSuffix1 = pathNSuffix + '1' if verbosity > 1: sys.stderr.write("Working dir: %s\n" % os.path.dirname(pathNSuffix)) # is there anything to do? (existence, then size, then checksum) checksum_type = 'md5' # FIXME: this should be configuation option if os.path.exists(pathNSuffix1) and os.path.isfile(pathNSuffix1) \ and os.stat(filepath)[6] == os.stat(pathNSuffix1)[6] \ and getFileChecksum(checksum_type, filepath) == \ getFileChecksum(checksum_type, pathNSuffix1): # nothing to do if verbosity: sys.stderr.write("File '%s' is identical to its rotation. " "Nothing to do.\n" % os.path.basename(filepath)) return None # find last in series (of rotations): last = 0 while os.path.exists('%s%d' % (pathNSuffix, last+1)): last = last+1 # percolate renames: for i in range(last, 0, -1): os.rename('%s%d' % (pathNSuffix, i), '%s%d' % (pathNSuffix, i+1)) if verbosity > 1: filename = os.path.basename(pathNSuffix) sys.stderr.write("Moving file: %s%d --> %s%d\n" % (filename, i, filename, i+1)) # blow away excess rotations: if depth != -1: last = last+1 for i in range(depth+1, last+1): path = '%s%d' % (pathNSuffix, i) os.unlink(path) if verbosity: sys.stderr.write("Rotated out: '%s'\n" % ( os.path.basename(path))) # do the actual rotation shutil.copy2(filepath, pathNSuffix1) if os.path.exists(pathNSuffix1) and verbosity: sys.stderr.write("Backup made: '%s' --> '%s'\n" % (os.path.basename(filepath), os.path.basename(pathNSuffix1))) # return the full filepath of the backed up file return pathNSuffix1
def rotateFile(filepath, depth=5, suffix='.', verbosity=0): """ backup/rotate a file depth (-1==no limit) refers to num. of backups (rotations) to keep. Behavior: (1) x.txt (current) x.txt.1 (old) x.txt.2 (older) x.txt.3 (oldest) (2) all file stats preserved. Doesn't blow away original file. (3) if x.txt and x.txt.1 are identical (size or checksum), None is returned """ # check argument sanity (should really be down outside of this function) if not filepath or type(filepath) != type(''): raise ValueError("filepath '%s' is not a valid arguement" % filepath) if type(depth) != type(0) or depth < -1 \ or depth > sys.maxint - 1 or depth == 0: raise ValueError("depth must fall within range " "[-1, 1...%s]" % (sys.maxint - 1)) # force verbosity to be a numeric value verbosity = verbosity or 0 if type(verbosity) != type(0) or verbosity < -1 \ or verbosity > sys.maxint - 1: raise ValueError('invalid verbosity value: %s' % (verbosity)) filepath = cleanupAbsPath(filepath) if not os.path.isfile(filepath): raise ValueError("filepath '%s' does not lead to a file" % filepath) pathNSuffix = filepath + suffix pathNSuffix1 = pathNSuffix + '1' if verbosity > 1: sys.stderr.write("Working dir: %s\n" % os.path.dirname(pathNSuffix)) # is there anything to do? (existence, then size, then checksum) checksum_type = 'sha1' if os.path.exists(pathNSuffix1) and os.path.isfile(pathNSuffix1) \ and os.stat(filepath)[6] == os.stat(pathNSuffix1)[6] \ and getFileChecksum(checksum_type, filepath) == \ getFileChecksum(checksum_type, pathNSuffix1): # nothing to do if verbosity: sys.stderr.write("File '%s' is identical to its rotation. " "Nothing to do.\n" % os.path.basename(filepath)) return None # find last in series (of rotations): last = 0 while os.path.exists('%s%d' % (pathNSuffix, last + 1)): last = last + 1 # percolate renames: for i in range(last, 0, -1): os.rename('%s%d' % (pathNSuffix, i), '%s%d' % (pathNSuffix, i + 1)) if verbosity > 1: filename = os.path.basename(pathNSuffix) sys.stderr.write("Moving file: %s%d --> %s%d\n" % (filename, i, filename, i + 1)) # blow away excess rotations: if depth != -1: last = last + 1 for i in range(depth + 1, last + 1): path = '%s%d' % (pathNSuffix, i) os.unlink(path) if verbosity: sys.stderr.write("Rotated out: '%s'\n" % (os.path.basename(path))) # do the actual rotation shutil.copy2(filepath, pathNSuffix1) if os.path.exists(pathNSuffix1) and verbosity: sys.stderr.write( "Backup made: '%s' --> '%s'\n" % (os.path.basename(filepath), os.path.basename(pathNSuffix1))) # return the full filepath of the backed up file return pathNSuffix1