| @ -0,0 +1,115 @@ | |||||
| #!/usr/bin/env python3 | |||||
| import os | |||||
| import logging | |||||
| import string | |||||
| import hashlib | |||||
| import time | |||||
| class Cloner: | |||||
| def __init__(self, targetDir: str, backupDir: str): | |||||
| self.targetDir = targetDir | |||||
| self.backupDir = backupDir | |||||
| self.interval = 1 | |||||
| def handleDir(self, dirPath: str): | |||||
| backupDirPath = dirPath.replace(self.targetDir, self.backupDir) | |||||
| if not os.path.isdir(backupDirPath): | |||||
| os.system('mkdir {path}'.format(path=backupDirPath)) | |||||
| with os.scandir(dirPath) as it: | |||||
| for entry in it: | |||||
| if entry.is_dir(): | |||||
| self.handleDir(entry.path) | |||||
| elif entry.is_file(): | |||||
| self.handleFile(entry.path) | |||||
| def handleFile(self, fileName: str): | |||||
| cloneFileName = fileName.replace(self.targetDir, self.backupDir) | |||||
| if os.path.exists(cloneFileName): | |||||
| with open(cloneFileName + '.md5', 'r') as cloneHashFile, open(fileName, 'rb') as targetFile: | |||||
| cloneHashFileContent = cloneHashFile.read() | |||||
| targetFileContent = targetFile.read() | |||||
| realHash = hashlib.md5(targetFileContent).hexdigest() | |||||
| if realHash != cloneHashFileContent: | |||||
| os.system('cp {backup} {real}'.format( | |||||
| backup=cloneFileName, real=fileName)) | |||||
| logging.warning( | |||||
| '{file} failed check, restoring...'.format(file=fileName)) | |||||
| else: | |||||
| logging.info('{file} is fine'.format(file=fileName)) | |||||
| else: | |||||
| logging.warning( | |||||
| '{file} is a new file, added to backup dir'.format(file=fileName)) | |||||
| os.system('cp {real} {clone}'.format( | |||||
| real=fileName, clone=cloneFileName)) | |||||
| with open(cloneFileName + '.md5', 'w') as cloneHashFile, open(fileName, 'rb') as targetFile: | |||||
| targetFileContent = targetFile.read() | |||||
| cloneHashFile.write(hashlib.md5(targetFileContent).hexdigest()) | |||||
| def setLog(self, logFileName: str): | |||||
| logFormatter = logging.Formatter( | |||||
| fmt="[%(asctime)s][%(levelname)s] %(message)s", | |||||
| datefmt='%d-%b-%y %H:%M:%S') | |||||
| rootLogger = logging.getLogger() | |||||
| fileHandler = logging.FileHandler(logFileName, mode='a') | |||||
| fileHandler.setFormatter(logFormatter) | |||||
| rootLogger.addHandler(fileHandler) | |||||
| # # uncomment this to enable console logging | |||||
| # consoleHandler = logging.StreamHandler() | |||||
| # consoleHandler.setFormatter(logFormatter) | |||||
| # rootLogger.addHandler(consoleHandler) | |||||
| rootLogger.setLevel(logging.WARNING) | |||||
| def setInterval(self, interval: int): | |||||
| self.interval = interval | |||||
| def start(self): | |||||
| while True: | |||||
| self.handleDir(self.targetDir) | |||||
| time.sleep(5) | |||||
| if __name__ == "__main__": | |||||
| import argparse | |||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument( | |||||
| '--target', | |||||
| metavar='PATH', | |||||
| type=str, | |||||
| required=True, | |||||
| help='directory to clone and monitor') | |||||
| parser.add_argument( | |||||
| '--backup', | |||||
| metavar='PATH', | |||||
| type=str, | |||||
| required=True, | |||||
| help='backup directory to store clone' | |||||
| ) | |||||
| parser.add_argument( | |||||
| '--interval', | |||||
| metavar='SECOND', | |||||
| type=int, | |||||
| default=1, | |||||
| help='interval in second(s) which the scripts run, default is 1' | |||||
| ) | |||||
| parser.add_argument( | |||||
| '--log', | |||||
| metavar='LOGFILE', | |||||
| type=str, | |||||
| default='/var/log/py-dir-watcher/log.txt', | |||||
| help='specify custom logfile target, default is \'/var/log/py-dir-watcher/log.txt\' ' | |||||
| ) | |||||
| args = parser.parse_args() | |||||
| app = Cloner(args.target, args.backup) | |||||
| app.setLog(args.log) | |||||
| app.setInterval(args.interval) | |||||
| app.start() | |||||