diff --git a/filetimelapse.py b/filetimelapse.py new file mode 100644 index 0000000..57b93c9 --- /dev/null +++ b/filetimelapse.py @@ -0,0 +1,76 @@ +''' +Copy your file every few minutes while you work on it, so that you can have snapshots of its history. +Not a replacement for real version control but could be applicable in very simple situations or in +cases where e.g. git is not. +''' +import argparse +import glob +import hashlib +import os +import shutil +import sys +import time + +from voussoirkit import bytestring +from voussoirkit import pathclass + +def hash_file(filepath, hasher): + bytestream = read_filebytes(filepath) + for chunk in bytestream: + hasher.update(chunk) + return hasher.hexdigest() + +def hash_file_md5(filepath): + return hash_file(filepath, hasher=hashlib.md5()) + +def read_filebytes(filepath, chunk_size=bytestring.MIBIBYTE): + ''' + Yield chunks of bytes from the file between the endpoints. + ''' + filepath = pathclass.Path(filepath) + if not filepath.is_file: + raise FileNotFoundError(filepath) + + f = open(filepath.absolute_path, 'rb') + with f: + while True: + chunk = f.read(chunk_size) + if len(chunk) == 0: + break + + yield chunk + +def filetimelapse(filepath, rate): + (noext, extension) = os.path.splitext(filepath) + + last_hash = None + existing_timelapses = glob.glob(f'{noext}-*.filetimelapse{extension}') + if existing_timelapses: + last_hash = hash_file_md5(existing_timelapses[-1]) + print(f'Starting with previous {existing_timelapses[-1]} {last_hash}') + + while True: + new_hash = hash_file_md5(filepath) + if new_hash != last_hash: + timestamp = time.strftime('%Y%m%d%H%M%S') + copy_name = f'{noext}-{timestamp}.filetimelapse{extension}' + shutil.copy(filepath, copy_name) + last_hash = new_hash + print(copy_name, new_hash) + time.sleep(rate) + +def filetimelapse_argparse(args): + return filetimelapse(args.filepath, args.rate) + +def main(argv): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument('filepath') + parser.add_argument('--rate', dest='rate', default=None, required=True, type=int) + parser.set_defaults(func=filetimelapse_argparse) + + args = parser.parse_args(argv) + args.func(args) + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:]))