# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2002 Ben Escoto <ben@emerose.org>
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
#
# This file is part of duplicity.
#
# Duplicity 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.
#
# Duplicity 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 duplicity; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Parse command line, check for consistency, and set globals"""

import getopt
import os
import re
import sys

try:
    from hashlib import md5
except ImportError:
    from md5 import new as md5

import duplicity.backends

from duplicity import backend
from duplicity import dup_time
from duplicity import globals
from duplicity import gpg
from duplicity import log
from duplicity import path
from duplicity import selection


select_opts = []            # Will hold all the selection options
select_files = []           # Will hold file objects when filelist given

full_backup = None          # Will be set to true if full command given
list_current = None         # Will be set to true if list-current command given
collection_status = None    # Will be set to true if collection-status command given
cleanup = None              # Set to true if cleanup command given
verify = None               # Set to true if verify command given

commands = ["cleanup",
            "collection-status",
            "full",
            "incremental",
            "list-current-files",
            "remove-older-than",
            "remove-all-but-n-full",
            "restore",
            "verify",
            ]

options = ["allow-source-mismatch",
           "archive-dir=",
           "asynchronous-upload",
           "current-time=",
           "dry-run",
           "encrypt-key=",
           "exclude=",
           "exclude-if-present=",
           "exclude-device-files",
           "exclude-filelist=",
           "exclude-globbing-filelist=",
           "exclude-filelist-stdin",
           "exclude-other-filesystems",
           "exclude-regexp=",
           "extra-clean",
           "fail-on-volume=",
           "file-to-restore=",
           "force",
           "ftp-passive",
           "ftp-regular",
           "full-if-older-than=",
           "gio",
           "gpg-options=",
           "help",
           "ignore-errors",
           "imap-full-address",
           "imap-mailbox=",
           "include=",
           "include-filelist=",
           "include-filelist-stdin",
           "include-globbing-filelist=",
           "include-regexp=",
           "log-fd=",
           "log-file=",
           "name=",
           "no-encryption",
           "no-print-statistics",
           "null-separator",
           "num-retries=",
           "old-filenames",
           "restore-time=",
           "s3-european-buckets",
           "s3-use-new-style",
           "scp-command=",
           "sftp-command=",
           "short-filenames",
           "sign-key=",
           "ssh-askpass",
           "ssh-options=",
           "tempdir=",
           "time=",
           "timeout=",
           "time-separator=",
           "use-agent",
           "verbosity=",
           "version",
           "volsize=",
           ]


def old_fn_deprecation(opt):
    print >>sys.stderr, _("Warning: Option %s is pending deprecation "
                          "and will be removed in a future release.\n"
                          "Use of default filenames is strongly suggested.") % opt


def expand_fn(filename):
    return os.path.expanduser(os.path.expandvars(filename))


def expand_archive_dir(archdir, backname):
    """
    Return expanded version of archdir joined with backname.
    """
    assert globals.backup_name is not None, \
        "expand_archive_dir() called prior to globals.backup_name being set"

    return expand_fn(os.path.join(archdir, backname))


def generate_default_backup_name(backend_url):
    """
    @param backend_url: URL to backend.
    @returns A default backup name (string).
    """
    # For default, we hash args to obtain a reasonably safe default.
    # We could be smarter and resolve things like relative paths, but
    # this should actually be a pretty good compromise. Normally only
    # the destination will matter since you typically only restart
    # backups of the same thing to a given destination. The inclusion
    # of the source however, does protect against most changes of
    # source directory (for whatever reason, such as
    # /path/to/different/snapshot). If the user happens to have a case
    # where relative paths are used yet the relative path is the same
    # (but duplicity is run from a different directory or similar),
    # then it is simply up to the user to set --archive-dir properly.
    burlhash = md5()
    burlhash.update(backend_url)
    return burlhash.hexdigest()


def parse_cmdline_options(arglist):
    """Parse argument list"""
    global select_opts, select_files, full_backup
    global list_current, collection_status, cleanup, remove_time, verify


    def sel_fl(filename):
        """Helper function for including/excluding filelists below"""
        try:
            return open(filename, "r")
        except IOError:
            log.FatalError(_("Error opening file %s") % filename,
                           log.ErrorCode.cant_open_filelist)

    # expect no cmd and two positional args
    cmd = ""
    num_expect = 2

    # process first arg as command
    if arglist and arglist[0][0] != '-':
        cmd = arglist.pop(0)
        possible = [c for c in commands if c.startswith(cmd)]
        # no unique match, that's an error
        if len(possible) > 1:
            command_line_error("command '%s' not unique, could be %s" % (cmd, possible))
        # only one match, that's a keeper
        elif len(possible) == 1:
            cmd = possible[0]
        # no matches, assume no cmd
        elif not possible:
            arglist.insert(0, cmd)

    if cmd == "cleanup":
        cleanup = True
        num_expect = 1
    elif cmd == "collection-status":
        collection_status = True
        num_expect = 1
    elif cmd == "full":
        full_backup = True
        num_expect = 2
    elif cmd == "incremental":
        globals.incremental = True
        num_expect = 2
    elif cmd == "list-current-files":
        list_current = True
        num_expect = 1
    elif cmd == "remove-older-than":
        try:
            arg = arglist.pop(0)
        except:
            command_line_error("Missing time string for remove-older-than")
        globals.remove_time = dup_time.genstrtotime(arg)
        num_expect = 1
    elif cmd == "remove-all-but-n-full":
        try:
            arg = arglist.pop(0)
        except:
            command_line_error("Missing count for remove-all-but-n-full")
        globals.keep_chains = int(arg)
        if not globals.keep_chains > 0:
            command_line_error("remove-all-but-n-full count must be > 0")
        num_expect = 1
    elif cmd == "verify":
        verify = True

    # parse the remaining args
    try:
        optlist, args = getopt.gnu_getopt(arglist, "hrt:v:V", options)
    except getopt.error, e:
        command_line_error("%s" % (str(e),))

    for opt, arg in optlist:
        if opt == "--allow-source-mismatch":
            globals.allow_source_mismatch = 1
        elif opt == "--archive-dir":
            globals.archive_dir = arg
        elif opt == "--asynchronous-upload":
            globals.async_concurrency = 1 # (yes 1, this is not a boolean)
        elif opt == "--current-time":
            dup_time.setcurtime(get_int(arg, opt))
        elif opt == "--dry-run":
            globals.dry_run = True
        elif opt == "--encrypt-key":
            globals.gpg_profile.recipients.append(arg)
        elif opt in ["--exclude",
                     "--exclude-regexp",
                     "--exclude-if-present",
                     "--include",
                     "--include-regexp"]:
            if not opt.endswith("regexp"):
                arg = expand_fn(arg)
            select_opts.append((opt, arg))
        elif opt in ["--exclude-device-files",
                     "--exclude-other-filesystems"]:
            select_opts.append((opt, None))
        elif opt in ["--exclude-filelist",
                     "--include-filelist",
                     "--exclude-globbing-filelist",
                     "--include-globbing-filelist"]:
            arg = expand_fn(arg)
            select_opts.append((opt, arg))
            select_files.append(sel_fl(arg))
        elif opt == "--exclude-filelist-stdin":
            select_opts.append(("--exclude-filelist", "standard input"))
            select_files.append(sys.stdin)
        elif opt == "--extra-clean":
            globals.extra_clean = True
        elif opt == "--fail-on-volume":
            globals.fail_on_volume = get_int(arg, opt)
        elif opt == "--full-if-older-than":
            globals.full_force_time = dup_time.genstrtotime(arg)
        elif opt == "--force":
            globals.force = 1
        elif opt == "--ftp-passive":
            globals.ftp_connection = 'passive'
        elif opt == "--ftp-regular":
            globals.ftp_connection = 'regular'
        elif opt == "--imap-mailbox":
            globals.imap_mailbox = arg.strip()
        elif opt == "--gio":
            try:
                import duplicity.backends.giobackend
                backend.force_backend(duplicity.backends.giobackend.GIOBackend)
            except ImportError:
                log.FatalError(_("Unable to load gio module"), log.ErrorCode.gio_not_available)
        elif opt == "--gpg-options":
            gpg.gpg_options = (gpg.gpg_options + ' ' + arg).strip()
        elif opt in ["-h", "--help"]:
            usage();
            sys.exit(0);
        elif opt == "--include-filelist-stdin":
            select_opts.append(("--include-filelist", "standard input"))
            select_files.append(sys.stdin)
        elif opt == "--log-fd":
            log_fd = get_int(arg, opt)
            if log_fd < 1:
                command_line_error("log-fd must be greater than zero.")
            try:
                log.add_fd(log_fd)
            except:
                command_line_error("Cannot write to log-fd %s." % arg)
        elif opt == "--log-file":
            arg = expand_fn(arg)
            try:
                log.add_file(arg)
            except:
                command_line_error("Cannot write to log-file %s." % arg)
        elif opt == "--name":
            globals.backup_name = arg
        elif opt == "--no-encryption":
            globals.encryption = 0
        elif opt == "--no-print-statistics":
            globals.print_statistics = 0
        elif opt == "--null-separator":
            globals.null_separator = 1
        elif opt == "--num-retries":
            globals.num_retries = get_int(arg, opt)
        elif opt == "--old-filenames":
            globals.old_filenames = True
            old_fn_deprecation(opt)
        elif opt in ["-r", "--file-to-restore"]:
            globals.restore_dir = expand_fn(arg.rstrip('/'))
        elif opt in ["-t", "--time", "--restore-time"]:
            globals.restore_time = dup_time.genstrtotime(arg)
        elif opt == "--s3-european-buckets":
            globals.s3_european_buckets = True
        elif opt == "--s3-use-new-style":
            globals.s3_use_new_style = True
        elif opt == "--scp-command":
            globals.scp_command = arg
        elif opt == "--sftp-command":
            globals.sftp_command = arg
        elif opt == "--short-filenames":
            globals.short_filenames = 1
            old_fn_deprecation(opt)
        elif opt == "--sign-key":
            set_sign_key(arg)
        elif opt == "--ssh-askpass":
            globals.ssh_askpass = True
        elif opt == "--ssh-options":
            globals.ssh_options = (globals.ssh_options + ' ' + arg).strip()
        elif opt == "--tempdir":
            globals.temproot = arg
        elif opt == "--timeout":
            globals.timeout = get_int(arg, opt)
        elif opt == "--time-separator":
            if arg == '-':
                command_line_error("Dash ('-') not valid for time-separator.")
            globals.time_separator = arg
            dup_time.curtimestr = dup_time.timetostring(dup_time.curtime)
            old_fn_deprecation(opt)
        elif opt == "--use-agent":
            globals.use_agent = True
        elif opt in ["-V", "--version"]:
            print "duplicity", str(globals.version)
            sys.exit(0)
        elif opt in ["-v", "--verbosity"]:
            arg = arg.lower()
            if arg in ['e', 'error']:
                verb = log.ERROR
            elif arg in ['w', 'warning']:
                verb = log.WARNING
            elif arg in ['n', 'notice']:
                verb = log.NOTICE
            elif arg in ['i', 'info']:
                verb = log.INFO
            elif arg in ['d', 'debug']:
                verb = log.DEBUG
            elif arg.isdigit() and (len(arg) == 1):
                verb = get_int(arg, opt)
            else:
                command_line_error("\nVerbosity must be one of: digit [0-9], character [ewnid],\n"
                                   "or word ['error', 'warning', 'notice', 'info', 'debug'].\n"
                                   "The default is 4 (Notice).  It is strongly recommended\n"
                                   "that verbosity level is set at 2 (Warning) or higher.")
            log.setverbosity(verb)
        elif opt == "--volsize":
            globals.volsize = get_int(arg, opt)*1024*1024
        elif opt == "--ignore-errors":
            log.Warn(_("running in 'ignore errors' mode due to --ignore-errors; please "
                       "re-consider if this was not intended"))
            globals.ignore_errors = True
        elif opt == "--imap-full-address":
            globals.imap_full_address = True
        else:
            command_line_error("Unknown option %s" % opt)

    # if we change the time format then we need a new curtime
    if globals.old_filenames:
        dup_time.curtimestr = dup_time.timetostring(dup_time.curtime)

    if len(args) != num_expect:
        command_line_error("Expected %d args, got %d" % (num_expect, len(args)))

    # expand pathname args, but not URL
    for loc in range(len(args)):
        if not '://' in args[loc]:
            args[loc] = expand_fn(args[loc])

    # Note that ProcessCommandLine depends on us verifying the arg
    # count here; do not remove without fixing it. We must make the
    # checks here in order to make enough sense of args to identify
    # the backend URL/lpath for args_to_path_backend().
    if len(args) < 1:
        command_line_error("Too few arguments")
    elif len(args) == 1:
        backend_url = args[0]
    elif len(args) == 2:
        lpath, backend_url = args_to_path_backend(args[0], args[1])
    else:
        command_line_error("Too many arguments")

    if globals.backup_name is None:
        globals.backup_name = generate_default_backup_name(backend_url)

    # set and expand archive dir
    set_archive_dir(expand_archive_dir(globals.archive_dir,
                                       globals.backup_name))

    log.Info(_("Using archive dir: %s") % (globals.archive_dir.name,))
    log.Info(_("Using backup name: %s") % (globals.backup_name,))

    return args


def command_line_error(message):
    """Indicate a command line error and exit"""
    log.FatalError(_("Command line error: %s") % (message,) + "\n" +
                   _("Enter 'duplicity --help' for help screen."),
                   log.ErrorCode.command_line)


def usage():
    """Print terse usage info. The code is broken down into pieces for ease of
    translation maintenance. Any comments that look extraneous or redundant should
    be assumed to be for the benefit of translators, since they can get each string
    (paired with its preceding comment, if any) independently of the others."""

    dict = {
        # TRANSL: Used in usage help to represent a Unix-style path name. Example:
        # rsync://user[:password]@other_host[:port]//absolute_path
        'absolute_path'  : _("absolute_path"),

        # TRANSL: Used in usage help. Example:
        # tahoe://alias/some_dir
        'alias'          : _("alias"),

        # TRANSL: Used in usage help (noun)
        'backup_name'    : _("backup name"),

        # TRANSL: Used in help to represent a "bucket name" for Amazon Web
        # Services' Simple Storage Service (S3). Example:
        # s3://other.host/bucket_name[/prefix]
        'bucket_name'    : _("bucket_name"),

        # TRANSL: abbreviation for "character" (noun)
        'char'           : _("char"),

        # TRANSL: noun
        'command'        : _("command"),
        
        # TRANSL: Used in usage help to represent the name of a container in
        # Amazon Web Services' Cloudfront. Example:
        # cf+http://container_name
        'container_name' : _("container_name"),

        # TRANSL: noun
        'count'          : _("count"),
        
        # TRANSL: Used in usage help to represent the name of a file directory
        'directory'      : _("directory"),

        # TRANSL: Used in usage help to represent the name of a file. Example:
        # --log-file <filename>
        'filename'       : _("filename"),
        
        # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example:
        # --encrypt-key <gpg_key_id>
        'gpg_key_id'     : _("gpg-key-id"),

        # TRANSL: Used in usage help, e.g. to represent the name of a code
        # module. Example:
        # rsync://user[:password]@other.host[:port]::/module/some_dir
        'module'         : _("module"),

        # TRANSL: Used in usage help to represent a desired number of
        # something. Example:
        # --num-retries <number>
        'number'         : _("number"),

        # TRANSL: Used in usage help. (Should be consistent with the "Options:"
        # header.) Example:
        # duplicity [full|incremental] [options] source_dir target_url
        'options'        : _("options"),

        # TRANSL: Used in usage help to represent an internet hostname. Example:
        # ftp://user[:password]@other.host[:port]/some_dir
        'other_host'     : _("other.host"),

        # TRANSL: Used in usage help. Example:
        # ftp://user[:password]@other.host[:port]/some_dir
        'password'       : _("password"),

        # TRANSL: Used in usage help to represent a Unix-style path name. Example:
        # --archive-dir <path>
        'path'           : _("path"),

        # TRANSL: Used in usage help to represent a TCP port number. Example:
        # ftp://user[:password]@other.host[:port]/some_dir
        'port'           : _("port"),

        # TRANSL: Used in usage help. This represents a string to be used as a
        # prefix to names for backup files created by Duplicity. Example:
        # s3://other.host/bucket_name[/prefix]
        'prefix'         : _("prefix"),

        # TRANSL: Used in usage help to represent a Unix-style path name. Example:
        # rsync://user[:password]@other.host[:port]/relative_path
        'relative_path'  : _("relative_path"),

        # TRANSL: Used in usage help. Example:
        # --timeout <seconds>
        'seconds'        : _("seconds"),

        # TRANSL: Used in usage help to represent a "glob" style pattern for
        # matching one or more files, as described in the documentation.
        # Example:
        # --exclude <shell_pattern>
        'shell_pattern'  : _("shell_pattern"),

        # TRANSL: Used in usage help to represent the name of a single file
        # directory or a Unix-style path to a directory. Example:
        # file:///some_dir
        'some_dir'       : _("some_dir"),

        # TRANSL: Used in usage help to represent the name of a single file
        # directory or a Unix-style path to a directory where files will be
        # coming FROM. Example:
        # duplicity [full|incremental] [options] source_dir target_url
        'source_dir'     : _("source_dir"),

        # TRANSL: Used in usage help to represent a URL files will be coming
        # FROM. Example:
        # duplicity [restore] [options] source_url target_dir
        'source_url'     : _("source_url"),

        # TRANSL: Used in usage help to represent the name of a single file
        # directory or a Unix-style path to a directory. where files will be
        # going TO. Example:
        # duplicity [restore] [options] source_url target_dir
        'target_dir'     : _("target_dir"),

        # TRANSL: Used in usage help to represent a URL files will be going TO.
        # Example:
        # duplicity [full|incremental] [options] source_dir target_url
        'target_url'     : _("target_url"),
        
        # TRANSL: Used in usage help to represent a time spec for a previous
        # point in time, as described in the documentation. Example:
        # duplicity remove-older-than time [options] target_url
        'time'           : _("time"),
        
        # TRANSL: Used in usage help to represent a user name (i.e. login).
        # Example:
        # ftp://user[:password]@other.host[:port]/some_dir
        'user'           : _("user") }

    msg = _("duplicity version %s running on %s.") % (globals.version, sys.platform)
    msg = "\n" + msg + "\n"

    # TRANSL: Header in usage help
    msg = msg + _("Usage:") + """
    duplicity [full|incremental] [%(options)s] %(source_dir)s %(target_url)s
    duplicity [restore] [%(options)s] %(source_url)s %(target_dir)s
    duplicity verify [%(options)s] %(source_url)s %(target_dir)s
    duplicity collection-status [%(options)s] %(target_url)s
    duplicity list-current-files [%(options)s] %(target_url)s
    duplicity cleanup [%(options)s] %(target_url)s
    duplicity remove-older-than %(time)s [%(options)s] %(target_url)s
    duplicity remove-all-but-n-full %(count)s [%(options)s] %(target_url)s

""" % dict

    # TRANSL: Header in usage help
    msg = msg + _("Backends and their URL formats:") + """
    cf+http://%(container_name)s
    file:///%(some_dir)s
    ftp://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
    hsi://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
    imap://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
    rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]::/%(module)s/%(some_dir)s
    rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(relative_path)s
    rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]//%(absolute_path)s
    s3://%(other_host)s/%(bucket_name)s[/%(prefix)s]
    s3+http://%(bucket_name)s[/%(prefix)s]
    scp://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
    ssh://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
    tahoe://%(alias)s/%(directory)s
    webdav://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
    webdavs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s

""" % dict

    # TRANSL: Header in usage help
    msg = msg + _("Commands:") + """
    cleanup <%(target_url)s>
    collection-status <%(target_url)s>
    full <%(source_dir)s> <%(target_url)s>
    incr <%(source_dir)s> <%(target_url)s>
    list-current-files <%(target_url)s>
    restore <%(target_url)s> <%(source_dir)s>
    remove-older-than <%(time)s> <%(target_url)s>
    remove-all-but-n-full <%(count)s> <%(target_url)s>
    verify <%(target_url)s> <%(source_dir)s>

""" % dict

    # TRANSL: Header in usage help
    msg = msg + _("Options:") + """
    --allow-source-mismatch
    --archive-dir <%(path)s>
    --asynchronous-upload
    --dry-run
    --encrypt-key <%(gpg_key_id)s>
    --exclude <%(shell_pattern)s>
    --exclude-device-files
    --exclude-filelist <%(filename)s>
    --exclude-filelist-stdin
    --exclude-globbing-filelist <%(filename)s>
    --exclude-other-filesystems
    --exclude-regexp <regexp>
    --extra-clean
    --file-to-restore <%(path)s>
    --full-if-older-than <%(time)s>
    --force
    --ftp-passive
    --ftp-regular
    --gio
    --gpg-options
    --include <%(shell_pattern)s>
    --include-filelist <%(filename)s>
    --include-filelist-stdin
    --include-globbing-filelist <%(filename)s>
    --include-regexp <regexp>
    --log-fd <fd>
    --log-file <%(filename)s>
    --name <%(backup_name)s>
    --no-encryption
    --no-print-statistics
    --null-separator
    --num-retries <%(number)s>
    --old-filenames
    --s3-european-buckets
    --s3-use-new-style
    --scp-command <%(command)s>
    --sftp-command <%(command)s>
    --sign-key <%(gpg_key_id)s>
    --ssh-askpass
    --ssh-options
    --short-filenames
    --tempdir <%(directory)s>
    --timeout <%(seconds)s>
    -t<%(time)s>, --time <%(time)s>, --restore-time <%(time)s>
    --time-separator <%(char)s>
    --use-agent
    --version
    --volsize <%(number)s>
    -v[0-9], --verbosity [0-9]
""" % dict

    # TRANSL: In this portion of the usage instructions, "[ewnid]" indicates which
    # characters are permitted (e, w, n, i, or d); the brackets imply their own
    # meaning in regex; i.e., only one of the characters is allowed in an instance.
    msg = msg + _("""    Verbosity must be one of: digit [0-9], character [ewnid],
    or word ['error', 'warning', 'notice', 'info', 'debug'].
    The default is 4 (Notice).  It is strongly recommended
    that verbosity level is set at 2 (Warning) or higher.
""")

    sys.stdout.write(msg)


def get_int(int_string, description):
    """Require that int_string be an integer, return int value"""
    try:
        return int(int_string)
    except ValueError:
        command_line_error("Received '%s' for %s, need integer" %
                                          (int_string, description.lstrip('-')))

def set_archive_dir(dirstring):
    """Check archive dir and set global"""
    if not os.path.exists(dirstring):
        try:
            os.makedirs(dirstring)
        except:
            pass
    archive_dir = path.Path(dirstring)
    if not archive_dir.isdir():
        log.FatalError(_("Specified archive directory '%s' does not exist, "
                         "or is not a directory") % (archive_dir.name,),
                       log.ErrorCode.bad_archive_dir)
    globals.archive_dir = archive_dir


def set_sign_key(sign_key):
    """Set globals.sign_key assuming proper key given"""
    if not len(sign_key) == 8 or not re.search("^[0-9A-F]*$", sign_key):
        log.FatalError(_("Sign key should be an 8 character hex string, like "
                         "'AA0E73D2'.\nReceived '%s' instead.") % (sign_key,),
                       log.ErrorCode.bad_sign_key)
    globals.gpg_profile.sign_key = sign_key


def set_selection():
    """Return selection iter starting at filename with arguments applied"""
    global select_opts, select_files
    sel = selection.Select(globals.local_path)
    sel.ParseArgs(select_opts, select_files)
    globals.select = sel.set_iter()

def args_to_path_backend(arg1, arg2):
    """
    Given exactly two arguments, arg1 and arg2, figure out which one
    is the backend URL and which one is a local path, and return
    (local, backend).
    """
    arg1_is_backend, arg2_is_backend = backend.is_backend_url(arg1), backend.is_backend_url(arg2)

    if not arg1_is_backend and not arg2_is_backend:
        command_line_error(
"""One of the arguments must be an URL.  Examples of URL strings are
"scp://user@host.net:1234/path" and "file:///usr/local".  See the man
page for more information.""")
    if arg1_is_backend and arg2_is_backend:
        command_line_error("Two URLs specified.  "
                           "One argument should be a path.")
    if arg1_is_backend:
        return (arg2, arg1)
    elif arg2_is_backend:
        return (arg1, arg2)
    else:
        raise AssertionError('should not be reached')

def set_backend(arg1, arg2):
    """Figure out which arg is url, set backend

    Return value is pair (path_first, path) where is_first is true iff
    path made from arg1.

    """
    path, bend = args_to_path_backend(arg1, arg2)

    globals.backend = backend.get_backend(bend)

    if path == arg2:
        return (None, arg2) # False?
    else:
        return (1, arg1) # True?


def process_local_dir(action, local_pathname):
    """Check local directory, set globals.local_path"""
    local_path = path.Path(path.Path(local_pathname).get_canonical())
    if action == "restore":
        if (local_path.exists() and not local_path.isemptydir()) and not globals.force:
            log.FatalError(_("Restore destination directory %s already "
                             "exists.\nWill not overwrite.") % (local_pathname,),
                           log.ErrorCode.restore_dir_exists)
    elif action == "verify":
        if not local_path.exists():
            log.FatalError(_("Verify directory %s does not exist") %
                           (local_path.name,),
                           log.ErrorCode.verify_dir_doesnt_exist)
    else:
        assert action == "full" or action == "inc"
        if not local_path.exists():
            log.FatalError(_("Backup source directory %s does not exist.")
                           % (local_path.name,),
                           log.ErrorCode.backup_dir_doesnt_exist)

    globals.local_path = local_path


def check_consistency(action):
    """Final consistency check, see if something wrong with command line"""
    global full_backup, select_opts, list_current
    def assert_only_one(arglist):
        """Raises error if two or more of the elements of arglist are true"""
        n = 0
        for m in arglist:
            if m:
                n+=1
        assert n <= 1, "Invalid syntax, two conflicting modes specified"
    if action in ["list-current", "collection-status",
                  "cleanup", "remove-old", "remove-all-but-n-full"]:
        assert_only_one([list_current, collection_status, cleanup,
                         globals.remove_time is not None])
    elif action == "restore" or action == "verify":
        if full_backup:
            command_line_error("--full option cannot be used when "
                               "restoring or verifying")
        elif globals.incremental:
            command_line_error("--incremental option cannot be used when "
                               "restoring or verifying")
        if select_opts and action == "restore":
            command_line_error("Selection options --exclude/--include\n"
                               "currently work only when backing up, "
                               "not restoring.")
    else:
        assert action == "inc" or action == "full"
        if verify:
            command_line_error("--verify option cannot be used "
                                      "when backing up")
        if globals.restore_dir:
            command_line_error("restore option incompatible with %s backup"
                               % (action,))


def ProcessCommandLine(cmdline_list):
    """Process command line, set globals, return action

    action will be "list-current", "collection-status", "cleanup",
    "remove-old", "restore", "verify", "full", or "inc".

    """
    globals.gpg_profile = gpg.GPGProfile()

    args = parse_cmdline_options(cmdline_list)

    # we can now try to import all the backends
    duplicity.backend.import_backends()

    # parse_cmdline_options already verified that we got exactly 1 or 2
    # non-options arguments
    assert len(args) >= 1 and len(args) <= 2, "arg count should have been checked already"

    if len(args) == 1:
        if list_current:
            action = "list-current"
        elif collection_status:
            action = "collection-status"
        elif cleanup:
            action = "cleanup"
        elif globals.remove_time is not None:
            action = "remove-old"
        elif globals.keep_chains is not None:
            action = "remove-all-but-n-full"
        else:
            command_line_error("Too few arguments")
        globals.backend = backend.get_backend(args[0])
        if not globals.backend:
            log.FatalError(_("""Bad URL '%s'.
Examples of URL strings are "scp://user@host.net:1234/path" and
"file:///usr/local".  See the man page for more information.""") % (args[0],),
                                               log.ErrorCode.bad_url)
    elif len(args) == 2:
        # Figure out whether backup or restore
        backup, local_pathname = set_backend(args[0], args[1])
        if backup:
            if full_backup:
                action = "full"
            else:
                action = "inc"
        else:
            if verify:
                action = "verify"
            else:
                action = "restore"

        process_local_dir(action, local_pathname)
        if action in ['full', 'inc', 'verify']:
            set_selection()
    elif len(args) > 2:
        raise AssertionError("this code should not be reachable")

    check_consistency(action)
    log.Info(_("Main action: ") + action)
    return action
