# Part of the A-A-P recipe executive: copy and move files (remotely)

# Copyright (C) 2002 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

#
# These are functions to copy and move files.  Not only on the local system,
# also to remote systems, using http://, ftp://, etc.
#

import os
import os.path
import string

from Commands import aap_eval
from Dictlist import string2dictlist
from Error import *
from Process import recipe_error
from Util import *
from Work import getrpstack
from Message import *


def copy_move(line_nr, globals, raw_arg, copy):
    """Implementation of ":copy -x from to" and ":move -x from to".
       When "copy" is non-zero copying is done, otherwise moving.
       "raw_arg" is the whole argument.
       "line_nr" is used for error messages."""

    # Evaluate $VAR things
    arg = aap_eval(line_nr, globals, raw_arg, Expand(0, Expand.quote_aap))
	
    rpstack = getrpstack(globals, line_nr)

    # TODO: check if all flags are handled properly
    # Copy flags:
    # -f    forcefully overwrite
    # -i    interactive, ask the user before overwriting
    # -L    don't keep symbolic links, create new files and directories
    # -p    preserve last modification time and permissions as much as possible
    # -r -R recursive, enter directories
    # -v    verbose
    # Move flags:
    # -f    forcefully overwrite
    # -i    interactive, ask the user before overwriting
    # -v    verbose
    try:
	flags, i = get_flags(arg, 0, (copy and "filLprRv") or "fiv")
    except UserError, e:
	recipe_error(rpstack, e)

    # -l: change symlinks to real files
    if 'L' in flags:
	keepsymlinks = 0
    else:
	keepsymlinks = 1

    # Get the remaining arguments, should be at least two of them.
    arglist = string2dictlist(rpstack, arg[i:])
    if len(arglist) < 2:
	recipe_error(rpstack, _(":%s command requires at least two arguments")
						 % (copy and "copy") or "move")

    from Remote import url_split3
    import glob

    # Expand and check the "from" arguments.
    fromlist = []
    for a in arglist[:-1]:
	fname = a["name"]
	fscheme, fmach, fpath = url_split3(fname)
	if fscheme == '':
	    # It's a local file, expand ~user and wildcards.
	    fl = glob.glob(os.path.expanduser(fname))
	    if len(fl) == 0:
		recipe_error(rpstack, _('No match for "%s"') % fname)
	    # For copy without -r sources can't be a directory.
	    if copy and not ('r' in flags or 'R' in flags):
		for l in fl:
		    if os.path.isdir(l):
			recipe_error(rpstack,
			     _('Copying a directory requires -r flag: %s') % l)
	    fromlist.extend(fl)

	else:
	    # It's a URL, append without expanding.
	    if not copy:
		recipe_error(rpstack, _('Cannot move from a URL yet: "%s"')
								       % fname)
	    fromlist.append(fname)

    # Expand and check the "to" argument.
    tname = arglist[-1]["name"]
    tscheme, tmach, tpath = url_split3(tname)
    if tscheme == '':
	# For a local destination file expand ~user and wildcards.
	l = glob.glob(os.path.expanduser(tname))
	if len(l) > 1:
	    recipe_error(rpstack, _('More than one match for "%s"') % tname)
	if len(l) == 1:
	    tname = l[0]

    # If there is more than one source, target must be a directory.
    if tscheme == '' and len(fromlist) > 1 and not os.path.isdir(tpath):
	recipe_error(rpstack, _('Destination must be a directory: "%s"')
								       % tpath)

    if tscheme == "ftp":
	#
	# Prepare for uploading through ftp.
	#
	import ftplib

	# For "user+passwd@ftp.com" split at the @
	passwd = ''
	acct = ''
	i = string.find(tmach, '@')
	if i > 0:
	    user = tmach[:i]
	    machname = tmach[i+1:]
	    i = string.find(user, '+')
	    if i > 0:
		passwd = user[i+1:]
		user = user[:i]
	    else:
		prompt = (_('Enter password for user %s at %s: ')
							    % (user, machname))
		try:
		    import getpass
		    passwd = getpass.getpass(prompt)
		except:
		    # TODO: should display stars for typed chars
		    passwd = raw_input(prompt)
	else:
	    user = ''
	    machname = tmach

	    import aapnetrc
	    # obtain the login name and password from the netrc file
	    try:
		n = aapnetrc.netrc()
		res = n.authenticators(machname)
		if res is None:
		    user = ''
		else:
		    user, acct, passwd = res
	    except aapnetrc.NetrcParseError, e:
		pass

	# Try to open the connection to the ftp server.
	try:
	    ftp = ftplib.FTP(machname)
	    if user != '':
		if passwd != '':
		    if acct != '':
			ftp.login(user, passwd, acct)
		    else:
			ftp.login(user, passwd)
		else:
		    ftp.login(user)
	    else:
		ftp.login()
	except ftplib.all_errors, e:
	    recipe_error(rpstack, (_('Cannot open connection for "%s"')
							     % tname) + str(e))


    elif tscheme == "scp":
	#
	# TODO: Prepare for uploading through scp.
	#
	pass

    elif tscheme != '':
	recipe_error(rpstack, _('Can only upload to scp:// and ftp://'))


    #
    # Loop over all "from" files.
    #
    for fname in fromlist:
	fscheme, fmach, fpath = url_split3(fname)

	# If the destination is a directory, append the source file name to the
	# destination directory.
	if ((tscheme != '' and len(fromlist) > 1)
				  or (tscheme == '' and os.path.isdir(tname))):
	    dest = os.path.join(tname, os.path.basename(fname))
	    destpath = os.path.join(tpath, os.path.basename(fname))
	else:
	    dest = tname
	    destpath = tpath

	# If destination is a local file and "-i" flag used, ask for
	# overwriting.  Use a special string to allow translating the response
	# characters.
	if tscheme == '' and 'i' in flags:
	    if os.path.exists(dest):
		reply = raw_input(_('"%s" exists, overwrite? (y/n) ') % dest)
		if (len(reply) == 0 or not reply[0]
			      in _("yY   up to four chars that mean yes")[:4]):
		    if copy:
			msg_warning(_("file not copied"))
		    else:
			msg_warning(_("file not moved"))
		    continue

	if fscheme == '' and tscheme == '':
	    #
	    # local file copy or move
	    #
	    if not copy:
		try:
		    os.rename(fname, dest)
		    done = 1
		except:
		    done = 0	    # renaming failed, try copying

	    if copy or not done:
		try:
		    import shutil

		    if os.path.isdir(fname):
			# TODO: if -p not used, set timestamps
			shutil.copytree(fname, dest, keepsymlinks)
		    elif not copy or 'p' in flags:
			shutil.copy2(fname, dest)
		    else:
			shutil.copy(fname, dest)
		except IOError, e:
		    recipe_error(rpstack, (_('Cannot copy "%s" to "%s"')
						     % (fname, dest)) + str(e))
		if not copy:
		    os.remove(fname)

	    if copy:
		msg_info(_('Copied "%s" to "%s"') % (fname, dest))
	    else:
		msg_info(_('Moved "%s" to "%s"') % (fname, dest))

	else:
	    if fscheme != '':
		# download to local file
		from Remote import url_download

		try:
		    if tscheme != '':
			tmpfile, rtime = url_download(fname)
		    else:
			tmpfile, rtime = url_download(fname, dest)
		except IOError, e:
		    recipe_error(rpstack, (_('Cannot download "%s" to "%s"')
						    % (fname, dest)) + str(e))

	    if tscheme != '':
		msg_info(_('Attempting upload to "%s"' % dest))
		if fscheme != '':
		    # use temporary file
		    fromfile = tmpfile
		else:
		    fromfile = fpath

		if tscheme == 'ftp':
		    try:
			f = open(fromfile, "r")
			ftp.storbinary("STOR " + destpath, f, 8192)
			f.close()
		    except ftplib.all_errors, e:
			recipe_error(rpstack, (_('Cannot upload "%s" to "%s"')
						     % (fname, dest)) + str(e))
		elif tscheme == 'scp':
		    cmd = 'scp %s %s:%s' % (fromfile, tmach, destpath)
		    try:
			logged_system(cmd)
		    except IOError, e:
			recipe_error(rpstack, (_('Cannot upload "%s" to "%s"')
						     % (fname, dest)) + str(e))
		if fscheme != '':
		    # delete temporary file
		    os.remove(tmpfile)
		    msg_info(_('Uploaded to "%s"' % dest))
		else:
		    if not copy:
			os.remove(fname)
			msg_info(_('Moved "%s" to "%s"' % (fname, dest)))
		    else:
			msg_info(_('Uploaded "%s" to "%s"' % (fname, dest)))



    # End of loop over all "from" files.

    if tscheme == 'ftp':
	ftp.quit()	# close connection to ftp server
    if fscheme != '':
	from Remote import url_cleanup
	url_cleanup(fscheme)



# vim: set sw=4 sts=4 tw=79 fo+=l:
