git @ Cat's Eye Technologies tagfarm / master src / tagfarm / utils.py
master

Tree @master (Download .tar.gz)

utils.py @masterraw · history · blame

# encoding: UTF-8

import errno
import os


def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise


def find_media_root(path):
    while path != '/':
        if os.path.isdir(os.path.join(path, 'by-tag')):
            return path
        path = os.path.abspath(os.path.join(path, os.pardir))
    raise ValueError("could not locate `by-tag` directory in curent directory or any parent directory thereof")


def index_files(media_root):
    index = {}
    for root, dirs, files in os.walk(media_root):
        if os.path.normpath(root) == os.path.normpath(os.path.join(media_root, 'by-tag')):
            dirs[:] = []
            continue

        for basename in files:
            filename = os.path.normpath(os.path.join(root, basename))
            if os.path.islink(filename):
                continue

            index.setdefault(basename, set()).add(filename)
    return index


def is_broken_link(path):
    return os.path.lexists(path) and not os.path.exists(path)


def is_absolute_link(path):
    return os.path.lexists(path) and os.readlink(path).startswith('/')


def readlink_or_broken(path):
    return '*BROKEN*' if is_broken_link(path) else os.readlink(path)


def relativize_target(media_root, path):
    # Prepends `../..` because the link always resides in `by-tag/<tagname>`
    return os.path.join('..', '..', os.path.relpath(path, media_root))


def tag_file(media_root, filename, tag):
    mkdir_p(os.path.join(media_root, 'by-tag', tag))
    linkname = os.path.join(media_root, 'by-tag', tag, os.path.basename(filename))
    if not os.path.lexists(linkname):
        srcname = relativize_target(media_root, filename)
        os.symlink(srcname, linkname)


def perform_repair(media_root, verbose=False, force_relink=False, restrict_to_tag=None, prune=False):
    index = index_files(media_root)

    by_tags_dir = os.path.join(media_root, 'by-tag')
    tags = [restrict_to_tag] if restrict_to_tag else sorted(os.listdir(by_tags_dir))
    for tag in tags:
        tagdir = os.path.join(by_tags_dir, tag)
        if not os.path.isdir(tagdir):
            continue
        repairs_made = []
        for basename in sorted(os.listdir(tagdir)):
            linkname = os.path.join(tagdir, basename)

            if basename.startswith('Link to '):
                new_basename = basename[8:]
                new_linkname = os.path.join(tagdir, new_basename)
                if os.path.lexists(new_linkname):
                    repairs_made.append(
                        "WARNING: not renaming '{}' (-> '{}') because '{}' (-> '{}') already exists".format(
                            linkname, readlink_or_broken(linkname), new_linkname, readlink_or_broken(new_linkname)
                        )
                    )
                else:
                    repairs_made.append("RENAMING {} -> {}".format(linkname, new_linkname))
                    os.rename(linkname, new_linkname)
                    linkname = new_linkname

            if not force_relink:
                if not os.path.islink(linkname):
                    repairs_made.append("WARNING: skipping {} (is regular file, but --force-relink not given)".format(linkname))
                    continue
                if not (is_broken_link(linkname) or is_absolute_link(linkname)):
                    if verbose:
                        repairs_made.append('kept {} -> {}'.format(linkname, os.readlink(linkname)))
                    continue

            candidates = index.get(basename, set())
            if len(candidates) == 0:
                if prune:
                    repairs_made.append("NOTICE: no candidates for {}, DELETING".format(basename))
                    os.remove(linkname)
                else:
                    repairs_made.append("WARNING: no candidates for {}".format(basename))
            elif len(candidates) > 1:
                repairs_made.append("WARNING: multiple candidates for {}:  {}".format(basename, candidates))
            else:
                os.remove(linkname)
                filename = list(candidates)[0]
                srcname = relativize_target(media_root, filename)
                os.symlink(srcname, linkname)
                repairs_made.append('FIXED {} -> {}'.format(linkname, srcname))

        if repairs_made:
            print('*** {}'.format(tag))
            for repair_made in repairs_made:
                print(repair_made)