#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: ileler@qq.com
import os
import re
import sys
import getopt
import zipfile
import hashlib
from shutil import copy, copyfile, copytree, rmtree, move

def usage():
    print('''
Usage:%s [option]

 option:

     -h or --help: show help info

     -l target-jar [-o list-file-path]: list table of contents for jar
        target-jar:                 string, path of jar file that need to list
        list-file-path:             string, path of 'list-file' file

     -i old-jar -t new-jar [-n incremental-pkg-name] [-d incremental-pkg-dir] [-f force-path]: generate incremental jar
        old-jar:                    string, path of 'old-jar|list-file' file
        new-jar:                    string, path of new-jar file
        incremental-pkg-name:       string, name of output, default is [old-jar].incremental
        incremental-pkg-dir:        string, dir of output, default is current dir
        force-path:                 regex, match the file path that needs to be forced to update

     -u target-jar -a incremental-pkg [-n new-pkg-name] [-d new-pkg-dir] [-I ignore-path]: update jar from incremental jar
        target-jar:                 string, path of jar file that need to update
        incremental-pkg:            string, path of incremental pkg[from -i generate]
        new-pkg-name:               string, new jar name of output, default is replace target-jar
        new-pkg-dir:                string, dir of output, default is dir of target-jar
        ignore-path:                regex, match the file path that needs to be ignore to update

 example:
     %s -i temp/test.jar -t temp/test_new.jar -d temp.out -f '/BOOT-INF/(classes/*|lib/org\.ileler\..*)|/META-INF/*'
     %s -u temp/test.jar -a temp.out/test.jar.incremental -d temp.out

    ''' % (sys.argv[0], sys.argv[0], sys.argv[0]))


def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, 'rb') as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()


def increment():
    oj, nj, ipn, ipd, fp = incrementConfig['oj'], incrementConfig['nj'], incrementConfig['ipn'], incrementConfig['ipd'], incrementConfig['fp']
    if oj is None or not os.path.exists(oj):
        print('[-i old-jar] invalid')
        sys.exit()
    if nj is None or not os.path.exists(nj) or not zipfile.is_zipfile(nj):
        print('[-t new-jar] invalid')
        sys.exit()
    if ipn is None:
        ipn = os.path.basename(oj) + '.incremental'
    if ipd is None:
        ipd = './'    
    if fp is not None:
        print('force update: ' + fp)

    #mkdir temp work dir
    tempdir = ipd + '/' + ipn + '.tmp'
    tempdir_new = tempdir + '/' + 'new'
    tempdir_inc = tempdir + '/' + 'inc'
    if os.path.exists(tempdir): rmtree(tempdir)
    os.makedirs(tempdir)
    os.makedirs(tempdir_new)
    os.makedirs(tempdir_inc)

    ofns = None
    tempdir_old = None
    if zipfile.is_zipfile(oj):
        if md5(oj) == md5(nj) and fp is None:
            print('two files are the same file')
            rmtree(tempdir)
            return
        tempdir_old = tempdir + '/' + 'old'
        os.makedirs(tempdir_old)
        with zipfile.ZipFile(oj, 'r') as jar: jar.extractall(tempdir_old)
    else:
        ofns = open(oj)
        
    
    #extract dir
    with zipfile.ZipFile(nj, 'r') as jar: jar.extractall(tempdir_new)

    if fp is not None:
        open(tempdir_inc + '/__force-path__', 'w').write(fp)

    #find delete file
    with open(tempdir_inc + '/__delete__', 'w') as delfile:
        delfiles = ''
        if tempdir_old is not None:
            for root, dirs, files, in os.walk(tempdir_old):
                for d in dirs:
                    newpath = os.path.join(root.replace(tempdir_old, '') if root != tempdir_old else '/', d)
                    _newpath = tempdir_new + '/' + newpath
                    if os.path.exists(os.path.dirname(_newpath)) and not os.path.exists(_newpath):
                        delfiles += newpath + '\n'
                        print('increment delete: ' + newpath)
                for f in files:
                    newpath = os.path.join(root.replace(tempdir_old, '') if root != tempdir_old else '/', f)
                    _newpath = tempdir_new + '/' + newpath
                    if os.path.exists(os.path.dirname(_newpath)) and not os.path.exists(_newpath):
                        delfiles += newpath + '\n'
                        print('increment delete: ' + newpath)
        else:
            for ofn in ofns:
                nfn = tempdir_new + '/' + ofn.rstrip('\n')
                if not os.path.exists(nfn):
                    delfiles += ofn
                    print('increment delete: ' + ofn.rstrip('\n'))
        delfile.write(delfiles)

    #find update file
    for root, dirs, files in os.walk(tempdir_new):
        _fpd = root.replace(tempdir_new, tempdir_inc)
        if _fpd != tempdir_inc and os.path.exists(_fpd): continue
        fpd = root.replace(tempdir_new, '')
        if fpd != '' and (fp is not None and re.match(r'' + fp, fpd)):
            copytree(root, _fpd)
            print('increment update: ' + _fpd.replace(tempdir_inc, '' if tempdir_inc != root else '/'))
            continue
        for f in files:
            newpath = os.path.join(root, f)
            oldpath = None if tempdir_old is None else os.path.join(root.replace(tempdir_new, tempdir_old), f)
            fpf = os.path.join(root.replace(tempdir_new, '' if tempdir_new != root else '/'), f)
            if (fp is not None and re.match(r'' + fp, fpf)) or ((oldpath is not None and (not os.path.exists(oldpath) or md5(oldpath) != md5(newpath))) or (ofns is not None and (fpf + '\n') not in ofns)):
                if not os.path.exists(_fpd): os.makedirs(_fpd)
                copyfile(newpath, os.path.join(_fpd, f))
                print('increment update: ' + fpf)

    #generate increment jar
    _tf = os.path.join(ipd, ipn)
    with zipfile.ZipFile(_tf, 'w') as jar:
        for root, dirs, files in os.walk(tempdir_inc):
            for d in dirs:
                jar.write(os.path.join(root, d), os.path.join(root.replace(tempdir_inc, ''), d))
            for f in files:
                jar.write(os.path.join(root, f), os.path.join(root.replace(tempdir_inc, ''), f))
        print('success increment: ' + _tf)

    #rm temp work dir
    rmtree(tempdir)



def update():
    tj, ip, npn, npd, ips = updateConfig['tj'], updateConfig['ip'], updateConfig['npn'], updateConfig['npd'], updateConfig['ips']
    if tj is None or not os.path.exists(tj) or not zipfile.is_zipfile(tj):
        print('[-u target-jar] invalid')
        sys.exit()
    if ip is None or not os.path.exists(ip) or not zipfile.is_zipfile(ip):
        print('[-a incremental-pkg] invalid')
        sys.exit()
    if npn is None:
        npn = os.path.basename(tj)
    if npd is None:
        npd = os.path.dirname(tj)    

    #mkdir temp work dir
    tempdir = npd + '/' + npn + '.tmp'
    tempdir_src = tempdir + '/' + 'src'
    tempdir_inc = tempdir + '/' + 'inc'
    if os.path.exists(tempdir): rmtree(tempdir)
    os.makedirs(tempdir)
    os.makedirs(tempdir_src)
    os.makedirs(tempdir_inc)
    
    #extract dir
    with zipfile.ZipFile(tj, 'r') as jar: jar.extractall(tempdir_src)
    with zipfile.ZipFile(ip, 'r') as jar: jar.extractall(tempdir_inc)

    #find delete file
    with open(tempdir_inc + '/__delete__', 'r') as delfile:
        fpfc = None
        delfiles = delfile.read()
        fpfn = tempdir_inc + '/__force-path__'
        if os.path.exists(fpfn):
            fpfc = open(fpfn).read()
            print('force update: ' + fpfc)
        for root, dirs, files in os.walk(tempdir_src):
            for d in dirs:
                newpath = os.path.join(root.replace(tempdir_src, '') if root != tempdir_src else '/', d)
                if ((fpfc is not None and re.match(r'' + fpfc, newpath)) or (newpath + '\n') in delfiles) and (ips is None or not re.match(r'' + ips, newpath)):
                    print('delete dir: ' + newpath)
                    rmtree(os.path.join(root, d))
            for f in files:
                newpath = os.path.join(root.replace(tempdir_src, '') if root != tempdir_src else '/', f)
                if ((fpfc is not None and re.match(r'' + fpfc, newpath)) or (newpath + '\n') in delfiles) and (ips is None or not re.match(r'' + ips, newpath)):
                    print('delete file: ' + newpath)
                    os.remove(os.path.join(root, f))
        for root, dirs, files in os.walk(tempdir_inc):
           for d in dirs:
                newpath = os.path.join(root.replace(tempdir_inc, '') if root != tempdir_inc else '/', d)
                _newpath = tempdir_src + '/' + newpath
                if (ips is None or not re.match(r'' + ips, newpath)) and not os.path.exists(_newpath):
                    print('update dir: ' + newpath)
                    move(os.path.join(root, d), _newpath)
           for f in files:
                if f == '__delete__' or f == '__force-path__': continue
                fp = os.path.join(root, f)
                newpath = os.path.join(root.replace(tempdir_inc, '') if root != tempdir_inc else '/', f)
                if os.path.exists(fp) and (ips is None or not re.match(r'' + ips, newpath)):
                    print('update file: ' + newpath)
                    copy(os.path.join(root, f), tempdir_src + '/' + newpath)
        
    #update old jar
    _tf = os.path.join(npd, npn)
    with zipfile.ZipFile(_tf, 'w') as jar:
        for root, dirs, files in os.walk(tempdir_src):
            for d in dirs:
                jar.write(os.path.join(root, d), os.path.join(root.replace(tempdir_src, ''), d))
            for f in files:
                jar.write(os.path.join(root, f), os.path.join(root.replace(tempdir_src, ''), f))
        print('success update: ' + _tf)

    #rm temp work dir
    rmtree(tempdir)



def listJar():
    tj, ofp = listJarConfig['tj'], listJarConfig['ofp']
    if tj is None or not os.path.exists(tj) or not zipfile.is_zipfile(tj):
        print('[-u target-jar] invalid')
        sys.exit()
    #print('\n'.join(n for n in zipfile.ZipFile(tj).namelist()))
    ofile = None
    if ofp is not None:
        dname = os.path.dirname(os.path.abspath(ofp))
        if not os.path.exists(dname):
            os.makedirs(dname)
        ofile = open(ofp, 'w')
    for n in zipfile.ZipFile(tj).namelist():
        if ofile is not None:
            ofile.write("%s\n" % n)
        print(n)
    if ofp is not None:
        ofile.close()



incrementConfig = {
    'oj': None,
    'nj': None,
    'ipn': None,
    'ipd': None,
    'fp': None
}

updateConfig = {
    'tj': None,
    'ip': None,
    'npn': None,
    'npd': None,
    'ips': None
}

listJarConfig = {
    'tj': None,
    'ofp': None
}

try:
    opts, args = getopt.getopt(sys.argv[1:] if len(sys.argv) > 1 else [], 'hi:u:n:d:t:I:f:a:l:o:', ['help'])
    cmd, arg = opts[0] if len(opts) > 0 else (None, None)
    if cmd is None or cmd in ('-h', '--help'):
        usage()
        sys.exit()
    elif cmd in ('-i'):
        incrementConfig['oj'] = arg
    elif cmd in ('-u'):
        updateConfig['tj'] = arg
    elif cmd in ('-l'):
        listJarConfig['tj'] = arg
    else:
        print('unknow cmd: ' + cmd)
        sys.exit()
except getopt.GetoptError:
    print('argv error, please input')
    usage()
    sys.exit()

for cmd, arg in opts:
    if cmd in ('-n'):
        if incrementConfig['oj'] is not None:
            incrementConfig['ipn'] = arg
        else:
            updateConfig['npn'] = arg
    elif cmd in ('-d'):
        if incrementConfig['oj'] is not None:
            incrementConfig['ipd'] = arg
        else:
            updateConfig['npd'] = arg
    elif cmd in ('-t'):
        incrementConfig['nj'] = arg
    elif cmd in ('-I'): 
        updateConfig['ips'] = arg
    elif cmd in ('-f'): 
        incrementConfig['fp'] = arg
    elif cmd in ('-a'): 
        updateConfig['ip'] = arg
    elif cmd in ('-o'): 
        listJarConfig['ofp'] = arg

if incrementConfig['oj'] is not None:
    increment()
elif updateConfig['tj'] is not None:
    update()
elif listJarConfig['tj'] is not None:
    listJar()
else:
    usage()

