#!/usr/bin/python3

'''Only allow one of something to run at a time'''

from __future__ import print_function

import os
import pwd
import sys
import errno
import subprocess


def usage(retval):
    '''Output a usage message'''

    if retval == 0:
        file_ = sys.stdout
    else:
        file_ = sys.stderr

    file_.write("Usage: %s --command command --string string\n" % sys.argv[0])
    sys.exit(retval)


def start_command(verbose, command, filename):
    '''Start the command - return a shell-compatible exit status'''
    if verbose:
        print('about to start command')
    sys.stdout.flush()
    try:
        subp = subprocess.Popen(command, shell=True)
        pid = subp.pid
        with open(filename, 'w') as file_:
            file_.write('{}\n'.format(pid))
        subp.communicate()
        return subp.returncode
    finally:
        os.unlink(filename)


def pid_exists(verbose, pid):
    '''Return True iff there is a process running corresponding to the given pid'''
    command = ['ps', '-p', str(pid)]

    with open(os.devnull, 'wb') as devnull:
        if subprocess.call(command, stdout=devnull, stderr=devnull) == 0:
            if verbose:
                print('pid does exist')
            return True
        else:
            if verbose:
                print('pid does not exist')
            return False


def get_just_one_dir():
    '''Build the directory to use, creating it if it doesn't yet exist'''

    uid = os.geteuid()
    pwent = pwd.getpwuid(uid)
    home_dir = pwent.pw_dir
    just_one_dir = os.path.join(home_dir, '.just-one')

    try:
        os.mkdir(just_one_dir, 7 * 64 + 5 * 8 + 5)
    except (OSError, IOError):
        _unused, mkdir_extra, _unused = sys.exc_info()
        if mkdir_extra.errno == errno.EEXIST:
            pass
        else:
            raise

    return just_one_dir


class Options(object):
    # pylint: disable=too-few-public-methods
    '''Deal with command line options'''
    def __init__(self):
        self.command = None
        self.string = None
        self.verbose = True

    def parse(self):
        '''Parse sys.argv'''
        while sys.argv[1:]:
            if sys.argv[1] == '--command':
                self.command = sys.argv[2]
                del sys.argv[1]
            elif sys.argv[1] == '--string':
                self.string = sys.argv[2]
                del sys.argv[1]
            elif sys.argv[1] == '--verbose':
                self.verbose = True
            elif sys.argv[1] == '--quiet':
                self.verbose = False
            elif sys.argv[1] in ['--help', '-h']:
                usage(0)
            else:
                sys.stderr.write("%s: Unrecognized option: %s\n" % (sys.argv[0], sys.argv[1]))
                usage(1)
            del sys.argv[1]

        required_missing = False

        if not self.command:
            sys.stderr.write("%s: --command command is a required option\n" % sys.argv[0])
            required_missing = True

        if not self.string:
            sys.stderr.write("%s: --string string is a required option\n" % sys.argv[0])
            required_missing = True

        if required_missing:
            sys.stderr.write('%s: one or more required options missing\n' % sys.argv[0])
            usage(1)


def run_one(verbose, command, string):
    '''Run the specified process, but only if it isn't already running'''
    if verbose:
        print('about to mkdir')

    just_one_dir = get_just_one_dir()

    if verbose:
        print('about to get pid')

    filename = os.path.join(just_one_dir, string)

    pid = None

    try:
        file_ = open(filename, 'r')
    except (OSError, IOError):
        _unused, open_extra, _unused = sys.exc_info()
        if open_extra.errno == errno.ENOENT:
            pass
        else:
            raise
    else:
        pid = int(file_.readline())
        file_.close()

    if pid:
        if verbose:
            print('got pid %d' % pid)
        if pid_exists(verbose, pid):
            sys.stderr.write("%s: %s is locked\n" % (sys.argv[0], string))
            exit_code = 1
        else:
            exit_code = start_command(verbose, command, filename)
    else:
        exit_code = start_command(verbose, command, filename)

    return exit_code


def main():
    '''Main function'''

    options = Options()
    options.parse()

    exit_code = run_one(verbose=options.verbose, command=options.command, string=options.string)

    sys.exit(exit_code)


if __name__ == '__main__':
    main()