#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ts=4:sw=4
# GPLv3 or later, Dirk Sohler
# http://dev.0x7be.de/mycron.html

import os
import sys
import re
import time
import configparser

from optparse import OptionParser

progversion = '3.0'


basedir = os.path.expanduser('~/.config/mycron')
crontab = os.path.join(basedir, 'mycron.crontab')
config = configparser.ConfigParser()
config.read(os.path.join(basedir,'mycron.conf'))


def options():
    parser = OptionParser()
    parser = OptionParser(
            usage='%prog [options]',
            version='%prog '+progversion)
    parser.add_option('-e', '--edit',
            action='store_true',
            dest='edit',
            default=False,
            help='edit crontab')
    parser.add_option('-l', '--list',
            action='store_true',
            dest='listjobs',
            default=False,
            help='list cronjobs')
    parser.add_option('-c', '--check',
            action='store_true',
            dest='check',
            default=False,
            help='check crontab for validity')
    parser.add_option('-o', '--once',
            action='store_true',
            dest='once',
            default=False,
            help='only run once instead of daemon-like')
    return parser
params = options()
options,userinput = params.parse_args()


def getcrontab():
    res = []
    counter = 0
    match = '^(....-..-..T..:..)\s+([1-7]{1,7}|\.)\s+(.*)$'
    try:
        crontabcontents = open(crontab, 'r')
    except IOError:
        return res
    try:
        for l in crontabcontents:
            if l != '\n' and l[0] != '#':
                try:
                    line = {}
                    jobline = re.match(match, l.strip())
                    line['jobline'] = l.replace('\n', '')
                    line['date'] = jobline.group(1).split('T')[0]
                    line['time'] = jobline.group(1).split('T')[1]
                    line['day'] = jobline.group(2)
                    line['command'] = jobline.group(3)
                    line['malformed'] = False
                except AttributeError:
                    line['malformed'] = True
                res.append(line)
        return res
    except UnicodeDecodeError:
        print('One of the cronjobs contains non-ASCII characters. No ' +
            'cronjobs were executed!')
        sys.exit(1);


def runcrontab():
    date = time.strftime('%Y-%m-%dT%H:%M')
    day = time.strftime('%u')
    for job in getcrontab():
        if job['malformed'] == False:
            dateyes = re.match(job['date'] + 'T' + job['time'], date)
            dowyes = re.match(job['day'], day) or day in job['day']
            if dateyes and dowyes:
                os.system(job['command'] + ' &')
        else:
            print('Malformed job was not executed: %s'
                    % (job['jobline']), file=sys.stderr)


def editcrontab():
    if not os.path.isdir(basedir):
        os.makedirs(basedir)
    if not os.path.isfile(crontab):
        with open(crontab, 'w') as crontabfile:
            crontabfile.write('# Syntax is YYYY-MM-DDTHH:MM D command\n' +
                    '# ....-..-..T..:.. . will be executed every run\n' +
                    '# ....-..-..T15:30 3 every wednesday at 3:30pm')
    os.system('%s "%s"' % (config.get('default', 'editor',
        fallback='$VISUAL'),crontab))


def listjobs():
    for job in getcrontab():
        if job['malformed'] == True:
            print('x %s' % job['jobline'])
        else:
            print('o %s' % (job['jobline']))


def checkcrontab():
    crontabcontent = getcrontab()
    lines = len(crontabcontent)
    valid = 0
    malformed = 0
    for job in crontabcontent:
        if job['malformed'] == True:
            malformed += 1
        elif job['malformed'] == False:
            valid += 1
    print('cronjobs defined: %s' % str(lines))
    print('valid cronjob: %s' % str(valid))
    print('malformed cronjobs: %s' % str(malformed))
    if (malformed == 0) and (valid == lines):
        print('Crontab is fully valid!')
    else:
        print('\033[1;31mCrontab is not valid!\033[0m')


def main():
    if not sys.version.startswith('3'):
        print('You need at least Python 3.x (out since June 2009!)')
        exit(1)
    if options.edit == True:
        editcrontab()
        checkcrontab()
        sys.exit(0)
    elif options.listjobs == True:
        listjobs()
        sys.exit(0)
    elif options.check == True:
        checkcrontab()
        sys.exit(0)
    if options.once == True:
        print('Running mycron once at %s' % time.strftime('%H:%M:%S'))
        runcrontab()
        sys.exit(0)

    delay = 60 - int(time.strftime('%S'))

    if delay in range(1,60):
        print('Delaying startup for %i seconds for being minute-exact' % delay)
        time.sleep(delay)

    print('Running mycron continuously since %s' % time.strftime('%H:%M:%S'))

    while True:
        try:
            runcrontab()
            time.sleep(60)
        except KeyboardInterrupt:
            print('\b\bManual break by user (Ctrl+C or equivalent signal)')
            return

    sys.exit(0)


if __name__ == '__main__':
    main()
