musicguru / core /

Full commit
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2004-12-27
# Copyright 2010 Hardcoded Software (
# This software is licensed under the "BSD" License as described in the "LICENSE" file, 
# which should be included with this package. The terms are also available at 

import hsfs as fs
from hscommon import job
from hsutil.misc import nonone
from hsutil.conflict import get_conflicted_name, is_conflicted

class _CopyOf(object):
    #--- Public
    def copy(self, refnode):
        self.copyof = refnode
    def detach_copy(self, keep_original_files=False, keep_original_dirs=False):
        if self.is_container:
            keep = keep_original_dirs
            keep = keep_original_files
        if keep:
            self.copyof = self.original
            self.copyof = None
        for child in self:
    #--- Properties
    copyof = None
    def original(self):
        if hasattr(self.copyof, 'original'):
            return self.copyof.original
            return nonone(self.copyof, self)

class Node(fs.Node):
    #--- Override
    def __init__(self, parent=None, name=''):
            super(Node, self).__init__(parent,name)
        except fs.AlreadyExistsError:
            newname = parent._resolve_conflict(parent[name], self, name)
            if newname:
                if isinstance(newname, str):
                    super(Node, self).__init__(parent, newname)
    def _set_name(self, newname):
            super(Node, self)._set_name(newname)
        except fs.AlreadyExistsError:
            newname = self.parent._resolve_conflict(self.parent[newname], self, newname)
            if newname:
                if isinstance(newname, str):
                    super(Node, self)._set_name(newname)
    #--- Public
    def delete(self):
        self.parent = None
    def move(self, dest, newname=None):
        dest.add_child(self, newname)
    def rename(self, newname): = newname

class File(fs.File, Node, _CopyOf):
    #--- Public
    def copy(self, reffile):
        for attrname in reffile.INITIAL_INFO:
            if attrname in reffile.__dict__:
                setattr(self, attrname, getattr(reffile, attrname))
        self.INITIAL_INFO = reffile.INITIAL_INFO

class Directory(fs.Directory, Node, _CopyOf):
    """A Directory that you can manipulate at will
    This is the opposite of auto.Directory. When you subclass this, you have
    to manually add/delete/move everything.
    Littles notes:
    You might notice that some AlreadyExistsError are raised in this unit.
    You might think "hey, fs.Directory covers all possible occurance of
    AlreadyExistsError, why do you duplicate code here?" It is true that
    fs.Directory takes care of all this. However, if you look at the code
    after the raise (in this unit), you will see that , first, it is only in
    move. And what's special about move funcs is that you can change the
    name as you move. And to do this, you must delete the child from
    it's former parent before you add it in it's new parent. If you don't
    check for conflict *before* and there's is a conflict occuring, you're
    left with a parent less child.
    #--- Class Attributes
    cls_file_class = File
    #--- Overrides
    def __init__(self, parent=None, dirname=''):
        if isinstance(parent, Directory):
            self.__case_sensitive = parent.case_sensitive
            self.__case_sensitive = True
        self._attrs_to_read = None
        super(Directory, self).__init__(parent, dirname)
    def _do_hash(self, value):
        if (not self.case_sensitive) and isinstance(value, str):
            return value.lower()
            return value
    #--- Protected
    def _conflict_check(self, name, node):
        if name in self:
            newname = self._resolve_conflict(self[name], node, name)
            if newname:
                return newname
                raise fs.AlreadyExistsError(name, self)
            return name
    def _resolve_conflict(self, offended, offender, conflicted_name): # Virtual
        """Override this to automatically resolve a name conflict instead
        of raising an AlreadyExistsError. If you return something else than
        None or '', there will be a second try to add name. There is no
        third try. if the result of ResolveConflict is also conflictual,
        an error will be raised. You can also return a True value that is not
        a string, and it will cancel the exception raise, but not make a second
    #--- Public
    def add_child(self, child, newname=None):
        if child in self:
            return child
        if not newname:
            newname =
        newname = self._conflict_check(newname, child)
        if not isinstance(newname, str):
            return child #Just don't perform the add, _resolve_conflict has taken 
                         #care of everything
        child.parent = None = newname
        child.parent = self
        if isinstance(child, Directory):
            child.case_sensitive = self.case_sensitive
        return child
    def add_dir_copy(self, refdir, newname='', job=job.nulljob):
        if not newname:
            newname =
        result = self._create_sub_dir(newname, False)
        result.copy(refdir, job)
        return result
    def add_file_copy(self, reffile, newname=''):
        if not newname:
            newname =
        result = self._create_sub_file(newname, False)
        return result
    def add_path(self, path):
        Creates the first item of path (a tuple), and calls _AddPath in this new
        directory. If the directory already exists, uses this directory.
        Returns the added (or found) directory.
        if not path:
            return self
                founddir = self[path[0]]
                if not isinstance(founddir, Directory):
                    raise fs.InvalidPath(founddir)
            except KeyError:
                founddir = self._create_sub_dir(path[0])
            return founddir.add_path(path[1:])
    def clean_empty_dirs(self):
        for directory in self.dirs:
        to_delete = (d for d in self.dirs if not len(d))
        for directory in to_delete:
    def copy(self, refdir, job=job.nulljob):
        super(Directory, self).copy(refdir)
        filecount = refdir.filecount
        dircount  = refdir.dircount
        if filecount > 0:
            job = job.start_subjob(dircount + 1)
            job = job.start_subjob(dircount)
        for myfile in refdir.files:
        for directory in refdir.dirs:
            self.add_dir_copy(directory, '', job)
    def new_directory(self, name):
        return self._create_sub_dir(name)
    def new_file(self, name):
        return self._create_sub_file(name)
    #--- Properties
    def case_sensitive(self):
        return self.__case_sensitive
    def case_sensitive(self, value):
        if value != self.__case_sensitive:
            self.__case_sensitive = value
            for subdir in self:
                if isinstance(subdir, Directory):
                    subdir.case_sensitive = value

class AutoResolve(Directory):
    def _resolve_conflict(self, offended, offender, conflicted_name):
        if offended.is_container and offender.is_container:
            should_merge = self.on_should_merge(offender, offended)
            if should_merge:
                # There's a circular reference problem
                from .fs_utils import smart_move
                smart_move(offender, offended)
                return True
        return get_conflicted_name(self, conflicted_name)
    def on_should_merge(self, source, dest):
        if (self.parent is not None) and hasattr(self.parent, 'on_should_merge'):
            return self.parent.on_should_merge(source, dest)
    def allconflicts(self):
        return self.get_stat('conflicts', [])
    def conflicts(self):
        return [y for y in self.files if is_conflicted(]

class AutoMerge(AutoResolve):
    def on_should_merge(self, source, dest):
        return True