# -*- coding: utf-8 -*-
"""
Represent a directory found on the file system.
"""
import os
from tagrenamer.fs.node import Node
from tagrenamer.fs.file import File
from tagrenamer.fs.musicfile import MusicFile, music_extensions
[docs]
class Directory(Node):
"""
Represent a directory found on the file system.
Hooks:
- traverse_filter (obj, path), return True/False
- mkdir (obj)
"""
def __init__(self, output, path, hooks={}, parent=None, dl=1):
"""Initialize the directory object."""
self.children = []
Node.__init__(
self,
output,
path=path,
hooks=hooks,
parent=parent,
dl=dl)
[docs]
def enableDryRun(self):
"""Enable a dry-run mode on this object and its children.."""
Node.enableDryRun(self)
if len(self.children):
for c in self.children:
c.enableDryRun()
[docs]
def addChild(self, child):
"""Add a child node."""
children = []
for c in self.children:
children.append(c)
children.append(child)
self.children = children
[docs]
def removeChild(self, child):
"""Remove a child from this node."""
children = []
for c in self.children:
if c.path != child.path:
children.append(c)
self.children = children
[docs]
def traverse(self):
"""Traverse into subdirectories and load our children."""
self.children = []
self.out.log(str(self), '%s.traverse' % self.type, self.dl)
# Only really traverse if the object exists.
if self.exists():
for path in os.listdir(self.path):
dl = self.dl + 1
path = "%s/%s" % (self.path, path)
# Invoke the traverse_filter hook (see main class description) and
# determine if we should include this object (True) or not (False).
filter_outcome = self.invoke('traverse_filter', self, path)
if filter_outcome is not None:
if filter_outcome is False:
self.out.log('%s (skipping)' % str(self),
'%s.traverse' % self.type,
self.dl)
continue
# Perform a set of tests and load the correct class for the found child.
if os.path.isdir(path):
node = Directory(output=self.out,
path=path,
hooks=self.hooks,
parent=self,
dl=dl)
node.traverse()
elif os.path.isfile(path):
extension = path.split('.').pop()
if extension in music_extensions:
node = MusicFile(output=self.out,
path=path,
extension=extension,
hooks=self.hooks,
parent=self,
dl=dl)
else:
node = File(output=self.out,
path=path,
extension=extension,
hooks=self.hooks,
parent=self,
dl=dl)
else:
node = Node(output=self.out,
path=path,
hooks=self.hooks,
parent=self,
dl=dl)
# Enable dry run if on the node if it applies to us.
if self.dryrun:
node.enableDryRun()
# Append the child to our list of children.
self.children.append(node)
[docs]
def mkdir(self):
"""Make this directory if it doesn't exist on disk yet."""
self.out.log(str(self), '%s.mkdir' % self.type, self.dl)
if not self.dryrun:
os.mkdir(self.path)
self.shellCollect('mkdir -v "{}"', self.path)
# Invoke the mkdir hook, see main class description.
self.invoke('mkdir', self)
[docs]
def mkdirs(self, path):
"""Make multiple directories at once and assure that a path exists."""
self.out.log(context='%s.mkdirs' % self.type, level=self.dl)
# Calculate the sub path and current base being looked for.
path = path.split('/')
base = path[0]
del path[0]
# Determine whether the top level of the trail already exists.
existingDir = False
for c in self.children:
if c.base == base:
existingDir = c
# Load the new directory object and create it if needed.
if not existingDir:
dl = self.dl + 1
npath = "%s/%s" % (self.path, base)
dir = Directory(output=self.out,
path=npath,
hooks=self.hooks,
parent=self,
dl=dl)
if self.dryrun:
dir.enableDryRun()
dir.mkdir()
self.children.append(dir)
else:
dir = existingDir
# Let the fresh directory object recurse into itself.
if len(path):
return dir.mkdirs('/'.join(path))
return dir
[docs]
def remove(self):
"""Delete this directory and it's siblings from disk."""
self.out.log(str(self), '%s.remove' % self.type, self.dl)
# Start with removing my children and their references.
for c in self.children:
c.remove()
self.children = []
# Remove the directory when it's emptied.
self.shellCollect('rm -Rv "{}"', self.path)
if not self.dryrun:
os.rmdir(self.path)
# Remove this instance from the parents list of children.
if self.parent is not None:
self.parent.removeChild(self)
# Invoke the remove hook, see main class description.
self.invoke('remove', self)
[docs]
def move(self, dest, onlyReferences=False):
"""Move the file system object onto a different location."""
self.out.log(str(self), '%s.move' % self.type, self.dl)
# Thrown an exception when we're being moved to the same location:
if id(self) == id(dest):
raise AssertionError("Can't move '%s' to itself" % dest)
# Declare the new path and physically move the object.
self.oldpath = self.path
self.path = os.path.abspath('%s/%s' % (dest.path, self.base))
if not onlyReferences:
self.shellCollect('mv -v "{}" "{}"', self.oldpath, self.path)
if not self.dryrun:
os.rename(self.oldpath, self.path)
# Unregister ourselves at our current parent and register at new parent.
if self.parent:
self.parent.removeChild(self)
dest.addChild(self)
# Re-parent ourselves and update several properties.
self.parent = dest
self.base = os.path.basename(self.path)
self.dryrun = self.parent.dryrun
self.root = self.parent.root
self.relpath = self.path.replace('%s/' % self.root, '')
self.dl = self.parent.dl + 1
# Iterate our children and ensure they're moved too.
for c in self.children:
c.move(dest=self, onlyReferences=True)
# Invoke the move hook, see main class description.
self.invoke('move', self, dest)