#!/usr/bin/env python
# -*- Mode: Python; tab-width: 4 -*-
#
# rejar.py - Recompress jar files with max compression
# Copyright (C) 2005 Gianluigi Tiesi <sherpya@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.
# ======================================================================
__version__ = '0.3'

from sys import argv, platform
from sys import exit as sys_exit
from cStringIO import StringIO
from zipfile import ZipFile, ZipInfo, ZIP_STORED, ZIP_DEFLATED
from os import sep, unlink, rename, stat
import os.path

# from stat.py
ST_SIZE = 6

## for win32
global status_ctrl
global isWorking
isWorking = 0

if platform == 'win32':
    ### Widgets
    IDC_BROWSE = 100
    IDC_GO = 101
    IDC_STATUS = 102
    
    # from win32con
    BS_DEFPUSHBUTTON = 1
    BS_PUSHBUTTON = 0
    COLOR_WINDOW = 5
    CS_VREDRAW = 1
    CS_HREDRAW = 2
    DLGWINDOWEXTRA = 30
    DS_SETFONT = 64
    IDC_ARROW = 32512
    SS_LEFT = 0
    WM_CLOSE = 16
    WM_COMMAND = 273
    WM_DESTROY = 2
    WM_INITDIALOG = 272
    WM_SIZE = 5
    WS_BORDER = 8388608
    WS_CAPTION = 12582912
    WS_CHILD = 1073741824
    WS_DISABLED = 134217728
    WS_MINIMIZEBOX = 131072
    WS_POPUP = -2147483648
    WS_SYSMENU = 524288
    WS_TABSTOP = 65536
    WS_THICKFRAME = 262144
    WS_VISIBLE = 268435456

    # win32 imports
    import win32api
    import win32gui
    import struct
    from win32com.shell import shell
    from threading import Thread
    from sys import executable

ZIPINFO  = 0
ZIPFILES = 1

LOOKEXT    = [ '.jar', '.xpi' ]
NOCOMPRESS = [ '.png', '.gif', '.jpg' ]

### Cmd Callback - prints to stdout
def stdout_callback(action, current, total, zipname, filename):
    if action != ZIPINFO:
        return
    print "Re-compressing %s" % zipname

### Gui callback
def gui_callback(action, current, total, zipname, filename):
    global status_ctrl
    if action != ZIPINFO:
        return
    win32gui.SetWindowText(status_ctrl, ("Re-compressing %s" % os.path.basename(zipname)))

def recurse(srczip, destzip, progress_callback):
    count = len(srczip.filelist)
    now = 0
    for f in srczip.filelist:
        dest = None
        ext = os.path.splitext(f.filename)[1].lower()
        if f.file_size and ext in LOOKEXT:
            childzip = ZipFile(StringIO(srczip.read(f.filename)), 'r')
            dest = StringIO()
            tempzip = ZipFile(dest, 'w', ZIP_DEFLATED)
            recurse(childzip, tempzip, progress_callback)
            tempzip.close()
            del childzip

        newfile = ZipInfo()
        newfile.date_time = f.date_time
        newfile.filename = f.filename

        if f.file_size and ext not in NOCOMPRESS:
            newfile.compress_type = ZIP_DEFLATED
        else:
            newfile.compress_type = ZIP_STORED

        if dest is None:
            destzip.writestr(newfile, srczip.read(f.filename))
        else:
            destzip.writestr(newfile, dest.getvalue())
            del tempzip
            del dest
        
        
def process(zipname, progress_callback):
    progress_callback(ZIPINFO, None, None, zipname, None)

    destzipname = zipname + '.rejar'
    try:
        unlink(destzipname)
    except:
        pass

    srczip = ZipFile(zipname, 'r')
    destzip = ZipFile(destzipname, 'w', ZIP_DEFLATED)

    recurse(srczip, destzip, progress_callback)
        
    srczip.close()
    destzip.close()

    if stat(destzipname)[ST_SIZE] < stat(zipname)[ST_SIZE]:
        unlink(zipname)
        rename(destzipname, zipname)
    else:
        ## No need to use my own :)
        unlink(destzipname)

def walk_callback(progress_callback, dirname, names):
    for name in names:
        fullpath = os.path.join(dirname, name)
        if os.path.isfile(fullpath) and os.path.splitext(name)[1].lower() in LOOKEXT:
            process(fullpath, progress_callback)

### Linux/Windows cmdline interface
def cmdline(argv):
    if len(argv) < 2:
        print "Usage: %s directory" % argv[0]
        sys_exit()

    rejar(argv[1], stdout_callback)
    sys_exit()

def rejar(directory, progress_callback):
    global isWorking
    if directory.endswith(sep):
        directory = directory[:-1]
    os.path.walk(directory, walk_callback, progress_callback)
    if platform == 'win32':
        win32gui.SetWindowText(status_ctrl, "Done.")
        isWorking = 0
    
### GUI only on win32 + ActiveState python

class GuiWindow:
    def __init__(self):
        win32gui.InitCommonControls()
        self.hinst = win32api.GetModuleHandle(None)
        self.directory = None

    def _RegisterWndClass(self):
        className = "PythonRejar"
        message_map = {}
        wc = win32gui.WNDCLASS()
        wc.SetDialogProc() # Make it a dialog class.
        self.hinst = wc.hInstance = win32api.GetModuleHandle(None)
        wc.lpszClassName = className
        wc.style = CS_VREDRAW | CS_HREDRAW
        wc.hCursor = win32gui.LoadCursor( 0, IDC_ARROW )
        wc.hbrBackground = COLOR_WINDOW + 1
        wc.lpfnWndProc = message_map # could also specify a wndproc.
        # C code: wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(HBRUSH) + (sizeof(COLORREF));
        wc.cbWndExtra = DLGWINDOWEXTRA + struct.calcsize("Pi")
        wc.hIcon = win32gui.LoadIcon(self.hinst, 1)
        try:
            classAtom = win32gui.RegisterClass(wc)
        except:
            pass
        return className

    def _GetDialogTemplate(self, dlgClassName):
        style = WS_THICKFRAME | \
                WS_POPUP | \
                WS_VISIBLE | \
                WS_CAPTION | \
                WS_SYSMENU | \
                DS_SETFONT | \
                WS_MINIMIZEBOX
             
        cs = WS_CHILD | WS_VISIBLE
        title = "Python Rejar by Sherpya (c) 2005"
                
        # Window frame and title
        dlg = [ [title, (0, 0, 210, 40), style, None, (8, "MS Sans Serif"), None, dlgClassName], ]
        
        # ID label and text box
        #dlg.append([130, "Enter something", -1, (5, 5, 200, 9), cs | SS_LEFT])
        s = cs | WS_TABSTOP | WS_BORDER | WS_DISABLED
        dlg.append(['EDIT', None, IDC_STATUS, (5, 5, 200, 12), s])
        
        # (x positions don't matter here)
        s = cs | WS_TABSTOP
        
        dlg.append([128, "Rejar!", IDC_GO, (5, 25, 50, 14), s | BS_DEFPUSHBUTTON])
        s = BS_PUSHBUTTON | s
        dlg.append([128, "Browse", IDC_BROWSE, (100, 25, 50, 14), s])
        return dlg

    def CreateWindow(self):
        self._DoCreate(win32gui.CreateDialogIndirect)

    def DoModal(self):
        return self._DoCreate(win32gui.DialogBoxIndirect)

    def OnInitDialog(self, hwnd, msg, wparam, lparam):
        global status_ctrl
        self.hwnd = hwnd
        # center the dialog
        desktop = win32gui.GetDesktopWindow()
        l,t,r,b = win32gui.GetWindowRect(self.hwnd)
        dt_l, dt_t, dt_r, dt_b = win32gui.GetWindowRect(desktop)
        centre_x, centre_y = win32gui.ClientToScreen( desktop, ( (dt_r-dt_l)/2, (dt_b-dt_t)/2) )
        win32gui.MoveWindow(hwnd, centre_x-(r/2), centre_y-(b/2), r-l, b-t, 0)
        l,t,r,b = win32gui.GetClientRect(self.hwnd)
        self._DoSize(r-l,b-t, 1)
        status_ctrl = win32gui.GetDlgItem(self.hwnd, IDC_STATUS)
        win32gui.SetWindowText(status_ctrl, "Select jar directory")
        

    def OnClose(self, hwnd, msg, wparam, lparam):
        win32gui.EndDialog(hwnd, 0)
        
    def OnDestroy(self, hwnd, msg, wparam, lparam):
        win32gui.PostQuitMessage(0) # Terminate the app.
        
    def _DoSize(self, cx, cy, repaint = 1):
        # right-justify the textbox.
        ctrl = win32gui.GetDlgItem(self.hwnd, IDC_STATUS)
        l, t, r, b = win32gui.GetWindowRect(ctrl)
        l, t = win32gui.ScreenToClient(self.hwnd, (l,t) )
        r, b = win32gui.ScreenToClient(self.hwnd, (r,b) )
        win32gui.MoveWindow(ctrl, l, t, cx-l-5, b-t, repaint)
        # Rejar! Button
        ctrl = win32gui.GetDlgItem(self.hwnd, IDC_GO)
        l, t, r, b = win32gui.GetWindowRect(ctrl)
        l, t = win32gui.ScreenToClient(self.hwnd, (l,t) )
        r, b = win32gui.ScreenToClient(self.hwnd, (r,b) )
        list_y = b + 10
        w = r - l
        win32gui.MoveWindow(ctrl, cx - 5 - w, t, w, b-t, repaint)

        # Go Button
        ctrl = win32gui.GetDlgItem(self.hwnd, IDC_BROWSE)
        win32gui.MoveWindow(ctrl, cx - 10 - (2*w), t, w, b-t, repaint)
        
        
    def OnSize(self, hwnd, msg, wparam, lparam):
        x = win32api.LOWORD(lparam)
        y = win32api.HIWORD(lparam)
        self._DoSize(x,y)
        return 1
        
    def _DoCreate(self, fn):
        message_map = {
            WM_SIZE: self.OnSize,
            WM_COMMAND: self.OnCommand,
            WM_INITDIALOG: self.OnInitDialog,
            WM_CLOSE: self.OnClose,
            WM_DESTROY: self.OnDestroy,
            }
        dlgClassName = self._RegisterWndClass()
        template = self._GetDialogTemplate(dlgClassName)
        return fn(self.hinst, template, 0, message_map)

    def OnCommand(self, hwnd, msg, wparam, lparam):
        global isWorking
        global status_ctrl

        id = win32api.LOWORD(wparam)

        if (id == IDC_BROWSE) and not isWorking:
            isWorking = 1
            idl, name, temp = shell.SHBrowseForFolder(0, None,
                                                      "Select the folder containg jar/xpi files",
                                                      0, None, 0)
            if idl is not None:
                self.directory = shell.SHGetPathFromIDList(idl)
                win32gui.SetWindowText(status_ctrl, self.directory)
            isWorking = 0
            
        elif (id == IDC_GO) and not isWorking:
            if self.directory is not None:
                isWorking = 1
                win32gui.SetWindowText(status_ctrl, "Rejar-ing...")
                thread = Thread(target = rejar, args=(self.directory, gui_callback))
                thread.start()
            else:
                win32gui.SetWindowText(status_ctrl, "No dir selected!!")

def gui(argv):
    w = GuiWindow()
    w.DoModal()

if __name__ == '__main__':
    if platform != 'win32':
        cmdline(argv)
    gui(argv)
