rmf actions

This commit is contained in:
Geoffrey Frogeye 2019-06-18 08:47:55 +02:00
parent 21615a1f9c
commit e76aaec03c

View file

@ -13,7 +13,7 @@ import progressbar
import logging import logging
progressbar.streams.wrap_stderr() progressbar.streams.wrap_stderr()
coloredlogs.install(level='INFO', fmt='%(levelname)s %(message)s') coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s')
log = logging.getLogger() log = logging.getLogger()
# 1) Create file list with conflict files # 1) Create file list with conflict files
@ -34,12 +34,24 @@ class Table():
def __init__(self, width, height): def __init__(self, width, height):
self.width = width self.width = width
self.height = height self.height = height
self.data = [['' ** self.height] ** self.width] self.data = [['' for _ in range(self.height)]
for _ in range(self.width)]
def set(x, y, data): def set(self, x, y, data):
self.data[x][y] = str(data) self.data[x][y] = str(data)
def print(self):
widths = [max([len(cell) for cell in column]) for column in self.data]
for y in range(self.height):
for x in range(self.width):
cell = self.data[x][y]
l = len(cell)
width = widths[x]
if x > 0:
cell = ' | ' + cell
cell = cell + ' ' * (width - l)
print(cell, end='\t')
print()
class Database(): class Database():
@ -137,7 +149,8 @@ class Database():
max_value=self.totalChecksumSize(), widgets=widgets).start() max_value=self.totalChecksumSize(), widgets=widgets).start()
f = 0 f = 0
for databaseFile in self.data.values(): for databaseFile in self.data.values():
bar.update(f, dir=databaseFile.root[(len(self.directory)+1):], file=databaseFile.filename) bar.update(f, dir=databaseFile.root[(
len(self.directory)+1):], file=databaseFile.filename)
f += databaseFile.totalChecksumSize() f += databaseFile.totalChecksumSize()
try: try:
databaseFile.getChecksums() databaseFile.getChecksums()
@ -148,14 +161,20 @@ class Database():
pass pass
bar.finish() bar.finish()
def act(self): def printDifferences(self):
pass for databaseFile in self.data.values():
print()
databaseFile.printInfos(diff=True)
def takeAction(self, execute=False, *args, **kwargs):
for databaseFile in self.data.values():
databaseFile.decideAction(*args, **kwargs)
databaseFile.takeAction(execute=execute)
class DatabaseFile(): class DatabaseFile():
BLOCK_SIZE = 4096 BLOCK_SIZE = 4096
RELEVANT_STATS = ('st_mode', 'st_uid', 'st_gid', RELEVANT_STATS = ('st_mode', 'st_uid', 'st_gid', 'st_size', 'st_mtime')
'st_size', 'st_mtime', 'st_ctime')
def __init__(self, root, filename): def __init__(self, root, filename):
self.root = root self.root = root
@ -163,6 +182,7 @@ class DatabaseFile():
self.stats = [] self.stats = []
self.conflicts = [] self.conflicts = []
self.checksums = [] self.checksums = []
self.action = None
log.debug(f"{self.root}/{self.filename} - new") log.debug(f"{self.root}/{self.filename} - new")
def addConflict(self, conflict): def addConflict(self, conflict):
@ -190,16 +210,16 @@ class DatabaseFile():
del self.checksums[f] del self.checksums[f]
log.debug(f"{self.root}/{self.filename} - del: {conflict}") log.debug(f"{self.root}/{self.filename} - del: {conflict}")
def getPathFile(self, conflict): def getPath(self, conflict):
return os.path.join(self.root, conflict) return os.path.join(self.root, conflict)
def getPathFiles(self): def getPaths(self):
return [self.getPathFile(conflict) for conflict in self.conflicts] return [self.getPath(conflict) for conflict in self.conflicts]
def prune(self): def prune(self):
toPrune = list() toPrune = list()
for conflict in self.conflicts: for conflict in self.conflicts:
if not os.path.isfile(self.getPathFile(conflict)): if not os.path.isfile(self.getPath(conflict)):
toPrune.append(conflict) toPrune.append(conflict)
if len(toPrune): if len(toPrune):
@ -236,7 +256,7 @@ class DatabaseFile():
def getStats(self): def getStats(self):
for f, conflict in enumerate(self.conflicts): for f, conflict in enumerate(self.conflicts):
oldStat = self.stats[f] oldStat = self.stats[f]
newStat = os.stat(self.getPathFile(conflict)) newStat = os.stat(self.getPath(conflict))
oldChecksum = self.checksums[f] oldChecksum = self.checksums[f]
# If it's been already summed, and we have the same inode and same ctime, don't resum # If it's been already summed, and we have the same inode and same ctime, don't resum
@ -262,7 +282,7 @@ class DatabaseFile():
if self.checksums[f] is not None: if self.checksums[f] is not None:
continue continue
self.checksums[f] = 1 self.checksums[f] = 1
filedescs[f] = open(self.getPathFile(conflict), 'rb') filedescs[f] = open(self.getPath(conflict), 'rb')
while len(filedescs): while len(filedescs):
toClose = set() toClose = set()
@ -285,10 +305,12 @@ class DatabaseFile():
def getFeatures(self): def getFeatures(self):
features = dict() features = dict()
features['name'] = self.conflicts
features['sum'] = self.checksums features['sum'] = self.checksums
for stat in DatabaseFile.RELEVANT_STATS: for statName in DatabaseFile.RELEVANT_STATS:
features[stat] = [self.stats[f].__getattribute__( # Rounding beause I Syncthing also rounds
stat) for f in enumerate(self.stats)] features[statName] = [
int(stat.__getattribute__(statName)) for stat in self.stats]
return features return features
def getDiffFeatures(self): def getDiffFeatures(self):
@ -299,18 +321,64 @@ class DatabaseFile():
diffFeatures[key] = vals diffFeatures[key] = vals
return diffFeatures return diffFeatures
def printInfos(self): @staticmethod
print(os.path.join(self.root, self.name)) def shortConflict(conflict):
match = Database.CONFLICT_PATTERN.search(conflict)
if match:
return match[0][15:]
else:
return '-'
# nf = re.sub( '', f) def printInfos(self, diff=True):
# F = os.path.join(root, f) print(os.path.join(self.root, self.filename))
# NF = os.path.join(root, nf) if diff:
# if os.path.exists(NF): features = self.getDiffFeatures()
# print(f"'{F}' → '{NF}': file already exists") else:
# else: features = self.getFeatures()
# print(f"'{F}' → '{NF}': done") features['name'] = [DatabaseFile.shortConflict(
# # os.rename(F, NF) c) for c in self.conflicts]
table = Table(len(features), len(self.conflicts)+1)
for x, featureName in enumerate(features.keys()):
table.set(x, 0, featureName)
for x, featureName in enumerate(features.keys()):
for y in range(len(self.conflicts)):
table.set(x, y+1, features[featureName][y])
table.print()
def decideAction(self):
# TODO More arguments for choosing
reason = "undecided"
self.action = None
if len(self.conflicts) == 1:
self.action = 0
reason = "only file"
else:
features = self.getDiffFeatures()
if len(features) == 1:
reason = "same files"
self.action = 0
if self.action is None:
log.warning(
f"{self.root}/{self.filename}: skip, cause: {reason}")
else:
log.info(
f"{self.root}/{self.filename}: keep {DatabaseFile.shortConflict(self.conflicts[self.action])}, cause: {reason}")
def takeAction(self, execute=False):
if self.action is None:
return
actionName = self.conflicts[self.action]
if actionName != self.filename:
log.debug(
f"Rename {self.getPath(actionName)} → {self.getPath(self.filename)}")
if execute:
os.rename(self.getPath(actionName), self.getPath(self.filename))
for conflict in self.conflicts:
if conflict is actionName:
continue
log.debug(f"Delete {self.getPath(conflict)}")
if execute:
os.unlink(self.getPath(conflict))
if __name__ == "__main__": if __name__ == "__main__":
@ -318,11 +386,14 @@ if __name__ == "__main__":
description="Handle Syncthing's .sync-conflict files ") description="Handle Syncthing's .sync-conflict files ")
# Execution flow # Execution flow
parser.add_argument(
'--database', help='Database path for file informations')
parser.add_argument('directory', metavar='DIRECTORY', parser.add_argument('directory', metavar='DIRECTORY',
nargs='?', help='Directory to analyse') nargs='?', help='Directory to analyse')
parser.add_argument('-d', '--database',
help='Database path for file informations')
parser.add_argument('-e', '--execute', action='store_true',
help='Really apply changes')
parser.add_argument('-p', '--print', action='store_true',
help='Only print differences between files')
args = parser.parse_args() args = parser.parse_args()
@ -362,4 +433,7 @@ if __name__ == "__main__":
database.getChecksums() database.getChecksums()
saveDatabase() saveDatabase()
database.act() if args.print:
database.printDifferences()
else:
database.takeAction(execute=args.execute)