#!/usr/bin/env python
# -*- Mode: Python; tab-width: 4 -*-
#
# Zope Dumper - A Zope Exporting tool
#
# Copyright (C) 2005 Gianluigi Tiesi <sherpya@netfarm.it>
# Copyright (C) 2005 NetFarm S.r.l.  [http://www.netfarm.it]
#
# 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, 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 MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
# ======================================================================
## @file zopedumper.py
## Zope Export Tool

__doc__ = '''Zope Dumper - A Zope Exporting tool'''
__version__ = '1.1'

from sys import exit as sys_exit
from sys import argv, exc_info, stdout
from os import makedirs
from os.path import join as path_join
from davlib import DAV
from base64 import encodestring
from xml.parsers.expat import ParserCreate
from getopt import getopt, GetoptError
from urlparse import urlparse

BLACKLIST = [ '/', '/Control_Panel/', '/temp_folder/' ]
EXPORTURL = '/manage_exportObject?download:int=1'
TOXML     = '&toxml=Y'
BLOCKSIZE = 4096

in_href  = 0
href = None
folders = []

def usage():
    print '''usage: %s [options] base-url out-dir
        options:
        -h, --help      Get usage help
        -v, --verbose   Display progress when downloading
        -t, --timeout   Set socket timeout, Python >= 2.3 only
        -s, --silent    Only prints errors
        -n, --dry-run   Dry run (no actions)
        -x, --xml       Export using xml
        -u, --username  Basic auth username
        -p, --password  Basic auth password''' % argv[0]

def start_element(name, unused):
    global in_href, href, folders
    if name == 'd:href': in_href = 1
    if name == 'n:collection' and href not in BLACKLIST: folders.append(href)

def end_element(name):
    global in_href
    if name == 'd:href': in_href = 0

def char_data(data):
    global in_href, href
    if in_href: href = data

def zope_export(dav, folder, outdir):
    global format, verbose, silent, dry_run
    filename = path_join(outdir, (folder.replace('/','') + '.' + format))
    uri = dav.baseurl + folder[1:]
    
    if not silent: print 'Exporting %s to %s' % (uri, filename)
    if dry_run: return 0

    url = EXPORTURL
    if format == 'xml':
        url = url + TOXML

    try:
        resp = dav.get(folder + url, dav.headers)
    except:
        t, val, tb = exc_info()
        del tb
        print 'Error in HTTP connection:', str(val)
        return 1
        
    if (resp.status != 200) or (resp.length == 0):
        print 'Error Dumping %s: %d %s' % \
              (uri, resp.status, resp.msg.dict.get('bobo-exception-type', 'Unknown Error'))
        resp.close()
        return 1

    if verbose:
        stdout.write('Downloading file:   0%')
        stdout.flush()

    total = resp.length
    mult = 100.0 / total

    ### 4k buffered socket to file writer
    fd = open(filename, 'wb')
    data = ''
    last = 0
    while 1:
        data = resp.read(BLOCKSIZE)
        if len(data) < 1: break
        fd.write(data)

        if verbose:
            perc = int(100.0 - (resp.length * mult))
            if perc != last:
                last = perc
                stdout.write('\b\b\b\b%3d%%' % perc)
                stdout.flush()
    fd.close()
    resp.close()
    if verbose:
        stdout.write('\n')
        stdout.flush()

    if resp.length:
        print 'Error the file is incomplete'
    return resp.length
    
if __name__ == '__main__':
    global format, verbose, dry_run
    try:
        opts, args = getopt(argv[1:],
                            't:u:p:xnhsv',
                            ['timeout=',
                             'username=',
                             'password=',
                             'xml',
                             'dry-run',
                             'verbose',
                             'silent',
                             'help'])
    except GetoptError:
        usage()
        sys_exit(-1)
    try:
        baseurl = args[0]
        outdir = args[1]
    except:
        usage()
        sys_exit(-1)

    username = None
    password = None
    verbose  = 0
    silent   = 0
    dry_run  = 0
    format   = 'zexp'
    headers  = { 'User-Agent' : 'Zope Backup Agent v%s' % __version__ }
    timeout  = 0
    
    for opt, args in opts:
        if opt in ('-u', '--username'):
            username = args
        elif opt in ('-p', '--password'):
            password = args
        elif opt in ('-t', '--timeout'):
            try:
                timeout = int(args) # Yes I kwnon settimeout takes a float
            except: pass
        elif opt in ('-x', '--xml'):
            format = 'xml'
        elif opt in ('-n', '--dry-run'):
            dry_run = 1
        elif opt in ('-v', '--verbose'):
            verbose = 1
        elif opt in ('-s', '--silent'):
            silent = 1
        elif opt in ('-h', '--help'):
            usage()
            sys_exit(0)
 
    if username and password:
        authcookie = encodestring('%s:%s' % (username, password)).strip()
        headers['Authorization'] = 'Basic %s' % authcookie

    if timeout:
        if verbose:
            print 'Setting socket timeout to %d secs' % timeout
        import davlib
        davlib.httplib.socket.setdefaulttimeout(timeout)

    machine = urlparse(baseurl)
    dav = DAV(machine[1])
    dav.headers = headers
    dav.baseurl = baseurl

    try:
        resp = dav.propfind(machine[2], depth=1, extra_hdrs=dav.headers)
    except:
        t, val, tb = exc_info()
        del tb
        print 'Error in HTTP connection:', str(val)
        sys_exit(-1)
        
    if resp.status != 207:
        print 'Error in WebDav PROPFIND call:', \
              resp.status, \
              resp.msg.dict.get('bobo-exception-type', 'Unknown Error')
        sys_exit(-1)

    p = ParserCreate()

    p.StartElementHandler = start_element
    p.EndElementHandler = end_element
    p.CharacterDataHandler = char_data

    try:
        p.Parse(resp.read())
    except:
        t, val, tb = exc_info()
        del tb
        print 'Error parsing server reply:', str(val)
        resp.close()
        sys_exit(-1)

    resp.close()

    if not dry_run:
        try:
            makedirs(outdir)
        except OSError: pass

    for folder in folders:
        if zope_export(dav, folder, outdir):
            dav = DAV(machine[1]) # HTTP Connection gets corrupted here ;(
            dav.headers = headers
            dav.baseurl = baseurl
