# sapie_unzip.py (encoding: cp932)
  # サピエの図書ファイルを解凍するコマンド (2020/08/18)
  # Copyright (C) T. Yoshiizumi, 2020 All rights reserved.
import os
import sys
import re
import zipfileWIN
import datetime
import win32_setctime
import win32com.client
#import win32api
#from win32con import MB_SYSTEMMODAL
import winsound as ws
from tkinter import messagebox

# ファイル名として使えない半角文字を全角文字に置換その他
def replace_unusable(s):
    s = s.replace('\\', '￥')
    s = s.replace('/', '／')
    s = s.replace(':', '：')
    s = s.replace('*', '＊')
    s = s.replace('?', '？')
    s = s.replace('"', '“')
    s = s.replace('|', '｜')
    s = s.replace('>', '＞')
    s = s.replace('<', '＜')
    s = re.sub(r'\s+', ' ', s)
    s = s.rstrip()
    return s

# 図書のタイトルとタイプを得る(そのタイトルがフォルダ名になる)。
def get_book_title(srcFile, opt):
    srcPath = os.path.abspath(srcFile)
    title = ""
    bookType = ""
    try:
        z = zipfileWIN.ZipFile(srcPath)
    except:
        return(None, None)
    ary = []
    bookType = ""
    tempTitle = ""
    for info in z.infolist():
        ary = info.filename.split('/')
        if not len(ary) == 2:
            continue
        if ary[1].lower() == "detail.txt":
            if opt['han']:
                binStr = z.read(info.filename)
                wstr = binStr.decode('cp932', errors='ignore')
                lines = re.split(r'\n', wstr)
                tempTitle = replace_unusable(lines[0])
            else:
                tempTitle = re.sub(r'(.+)[A-Za-z0-9]{14}$', '\\1', ary[0])
        elif re.search(r'\.be[st]$', ary[1], re.IGNORECASE):
            bookType = "braille"
        elif ary[1].lower() == "discinfo.html":
            bookType = "daisy"
            title = re.sub(r'(.+)[A-Za-z0-9]{14}$', '\\1', ary[0])
        elif re.search(r'\d\.smil$', ary[1], re.IGNORECASE):
            bookType = "text daisy"
            if title == "":
                title = re.sub(r'(.+)[A-Za-z0-9]{14}$', '\\1', ary[0])
    z.close
    if bookType == "braille" and tempTitle != "":
        title = tempTitle
    return(title, bookType)

# zipの解凍。
  # 戻り値: うまく解凍できなかったファイルの名前を配列にして返す。
  # 正常に解凍できれば [] を返す。
def zip_extract(srcFile, opt):
    srcPath = os.path.abspath(srcFile)
    dst = os.path.dirname(srcPath)
    title, bookType = get_book_title(srcPath, opt)
    if title is None:
        return [None]
    ary = []
    z = zipfileWIN.ZipFile(srcPath)
    if title == "":
        for info in z.infolist():
            dt = info.date_time
            mtime = datetime.datetime(dt[0], dt[1], dt[2],
                dt[3], dt[4], dt[5]).timestamp()
            z.extract(info, path=dst)
            ary = info.filename.split('/')
            if ary[-1] == "":
                continue
            fullName = dst + os.sep + os.sep.join(ary)
            xCount = 0
            while xCount < 5:
                win32_setctime.setctime(fullName, mtime)
                os.utime(fullName, (mtime, mtime))
                t = os.stat(fullName)
                if t.st_mtime == mtime:
                    break
                xCount = xCount + 1
        z.close
        return []
    # ↓ if title != ""
    errorFiles = []
    for info in z.infolist():
        dt = info.date_time
        mtime = datetime.datetime(dt[0], dt[1], dt[2],
            dt[3], dt[4], dt[5]).timestamp()
        ary = info.filename.split('/')
        ary[0] = title
        if bookType == "daisy" and opt['original'] == False:
            if ary[1].lower() == "discinfo.html":
                continue
            elif len(ary) > 2:
                del ary[1]
        dname = dst + os.sep + os.sep.join(ary[:-1])
        if not os.path.isdir(dname):
            os.makedirs(dname)
        if ary[-1] == "":
            continue
        tail = re.sub(r'(.+)(-\d+\.[Bb][Ee][SsTt])$', '\\2', ary[-1])
        if ary[-1] != tail:
            ary[-1] = title + tail
        innerName = os.sep.join(ary)
        fullName = dst + os.sep + innerName
        binStr = z.read(info.filename)
        write_flag = True
        with open(fullName, "wb") as f:
            try:
                f.write(binStr)
            except:
                write_flag = False
                errorFiles.append(innerName)
        if write_flag:  # タイムスタンプの修正
            xCount = 0
            while xCount < 5:
                win32_setctime.setctime(fullName, mtime)
                os.utime(fullName, (mtime, mtime))
                t = os.stat(fullName)
                if t.st_mtime == mtime:
                    break
                xCount = xCount + 1
    z.close
    return errorFiles

# sapie_unzip.cfg からオプションを読み取る
def get_options(fileName, options = None):
    if options is None:
        options = {'han':True, 'delete':True, 'original':True}
    if not os.path.isfile(fileName):
        return options
    with open(fileName, "r") as f:
        wstr = f.read()
    lines = re.split(r'\n', wstr)
    for line in lines:
        if re.search(r'^-han', line, re.IGNORECASE):
            options['han'] = True
        elif re.search(r'^-zen', line, re.IGNORECASE):
            options['han'] = False
        elif re.search(r'^-delete', line, re.IGNORECASE):
            options['delete'] = True
        elif re.search(r'^-keep', line, re.IGNORECASE):
            options['delete'] = False
        elif re.search(r'^-original', line, re.IGNORECASE):
            options['original'] = True
        elif re.search(r'^-ptp', line, re.IGNORECASE):
            options['original'] = False
    return options

## main
selfName = os.path.abspath(sys.argv[0])
selfName = re.sub(r'\.[A-Za-z]+$', '', selfName)
scriptPath = selfName + ".exe"
optionPath = selfName + ".cfg"
options = get_options(optionPath)
if len(sys.argv) > 1:  # 引数があるので解凍処理
    rm_list = []
    msgStr = ""
    dname = ""
    for i in range(1, len(sys.argv)):
        srcFile = os.path.abspath(sys.argv[i])
        if not re.search(r'\.(zip|exe)$', srcFile, re.IGNORECASE):
            continue
        if dname == "":
            dname = os.path.dirname(srcFile)
            optionPath = dname + os.sep + "sapie_unzip.cfg"
            if os.path.isfile(optionPath):
                options = get_options(optionPath, options)
        errorFiles = zip_extract(srcFile, options)
        if len(errorFiles) == 0:
            rm_list.append(srcFile)
        elif not errorFiles[0] is None:
            msgStr = msgStr + "* '%s': 書き出し失敗ファイル↓\n" % \
                os.path.basename(srcFile)
            for fname in errorFiles:
                msgStr = msgStr + "\t%s\n" % fname
    count = len(rm_list)
    if options['delete']:
        for srcFile in rm_list:
            os.remove(srcFile)
    if msgStr == "":
        ws.PlaySound('SystemAsterisk', ws.SND_ALIAS)
    else:
        ws.PlaySound('SystemHand', ws.SND_ALIAS)
    messagebox.showinfo("sapie_unzip", "%d個のファイルを解凍しました." % count)
    if msgStr != "":
        logFile = dname + os.sep + "log.txt"
        with open(logFile, "w") as f:
            f.write(msgStr)
else:  # 引数がないので「送るメニュー」に登録など
    targetName = os.path.splitext(os.path.basename(scriptPath))[0]
    sho = win32com.client.Dispatch("WScript.Shell")
    sendtoDir = sho.SpecialFolders("SendTo")
    lnkFile = sendtoDir + os.sep + targetName + ".lnk"
    if os.path.isfile(lnkFile):
        os.remove(lnkFile)
        messagebox.showinfo("sapie_unzip",
            "送るメニューの sapie_unzip を削除しました.")
    else:
        shortcut = sho.CreateShortCut(lnkFile)
        shortcut.TargetPath = scriptPath
        shortcut.save()
        messagebox.showinfo("sapie_unzip",
            "送るメニューに sapie_unzip を登録しました.")
