rmf actions
This commit is contained in:
parent
21615a1f9c
commit
e76aaec03c
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue