Files
MultiPar/alpha/tool/diff_folder.py
2023-03-20 14:09:49 +09:00

429 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sys
import os
import glob
import re
import subprocess
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
# Set path of par2j
client_path = "../par2j64.exe"
gui_path = "../MultiPar.exe"
# Initialize global variables
list_dir = []
dir_index = 0;
current_dir = "./"
list_par = []
list_src = []
list_size = []
list_now = []
# Check a folder exists in list already
def check_dir_exist(one_path):
global list_dir
for list_item in list_dir:
if os.path.samefile(one_path, list_item):
return True
return False
# Search PAR2 sets in a folder
def search_par_set(one_path):
global list_par, list_src, list_size, list_now
if one_path == "":
return
# Clear lists at first
list_par.clear()
list_src.clear()
list_size.clear()
list_now.clear()
for item in treeview_list.get_children():
treeview_list.delete(item)
found_base = ""
button_check.config(state=tk.DISABLED)
button_open.config(state=tk.DISABLED)
treeview_list.heading('PAR', text="Items in PAR", anchor='center')
treeview_list.heading('Now', text="Directory content", anchor='center')
# Add found PAR sets
for par_path in glob.glob(glob.escape(one_path) + "/*.par2"):
file_name = os.path.basename(par_path)
# Remove extension ".par2"
one_name = os.path.splitext(file_name)[0]
# Compare filename in case insensitive
base_name = one_name.lower()
# Remove ".vol#-#", ".vol#+#", or ".vol_#" at the last
base_name = re.sub(r'[.]vol\d*[-+_]\d+$', "", base_name)
# Confirm only one PAR2 set in the directory.
if found_base != "":
if base_name != found_base:
list_par.clear()
label_status.config(text= "There are multiple PAR sets in \"" + one_path + "\".")
return
else:
found_base = base_name
#print(file_name)
list_par.append(file_name)
if len(list_par) == 0:
label_status.config(text= "PAR set isn't found in \"" + one_path + "\".")
return
label_status.config(text= "Directory = \"" + one_path + "\", PAR file = \"" + list_par[0] + "\"")
button_check.config(state=tk.NORMAL)
button_open.config(state=tk.NORMAL)
# Check automatically
root.after(100, button_check_clicked)
# Select a folder to search PAR files
def button_folder_clicked():
global current_dir, list_dir, dir_index
if os.path.exists(current_dir) == False:
current_dir = "./"
search_dir = filedialog.askdirectory(initialdir=current_dir)
if search_dir == "":
return
# This replacing seems to be worthless.
#search_dir = search_dir.replace('\\', '/')
# Insert the folder to list
dir_count = len(list_dir)
if dir_count == 0:
dir_index = 0
list_dir.append(search_dir)
dir_count += 1
else:
# Check the folder exists already
if check_dir_exist(search_dir):
dir_index = list_dir.index(search_dir)
else:
dir_index += 1
list_dir.insert(dir_index, search_dir)
dir_count += 1
if dir_count > 1:
root.title('PAR Diff ' + str(dir_index + 1) + '/' + str(dir_count))
current_dir = os.path.dirname(search_dir)
search_par_set(search_dir)
# Read text and get source file
def parse_text(output_text):
global list_src, list_size
multi_lines = output_text.split('\n')
# Search starting point of list
offset = multi_lines.index("Input File list\t:")
offset += 2
# Get file size and name
while offset < len(multi_lines):
single_line = multi_lines[offset]
if single_line == "":
break
# File size
single_line = single_line.lstrip()
fisrt_item = single_line.split()[0]
file_size = int(fisrt_item)
list_size.append(file_size)
#print(file_size)
# Compare filename in case insensitive
file_name = single_line.split("\"")[1]
file_name = file_name.lower()
list_src.append(file_name)
#print(file_name)
offset += 1
# Check PAR files and list source files
def button_check_clicked():
global list_dir, dir_index, list_par, list_src, list_size, list_now
if len(list_dir) == 0:
return
treeview_list.heading('PAR', text="? items in PAR", anchor='center')
treeview_list.heading('Now', text="Directory content", anchor='center')
search_dir = list_dir[dir_index]
if os.path.exists(search_dir) == False:
label_status.config(text= "\"" + search_dir + "\" doesn't exist.")
return
if len(list_par) == 0:
return
if os.path.exists(client_path) == False:
label_status.config(text= "Cannot call \"" + client_path + "\". Set path correctly.")
return
# Clear lists at first
list_src.clear()
list_size.clear()
list_now.clear()
for item in treeview_list.get_children():
treeview_list.delete(item)
list_lost = []
add_count = 0
diff_count = 0
# Read source files in PAR set
for par_path in list_par:
# Call par2j's list command
cmd = "\"" + client_path + "\" l /uo \"" + search_dir + "/" + par_path + "\""
res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8')
#print("return code: {}".format(res.returncode))
#print("captured stdout: {}".format(res.stdout))
if res.returncode == 0:
#label_status.config(text= "Read \"" + par_path + "\" ok.")
parse_text(res.stdout)
break
if (len(list_src) == 0) or (len(list_src) != len(list_size)):
label_status.config(text= "Failed to read source files in the PAR set.")
return
# Get current directory-tree
for dirs, subdirs, files in os.walk(search_dir):
# Get sub-directory from base directory
sub_dir = dirs.lstrip(search_dir)
sub_dir = sub_dir.replace('\\', '/')
sub_dir = sub_dir.lstrip('/')
sub_dir = sub_dir.lower()
if sub_dir != "":
sub_dir += "/"
# Add folders
for dir_name in subdirs:
item_name = sub_dir + dir_name.lower() + "/"
list_now.append(item_name)
#print("folder", item_name)
# Add files
for file_name in files:
item_name = sub_dir + file_name
if (sub_dir == "") and (item_name in list_par):
continue
item_name = item_name.lower()
list_now.append(item_name)
#print("file:", item_name)
# Make list of missing items
for item_name in list_src:
#print(item_name)
if not item_name in list_now:
# The item doesn't exit now.
list_lost.append(item_name)
list_now.append(item_name)
# Compare lists to find additional items
list_now.sort()
for item_name in list_now:
#print(item_name)
if item_name.endswith("/"):
# This item is a folder.
if item_name in list_lost:
treeview_list.insert(parent='', index='end', values=(item_name, ""), tags='red')
elif item_name in list_src:
if not bool_diff.get():
treeview_list.insert(parent='', index='end', values=(item_name, item_name))
else:
find_flag = 0
for src_name in list_src:
if src_name.startswith(item_name):
# The folder exists as sub-directory.
find_flag = 1
break;
if find_flag == 0:
# The folder doesn't exit in PAR set.
treeview_list.insert(parent='', index='end', values=("", item_name), tags='blue')
add_count += 1;
else:
# This item is a file.
if item_name in list_lost:
treeview_list.insert(parent='', index='end', values=(item_name, ""), tags='red')
elif item_name in list_src:
file_path = search_dir + "/" + item_name
item_index = list_src.index(item_name)
file_size = os.path.getsize(file_path)
#print(item_index, list_size[item_index], file_size)
if file_size == list_size[item_index]:
if not bool_diff.get():
treeview_list.insert(parent='', index='end', values=(item_name, item_name))
else:
treeview_list.insert(parent='', index='end', values=(item_name, item_name), tags='yellow')
diff_count += 1
else:
# The file doesn't exit in PAR set.
treeview_list.insert(parent='', index='end', values=("", item_name), tags='blue')
add_count += 1;
# Number of missing or additional items
item_count = len(list_src)
lost_count = len(list_lost)
if lost_count == 0:
treeview_list.heading('PAR', text= str(item_count) + " items in PAR", anchor='center')
else:
treeview_list.heading('PAR', text= str(item_count) + " items in PAR ( " + str(lost_count) + " miss )", anchor='center')
if add_count + diff_count > 0:
if add_count == 0:
treeview_list.heading('Now', text="Directory content ( " + str(diff_count) + " diff )", anchor='center')
elif diff_count == 0:
treeview_list.heading('Now', text="Directory content ( " + str(add_count) + " add )", anchor='center')
else:
treeview_list.heading('Now', text="Directory content ( " + str(add_count) + " add, " + str(diff_count) + " diff )", anchor='center')
# If you want to see some summary, uncomment below section.
#status_text = "Directory = \"" + search_dir + "\", PAR file = \"" + par_path + "\"\nTotal items = " + str(item_count) + ". "
#if lost_count + add_count + diff_count == 0:
# status_text += "Directory and Par File structure match."
#else:
# if lost_count > 0:
# status_text += str(lost_count) + " items are missing. "
# if add_count > 0:
# status_text += str(add_count) + " items are additional. "
# if diff_count > 0:
# status_text += str(diff_count) + " items are different size."
#label_status.config(text=status_text)
# Open PAR set by MultiPar
def button_open_clicked():
global list_dir, dir_index, list_par
if len(list_dir) == 0:
return
search_dir = list_dir[dir_index]
if os.path.exists(search_dir) == False:
return
if len(list_par) == 0:
return
if os.path.exists(gui_path) == False:
label_status.config(text= "Cannot call \"" + gui_path + "\". Set path correctly.")
return
# Set command-line
# Cover path by " for possible space
par_path = search_dir + "/" + list_par[0]
cmd = "\"" + gui_path + "\" /verify \"" + par_path + "\""
# Open MultiPar GUI to see details
# Because this doesn't wait finish of MultiPar, you may open some at once.
subprocess.Popen(cmd)
# Move to next folder
def button_next_clicked():
global current_dir, list_dir, dir_index
dir_count = len(list_dir)
search_dir = ""
# Goto next directory
while dir_count > 1:
dir_index += 1
if dir_index >= dir_count:
dir_index = 0
search_dir = list_dir[dir_index]
# Check the directory exists
if os.path.exists(search_dir):
break
else:
list_dir.pop(dir_index)
dir_index -= 1
dir_count -= 1
search_dir = ""
if search_dir == "":
return
root.title('PAR Diff ' + str(dir_index + 1) + '/' + str(dir_count))
current_dir = os.path.dirname(search_dir)
search_par_set(search_dir)
# Window size and title
root = tk.Tk()
root.title('PAR Diff')
root.minsize(width=480, height=240)
# Centering window
init_width = 640
init_height = 480
init_left = (root.winfo_screenwidth() - init_width) // 2
init_top = (root.winfo_screenheight() - init_height) // 2
root.geometry('{}x{}+{}+{}'.format(init_width, init_height, init_left, init_top))
#root.geometry("640x480")
root.columnconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Control panel
frame_top = ttk.Frame(root, padding=(3,4,3,2))
frame_top.grid(row=0, column=0, sticky=(tk.E,tk.W))
button_next = ttk.Button(frame_top, text="Next folder", width=12, command=button_next_clicked)
button_next.pack(side=tk.LEFT, padx=2)
button_folder = ttk.Button(frame_top, text="Select folder", width=12, command=button_folder_clicked)
button_folder.pack(side=tk.LEFT, padx=2)
button_check = ttk.Button(frame_top, text="Check again", width=12, command=button_check_clicked, state=tk.DISABLED)
button_check.pack(side=tk.LEFT, padx=2)
button_open = ttk.Button(frame_top, text="Open with MultiPar", width=19, command=button_open_clicked, state=tk.DISABLED)
button_open.pack(side=tk.LEFT, padx=2)
bool_diff = tk.BooleanVar()
bool_diff.set(False) # You may change initial state here.
check_diff = ttk.Checkbutton(frame_top, variable=bool_diff, text="Diff only")
check_diff.pack(side=tk.LEFT, padx=2)
# List
frame_middle = ttk.Frame(root, padding=(4,2,4,2))
frame_middle.grid(row=1, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
frame_middle.rowconfigure(0, weight=1)
frame_middle.columnconfigure(0, weight=1)
column = ('PAR', 'Now')
treeview_list = ttk.Treeview(frame_middle, columns=column, selectmode='none')
treeview_list.grid(row=0, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
treeview_list.column('#0',width=0, stretch='no')
treeview_list.heading('#0', text='')
treeview_list.heading('PAR', text="Items in PAR", anchor='center')
treeview_list.heading('Now', text="Directory content", anchor='center')
treeview_list.tag_configure('red', background='#FFC0C0')
treeview_list.tag_configure('blue', background='#80FFFF')
treeview_list.tag_configure('yellow', background='#FFFF80')
scrollbar_list = ttk.Scrollbar(frame_middle, orient=tk.VERTICAL, command=treeview_list.yview)
scrollbar_list.grid(row=0, column=1, sticky=(tk.N, tk.S))
treeview_list["yscrollcommand"] = scrollbar_list.set
xscrollbar_list = ttk.Scrollbar(frame_middle, orient=tk.HORIZONTAL, command=treeview_list.xview)
xscrollbar_list.grid(row=1, column=0, sticky=(tk.E, tk.W))
treeview_list["xscrollcommand"] = xscrollbar_list.set
# Status text
frame_bottom = ttk.Frame(root)
frame_bottom.grid(row=2, column=0, sticky=(tk.E,tk.W))
label_status = ttk.Label(frame_bottom, text='Select folder to check difference.')
label_status.pack(side=tk.LEFT, padx=2)
# When folders are specified in command-line
if len(sys.argv) > 1:
search_dir = ""
for one_argv in sys.argv[1:]:
if os.path.isdir(one_argv):
one_path = os.path.abspath(one_argv)
one_path = one_path.replace('\\', '/')
if check_dir_exist(one_path) == False:
#print("argv=", one_path)
list_dir.append(one_path)
if search_dir == "":
search_dir = one_path
dir_count = len(list_dir)
if dir_count > 1:
root.title('PAR Diff 1/' + str(dir_count))
if search_dir != "":
current_dir = os.path.dirname(search_dir)
search_par_set(search_dir)
# Show window
root.mainloop()