#!/usr/bin/python
# dcut --- debian command upload tool
# Copyright (c) 2004,2005 Thomas Viehmann <tv@beamnet.de>
# portions ripped from dput:
#   Copyright (c) 2000,2001,2002,2003,2004 Christian Kurz
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import sys, getopt, os, tempfile, string, time

progname = "dcut"
version = "0.0.7"

USAGE = """Usage: %s [options] [host] command [, command]
 Supported options (see man page for long forms):
   -c file Config file to parse.
   -d      Activate debugging.
   -h      Display this help message.
   -s      Simulate the commands file creation only.
   -v      Display version information.
   -m maintaineraddress
           Use maintainer information in "Uploader:" field.
   -O file Write commands to file.
   -U file Upload specified commands file (presently no checks).
 Supported commands: mv, rm
   (No paths or command-line options allowed on ftp-master.)
"""%(sys.argv[0])

validcommands = ('mv','rm')
def getoptions():
  # seed some defaults
  options = {'debug':0, 'simulate':0, 'config':None, 'host':None,
	     'uploader':None, 'passive':0, 'filetocreate':None,
	     'filetoupload':None}
  # enable debugging very early
  if ('-d' in sys.argv[1:] or '--debug' in sys.argv[1:]):
    options['debug']=1
    print "D: %s %s" % (progname,version)

  # check environment for maintainer
  if options['debug']: print "D: trying to get maintainer email from environment"

  if os.environ.has_key('DEBEMAIL'):
    if os.environ['DEBEMAIL'].find('<')<0:
      options['uploader']=os.environ.get("DEBFULLNAME",'')
      if options['uploader']:
	options['uploader'] += ' '
      options['uploader'] += '<%s>'%(os.environ['DEBEMAIL'])
    else:
      options['uploader'] = os.environ['DEBEMAIL']
    if options['debug']: print "D: Uploader from env: %s"%(options['uploader'])
  else:
    if options['debug']: print "D: Guessing uploader"
    import pwd
    pwrec = pwd.getpwuid(os.getuid())
    try:
      s = open('/etc/mailname').read().strip()
    except IOError:
      s = ''
    if not s:
      if options['debug']: print "D: Guessing uploader: /etc/mailname was a failure"
      s = os.popen('/bin/hostname --fqdn').read().strip()
    if s:
      options['uploader'] = '%s <%s@%s>'%(pwrec[4].split(',')[0],pwrec[0],s)
      if options['debug']: print "D: Guessed uploader: %s"%(options['uploader'])
    else:
      if options['debug']: print "D: Couldn't guess uploader"
  # parse command line arguments
  try:
    (opts, arguments) = getopt.getopt(sys.argv[1:],
                            'c:dDhsvm:PU:O:',
                            ['config=', 'debug',
                             'help', 'simulate', 'version','host=',
			     'maintainteraddress=','passive','upload=',
			     'output='
			     ])
  except getopt.error, msg:
      print msg
      sys.exit(1)

  for (option, arg) in opts:
    if options['debug']: print 'D: processing arg "%s", option "%s"'%(option,arg)
    if option in ('-h', '--help'):
      print USAGE
      sys.exit(0)
    elif option in ('-v', '--version'):
      print prodname,version
      sys.exit(0)
    elif option in ('-d', '--debug'):
      options['debug'] = 1
    elif option in ('-c', '--config'):
      options['config'] = arg
    elif option in ('-m', '--maintaineraddress'):
      options['uploader'] = arg
    elif option in ('-s', '--simulate'):
      options['simulate'] = 1
    elif option in ('-P', '--passive'):
      options['passive'] = 1
    elif option in ('-U', '--upload'):
      options['filetoupload'] = arg
    elif option in ('-O', '--output'):
      options['filetocreate'] = arg
    elif option=='--host':
      options['host'] = arg
    else:
      print "%s internal error: Option %s, argument %s unknown"%(
	      progname,option,arg)
      sys.exit(1)
  
  if not options['host'] and arguments and arguments[0] not in validcommands:
    options['host'] = arguments[0]
    if options['debug']: print 'D: first argument "%s" treated as host'%(options['host'])
    del arguments[0]
    
  # we don't create command files without uploader
  if not options['uploader'] and not options['filetoupload']:
    print "%s error: command file cannot be create without maintainer email"%progname
    print '%s        please either set $DEBEMAIL or use the "-m" option'%(len(progname)*' ')
    sys.exit(1)
    
  return options, arguments

def parse_queuecommands(arguments,options,config):
  commands = []
  arguments = arguments[:] # want to consume a copy of arguments
  arguments.append(0)
  curarg = []
  while arguments:
    if arguments[0] in validcommands:
      curarg = [arguments[0]]
    else:
      if not curarg and arguments[0]!=0:
	print 'Error: Could not parse commands at "%s"'%(arguments[0])
        sys.exit(1)
      if str(arguments[0])[-1] in (',',';',0):
        curarg.append(arguments[0][0:-1])
        arguments[0] = ','
      if arguments[0] in (',',';',0) and curarg:
	# TV-TODO: syntax check for #args etc.
	if options['debug']: print 'D: Successfully parsed command "%s"'%(' '.join(curarg))
	commands.append(' '.join(curarg))
	curarg = []
      else:
	# TV-TODO: maybe syntax check the arguments here
	curarg.append(arguments[0])
    del arguments[0]
  if not commands:
    print 'Error: no arguments found'
    sys.exit(1)
  return commands

def write_commands(commands, options, config, tempdir):
  if options['filetocreate']:
    filename = options['filetocreate']
  else:
    translationorig =  ''.join(map(chr, range(256)))+string.ascii_letters+string.digits
    translationdest = 256*'_'+string.ascii_letters+string.digits
    uploadpartforname = string.translate(options['uploader'],
				       string.maketrans(
      translationorig,translationdest))
    filename = (progname+'.%s.%d.%d.commands'%
		(uploadpartforname,int(time.time()),os.getpid()))
    if tempdir:
      filename = os.path.join(tempdir,filename)
  f = open(filename+".asc","w")
  f.write("Uploader: %s\n"%options['uploader'])
  f.write("Commands:\n %s\n\n"%('\n '.join(commands)))
  f.close()
  if os.system("gpg --local-user '%s' --clearsign --no-show-policy-url --armor --textmode  --output '%s' '%s'"%(options['uploader'],filename,filename+'.asc')):
    print "Error: gpg failed."
    sys.exit(1)
  os.unlink(filename+'.asc')
  return filename

def upload_stolen_from_dput_main(host, upload_methods, config,debug,simulate,files_to_upload,ftp_passive_mode):
        # Check the upload methods that we have as default and per host
        if debug: print "D: Default Method: %s" % \
            config.get('DEFAULT', 'method')
        if not upload_methods.has_key(config.get('DEFAULT', 'method')):
            print "Unknown upload method: %s" % \
                config.get('DEFAULT', 'method')
            sys.exit(1)
        if debug: print "D: Host Method: %s" % config.get(host, 'method') 
        if not upload_methods.has_key(config.get(host, 'method')):
            print "Unknown upload method: %s" % config.get(host, 'method')
            sys.exit(1)

        # Inspect the Config and set appropriate upload method
        if not config.get(host, 'method'):
            method = config.get('DEFAULT', 'method')
        else:
            method = config.get(host, 'method')

        # Check now the login and redefine it if needed
	if config.has_option(host, 'login'):
	  login = config.get(host, 'login')
	elif config.has_option('DEFAULT', 'login'):
	  login = config.get('DEFAULT', 'login')
	else:
	  print 'Neither host "%s" not default section defines login'%(host)
	  sys.exit(1)
        if debug: print "D: Login to use: %s" % login

        # Messy, yes. But it isn't referenced by the upload method anyway.
        if config.get(host, 'method') == 'local':
            fqdn = 'localhost'
        else:
            fqdn = config.get(host, 'fqdn')
        incoming = config.get(host, 'incoming')
        # Do the actual upload
        if not simulate:
            if debug:
                print "D: FQDN: %s" % fqdn
                print "D: Login: %s" % login
                print "D: Incoming: %s" % incoming
            if method == 'ftp':
                ftp_mode = config.getboolean(host, 'passive_ftp')
                if ftp_passive_mode == 1: ftp_mode = 1
                if ftp_mode == 1:
                    if debug: 
		    	if ftp_passive_mode == 1:
                        	print "D: Using passive ftp"
                    	else:
                        	print "D: Using active ftp"
                upload_methods[method](fqdn, login, incoming, \
                    files_to_upload, debug, ftp_mode)
            elif method == 'scp':
                if debug and config.getboolean(host, 'scp_compress'):
                    print "D: Setting compression for scp"
                scp_compress = config.getboolean(host, 'scp_compress')
                upload_methods[method](fqdn, login, incoming, \
                    files_to_upload, debug, scp_compress)
            else:
                upload_methods[method](fqdn, login, incoming, \
                    files_to_upload, debug, 0)
        # Or just simulate it.
        else:
            for file in files_to_upload:
                print 'Uploading with %s: %s to %s:%s' % (method, \
                    file, fqdn, incoming)
		os.system("cat %s"%file)

def load_dput(options):
  if options['debug']: print 'D: loading dput'
  d = {}
  execfile("/usr/bin/dput",d)
  return d


def dcut():
  options,arguments = getoptions()
  dput = load_dput(options)
  # dput read_configs sets dput.config
  if options['debug']: print 'D: calling dput.read_configs'
  dput['read_configs'](options['config'], options['debug'])
  config = dput['config']
  if not options['host'] and config.has_option('DEFAULT', 'default_host_main'):
    options['host'] = config.get('DEFAULT', 'default_host_main')
    if options['debug']: print 'D: Using host "%s" (default_host_main)'%(options['host'])
  tempdir = None
  if not (options['filetoupload'] or options['filetocreate']):
    tempdir = tempfile.mkdtemp(prefix=progname+'.')
  if options['filetoupload']:
    if arguments:
      print 'Error: cannot take commands when uploading existing file,'
      print '       "%s" found'%(' '.join(arguments))
      sys.exit(1)
    commands = None
    filename = options['filetoupload']
    if not filename.endswith(".commands"):
      print 'Error: I\'m insisting on the .commands extension, which'
      print '       "%s" doesnt seem to have.'%filename
    # TV-TODO: check file to be readable?
  else:
    commands = parse_queuecommands(arguments,options,config)
    filename = write_commands(commands, options, config, tempdir)
  if not options['filetocreate']:
    if not options['host']:
      print "Error: No host specified and no default found in config"
      sys.exit(1)
    if not config.has_section(options['host']):
      print "No host %s found in config" % (options['host'])
      sys.exit(1)
    # hack to get upload_methods
    myglobals={'dput':dput}
    dput['import_upload_functions']()
    upload_methods = dput['upload_methods']
    upload_stolen_from_dput_main(options['host'],upload_methods, config,options['debug'],options['simulate'],[filename],options['passive'])
  if tempdir:
    # file is temporary iff in tempdir
    os.unlink(filename)
    os.rmdir(tempdir)

if __name__=="__main__":
  dcut()
