#!/usr/bin/python3

import argparse
import logging
import errno
import os
import random
import time

from koji import ensuredir as ensuredir_local


def main():
    global ensuredir
    parser = argparse.ArgumentParser(description='Trigger ensuredir race')
    parser.add_argument('-l', '--local', action='store_true', help='Use local ensuredir')
    parser.add_argument('-n', '--new', action='store_true', help='Use new ensuredir')
    parser.add_argument('-o', '--old', action='store_true', help='Use old ensuredir (default)')
    args = parser.parse_args()

    ensuredir = ensuredir_old
    if args.new:
        ensuredir = ensuredir_new
    elif args.local:
        ensuredir = ensuredir_local

    logging.basicConfig(level=logging.DEBUG)
    ensuredir_loop()


def ensuredir_loop():
    i = 0
    while True:
        i += 1
        lap = time.time()
        ts = round(lap, 1)
        path = "/mnt/koji/work/debug-ensuredir/%s/%s" % (ts, random.random())
        print("Attempt %i: %s" % (i, path))
        ensuredir(path)
        time.sleep(0.02)


def ensuredir_old(directory):
    """Create directory, if necessary.

    :param str directory: path of the directory

    :returns: str: normalized directory path

    :raises OSError: If argument already exists and is not a directory, or
                     error occurs from underlying `os.mkdir`.
    """
    directory = os.path.normpath(directory)
    if os.path.exists(directory):
        if not os.path.isdir(directory):
            raise OSError("Not a directory: %s" % directory)
    else:
        head, tail = os.path.split(directory)
        if not tail and head == directory:
            # can only happen if directory == '/' or equivalent
            # (which obviously should not happen)
            raise OSError("root directory missing? %s" % directory)
        if head:
            ensuredir(head)
        # note: if head is blank, then we've reached the top of a relative path
        try:
            os.mkdir(directory)
        except OSError:
            # do not thrown when dir already exists (could happen in a race)
            if not os.path.isdir(directory):
                # something else must have gone wrong
                raise
    return directory


def ensuredir_new(directory):
    """Create directory, if necessary.

    :param str directory: path of the directory

    :returns: str: normalized directory path

    :raises OSError: If argument already exists and is not a directory, or
                     error occurs from underlying `os.mkdir`.
    """
    directory = os.path.normpath(directory)
    if os.path.exists(directory):
        if not os.path.isdir(directory):
            raise OSError("Not a directory: %s" % directory)
    else:
        head, tail = os.path.split(directory)
        if not tail and head == directory:
            # can only happen if directory == '/' or equivalent
            # (which obviously should not happen)
            raise OSError("root directory missing? %s" % directory)
        if head:
            ensuredir(head)
            parent = head
        else:
            # if head is blank, then we've reached the top of a relative path
            parent = '.'
        try:
            os.mkdir(directory)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise
            # Work around an nfs glitch. Reading the parent dir gets the os to
            # notice the new dir in a race
            # See https://pagure.io/koji/issue/4417
            _listdir(parent)

            if not os.path.isdir(directory):
                # something else must have gone wrong
                raise

    return directory


def _listdir(path):
    # os.listdir, but returns None if dir does not exist
    try:
        return os.listdir(path)
    except FileNotFoundError:
        return None


if __name__ == '__main__':
    main()
