#!/usr/bin/env python
# rdiffdir -- Extend rdiff functionality to directories
# Version 0.6.23 released January 24, 2014
#
# 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
#
# See http://www.nongnu.org/duplicity for more information.
# Please send mail to me or the mailing list if you find bugs or have
# any suggestions.

import sys, getopt, gzip, os

import gettext
gettext.install('duplicity', codeset='utf8')

from duplicity import diffdir
from duplicity import patchdir
from duplicity import log
from duplicity import globals
from duplicity import selection
from duplicity import path
from duplicity import util

# If set, compress diff and delta files using gzip
gzip_compress = None

# If set, when computing delta, also compute signature and write to
# specified file.
sig_fileobj = None

select_opts = [] # Add selection argument tuples to this
select_files = [] # Will hold file objects when filelist given

def parse_cmdline_options(arglist):
    """Parse argument list"""
    global gzip_compress, select_opts, select_files, sig_fileobj
    def sel_fl(filename):
        """Helper function for including/excluding filelists below"""
        try:
            return open(filename, "r")
        except IOError:
            log.FatalError(_("Error opening file %s") % util.ufn(filename))

    try:
        optlist, args = getopt.getopt(arglist, "v:Vz",
                                      ["gzip-compress", "exclude=", "exclude-device-files",
                                       "exclude-filelist=", "exclude-filelist-stdin",
                                       "exclude-globbing-filelist", "exclude-other-filesystems",
                                       "exclude-regexp=", "include=", "include-filelist=",
                                       "include-filelist-stdin", "include-globbing-filelist",
                                       "include-regexp=", "max-blocksize", "null-separator",
                                       "verbosity=", "write-sig-to="])
    except getopt.error, e:
        command_line_error("Bad command line option: %s" % (str(e),))

    for opt, arg in optlist:
        if opt == "--gzip_compress" or opt == "-z":
            gzip_compress = 1
        elif (opt == "--exclude" or opt == "--exclude-regexp" or
              opt == "--include" or opt == "--include-regexp"):
            select_opts.append((opt, arg))
        elif (opt == "--exclude-device-files" or
              opt == "--exclude-other-filesystems"):
            select_opts.append((opt, None))
        elif (opt == "--exclude-filelist" or opt == "--include-filelist" or
              opt == "--exclude-globbing-filelist" or
              opt == "--include-globbing-filelist"):
            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 == "--include-filelist-stdin":
            select_opts.append(("--include-filelist", "standard input"))
            select_files.append(sys.stdin)
        elif opt == "--max-blocksize":
            globals.max_blocksize = int(arg)
        elif opt == "--null-separator":
            globals.null_separator = 1
        elif opt == "-V":
            print "rdiffdir", str(globals.version)
            sys.exit(0)
        elif opt == "-v" or opt == "--verbosity":
            log.setverbosity(int(arg))
        elif opt == "--write-sig-to" or opt == "--write-signature-to":
            sig_fileobj = get_fileobj(arg, "wb")
        else:
            command_line_error("Unknown option %s" % opt)

    return args

def command_line_error(message):
    """Indicate a command line error and exit"""
    sys.stderr.write("Error: %s\n" % (message,))
    sys.stderr.write("See the rdiffdir manual page for instructions\n")
    sys.exit(1)

def check_does_not_exist(filename):
    """Exit with error message if filename already exists"""
    try:
        os.lstat(filename)
    except OSError:
        pass
    else:
        log.FatalError(_("File %s already exists, will not "
                         "overwrite.") % util.ufn(filename))

def get_action(args):
    """Figure out the main action from the arguments"""
    def require_args(num, upper_bound_too=True):
        if len(args)-1 < num:
            command_line_error("Too few arguments")
        elif upper_bound_too and len(args)-1 > num:
            command_line_error("Too many arguments")

    if not args:
        command_line_error("No arguments found")
    command = args[0]
    if command == "sig" or command == "signature":
        require_args(2)
        command = "sig"
    elif command == "tar":
        require_args(2)
    elif command == "delta":
        require_args(3, False)
    elif command == "patch":
        require_args(2)
    return command, args[1:]

def get_selection(filename):
    """Return selection iter starting at path with arguments applied"""
    global select_opts, select_files
    sel = selection.Select(path.Path(filename))
    sel.ParseArgs(select_opts, select_files)
    return sel.set_iter()

def get_fileobj(filename, mode):
    """Get file object or stdin/stdout from filename"""
    if mode == "r" or mode == "rb":
        if filename == "-":
            fp = sys.stdin
        else:
            fp = open(filename, mode)
    elif mode == "w" or mode == "wb":
        if filename == "-":
            fp = sys.stdout
        else:
            check_does_not_exist(filename)
            fp = open(filename, mode)
    else:
        assert 0, "Unknown mode " + str(mode)

    if gzip_compress:
        return gzip.GzipFile(None, fp.mode, 9, fp)
    else:
        return fp

def write_sig(dirname, outfp):
    """Write signature of dirname into file object outfp"""
    diffdir.write_block_iter(diffdir.DirSig(get_selection(dirname)), outfp)

def write_delta(dirname, sig_infp, outfp):
    """Write delta to fileobj outfp, reading from dirname and sig_infp"""
    delta_iter = diffdir.DirDelta(get_selection(dirname), sig_infp)
    diffdir.write_block_iter(delta_iter, outfp)
    assert not outfp.close()

def write_delta_and_sig(dirname, sig_infp, outfp, sig_outfp):
    """Write delta and also signature of dirname"""
    sel = get_selection(dirname)
    delta_iter = diffdir.DirDelta_WriteSig(sel, sig_infp, sig_outfp)
    diffdir.write_block_iter(delta_iter, outfp)
    assert not sig_outfp.close()

def patch(dirname, deltafp):
    """Patch dirname, reading delta tar from deltafp"""
    patchdir.Patch(path.Path(dirname), deltafp)

def write_tar(dirname, outfp):
    """Store dirname into a tarfile, write to outfp"""
    diffdir.write_block_iter(diffdir.DirFull(get_selection(dirname)), outfp)

def write_tar_and_sig(dirname, outfp, sig_outfp):
    """Write tar of dirname to outfp, signature of same to sig_outfp"""
    full_iter = diffdir.DirFull_WriteSig(get_selection(dirname), sig_outfp)
    diffdir.write_block_iter(full_iter, outfp)


def main():
    """Start here"""
    log.setup()
    args = parse_cmdline_options(sys.argv[1:])
    action, file_args = get_action(args)
    if action == "sig":
        write_sig(file_args[0], get_fileobj(file_args[1], "wb"))
    elif action == "delta":
        sig_infp = map(lambda fname: get_fileobj(fname, "rb"), file_args[0:-2])
        delta_outfp = get_fileobj(file_args[-1], "wb")
        if sig_fileobj:
            write_delta_and_sig(file_args[-2], sig_infp,
                                delta_outfp, sig_fileobj)
        else:
            write_delta(file_args[-2], sig_infp, delta_outfp)
    elif action == "patch":
        patch(file_args[0], get_fileobj(file_args[1], "rb"))
    elif action == "tar":
        if sig_fileobj:
            write_tar_and_sig(file_args[0],
                              get_fileobj(file_args[1], "wb"),
                              sig_fileobj)
        else:
            write_tar(file_args[0], get_fileobj(file_args[1], "wb"))
    else:
        command_line_error("Bad command " + action)


if __name__ == "__main__":
    main()
