#!python
# ##############################################################################
# The contents of this file are subject to the PyTis Public License Version    #
# 2.0 (the "License"); you may not use this file except in compliance with     #
# the License. You may obtain a copy of the License at:                        #
#                                                                              #
#     http://www.PyTis.com/License/                                            #
#                                                                              #
#     Copyright (c) 2009 - 2016 Josh Lee                                       #
#                                                                              #
# Software distributed under the License is distributed on an "AS IS" basis,   #
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License     #
# for the specific language governing rights and limitations under the         #
# License.                                                                     #
#                                                                              #
# @auto-generated by the PyTis Copyright Tool on 06:52 04 Apr, 2016            #
############################################################################## #
"""wget-diff
=========

NAME:
  wget-diff

SYNOPSIS:
	wget-diff [--options] [-H] [-V] [-D]

DESCRIPTION:
	Outstanding program that can run as a scheduled cron job and notify you when
	a site chagnes.  Currently this may be ran on demand, or via a cron job, in
	the future, I'd like to get this to run as a deamon as well.  

CONFIGURING ACTIONS:
	There are 3 (three) methods for notifying the user, when a diff is found.

		1. runcommand: will execute provided string on the command line.

		2. runsql: User can provide the driver (postgresql or mysql) and connection
		settings, as well as an sql query to execute.

		3. email: User can provide To, CC, Subject, Body, and From address.  The
		diff is added under the body provided.  will execute a single l

	INI EXAMPLES:
	action = email - works, needs documented

	action = runcommand - works, needs documented

	action = runsql - not yet coded, will be coded to work for mysql, mssql, 
		and postgresql

	Multiple actions may be configured within a single section as such:

		action = email, runcommand

		OR

		action = email, runcommand, runsql

CODE:

"""

import cStringIO
from difflib import unified_diff
import time
import os, optparse
import sys
import subprocess
import urllib
from urllib2 import urlopen
from threading import Timer
import socket
import urllib2


def handler(fh):
	fh.close()

def download(url):
	timeout = 10.0
	try:
		fh = urlopen(url, timeout = timeout)
	except urllib2.URLError, e:
		# For Python 2.6
		if isinstance(e.reason, socket.timeout):
			log.error("Socket timeout")
			return
		else:
			# reraise the original error
			raise
	except socket.timeout, e:
		# For Python 2.7
		log.error("Socket timeout")
		return

	t = Timer(timeout, handler,[fh])
	t.start()
	data = fh.read()
	t.cancel()
	return data

def write_file(fpath, data):
	global log
	if not data or not data.strip(): return

	try: 
		handle = open(fpath,'wb')
	except (IOError, OSError) as e:
		log.error("An error occurred opening %s to write to." % fpath)
		# skip ahead to next url, cannot do anything if we can't open to 
		# write to fpath.
		try: 
			handle.close()
		except: 
			log.error("can't close data file, had open error: %s"%fpath)
		else:
			log.error("closed data file, had open error: %s"%fpath)
		raise PyTis.IdiotError()

	else:

		try: 
			handle.truncate()
			handle.seek(0)
			handle.write(data)
		except (IOError, OSError) as e:
			log.error("An error occurred writing to %s" % fpath)
			# skip ahead to next url, cannot do anything if we can't open to 
			# write to fpath.
			try: 
				handle.close()
			except: 
				log.error("can't close data file, had write error: %s"%fpath)
			else:
				log.error("closed data file, had write error: %s"%fpath)
			log.error("Calling continue from write data exception.")
			raise PyTis.IdiotError()

		else:
			try: 
				handle.close()
			except: 
				log.error("can't close data file: %s" % fpath)
			else:
				return True


# This program needs to import PyTis v3, which imports stuff from the
# sub-package pylib3, this program also needs to import from the sub-package
# awslib, pylib3.awslib itself, has to import from the parent, pytis3, which it
# can only do if the parent directory is a package, turning the parent (bin)
# into a package breaks importing pytis3 for this program in the first place
# and caused severe circular import errors.  To fix this, we have to adjust the
# path.
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')))
# 
# Internal
#
try:
	#import pytis as PyTis # Shared GPL/PPL License
	from bin import PyTis # Shared GPL/PPL License
	from pylib import configobj as COBJ
except ImportError as e:
	# We cannot go any further than this, we can't use the Parser or Logging tool
	# to display these errors because those very tools are loaded from PyTis.
	# Therefore, display errors now and exit with an errored exit code.
	print("This program requires the PyTis python library to run.")
	print("You may download the PyTis library, or do an SVN checkout from:")
	print("<https://sourceforge.net/projects/pytis/>")
	print("This program should be installed in the bin directory of the PyTis library.")
	print(str(e))
	sys.exit(1)


errors=[]

__author__ = 'Josh Lee'
__created__ = '11:48am 12 May, 2008'
__copyright__ = 'PyTis.com'
__curdir__ = os.path.abspath(os.path.dirname(__file__))
__version__ = '0.95'

__configdir__=os.path.abspath(os.path.join(PyTis.__configdir__,'.%s'%os.path.basename(sys.argv[0])))

def test_config_exists(cpath, cfile):
	return cpath.strip() and os.path.isdir(cpath) and os.path.exists(cpath) and \
		cfile.strip() and os.path.isfile(cfile) and os.path.exists(cfile)

def setup_config_file(cfile):
	global log
	
	if os.path.exists(cfile):
		raise PyTis.FileExists("%s file already exists, exiting." % cfile)
	else:
		try:
			COBJ.fload(cfile)
		except (PyTis.PermissionError, OSError, IOError) as e:
			if 'File exists' in str(e):
				log.info("%s file already exists, exiting." % cfile)
				return 0
			elif 'Permission denied' in str(e):
				log.error('ERROR: You do not have permission to create ' \
					'this file: %s' % cfile)
				return 13
			else:
				log.error(str(e))
				return 1

	log.info("%s file created succesfully." % cfile)
	return 0

def setup_config_dir(cpath):
	global log
	try:
		os.mkdir(cpath)
	except OSError, e:
		if 'File exists' in str(e):
			log.info("%s directory already exists, exiting." % cpath)
			return 0
		elif 'Permission denied' in str(e):
			log.error('ERROR: You do not have permission to create ' \
				'this directory: %s' % cpath)
			return 13
		else:
			log.error(str(e))
			return 1
	else:
		log.info("%s directory created succesfully." % cpath)
		return 0

def check_ini(opts,config):
	"""wget-diff run doc help"""
	global __configdir__, log

	fname = config.filename
	files = config.get('cache')
	dates = config.get('cache_dates')
	try:
		if not files: 
			files=config['cache']={}
			config.save()
		if not dates: 
			files=config['cache_dates']={}
			config.save()
	except (PyTis.PermissionError, OSError, IOError) as e:
		if 'Permission denied' in str(e):
			log.error('You do not have permission to save to this file: %s' % fname)
			return 13
		else:
			log.error(str(e))
			return 1

	for i, url in enumerate(config.sections):
		i+=1

		if url not in files.values() and url not in ('cache','cache_dates'):
			f_int_rep=i
			while str(f_int_rep) in [str(key) for key in files.keys()]: f_int_rep+=1
			config['cache'][str(f_int_rep)]=url
			config['cache_dates'][str(f_int_rep)]=()
			config.save()

		elif url in files.values():
			f_int_rep = {v: k for k, v in files.items()}[url]

		if url in ('cache','cache_dates'):
			continue

		section = config[url]
		actions = section.get('action',None)
		
		if not actions:
			log.error("Section %s has no action configured!" % url)
		else:
			if type(actions) is type([]):
				actions = [action.strip().lower() for action in actions]
			else:
				actions = [action.strip().lower() for action in actions.split(',')]

			for action in actions:
				if action not in ('email','runcommand','runsql'):
					log.error("Invalid action specified '%s' for url: %s"%(action,url))
					return 1
			
				# #####################################################################
				# BEGIN SQL -----------------------------------------------------------

				if action == 'runsql':
					sqlsection = section.get('sql')
					if not sqlsection:
						log.error("No sql configuration provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

					sql = sqlsection.get('sql')
					if not sql:
						log.error("No sql statement provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

					driver = sqlsection.get('driver')
					if not driver:
						log.error("No driver provided for %s and 'runsql' " \
							"was specified as the action (postgresql or mysql)." % url)
						return 1

					host = sqlsection.get('host')
					if not host:
						log.error("No host provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

					port = sqlsection.get('port')
					if not port:
						log.error("No port provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

					user = sqlsection.get('user')
					if not user:
						log.error("No user provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

					password = sqlsection.get('password')
					if not password:
						log.error("No password provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

					database = sqlsection.get('database')
					if not database:
						log.error("No database provided for %s and 'runsql' " \
							"was specified as the action." % url)
						return 1

				# END SQL -------------------------------------------------------------
				# #####################################################################
				# BEGIN RUN COMMAND ---------------------------------------------------

				if action == 'runcommand':
					command = section.get('command')
					if not command:
						log.error("No sh/bash command provided for %s and 'runcommand' " \
							"was specified as the action." % url)
						return 1

				# END RUN COMMAND -----------------------------------------------------
				# #####################################################################
				# BEGIN EMAIL ---------------------------------------------------------

				if action == 'email':

					body = section.get('body')
					if body is None:
						log.error("Body missing for %s and 'Email' was " \
							"specified as the action." % url)
						return 1

					host = section.get('host',None)
					if host is None:
						log.debug('The mail host has not been provided in the ' \
						'configuration ini file, assuming "localhost."  If localhost ' \
						'fails, will attempt "127.0.0.1."')
						section['host']='localhost'

					local_hostname = section.get('local_hostname',None)
					if local_hostname is None:
						log.debug('The mail local hostname has not been provided in the ' \
						'configuration ini file, assuming "%s" from uname.')
						section['local_hostname'] = os.uname()[1]

					subject = section.get('subject')
					if subject is None:
						log.error("Subject missing for %s and 'Email' was " \
							"specified as the action." % url)
						return 1

					from_address = section.get('from')
					if not from_address:
						log.error("From address missing for %s and 'Email' was " \
							"specified as the action." % url)
						return 1

					to_address = section.get('to')
					if not to_address:
						log.error("To address(s) missing for %s and 'Email' was " \
							"specified as the action." % url)
						return 1

					cc_address = section.get('cc')
					if not cc_address:
						log.debug("CC address(s) missing for %s and 'Email' was " \
							"specified as the action." % url)
						section['cc']=''
						# continue # it's okay, we don't need a CC

				# END EMAIL -----------------------------------------------------------
	config.save()
	return 0

	

def run(opts,config):
	"""wget-diff run doc help"""
	global __configdir__, log

	files = config.get('cache')
	dates = config.get('cache_dates')
	if not files: files=config['cache']={}
	if not dates: files=config['cache_dates']={}

	for i, url in enumerate(config.sections):
		i+=1
		trigger = False
		first_saved=''
		last_changed=''
		pretty_now=PyTis.prettyNow()
		try:
			true_diff.close()
		except:
			pass

		true_diff = cStringIO.StringIO()

		if url not in files.values() and url not in ('cache','cache_dates'):
			
			f_int_rep=i
			while str(f_int_rep) in [str(key) for key in files.keys()]: f_int_rep+=1
			config['cache'][str(f_int_rep)]=url
			config.save()
		elif url in files.values():
			f_int_rep = {v: k for k, v in files.items()}[url]

		if url in ('cache','cache_dates'):
			continue

		fname='.cache-%s'%f_int_rep
		fpath=os.path.abspath(os.path.join(__configdir__,fname))
		
		log.debug('processing section: %s' % (url))
		section = config[url]
		actions = section.get('action',None)
		
		if not actions:
			log.error("Section %s has no action configured!" % url)
		else:
			if type(actions) is type([]):
				actions = [action.strip().lower() for action in actions]
			else:
				actions = [action.strip().lower() for action in actions.split(',')]

			for action in actions:
				if action not in ('email','runcommand','runsql'):
					log.error("Invalid action specified '%s' for url: %s"%(action,url))
					continue
			
			if os.path.isfile(fpath) and os.path.exists(fpath):
				try:
					old_handle = open(fpath,'r')
					old_data = old_handle.read(-1)
					#if not old_data:  old_data = None
				except (IOError, OSError) as e:
					log.error("Could not read old cache file for: %s" % url)
					log.error("Assuming corrupt data and assuming DIFF exists!")
					old_data="Could not read old cache file for: %s Assuming corrupt " \
						"data and assuming DIFF exists!" % url
				finally:
					old_handle.close()
			else:
				#first time to create and cache data
				old_data = None

			try:
				new_data = urllib.urlopen(url).read()
			except IOError as e:
				log.debug("First attempt failed at opening: %s" % url)
				time.sleep(2)
				try:
					new_data = urllib.urlopen(url).read()
				except IOError as e:
					log.error("Two attempts failed at opening: %s" % url)
					try:
						new_data = download(url)
					except Exception as e:
						log.error("Third attempt failed at opening: %s" % url)
						log.error(str(e))

						if not opts.dry_run: 

							body = PyTis.to80("WARNING! wget-diff failure.\n\nwget " \
								"failure notice, 3rd and final attempt failed to query " \
								"URL: %s on %s." % (url, pretty_now))

							PyTis.sendEmail(body, "wget failure notice", section.get('to'),
								section.get('from'), section.get('cc'), 
								host=section.get('host'), 
								local_hostname=section.get('local_hostname'), port=25, 
								timeout=20)

						continue
					else:
						if new_data is None:
							log.error("Third and final attempt failed.")

							if not opts.dry_run: 

								body = PyTis.to80("WARNING! wget-diff failure.\n\nwget " \
									"failure notice, 3rd and final attempt failed to query " \
									"URL: %s on %s." % (url, pretty_now))
								
								PyTis.sendEmail(body, "wget failure notice", section.get('to'),
									section.get('from'), section.get('cc'), 
									host=section.get('host'), 
									local_hostname=section.get('local_hostname'), port=25, 
									timeout=20)


							continue
						else:
							log.debug("Third attempt succesful at opening: %s" % url)

				else:
					log.debug("Second attempt successful at opening: %s" % url)
			else:
				log.debug("First attempt successful at opening: %s" % url)

			
			# n N o N
			# n - o N
			# n N o -
			# n - o -

			if new_data is None and old_data is None:
				log.error("Cannot wget site: %s" % url)
				trigger = False
				continue # continue on to the next URL

			elif new_data is not None and old_data is None:
				if not new_data.strip():
					log.error("x"*80)
					log.error("SUPER ERROR, NEW DATA IS EMPTY STRING")
				trigger = False
				# Site was not ever cached before, this is the first time, just save it
				log.info("Site was not ever cached before, this is the first " \
					"time, saving.")
				if not opts.dry_run: 
					try:
						write_file(fpath, new_data)
					except PyTis.IdiotError as e:
						continue

				config['cache_dates'][str(f_int_rep)] = (pretty_now,pretty_now)
				config.save()

			elif new_data is None and old_data is not None:
				# site cannot be cached at this time, was in the past, just move on
				log.error("Cannot wget site: %s, last cached: %s" % \
					(url,config['cache_dates'][str(f_int_rep)][1]))
				trigger = False
			else:
				log.info("wget successfull of: %s caching on: %s"%(url,pretty_now))

				if new_data == old_data:
					# Nothing has changed.
					log.info("no change in: %s" % url)
					trigger = False
				else:
					# the file has changed!
					# grab the dates before changing them
					first_saved,last_changed = config['cache_dates'][str(f_int_rep)]
					# now update the last updated date
					config['cache_dates'][str(f_int_rep)]=(config['cache_dates'][str(f_int_rep)][0],pretty_now)
					config.save()
						
					trigger = True 
					log.info("%s, first cached on: %s, last updated on: %s, has since " \
						"changed." % (url, first_saved,last_changed))
					if not opts.dry_run: 
						try:
							write_file(fpath, new_data)
						except PyTis.IdiotError as e:
							continue

			if (trigger and not opts.dry_run) or (trigger and opts.dry_run and \
			opts.debug):

				# XXX-TODO capture diff first, then write new data.
				for line in unified_diff(old_data.split("\n"), new_data.split("\n"), lineterm=""):
					true_diff.write(line)
					true_diff.write("\n")

				log.debug("Re-caching %s as of today: %s."%(url,pretty_now))
				log.info("DIFF: %s" % url)
				#log.info("%s" % true_diff.getvalue())

				for action in actions:
				# #####################################################################
				# BEGIN SQL -----------------------------------------------------------

					if action == 'runsql':
						sql_section = section.get('sql')

						sql = sql_section.get('sql')
						driver = sql_section.get('driver')
						host = sql_section.get('host')
						port = sql_section.get('port')
						user = sql_section.get('user')
						password = sql_section.get('password')
						database = sql_section.get('database')

						log.debug("sql: %s" % sql)
						log.debug("driver: %s" % driver)
						log.debug("host: %s" % host)
						log.debug("port: %s" % port)
						log.debug("user: %s" % user)
						log.debug("password: %s" % password)
						log.debug("database: %s" % database)


				# END SQL -------------------------------------------------------------
				# #####################################################################
				# BEGIN RUN COMMAND ---------------------------------------------------

					if action == 'runcommand':
						command = section.get('command')

						cmd_list = []
						#cmd_list.extend(self.buildIoNice())
						#cmd_list.extend(self.buildNice())
						command_list = [a.strip() for a in command.split()]
						cmd_list.extend(command_list)
						#self.log.debug(repr(cmd_list))
						#log.debug(repr(cmd_list))
						try:
							subprocess.call(cmd_list)
						except Exception as e:
							log.error("An unknown error occured when trying to run " \
								"command \"%s\" for url: %s changed on %s" % \
								(repr(cmd_list),url,pretty_now))

				# END RUN COMMAND -----------------------------------------------------
				# #####################################################################
				# BEGIN EMAIL ---------------------------------------------------------

					if action == 'email':

						body = section.get('body')
						if not true_diff.getvalue():
							true_diff.write("no diff available\n")

						body = "%s\n\nURL: %s has changed on %s.\n\nDIFF of %s -\n\n%s" % \
							(body, url, pretty_now, url, true_diff.getvalue())

						PyTis.sendEmail(body, section.get('subject'), section.get('to'),
							section.get('from'), section.get('cc'), 
							host=section.get('host'), 
							local_hostname=section.get('local_hostname'), port=25, 
							timeout=20)

				# END EMAIL -----------------------------------------------------------



def main():
	"""usage: wget-diff"""
	global __configdir__, errors, log

	config_filename = os.path.abspath(os.path.join(PyTis.__configdir__, 
		'%s.ini' % os.path.basename(os.path.abspath(sys.argv[0]))))


	PyTis.__option_always__ = [True]
	help_dict = dict(version=__version__,
						 author=__author__,
						 created=__created__,
						 copyright=__copyright__)
	parser = PyTis.MyParser()

	parser.extra_txt = "\n\n%s\n" % run.__doc__ + """

EXAMPLES:	
	wget-dff

SEE ALSO:
	wget -- built-in
	diff -- built-in
	pg_diff -- pytis 

COPYRIGHT:
	%(copyright)s

AUTHOR:
	%(author)s

CHANGE LOG:

	v0.95 MINOR CHANGE
		Ran importnanny and removed un-needed imports.

	v0.91 MINOR CHANGE
		Added in documentation around dry-run and actions, and tweaked verbose and 
		quiet optional flag defaults for dry-run.

CREATED:
	%(created)s

HISTORY:
	Original Author

TODO:
	action=runsql - This is not yet coded, will be coded to work for mysql,
	mssql, and postgresql.  Completing this will bring wget-diff to 100%%, and
	thus, version 1.

VERSION:
	%(version)s
""" % help_dict

	parser.formatter.format_description = lambda s:s
	if '--help' in sys.argv:
		parser.set_description(__doc__)
		helpishere=True # to determine help mode (short or full)
		# "Only tell the user    "
		dry_run_help = 'Enable the dry-run flag (-d/--dry-run) in order to test ' \
			'what this script would do, if ran normally.  Dry Run does not ' \
			' re-cache, send emails, run commands, or run sql statements (see ' \
			'"Configuring Actions" section of full help text), and thus in ' \
			'repitition would continue to report that a file has changed.  ' \
			'If the file has changed since the previous non-dry-run caching of ' \
			'said URL, output will reflect such.  If a URL has not changed since ' \
			'the last cached URL, then the output will state there is no change. ' \
			'Dry Run [-d/--dry-run] is great for when an adminstrator (or user) ' \
			'wishes to just test and see if anything has changed, without any ' \
			'actions taking place.  Generally the actions are fired off when ' \
			'this program is callef rom the cron jobs.`$' \
			'Lastly, using the [-d/--dry-run] flag, implies verbosity, and unless ' \
			'the [-q/--quiet] flag is provided, this program will default to ' \
			'verbose mode.`$'
	else:

		parser.set_description('')
		helpishere=False # to determine help mode (short or full)
		dry_run_help = 'Reports changes as STDOUT, without executing any '\
			'configured actions, or caching.  Implies "verbosity."' \
			' *(use "--help" for more details)`$'

	runtime = optparse.OptionGroup(parser, "-- RUNTIME ARGUMENTS")

	runtime.add_option("-d", "--dry-run", action="store_true", #type="bool",
		default=False, dest='dry_run',
		help=dry_run_help)
		#help=optparse.SUPPRESS_HELP)

	parser.add_option_group(runtime)
	# -------------------------------------------------------------------------
	# variable setting
	vars = optparse.OptionGroup(parser, "-- CONFIGURATION SETTINGS")

	vars.add_option("-S", "--setup", action="store_true", default=False, 
										help="Runs setup to install this script.")

	parser.add_option_group(vars)
	# ----------------------------
	dbgroup = optparse.OptionGroup(parser, "-- DEBUG")
	dbgroup.add_option("-D", "--debug", action="store_true",
					 default=False, dest='debug',
					 help="Enable debugging`$")


	# This is a little trick to tell if the user entered the -V/--verbose flag.
	# We want verbosity on by default, but we also want to know if the user
	# entered it for debug items, and providing end messages vs informed output.
	dbgroup.add_option("", "--totaly-verbose", action="store_true",
		default=False, dest='totally_verbose', 
		help=optparse.SUPPRESS_HELP)

	dbgroup.add_option("-V", "--verbose", action="store_true",
					 default=False, dest='verbose',
					 help="Be more Verbose (make lots of noise).  Off by default, " \
					 "unless in dry-run.`$")

	dbgroup.add_option("-q", "--quiet", action="store_true",
					 default=False, dest='quiet',
					 help="be vewwy quiet (I'm hunting wabbits).  On by default, " \
					 "unless in dry-run.`$")

	dbgroup.add_option("-v", "--version", action="store_true",
					 default=False, dest='version',
					 help="Display Version")

	parser.add_option_group(dbgroup)
	# ----------------------------

	(opts, args) = parser.parse_args()
	if opts.verbose: 
		opts.totally_verbose = True
	if opts.quiet: opts.verbose = False

	if opts.dry_run and not opts.quiet: opts.verbose = True

	#if not opts.quiet: opts.verbose = True # Defaults to Verbose
	if not opts.verbose: opts.quiet = True # Defaults to Quiet 

	if opts.debug:
		main.__doc__ = "%s\n\n	CONFIG FILE: %s" % (main.__doc__, \
			os.path.abspath(config_filename))
	else:
		pass

	parser.set_usage(main.__doc__)

	old_version = opts.version
	opts.version = True
	log = PyTis.set_logging(opts, os.path.basename(sys.argv[0]))
	opts.version = old_version

	if opts.version:
		return PyTis.version(__version__)

	if not test_config_exists(__configdir__, config_filename) or opts.setup:
		if not opts.setup:
			log.info("wget-diff config file missing, running Setup now.")
		else:
			log.info("Running Setup not to create wget-diff config file.")

		try:
			retval = setup_config_dir(__configdir__)
		except KeyboardInterrupt,e:
			log.debug("Keyboard-Interrupt, bye!")
			if not opts.quiet:
				log.info("\nbye!")
			return 1
		else:
			if retval:
				return retval

		try:
			retval = setup_config_file(config_filename)
		except KeyboardInterrupt,e:
			log.debug("Keyboard-Interrupt, bye!")
			if not opts.quiet:
				log.info("\nbye!")
			return 1

		finally:
			return retval

	config = COBJ.load(config_filename)

	# if (opts.quiet or opts.verbose or len(args) != 0) and not errors:
	if not opts.quiet and not opts.verbose and len(args)==0 and not errors:
		return parser.print_usage()
	elif not errors:
		try:
			rcode = check_ini(opts, config)
			if rcode:
				if rcode == 1:
					log.error("program halted due to improper ini configuration.")
					parser.print_usage()
				return rcode
			else:
				run(opts, config)
		except KeyboardInterrupt,e:
			log.debug("Keyboard-Interrupt, bye!")
			if not opts.quiet:
				log.info("\nbye!")
			return 0
		else:
			if opts.totally_verbose: log.info("Done.")
			return 0
	else:
		parser.print_usage()
		if errors:
			log.error(str("\n".join(errors)))
		return parser.print_help(errors)

	parser.print_help("ERROR: Unknown, but invalid input.")
	sys.exit(0)

if __name__ == '__main__':
		sys.exit(main())

