|
|
|
@ -12,21 +12,21 @@ import anytree
|
|
|
|
|
from pyfzf.pyfzf import FzfPrompt
|
|
|
|
|
|
|
|
|
|
# local imports
|
|
|
|
|
import catcli.utils as utils
|
|
|
|
|
from catcli.utils import size_to_str, epoch_to_str, md5sum
|
|
|
|
|
from catcli.logger import Logger
|
|
|
|
|
from catcli.decomp import Decomp
|
|
|
|
|
from . import __version__ as VERSION
|
|
|
|
|
from catcli.version import __version__ as VERSION
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
There are 4 types of node:
|
|
|
|
|
|
|
|
|
|
class Noder:
|
|
|
|
|
"""
|
|
|
|
|
handles node in the catalog tree
|
|
|
|
|
There are 4 types of node:
|
|
|
|
|
* "top" node representing the top node (generic node)
|
|
|
|
|
* "storage" node representing a storage
|
|
|
|
|
* "dir" node representing a directory
|
|
|
|
|
* "file" node representing a file
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Noder:
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
TOPNAME = 'top'
|
|
|
|
|
METANAME = 'meta'
|
|
|
|
@ -41,11 +41,11 @@ class Noder:
|
|
|
|
|
'total_space,meta')
|
|
|
|
|
|
|
|
|
|
def __init__(self, debug=False, sortsize=False, arc=False):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
@debug: debug mode
|
|
|
|
|
@sortsize: sort nodes by size
|
|
|
|
|
@arch: handle archive
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
self.hash = True
|
|
|
|
|
self.debug = debug
|
|
|
|
|
self.sortsize = sortsize
|
|
|
|
@ -54,20 +54,20 @@ class Noder:
|
|
|
|
|
self.decomp = Decomp()
|
|
|
|
|
|
|
|
|
|
def get_storage_names(self, top):
|
|
|
|
|
'''return a list of all storage names'''
|
|
|
|
|
"""return a list of all storage names"""
|
|
|
|
|
return [x.name for x in list(top.children)]
|
|
|
|
|
|
|
|
|
|
def get_storage_node(self, top, name, path=None):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
return the storage node if any
|
|
|
|
|
if path is submitted, it will update the media info
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
found = None
|
|
|
|
|
for n in top.children:
|
|
|
|
|
if n.type != self.TYPE_STORAGE:
|
|
|
|
|
for node in top.children:
|
|
|
|
|
if node.type != self.TYPE_STORAGE:
|
|
|
|
|
continue
|
|
|
|
|
if n.name == name:
|
|
|
|
|
found = n
|
|
|
|
|
if node.name == name:
|
|
|
|
|
found = node
|
|
|
|
|
break
|
|
|
|
|
if found and path and os.path.exists(path):
|
|
|
|
|
found.free = shutil.disk_usage(path).free
|
|
|
|
@ -76,23 +76,23 @@ class Noder:
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
def get_node(self, top, path, quiet=False):
|
|
|
|
|
'''get the node by internal tree path'''
|
|
|
|
|
r = anytree.resolver.Resolver('name')
|
|
|
|
|
"""get the node by internal tree path"""
|
|
|
|
|
resolv = anytree.resolver.Resolver('name')
|
|
|
|
|
try:
|
|
|
|
|
p = os.path.basename(path)
|
|
|
|
|
return r.get(top, p)
|
|
|
|
|
bpath = os.path.basename(path)
|
|
|
|
|
return resolv.get(top, bpath)
|
|
|
|
|
except anytree.resolver.ChildResolverError:
|
|
|
|
|
if not quiet:
|
|
|
|
|
Logger.err(f'No node at path \"{p}\"')
|
|
|
|
|
Logger.err(f'No node at path \"{bpath}\"')
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_node_if_changed(self, top, path, treepath):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
return the node (if any) and if it has changed
|
|
|
|
|
@top: top node (storage)
|
|
|
|
|
@path: abs path to file
|
|
|
|
|
@treepath: rel path from indexed directory
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
treepath = treepath.lstrip(os.sep)
|
|
|
|
|
node = self.get_node(top, treepath, quiet=True)
|
|
|
|
|
# node does not exist
|
|
|
|
@ -116,34 +116,34 @@ class Noder:
|
|
|
|
|
if self.hash and node.md5:
|
|
|
|
|
md5 = self._get_hash(path)
|
|
|
|
|
if md5 != node.md5:
|
|
|
|
|
m = f'\tchange: checksum changed for \"{path}\"'
|
|
|
|
|
self._debug(m)
|
|
|
|
|
msg = f'\tchange: checksum changed for \"{path}\"'
|
|
|
|
|
self._debug(msg)
|
|
|
|
|
return node, True
|
|
|
|
|
self._debug(f'\tchange: no change for \"{path}\"')
|
|
|
|
|
return node, False
|
|
|
|
|
|
|
|
|
|
def _rec_size(self, node, store=True):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
recursively traverse tree and return size
|
|
|
|
|
@store: store the size in the node
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
if node.type == self.TYPE_FILE:
|
|
|
|
|
self._debug(f'getting node size for \"{node.name}\"')
|
|
|
|
|
return node.size
|
|
|
|
|
m = f'getting node size recursively for \"{node.name}\"'
|
|
|
|
|
self._debug(m)
|
|
|
|
|
msg = f'getting node size recursively for \"{node.name}\"'
|
|
|
|
|
self._debug(msg)
|
|
|
|
|
size = 0
|
|
|
|
|
for i in node.children:
|
|
|
|
|
if node.type == self.TYPE_DIR:
|
|
|
|
|
sz = self._rec_size(i, store=store)
|
|
|
|
|
size = self._rec_size(i, store=store)
|
|
|
|
|
if store:
|
|
|
|
|
i.size = sz
|
|
|
|
|
size += sz
|
|
|
|
|
i.size = size
|
|
|
|
|
size += size
|
|
|
|
|
if node.type == self.TYPE_STORAGE:
|
|
|
|
|
sz = self._rec_size(i, store=store)
|
|
|
|
|
size = self._rec_size(i, store=store)
|
|
|
|
|
if store:
|
|
|
|
|
i.size = sz
|
|
|
|
|
size += sz
|
|
|
|
|
i.size = size
|
|
|
|
|
size += size
|
|
|
|
|
else:
|
|
|
|
|
continue
|
|
|
|
|
if store:
|
|
|
|
@ -151,34 +151,34 @@ class Noder:
|
|
|
|
|
return size
|
|
|
|
|
|
|
|
|
|
def rec_size(self, node):
|
|
|
|
|
'''recursively traverse tree and store dir size'''
|
|
|
|
|
"""recursively traverse tree and store dir size"""
|
|
|
|
|
return self._rec_size(node, store=True)
|
|
|
|
|
|
|
|
|
|
###############################################################
|
|
|
|
|
# public helpers
|
|
|
|
|
###############################################################
|
|
|
|
|
def format_storage_attr(self, attr):
|
|
|
|
|
'''format the storage attr for saving'''
|
|
|
|
|
"""format the storage attr for saving"""
|
|
|
|
|
if not attr:
|
|
|
|
|
return ''
|
|
|
|
|
if type(attr) is list:
|
|
|
|
|
if isinstance(attr, list):
|
|
|
|
|
return ', '.join(attr)
|
|
|
|
|
attr = attr.rstrip()
|
|
|
|
|
return attr
|
|
|
|
|
|
|
|
|
|
def set_hashing(self, val):
|
|
|
|
|
'''hash files when indexing'''
|
|
|
|
|
"""hash files when indexing"""
|
|
|
|
|
self.hash = val
|
|
|
|
|
|
|
|
|
|
###############################################################
|
|
|
|
|
# node creationg
|
|
|
|
|
###############################################################
|
|
|
|
|
def new_top_node(self):
|
|
|
|
|
'''create a new top node'''
|
|
|
|
|
"""create a new top node"""
|
|
|
|
|
return anytree.AnyNode(name=self.TOPNAME, type=self.TYPE_TOP)
|
|
|
|
|
|
|
|
|
|
def update_metanode(self, top):
|
|
|
|
|
'''create or update meta node information'''
|
|
|
|
|
"""create or update meta node information"""
|
|
|
|
|
meta = self._get_meta_node(top)
|
|
|
|
|
epoch = int(time.time())
|
|
|
|
|
if not meta:
|
|
|
|
@ -192,7 +192,7 @@ class Noder:
|
|
|
|
|
return meta
|
|
|
|
|
|
|
|
|
|
def _get_meta_node(self, top):
|
|
|
|
|
'''return the meta node if any'''
|
|
|
|
|
"""return the meta node if any"""
|
|
|
|
|
try:
|
|
|
|
|
return next(filter(lambda x: x.type == self.TYPE_META,
|
|
|
|
|
top.children))
|
|
|
|
@ -200,15 +200,15 @@ class Noder:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def file_node(self, name, path, parent, storagepath):
|
|
|
|
|
'''create a new node representing a file'''
|
|
|
|
|
"""create a new node representing a file"""
|
|
|
|
|
if not os.path.exists(path):
|
|
|
|
|
Logger.err(f'File \"{path}\" does not exist')
|
|
|
|
|
return None
|
|
|
|
|
path = os.path.abspath(path)
|
|
|
|
|
try:
|
|
|
|
|
st = os.lstat(path)
|
|
|
|
|
except OSError as e:
|
|
|
|
|
Logger.err(f'OSError: {e}')
|
|
|
|
|
stat = os.lstat(path)
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
Logger.err(f'OSError: {exc}')
|
|
|
|
|
return None
|
|
|
|
|
md5 = None
|
|
|
|
|
if self.hash:
|
|
|
|
@ -216,20 +216,20 @@ class Noder:
|
|
|
|
|
relpath = os.sep.join([storagepath, name])
|
|
|
|
|
|
|
|
|
|
maccess = os.path.getmtime(path)
|
|
|
|
|
n = self._node(name, self.TYPE_FILE, relpath, parent,
|
|
|
|
|
size=st.st_size, md5=md5, maccess=maccess)
|
|
|
|
|
node = self._node(name, self.TYPE_FILE, relpath, parent,
|
|
|
|
|
size=stat.st_size, md5=md5, maccess=maccess)
|
|
|
|
|
if self.arc:
|
|
|
|
|
ext = os.path.splitext(path)[1][1:]
|
|
|
|
|
if ext.lower() in self.decomp.get_formats():
|
|
|
|
|
self._debug(f'{path} is an archive')
|
|
|
|
|
names = self.decomp.get_names(path)
|
|
|
|
|
self.list_to_tree(n, names)
|
|
|
|
|
self.list_to_tree(node, names)
|
|
|
|
|
else:
|
|
|
|
|
self._debug(f'{path} is NOT an archive')
|
|
|
|
|
return n
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
def dir_node(self, name, path, parent, storagepath):
|
|
|
|
|
'''create a new node representing a directory'''
|
|
|
|
|
"""create a new node representing a directory"""
|
|
|
|
|
path = os.path.abspath(path)
|
|
|
|
|
relpath = os.sep.join([storagepath, name])
|
|
|
|
|
maccess = os.path.getmtime(path)
|
|
|
|
@ -237,21 +237,21 @@ class Noder:
|
|
|
|
|
parent, maccess=maccess)
|
|
|
|
|
|
|
|
|
|
def clean_not_flagged(self, top):
|
|
|
|
|
'''remove any node not flagged and clean flags'''
|
|
|
|
|
"""remove any node not flagged and clean flags"""
|
|
|
|
|
cnt = 0
|
|
|
|
|
for node in anytree.PreOrderIter(top):
|
|
|
|
|
if node.type != self.TYPE_FILE and node.type != self.TYPE_DIR:
|
|
|
|
|
if node.type not in [self.TYPE_FILE, self.TYPE_DIR]:
|
|
|
|
|
continue
|
|
|
|
|
if self._clean(node):
|
|
|
|
|
cnt += 1
|
|
|
|
|
return cnt
|
|
|
|
|
|
|
|
|
|
def flag(self, node):
|
|
|
|
|
'''flag a node'''
|
|
|
|
|
"""flag a node"""
|
|
|
|
|
node.flag = True
|
|
|
|
|
|
|
|
|
|
def _clean(self, node):
|
|
|
|
|
'''remove node if not flagged'''
|
|
|
|
|
"""remove node if not flagged"""
|
|
|
|
|
if not self._has_attr(node, 'flag') or \
|
|
|
|
|
not node.flag:
|
|
|
|
|
node.parent = None
|
|
|
|
@ -260,7 +260,7 @@ class Noder:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def storage_node(self, name, path, parent, attr=None):
|
|
|
|
|
'''create a new node representing a storage'''
|
|
|
|
|
"""create a new node representing a storage"""
|
|
|
|
|
path = os.path.abspath(path)
|
|
|
|
|
free = shutil.disk_usage(path).free
|
|
|
|
|
total = shutil.disk_usage(path).total
|
|
|
|
@ -269,15 +269,15 @@ class Noder:
|
|
|
|
|
total=total, parent=parent, attr=attr, ts=epoch)
|
|
|
|
|
|
|
|
|
|
def archive_node(self, name, path, parent, archive):
|
|
|
|
|
'''crete a new node for archive data'''
|
|
|
|
|
"""crete a new node for archive data"""
|
|
|
|
|
return anytree.AnyNode(name=name, type=self.TYPE_ARC, relpath=path,
|
|
|
|
|
parent=parent, size=0, md5=None,
|
|
|
|
|
archive=archive)
|
|
|
|
|
|
|
|
|
|
def _node(self, name, type, relpath, parent,
|
|
|
|
|
def _node(self, name, nodetype, relpath, parent,
|
|
|
|
|
size=None, md5=None, maccess=None):
|
|
|
|
|
'''generic node creation'''
|
|
|
|
|
return anytree.AnyNode(name=name, type=type, relpath=relpath,
|
|
|
|
|
"""generic node creation"""
|
|
|
|
|
return anytree.AnyNode(name=name, type=nodetype, relpath=relpath,
|
|
|
|
|
parent=parent, size=size,
|
|
|
|
|
md5=md5, maccess=maccess)
|
|
|
|
|
|
|
|
|
@ -285,16 +285,16 @@ class Noder:
|
|
|
|
|
# printing
|
|
|
|
|
###############################################################
|
|
|
|
|
def _node_to_csv(self, node, sep=',', raw=False):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
print a node to csv
|
|
|
|
|
@node: the node to consider
|
|
|
|
|
@sep: CSV separator character
|
|
|
|
|
@raw: print raw size rather than human readable
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
if not node:
|
|
|
|
|
return ''
|
|
|
|
|
return
|
|
|
|
|
if node.type == self.TYPE_TOP:
|
|
|
|
|
return ''
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
out = []
|
|
|
|
|
if node.type == self.TYPE_STORAGE:
|
|
|
|
@ -302,16 +302,16 @@ class Noder:
|
|
|
|
|
out.append(node.name) # name
|
|
|
|
|
out.append(node.type) # type
|
|
|
|
|
out.append('') # fake full path
|
|
|
|
|
sz = self._rec_size(node, store=False)
|
|
|
|
|
out.append(utils.size_to_str(sz, raw=raw)) # size
|
|
|
|
|
out.append(utils.epoch_to_str(node.ts)) # indexed_at
|
|
|
|
|
size = self._rec_size(node, store=False)
|
|
|
|
|
out.append(size_to_str(size, raw=raw)) # size
|
|
|
|
|
out.append(epoch_to_str(node.ts)) # indexed_at
|
|
|
|
|
out.append('') # fake maccess
|
|
|
|
|
out.append('') # fake md5
|
|
|
|
|
out.append(str(len(node.children))) # nbfiles
|
|
|
|
|
# fake free_space
|
|
|
|
|
out.append(utils.size_to_str(node.free, raw=raw))
|
|
|
|
|
out.append(size_to_str(node.free, raw=raw))
|
|
|
|
|
# fake total_space
|
|
|
|
|
out.append(utils.size_to_str(node.total, raw=raw))
|
|
|
|
|
out.append(size_to_str(node.total, raw=raw))
|
|
|
|
|
out.append(node.attr) # meta
|
|
|
|
|
else:
|
|
|
|
|
# handle other nodes
|
|
|
|
@ -322,10 +322,10 @@ class Noder:
|
|
|
|
|
fullpath = os.path.join(storage.name, parents)
|
|
|
|
|
out.append(fullpath.replace('"', '""')) # full path
|
|
|
|
|
|
|
|
|
|
out.append(utils.size_to_str(node.size, raw=raw)) # size
|
|
|
|
|
out.append(utils.epoch_to_str(storage.ts)) # indexed_at
|
|
|
|
|
out.append(size_to_str(node.size, raw=raw)) # size
|
|
|
|
|
out.append(epoch_to_str(storage.ts)) # indexed_at
|
|
|
|
|
if self._has_attr(node, 'maccess'):
|
|
|
|
|
out.append(utils.epoch_to_str(node.maccess)) # maccess
|
|
|
|
|
out.append(epoch_to_str(node.maccess)) # maccess
|
|
|
|
|
else:
|
|
|
|
|
out.append('') # fake maccess
|
|
|
|
|
if node.md5:
|
|
|
|
@ -347,7 +347,7 @@ class Noder:
|
|
|
|
|
def _print_node(self, node, pre='', withpath=False,
|
|
|
|
|
withdepth=False, withstorage=False,
|
|
|
|
|
recalcparent=False, raw=False):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
print a node
|
|
|
|
|
@node: the node to print
|
|
|
|
|
@pre: string to print before node
|
|
|
|
@ -356,7 +356,7 @@ class Noder:
|
|
|
|
|
@withstorage: print the node storage it belongs to
|
|
|
|
|
@recalcparent: get relpath from tree instead of relpath field
|
|
|
|
|
@raw: print raw size rather than human readable
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
if node.type == self.TYPE_TOP:
|
|
|
|
|
# top node
|
|
|
|
|
Logger.out(f'{pre}{node.name}')
|
|
|
|
@ -374,8 +374,8 @@ class Noder:
|
|
|
|
|
attr = ''
|
|
|
|
|
if node.md5:
|
|
|
|
|
attr = f', md5:{node.md5}'
|
|
|
|
|
sz = utils.size_to_str(node.size, raw=raw)
|
|
|
|
|
compl = f'size:{sz}{attr}'
|
|
|
|
|
size = size_to_str(node.size, raw=raw)
|
|
|
|
|
compl = f'size:{size}{attr}'
|
|
|
|
|
if withstorage:
|
|
|
|
|
content = Logger.bold(storage.name)
|
|
|
|
|
compl += f', storage:{content}'
|
|
|
|
@ -396,35 +396,35 @@ class Noder:
|
|
|
|
|
storage = self._get_storage(node)
|
|
|
|
|
attr = []
|
|
|
|
|
if node.size:
|
|
|
|
|
attr.append(['totsize', utils.size_to_str(node.size, raw=raw)])
|
|
|
|
|
attr.append(['totsize', size_to_str(node.size, raw=raw)])
|
|
|
|
|
if withstorage:
|
|
|
|
|
attr.append(['storage', Logger.bold(storage.name)])
|
|
|
|
|
Logger.dir(pre, name, depth=depth, attr=attr)
|
|
|
|
|
elif node.type == self.TYPE_STORAGE:
|
|
|
|
|
# node of type storage
|
|
|
|
|
hf = utils.size_to_str(node.free, raw=raw)
|
|
|
|
|
ht = utils.size_to_str(node.total, raw=raw)
|
|
|
|
|
szfree = size_to_str(node.free, raw=raw)
|
|
|
|
|
sztotal = size_to_str(node.total, raw=raw)
|
|
|
|
|
nbchildren = len(node.children)
|
|
|
|
|
pcent = node.free * 100 / node.total
|
|
|
|
|
freepercent = f'{pcent:.1f}%'
|
|
|
|
|
# get the date
|
|
|
|
|
dt = ''
|
|
|
|
|
timestamp = ''
|
|
|
|
|
if self._has_attr(node, 'ts'):
|
|
|
|
|
dt = 'date:'
|
|
|
|
|
dt += utils.epoch_to_str(node.ts)
|
|
|
|
|
ds = ''
|
|
|
|
|
timestamp = 'date:'
|
|
|
|
|
timestamp += epoch_to_str(node.ts)
|
|
|
|
|
disksize = ''
|
|
|
|
|
# the children size
|
|
|
|
|
sz = self._rec_size(node, store=False)
|
|
|
|
|
sz = utils.size_to_str(sz, raw=raw)
|
|
|
|
|
ds = 'totsize:' + f'{sz}'
|
|
|
|
|
size = self._rec_size(node, store=False)
|
|
|
|
|
size = size_to_str(size, raw=raw)
|
|
|
|
|
disksize = 'totsize:' + f'{size}'
|
|
|
|
|
# format the output
|
|
|
|
|
name = node.name
|
|
|
|
|
args = [
|
|
|
|
|
'nbfiles:' + f'{nbchildren}',
|
|
|
|
|
ds,
|
|
|
|
|
disksize,
|
|
|
|
|
f'free:{freepercent}',
|
|
|
|
|
'du:' + f'{hf}/{ht}',
|
|
|
|
|
dt]
|
|
|
|
|
'du:' + f'{szfree}/{sztotal}',
|
|
|
|
|
timestamp]
|
|
|
|
|
argsstring = ' | '.join(args)
|
|
|
|
|
Logger.storage(pre,
|
|
|
|
|
name,
|
|
|
|
@ -437,35 +437,37 @@ class Noder:
|
|
|
|
|
else:
|
|
|
|
|
Logger.err(f'bad node encountered: {node}')
|
|
|
|
|
|
|
|
|
|
def print_tree(self, top, node, style=anytree.ContRoundStyle(),
|
|
|
|
|
fmt='native', header=False, raw=False):
|
|
|
|
|
'''
|
|
|
|
|
def print_tree(self, top, node,
|
|
|
|
|
fmt='native',
|
|
|
|
|
header=False,
|
|
|
|
|
raw=False):
|
|
|
|
|
"""
|
|
|
|
|
print the tree in different format
|
|
|
|
|
@node: start node
|
|
|
|
|
@style: when fmt=native, defines the tree style
|
|
|
|
|
@fmt: output format
|
|
|
|
|
@header: when fmt=csv, print the header
|
|
|
|
|
@raw: print the raw size rather than human readable
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
if fmt == 'native':
|
|
|
|
|
# "tree" style
|
|
|
|
|
rend = anytree.RenderTree(node, childiter=self._sort_tree)
|
|
|
|
|
for pre, fill, node in rend:
|
|
|
|
|
self._print_node(node, pre=pre, withdepth=True, raw=raw)
|
|
|
|
|
for pre, _, thenode in rend:
|
|
|
|
|
self._print_node(thenode, pre=pre, withdepth=True, raw=raw)
|
|
|
|
|
elif fmt == 'csv':
|
|
|
|
|
# csv output
|
|
|
|
|
self._to_csv(node, with_header=header, raw=raw)
|
|
|
|
|
elif fmt.startswith('fzf'):
|
|
|
|
|
# flat
|
|
|
|
|
self._to_fzf(top, node)
|
|
|
|
|
self._to_fzf(top, node, fmt)
|
|
|
|
|
|
|
|
|
|
def _to_csv(self, node, with_header=False, raw=False):
|
|
|
|
|
'''print the tree to csv'''
|
|
|
|
|
"""print the tree to csv"""
|
|
|
|
|
rend = anytree.RenderTree(node, childiter=self._sort_tree)
|
|
|
|
|
if with_header:
|
|
|
|
|
Logger.out(self.CSV_HEADER)
|
|
|
|
|
for _, _, node in rend:
|
|
|
|
|
self._node_to_csv(node, raw=raw)
|
|
|
|
|
for _, _, item in rend:
|
|
|
|
|
self._node_to_csv(item, raw=raw)
|
|
|
|
|
|
|
|
|
|
def _fzf_prompt(self, strings):
|
|
|
|
|
# prompt with fzf
|
|
|
|
@ -480,16 +482,16 @@ class Noder:
|
|
|
|
|
@node: node to start with
|
|
|
|
|
@fmt: output format for selected nodes
|
|
|
|
|
"""
|
|
|
|
|
rend = anytree.RenderTree(node, childiter=self._sort_tree)
|
|
|
|
|
nodes = dict()
|
|
|
|
|
rendered = anytree.RenderTree(node, childiter=self._sort_tree)
|
|
|
|
|
nodes = {}
|
|
|
|
|
# construct node names list
|
|
|
|
|
for _, _, node in rend:
|
|
|
|
|
if not node:
|
|
|
|
|
for _, _, rend in rendered:
|
|
|
|
|
if not rend:
|
|
|
|
|
continue
|
|
|
|
|
parents = self._get_parents(node)
|
|
|
|
|
storage = self._get_storage(node)
|
|
|
|
|
parents = self._get_parents(rend)
|
|
|
|
|
storage = self._get_storage(rend)
|
|
|
|
|
fullpath = os.path.join(storage.name, parents)
|
|
|
|
|
nodes[fullpath] = node
|
|
|
|
|
nodes[fullpath] = rend
|
|
|
|
|
# prompt with fzf
|
|
|
|
|
paths = self._fzf_prompt(nodes.keys())
|
|
|
|
|
# print the resulting tree
|
|
|
|
@ -499,11 +501,11 @@ class Noder:
|
|
|
|
|
continue
|
|
|
|
|
if path not in nodes:
|
|
|
|
|
continue
|
|
|
|
|
node = nodes[path]
|
|
|
|
|
self.print_tree(top, node, fmt=subfmt)
|
|
|
|
|
rend = nodes[path]
|
|
|
|
|
self.print_tree(top, rend, fmt=subfmt)
|
|
|
|
|
|
|
|
|
|
def to_dot(self, node, path='tree.dot'):
|
|
|
|
|
'''export to dot for graphing'''
|
|
|
|
|
"""export to dot for graphing"""
|
|
|
|
|
anytree.exporter.DotExporter(node).to_dotfile(path)
|
|
|
|
|
Logger.info(f'dot file created under \"{path}\"')
|
|
|
|
|
return f'dot {path} -T png -o /tmp/tree.png'
|
|
|
|
@ -515,7 +517,7 @@ class Noder:
|
|
|
|
|
script=False, directory=False,
|
|
|
|
|
startpath=None, parentfromtree=False,
|
|
|
|
|
fmt='native', raw=False):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
find files based on their names
|
|
|
|
|
@top: top node
|
|
|
|
|
@key: term to search for
|
|
|
|
@ -525,7 +527,7 @@ class Noder:
|
|
|
|
|
@parentfromtree: get path from parent instead of stored relpath
|
|
|
|
|
@fmt: output format
|
|
|
|
|
@raw: raw size output
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
self._debug(f'searching for \"{key}\"')
|
|
|
|
|
if not key:
|
|
|
|
|
# nothing to search for
|
|
|
|
@ -534,49 +536,49 @@ class Noder:
|
|
|
|
|
if startpath:
|
|
|
|
|
start = self.get_node(top, startpath)
|
|
|
|
|
found = anytree.findall(start, filter_=self._callback_find_name(key))
|
|
|
|
|
nb = len(found)
|
|
|
|
|
self._debug(f'found {nb} node(s)')
|
|
|
|
|
nbfound = len(found)
|
|
|
|
|
self._debug(f'found {nbfound} node(s)')
|
|
|
|
|
|
|
|
|
|
# compile found nodes
|
|
|
|
|
paths = dict()
|
|
|
|
|
paths = {}
|
|
|
|
|
nodes = []
|
|
|
|
|
for f in found:
|
|
|
|
|
if f.type == self.TYPE_STORAGE:
|
|
|
|
|
for item in found:
|
|
|
|
|
if item.type == self.TYPE_STORAGE:
|
|
|
|
|
# ignore storage nodes
|
|
|
|
|
continue
|
|
|
|
|
if directory and f.type != self.TYPE_DIR:
|
|
|
|
|
if directory and item.type != self.TYPE_DIR:
|
|
|
|
|
# ignore non directory
|
|
|
|
|
continue
|
|
|
|
|
nodes.append(f)
|
|
|
|
|
nodes.append(item)
|
|
|
|
|
|
|
|
|
|
if parentfromtree:
|
|
|
|
|
paths[self._get_parents(f)] = f
|
|
|
|
|
paths[self._get_parents(item)] = item
|
|
|
|
|
else:
|
|
|
|
|
paths[f.relpath] = f
|
|
|
|
|
paths[item.relpath] = item
|
|
|
|
|
|
|
|
|
|
if fmt == 'native':
|
|
|
|
|
for n in nodes:
|
|
|
|
|
self._print_node(n, withpath=True,
|
|
|
|
|
for item in nodes:
|
|
|
|
|
self._print_node(item, withpath=True,
|
|
|
|
|
withdepth=True,
|
|
|
|
|
withstorage=True,
|
|
|
|
|
recalcparent=parentfromtree,
|
|
|
|
|
raw=raw)
|
|
|
|
|
elif fmt == 'csv':
|
|
|
|
|
for n in nodes:
|
|
|
|
|
self._node_to_csv(n, raw=raw)
|
|
|
|
|
for item in nodes:
|
|
|
|
|
self._node_to_csv(item, raw=raw)
|
|
|
|
|
elif fmt.startswith('fzf'):
|
|
|
|
|
selected = self._fzf_prompt(paths)
|
|
|
|
|
newpaths = dict()
|
|
|
|
|
newpaths = {}
|
|
|
|
|
subfmt = fmt.replace('fzf-', '')
|
|
|
|
|
for s in selected:
|
|
|
|
|
if s not in paths:
|
|
|
|
|
for item in selected:
|
|
|
|
|
if item not in paths:
|
|
|
|
|
continue
|
|
|
|
|
newpaths[s] = paths[s]
|
|
|
|
|
self.print_tree(top, newpaths[s], fmt=subfmt)
|
|
|
|
|
newpaths[item] = paths[item]
|
|
|
|
|
self.print_tree(top, newpaths[item], fmt=subfmt)
|
|
|
|
|
paths = newpaths
|
|
|
|
|
|
|
|
|
|
if script:
|
|
|
|
|
tmp = ['${source}/' + x for x in paths.keys()]
|
|
|
|
|
tmp = ['${source}/' + x for x in paths]
|
|
|
|
|
tmpstr = ' '.join(tmp)
|
|
|
|
|
cmd = f'op=file; source=/media/mnt; $op {tmpstr}'
|
|
|
|
|
Logger.info(cmd)
|
|
|
|
@ -584,7 +586,7 @@ class Noder:
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
def _callback_find_name(self, term):
|
|
|
|
|
'''callback for finding files'''
|
|
|
|
|
"""callback for finding files"""
|
|
|
|
|
def find_name(node):
|
|
|
|
|
if term.lower() in node.name.lower():
|
|
|
|
|
return True
|
|
|
|
@ -596,19 +598,19 @@ class Noder:
|
|
|
|
|
###############################################################
|
|
|
|
|
def walk(self, top, path, rec=False, fmt='native',
|
|
|
|
|
raw=False):
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
walk the tree for ls based on names
|
|
|
|
|
@top: start node
|
|
|
|
|
@rec: recursive walk
|
|
|
|
|
@fmt: output format
|
|
|
|
|
@raw: print raw size
|
|
|
|
|
'''
|
|
|
|
|
"""
|
|
|
|
|
self._debug(f'walking path: \"{path}\"')
|
|
|
|
|
|
|
|
|
|
r = anytree.resolver.Resolver('name')
|
|
|
|
|
resolv = anytree.resolver.Resolver('name')
|
|
|
|
|
found = []
|
|
|
|
|
try:
|
|
|
|
|
found = r.glob(top, path)
|
|
|
|
|
found = resolv.glob(top, path)
|
|
|
|
|
if len(found) < 1:
|
|
|
|
|
# nothing found
|
|
|
|
|
return []
|
|
|
|
@ -627,20 +629,20 @@ class Noder:
|
|
|
|
|
withpath=False, withdepth=True, raw=raw)
|
|
|
|
|
elif fmt == 'csv':
|
|
|
|
|
self._node_to_csv(found[0].parent, raw=raw)
|
|
|
|
|
elif fmt == 'fzf':
|
|
|
|
|
elif fmt.startswith('fzf'):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# print all found nodes
|
|
|
|
|
for f in found:
|
|
|
|
|
for item in found:
|
|
|
|
|
if fmt == 'native':
|
|
|
|
|
self._print_node(f, withpath=False,
|
|
|
|
|
self._print_node(item, withpath=False,
|
|
|
|
|
pre='- ',
|
|
|
|
|
withdepth=True,
|
|
|
|
|
raw=raw)
|
|
|
|
|
elif fmt == 'csv':
|
|
|
|
|
self._node_to_csv(f, raw=raw)
|
|
|
|
|
elif fmt == 'fzf':
|
|
|
|
|
self._to_fzf(top, f)
|
|
|
|
|
self._node_to_csv(item, raw=raw)
|
|
|
|
|
elif fmt.startswith('fzf'):
|
|
|
|
|
self._to_fzf(top, item, fmt)
|
|
|
|
|
|
|
|
|
|
except anytree.resolver.ChildResolverError:
|
|
|
|
|
pass
|
|
|
|
@ -650,47 +652,47 @@ class Noder:
|
|
|
|
|
# tree creation
|
|
|
|
|
###############################################################
|
|
|
|
|
def _add_entry(self, name, top, resolv):
|
|
|
|
|
'''add an entry to the tree'''
|
|
|
|
|
"""add an entry to the tree"""
|
|
|
|
|
entries = name.rstrip(os.sep).split(os.sep)
|
|
|
|
|
if len(entries) == 1:
|
|
|
|
|
self.archive_node(name, name, top, top.name)
|
|
|
|
|
return
|
|
|
|
|
sub = os.sep.join(entries[:-1])
|
|
|
|
|
f = entries[-1]
|
|
|
|
|
nodename = entries[-1]
|
|
|
|
|
try:
|
|
|
|
|
parent = resolv.get(top, sub)
|
|
|
|
|
parent = self.archive_node(f, name, parent, top.name)
|
|
|
|
|
parent = self.archive_node(nodename, name, parent, top.name)
|
|
|
|
|
except anytree.resolver.ChildResolverError:
|
|
|
|
|
self.archive_node(f, name, top, top.name)
|
|
|
|
|
self.archive_node(nodename, name, top, top.name)
|
|
|
|
|
|
|
|
|
|
def list_to_tree(self, parent, names):
|
|
|
|
|
'''convert list of files to a tree'''
|
|
|
|
|
"""convert list of files to a tree"""
|
|
|
|
|
if not names:
|
|
|
|
|
return
|
|
|
|
|
r = anytree.resolver.Resolver('name')
|
|
|
|
|
resolv = anytree.resolver.Resolver('name')
|
|
|
|
|
for name in names:
|
|
|
|
|
name = name.rstrip(os.sep)
|
|
|
|
|
self._add_entry(name, parent, r)
|
|
|
|
|
self._add_entry(name, parent, resolv)
|
|
|
|
|
|
|
|
|
|
###############################################################
|
|
|
|
|
# diverse
|
|
|
|
|
###############################################################
|
|
|
|
|
def _sort_tree(self, items):
|
|
|
|
|
'''sorting a list of items'''
|
|
|
|
|
"""sorting a list of items"""
|
|
|
|
|
return sorted(items, key=self._sort, reverse=self.sortsize)
|
|
|
|
|
|
|
|
|
|
def _sort(self, lst):
|
|
|
|
|
'''sort a list'''
|
|
|
|
|
"""sort a list"""
|
|
|
|
|
if self.sortsize:
|
|
|
|
|
return self._sort_size(lst)
|
|
|
|
|
return self._sort_fs(lst)
|
|
|
|
|
|
|
|
|
|
def _sort_fs(self, node):
|
|
|
|
|
'''sorting nodes dir first and alpha'''
|
|
|
|
|
return (node.type, node.name.lstrip('\.').lower())
|
|
|
|
|
"""sorting nodes dir first and alpha"""
|
|
|
|
|
return (node.type, node.name.lstrip('.').lower())
|
|
|
|
|
|
|
|
|
|
def _sort_size(self, node):
|
|
|
|
|
'''sorting nodes by size'''
|
|
|
|
|
"""sorting nodes by size"""
|
|
|
|
|
try:
|
|
|
|
|
if not node.size:
|
|
|
|
|
return 0
|
|
|
|
@ -699,7 +701,7 @@ class Noder:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
def _get_storage(self, node):
|
|
|
|
|
'''recursively traverse up to find storage'''
|
|
|
|
|
"""recursively traverse up to find storage"""
|
|
|
|
|
if node.type == self.TYPE_STORAGE:
|
|
|
|
|
return node
|
|
|
|
|
return node.ancestors[1]
|
|
|
|
@ -708,7 +710,7 @@ class Noder:
|
|
|
|
|
return attr in node.__dict__.keys()
|
|
|
|
|
|
|
|
|
|
def _get_parents(self, node):
|
|
|
|
|
'''get all parents recursively'''
|
|
|
|
|
"""get all parents recursively"""
|
|
|
|
|
if node.type == self.TYPE_STORAGE:
|
|
|
|
|
return ''
|
|
|
|
|
if node.type == self.TYPE_TOP:
|
|
|
|
@ -720,10 +722,10 @@ class Noder:
|
|
|
|
|
|
|
|
|
|
def _get_hash(self, path):
|
|
|
|
|
"""return md5 hash of node"""
|
|
|
|
|
return utils.md5sum(path)
|
|
|
|
|
return md5sum(path)
|
|
|
|
|
|
|
|
|
|
def _debug(self, string):
|
|
|
|
|
'''print debug'''
|
|
|
|
|
"""print debug"""
|
|
|
|
|
if not self.debug:
|
|
|
|
|
return
|
|
|
|
|
Logger.debug(string)
|
|
|
|
|