#!/usr/bin/env python
# rdiffdir -- Extend rdiff functionality to directories
# Version 0.5.06 released June 30, 2002
#
# Copyright (C) 2002 Ben Escoto <bescoto@stanford.edu>
#
# 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 3 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
from duplicity import diffdir, patchdir, log, tarfile, globals, selection, path

import gettext
gettext.install('duplicity')

# 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" % 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=", "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 == "--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." % filename)

def get_action(args):
    """Figure out the main action from the arguments"""
    def require_args(num):
        if len(args)-1 < num: command_line_error("Too few arguments")
        elif 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)
    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(filename, sig_infp, delta_outfp, sig_outfp):
    """Write delta and also signature of filename"""
    sel = get_selection(filename)
    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(filename)), 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(filename), 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 = get_fileobj(file_args[0], "rb")
        delta_outfp = get_fileobj(file_args[2], "wb")
        if sig_fileobj: write_delta_and_sig(file_args[1], sig_infp,
                                            delta_outfp, sig_fileobj)
        else: write_delta(file_args[1], 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()
