|
|
@ -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() |