Add files via upload
This commit is contained in:
428
alpha/tool/diff_folder.py
Normal file
428
alpha/tool/diff_folder.py
Normal file
@@ -0,0 +1,428 @@
|
||||
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()
|
||||
327
alpha/tool/each_folder.py
Normal file
327
alpha/tool/each_folder.py
Normal file
@@ -0,0 +1,327 @@
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# This script is based on a script provided by Project Maintainer: Yukata-Sawada. I (markxu98) modified it for my own use. I tested it only on my machines
|
||||
# with only my own files. Although I believe the structure of my files should reflex many different types of users/organizations, I cannot guarantee this
|
||||
# script is suitable for ALL. So I suggest use it, understand it, and modify it by yourself to fit.
|
||||
|
||||
# The purpose of this script is to generate PAR2 files with (somewhat) "best" storage efficiency with (somewhat) "good" reliability, while spending
|
||||
# (somewhat) "less" time or using (somewhat) "less" processing power.
|
||||
|
||||
# The following "const" values are optimized by me. They should be okay to most cases. Use caution when changing all of them. I added more comments to
|
||||
# certain ones important to calculation.
|
||||
|
||||
# Set path of par2j
|
||||
client_path = "../par2j64.exe"
|
||||
|
||||
# Set options for par2j
|
||||
# Don't use /ss, /sn, /sr, or /sm here.
|
||||
cmd_option = "/rr10 /rd1 /rf3"
|
||||
|
||||
# How to set slices initially (either /ss, /sn, or /sr)
|
||||
# The default setting is /sr10 in MultiPar.
|
||||
init_slice_option = "/sr10"
|
||||
|
||||
# Slice size multiplier (used in all cmd in this script)
|
||||
# It would be good to set a cluster size of HDD/SSD. (4k == 4096 by default)
|
||||
slice_size_multiplier = 4096
|
||||
|
||||
# Max number of slices at searching good efficiency (20000 by default).
|
||||
# More number of slices is good for many files or varied size.
|
||||
# This value can be ignored if initial slice count is even larger.
|
||||
# Set this value to 32768 to ignore it completely.
|
||||
max_slice_count = 20000
|
||||
|
||||
# Max percent of slices at searching good efficiency (170% by default).
|
||||
# If initial_count is less than max_slice_count, the maximum slice count at
|
||||
# searching is the smaller value of max_slice_count and
|
||||
# (initial_count * max_slice_rate / 100).
|
||||
# If initial_count is not less than max_slice_count, max_slice_count is
|
||||
# ignored, and the maximum slice count at searching is the smaller value of
|
||||
# 32768 and (initial_count * max_slice_rate / 100).
|
||||
# This will also be used to calculate the min_slice_size.
|
||||
max_slice_rate = 170
|
||||
#max_slice_rate = 0
|
||||
# If you have good processing power, you can set above rate to 0, then BOTH
|
||||
# max_slice_count and max_slice_rate will be ignored. 32768 will be fixed as
|
||||
# max_slice_count at searching.
|
||||
|
||||
# Min number of slices at searching good efficiency (100 by default).
|
||||
# Normally less number of slices tend to archive higher efficiency.
|
||||
# But, too few slices is bad against random error.
|
||||
# If by very small chance, the calculated maximum slice count is less than
|
||||
# min_slice_count, no searching is needed and min_slice_count is used to
|
||||
# generate PAR2 file.
|
||||
min_slice_count = 100
|
||||
|
||||
# Min percent of slices at searching good efficiency (30% by default).
|
||||
# Normally less number of slices tend to archive higher efficiency.
|
||||
# But, too few slices is bad against random error. It may need at least 50%.
|
||||
# If initial_count is more than min_slice_count, the minimum slice count at
|
||||
# searching is the greater value of min_slice_count and
|
||||
# (initial_count * min_slice_rate / 100).
|
||||
# If initial_count is not more than min_slice_count, min_slice_rate is
|
||||
# ignored, and the minimum slice count at searching is min_slice_count.
|
||||
# This will also be used to calculate the max_slice_size.
|
||||
min_slice_rate = 30
|
||||
#min_slice_rate = 0
|
||||
# If you have good processing power, you can set above rate to 0, then this
|
||||
# rate will be ignored and min_slice_count is used at searching
|
||||
|
||||
# Caution: You CAN set max_slice_rate = 0 and min_slice_rate = 0 at the same
|
||||
# time to search (almost) "WHOLE" range, from min_slice_count to 32768.
|
||||
|
||||
# Min efficiency improvment that will be regarded as "better" (0.3% by default).
|
||||
# If the efficiency improvement is not so significant, it's unreasonable to
|
||||
# use a larger slice count. This value controls how significant to update the
|
||||
# best slice count at searching.
|
||||
min_efficiency_improvement = 0.3
|
||||
#min_efficiency_improvement = 0
|
||||
# If you want to achieve "absolute" best efficiency, you can set above to 0.
|
||||
|
||||
# Read "Efficiency rate"
|
||||
def read_efficiency(output_text):
|
||||
# Find from the last
|
||||
line_start = output_text.rfind("Efficiency rate\t\t:")
|
||||
if line_start != -1:
|
||||
line_start += 19
|
||||
line_end = output_text.find("%\n", line_start)
|
||||
#print("line_start=", line_start)
|
||||
#print("line_end=", line_end)
|
||||
return float(output_text[line_start:line_end])
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
# Read "Input File Slice count"
|
||||
def read_slice_count(output_text):
|
||||
# Find from the top
|
||||
line_start = output_text.find("Input File Slice count\t:")
|
||||
if line_start != -1:
|
||||
line_start += 25
|
||||
line_end = output_text.find("\n", line_start)
|
||||
return int(output_text[line_start:line_end])
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
# Read "Input File Slice size"
|
||||
def read_slice_size(output_text):
|
||||
# Find from the top
|
||||
line_start = output_text.find("Input File Slice size\t:")
|
||||
if line_start != -1:
|
||||
line_start += 24
|
||||
line_end = output_text.find("\n", line_start)
|
||||
return int(output_text[line_start:line_end])
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
# Search setting of good efficiency
|
||||
def test_efficiency(par_path):
|
||||
min_size = 0
|
||||
max_size = 0
|
||||
best_count = 0
|
||||
best_size = 0
|
||||
best_efficiency = 0
|
||||
best_efficiency_at_initial_count = 0
|
||||
best_count_at_max_count = 0
|
||||
best_size_at_max_count = 0
|
||||
best_efficiency_at_max_count = 0
|
||||
|
||||
# First time to get initial value
|
||||
cmd = "\"" + client_path + "\" t /uo " + init_slice_option + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + 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:
|
||||
return 0
|
||||
efficiency_rate = read_efficiency(res.stdout)
|
||||
if efficiency_rate < 0:
|
||||
return 0
|
||||
# DON'T change best_count, best_size and best_efficiency here. The following three values will be evaluated after the search is done.
|
||||
# Using initial_count may not be best case. If the search can find a slice count less than initial_count, whose efficiency is the same as
|
||||
# best_efficiency_at_initial_count, that slice count should be used instead of initial_count.
|
||||
initial_count = read_slice_count(res.stdout)
|
||||
if initial_count <= 0:
|
||||
return 0
|
||||
initial_size = read_slice_size(res.stdout)
|
||||
best_efficiency_at_initial_count = efficiency_rate
|
||||
#print("initial_size =", initial_size, ", initial_count =", initial_count, ", efficiency =", efficiency_rate)
|
||||
|
||||
# Get min and max of slice count and size to be used at searching
|
||||
# maximum slice count is co-related to minimum slice size
|
||||
if max_slice_rate != 0:
|
||||
if initial_count > max_slice_count:
|
||||
if (initial_count * max_slice_rate / 100) > 32768:
|
||||
max_count = 32768
|
||||
else:
|
||||
max_count = int(initial_count * max_slice_rate / 100)
|
||||
else:
|
||||
if (initial_count * max_slice_rate / 100) > max_slice_count:
|
||||
max_count = max_slice_count
|
||||
else:
|
||||
max_count = int(initial_count * max_slice_rate / 100)
|
||||
else:
|
||||
max_count = 32768
|
||||
# Giving out the calculated maximum slice count, get "real" max_count and min_size from result of Par2j64.exe
|
||||
# Here use option "/sn" to search around (from -12.5% to +6.25%) the calculated maximum slice count for best efficiency
|
||||
cmd = "\"" + client_path + "\" t /uo /sn" + str(max_count) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" *"
|
||||
res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8')
|
||||
if res.returncode != 0:
|
||||
return 0
|
||||
efficiency_rate = read_efficiency(res.stdout)
|
||||
if efficiency_rate < 0:
|
||||
return 0
|
||||
# DON'T change best_count, best_size and best_efficiency here. The following three values will be evaluated after the search is done.
|
||||
# Using max_count is the worst case as it will require more processing power. If the search can find a slice count less than max_count,
|
||||
# whose efficiency is the same as best_efficiency_at_max_count, that slice count should be used instead of best_count_at_max_count.
|
||||
best_count_at_max_count = read_slice_count(res.stdout)
|
||||
best_size_at_max_count = read_slice_size(res.stdout)
|
||||
best_efficiency_at_max_count = efficiency_rate
|
||||
max_count = read_slice_count(res.stdout)
|
||||
min_size = read_slice_size(res.stdout)
|
||||
#print("max_count =", max_count, ", min_size =", min_size, ", efficiency =", best_efficiency_at_max_count)
|
||||
|
||||
# Minimum slice count is co-related to maximum slice size
|
||||
if min_slice_rate > 0 and (initial_count * min_slice_rate / 100) > min_slice_count:
|
||||
min_count = int(initial_count * min_slice_rate / 100)
|
||||
else:
|
||||
min_count = min_slice_count
|
||||
# Giving out the calculated minimum slice count, get "real" min_count and max_size from result of Par2j64.exe
|
||||
# Here use option "/sn" to search around (from -12.5% to +6.25%) the calculated minimum slice count for best efficiency
|
||||
cmd = "\"" + client_path + "\" t /uo /sn" + str(min_count) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" *"
|
||||
res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8')
|
||||
if res.returncode != 0:
|
||||
return 0
|
||||
efficiency_rate = read_efficiency(res.stdout)
|
||||
if efficiency_rate < 0:
|
||||
return 0
|
||||
min_count = read_slice_count(res.stdout)
|
||||
max_size = read_slice_size(res.stdout)
|
||||
best_count = read_slice_count(res.stdout)
|
||||
best_size = read_slice_size(res.stdout)
|
||||
best_efficiency = efficiency_rate
|
||||
#print("min_count =", min_count, ", max_size =", max_size, ", efficiency =", best_efficiency)
|
||||
|
||||
# If the calculated maximum slice count is too small, no need to search (QUITE UNLIKELY to happen)
|
||||
if max_slice_rate > 0 and (initial_count * max_slice_rate / 100) <= min_slice_count:
|
||||
# Giving out min_slice_count, get "real" best_size from result of Par2j64.exe
|
||||
# Here use option "/sn" to search around (from -12.5% to +6.25%) the minimum slice count for best efficiency
|
||||
cmd = "\"" + client_path + "\" t /uo /sn" + str(min_slice_count) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" *"
|
||||
res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8')
|
||||
if res.returncode != 0:
|
||||
return 0
|
||||
efficiency_rate = read_efficiency(res.stdout)
|
||||
if efficiency_rate < 0:
|
||||
return 0
|
||||
best_count = read_slice_count(res.stdout)
|
||||
best_size = read_slice_size(res.stdout)
|
||||
best_efficiency = efficiency_rate
|
||||
#print("initial_count too small, best_count =", best_count, ", best_size =", best_size, ", best_efficiency =", best_efficiency)
|
||||
# Return slice size to archive the best efficiency
|
||||
return best_size
|
||||
else:
|
||||
# Try every (step) slice count between min_count and max_count
|
||||
step_slice_count_int = int((min_count + 1) * 8 / 7)
|
||||
while step_slice_count_int < max_count:
|
||||
#print(f"Testing slice count: (around) {step_slice_count_int}, from {(step_slice_count_int - int(step_slice_count_int / 8))} to {int(step_slice_count_int * 17 / 16)}")
|
||||
# Giving out the calculated step slice count, get "real" slice count and size from result of Par2j64.exe
|
||||
# Here use option "/sn" to search around (from -12.5% to +6.25%) the calculated step slice count for best efficiency
|
||||
cmd = "\"" + client_path + "\" t /uo /sn" + str(step_slice_count_int) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" *"
|
||||
res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8')
|
||||
if res.returncode != 0:
|
||||
break
|
||||
efficiency_rate = read_efficiency(res.stdout)
|
||||
if efficiency_rate < 0:
|
||||
break
|
||||
if efficiency_rate > best_efficiency + min_efficiency_improvement:
|
||||
best_count = read_slice_count(res.stdout)
|
||||
best_size = read_slice_size(res.stdout)
|
||||
best_efficiency = efficiency_rate
|
||||
# Next count should be more than 17/16 of the input count. (Range to +6.25% was checked already.)
|
||||
step_slice_count_int = int((int(step_slice_count_int * 17 / 16) + 1) * 8 / 7)
|
||||
# Evaluate slice count searched with initial_count
|
||||
if initial_count < best_count and best_efficiency_at_initial_count > best_efficiency - min_efficiency_improvement:
|
||||
best_count = initial_count
|
||||
best_size = initial_size
|
||||
best_efficiency = best_efficiency_at_initial_count
|
||||
# Evaluate slice count searched with max_count.
|
||||
if best_efficiency_at_max_count > best_efficiency + min_efficiency_improvement:
|
||||
best_count = best_count_at_max_count
|
||||
best_size = best_size_at_max_count
|
||||
best_efficiency = best_efficiency_at_max_count
|
||||
#print("best_count =", best_count, "best_size =", best_size, ", best_efficiency =", best_efficiency)
|
||||
|
||||
return best_size
|
||||
|
||||
|
||||
# Return sub-process's ExitCode
|
||||
def command(cmd):
|
||||
ret = subprocess.run(cmd, shell=True)
|
||||
return ret.returncode
|
||||
|
||||
|
||||
# Return zero for empty folder
|
||||
def check_empty(path='.'):
|
||||
total = 0
|
||||
with os.scandir(path) as it:
|
||||
for entry in it:
|
||||
if entry.is_file():
|
||||
total += entry.stat().st_size
|
||||
elif entry.is_dir():
|
||||
total += check_empty(entry.path)
|
||||
if total > 0:
|
||||
break
|
||||
return total
|
||||
|
||||
|
||||
# Read arguments of command-line
|
||||
for idx, arg in enumerate(sys.argv[1:]):
|
||||
one_path = arg
|
||||
one_name = os.path.basename(one_path)
|
||||
|
||||
# Check the folder exists
|
||||
if os.path.isdir(one_path) == False:
|
||||
print(one_name + " isn't folder.")
|
||||
continue
|
||||
|
||||
# Check empty folder
|
||||
if check_empty(one_path) == 0:
|
||||
print(one_name + " is empty folder.")
|
||||
continue
|
||||
|
||||
print(one_name + " is folder.")
|
||||
|
||||
# Path of creating PAR file
|
||||
par_path = one_path + "\\" + one_name + ".par2"
|
||||
|
||||
# Check the PAR file exists already
|
||||
if os.path.exists(par_path):
|
||||
print(one_name + " includes PAR file already.")
|
||||
continue
|
||||
|
||||
# Test setting for good efficiency
|
||||
slice_size = test_efficiency(par_path)
|
||||
if slice_size == 0:
|
||||
print("Failed to test options.")
|
||||
continue
|
||||
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
cmd = "\"" + client_path + "\" c /ss" + str(slice_size) + " " + cmd_option + " \"" + par_path + "\" *"
|
||||
# If you want to see creating result only, use "t" command instead of "c".
|
||||
|
||||
# Process the command
|
||||
print("Creating PAR files.")
|
||||
error_level = command(cmd)
|
||||
|
||||
# Check error
|
||||
# Exit loop, when error occur.
|
||||
if error_level > 0:
|
||||
print("Error=", error_level)
|
||||
break
|
||||
|
||||
# If you don't confirm result, comment out below line.
|
||||
input('Press [Enter] key to continue . . .')
|
||||
141
alpha/tool/group_files.py
Normal file
141
alpha/tool/group_files.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
# Set path of par2j
|
||||
client_path = "../par2j64.exe"
|
||||
|
||||
# Set path of file-list
|
||||
list_path = "../save/file-list.txt"
|
||||
|
||||
|
||||
# Return sub-process's ExitCode
|
||||
def command(cmd):
|
||||
ret = subprocess.run(cmd, shell=True)
|
||||
return ret.returncode
|
||||
|
||||
|
||||
# Return zero for empty folder
|
||||
def check_empty(path='.'):
|
||||
total = 0
|
||||
with os.scandir(path) as it:
|
||||
for entry in it:
|
||||
if entry.is_file():
|
||||
total += entry.stat().st_size
|
||||
elif entry.is_dir():
|
||||
total += check_empty(entry.path)
|
||||
if total > 0:
|
||||
break
|
||||
return total
|
||||
|
||||
|
||||
# Read arguments of command-line
|
||||
for idx, arg in enumerate(sys.argv[1:]):
|
||||
one_path = arg
|
||||
one_name = os.path.basename(one_path)
|
||||
|
||||
# Check the folder exists
|
||||
if os.path.isdir(one_path) == False:
|
||||
print(one_name + " isn't folder.")
|
||||
continue
|
||||
|
||||
# Check empty folder
|
||||
if check_empty(one_path) == 0:
|
||||
print(one_name + " is empty folder.")
|
||||
continue
|
||||
|
||||
print(one_name + " is folder.")
|
||||
|
||||
# Check the PAR file exists already
|
||||
par_path = one_path + "\\#1.par2"
|
||||
if os.path.exists(par_path):
|
||||
print(one_name + " includes PAR files already.")
|
||||
continue
|
||||
|
||||
# Create PAR file for each 1000 source files.
|
||||
group_index = 1
|
||||
file_count = 0
|
||||
error_level = 0
|
||||
|
||||
# Set options for par2j
|
||||
par_option = "/rr10 /rd2"
|
||||
|
||||
# Make file-list of source files in a folder
|
||||
f = open(list_path, 'w', encoding='utf-8')
|
||||
|
||||
# Search inner directories and files
|
||||
for cur_dir, dirs, files in os.walk(one_path):
|
||||
for file_name in files:
|
||||
# Ignore existing PAR2 files
|
||||
if file_name.lower().endswith('.par2'):
|
||||
continue
|
||||
|
||||
# Save filename and sub-directory
|
||||
file_path = os.path.join(os.path.relpath(cur_dir, one_path), file_name)
|
||||
if file_path.startswith('.\\'):
|
||||
file_path = os.path.basename(file_path)
|
||||
|
||||
#print("file name=", file_path)
|
||||
f.write(file_path)
|
||||
f.write('\n')
|
||||
file_count += 1
|
||||
|
||||
# If number of source files reaches 1000, create PAR file for them.
|
||||
if file_count >= 1000:
|
||||
f.close()
|
||||
#print("file_count=", file_count)
|
||||
par_path = one_path + "\\#" + str(group_index) + ".par2"
|
||||
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
# Specify source file by file-list
|
||||
cmd = "\"" + client_path + "\" c " + par_option + " /d\"" + one_path + "\" /fu \"" + par_path + "\" \"" + list_path + "\""
|
||||
|
||||
# Process the command
|
||||
print("Creating PAR files for group:", group_index)
|
||||
error_level = command(cmd)
|
||||
|
||||
# Check error
|
||||
# Exit loop, when error occur.
|
||||
if error_level > 0:
|
||||
print("Error=", error_level)
|
||||
break
|
||||
|
||||
# Set for next group
|
||||
group_index += 1
|
||||
file_count = 0
|
||||
f = open(list_path, 'w', encoding='utf-8')
|
||||
|
||||
# Exit loop, when error occur.
|
||||
if error_level > 0:
|
||||
break
|
||||
|
||||
# Finish file-list
|
||||
f.close()
|
||||
|
||||
# If there are source files still, create the last PAR file.
|
||||
#print("file_count=", file_count)
|
||||
if file_count > 0:
|
||||
par_path = one_path + "\\#" + str(group_index) + ".par2"
|
||||
cmd = "\"" + client_path + "\" c " + par_option + " /d\"" + one_path + "\" /fu \"" + par_path + "\" \"" + list_path + "\""
|
||||
|
||||
# Process the command
|
||||
print("Creating PAR files for group:", group_index)
|
||||
error_level = command(cmd)
|
||||
|
||||
# Check error
|
||||
# Exit loop, when error occur.
|
||||
if error_level > 0:
|
||||
print("Error=", error_level)
|
||||
break
|
||||
|
||||
elif group_index == 1:
|
||||
print(one_name + " doesn't contain source files.")
|
||||
|
||||
# Delete file-list after creation
|
||||
if (group_index > 1) or (file_count > 0):
|
||||
os.remove(list_path)
|
||||
|
||||
# If you don't confirm result, comment out below line.
|
||||
input('Press [Enter] key to continue . . .')
|
||||
111
alpha/tool/large_files.py
Normal file
111
alpha/tool/large_files.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
# Set path of MultiPar
|
||||
client_path = "../MultiPar.exe"
|
||||
|
||||
# Set path of file-list
|
||||
list_path = "../save/file-list.txt"
|
||||
|
||||
|
||||
# Make file-list of source files in a folder
|
||||
# Return number of found files
|
||||
def make_list(path):
|
||||
f = open(list_path, 'w', encoding='utf-8')
|
||||
file_count = 0
|
||||
|
||||
# Search inner files
|
||||
with os.scandir(path) as it:
|
||||
for entry in it:
|
||||
# Exclude sub-folder and short-cut
|
||||
if entry.is_file() and (not entry.name.endswith('lnk')):
|
||||
#print("file name=", entry.name)
|
||||
#print("file size=", entry.stat().st_size)
|
||||
|
||||
# Check file size and ignore small files
|
||||
# Set the limit number (bytes) on below line.
|
||||
if entry.stat().st_size >= 1048576:
|
||||
f.write(entry.name)
|
||||
f.write('\n')
|
||||
file_count += 1
|
||||
|
||||
# Finish file-list
|
||||
f.close()
|
||||
return file_count
|
||||
|
||||
|
||||
# Return sub-process's ExitCode
|
||||
def command(cmd):
|
||||
ret = subprocess.run(cmd, shell=True)
|
||||
return ret.returncode
|
||||
|
||||
|
||||
# Return zero for empty folder
|
||||
def check_empty(path='.'):
|
||||
total = 0
|
||||
with os.scandir(path) as it:
|
||||
for entry in it:
|
||||
if entry.is_file():
|
||||
total += entry.stat().st_size
|
||||
elif entry.is_dir():
|
||||
total += check_empty(entry.path)
|
||||
if total > 0:
|
||||
break
|
||||
return total
|
||||
|
||||
|
||||
# Read arguments of command-line
|
||||
for idx, arg in enumerate(sys.argv[1:]):
|
||||
one_path = arg
|
||||
one_name = os.path.basename(one_path)
|
||||
|
||||
# Check the folder exists
|
||||
if os.path.isdir(one_path) == False:
|
||||
print(one_name + " isn't folder.")
|
||||
continue
|
||||
|
||||
# Check empty folder
|
||||
if check_empty(one_path) == 0:
|
||||
print(one_name + " is empty folder.")
|
||||
continue
|
||||
|
||||
print(one_name + " is folder.")
|
||||
|
||||
# Path of creating PAR file
|
||||
par_path = one_path + "\\" + one_name + ".par2"
|
||||
|
||||
# Check the PAR file exists already
|
||||
# You must check MultiPar Option: "Always use folder name for base filename".
|
||||
if os.path.exists(par_path):
|
||||
print(one_name + " includes PAR file already.")
|
||||
continue
|
||||
|
||||
# Make file-list
|
||||
file_count = make_list(one_path)
|
||||
#print("file_count=", file_count)
|
||||
if file_count > 0:
|
||||
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
# Specify source file by file-list
|
||||
# The file-list will be deleted by MultiPar automatically.
|
||||
cmd = "\"" + client_path + "\" /create /base \"" + one_path + "\" /list \"" + list_path + "\""
|
||||
|
||||
# Process the command
|
||||
print("Creating PAR files.")
|
||||
error_level = command(cmd)
|
||||
|
||||
# Check error
|
||||
# Exit loop, when error occur.
|
||||
if error_level > 0:
|
||||
print("Error=", error_level)
|
||||
break
|
||||
|
||||
else:
|
||||
print(one_name + " doesn't contain source files.")
|
||||
os.remove(list_path)
|
||||
|
||||
# If you don't confirm result, comment out below line.
|
||||
input('Press [Enter] key to continue . . .')
|
||||
468
alpha/tool/par2_rename.py
Normal file
468
alpha/tool/par2_rename.py
Normal file
@@ -0,0 +1,468 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import struct
|
||||
import hashlib
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import filedialog
|
||||
|
||||
|
||||
# Initialize global variables
|
||||
folder_path = "./"
|
||||
set_id = None
|
||||
old_name = []
|
||||
|
||||
|
||||
# Search PAR files of same base name
|
||||
def search_par_file(file_path):
|
||||
global folder_path
|
||||
|
||||
# Check file type by filename extension
|
||||
file_name = os.path.basename(file_path)
|
||||
file_base, file_ext = os.path.splitext(file_name)
|
||||
if file_ext.lower() != '.par2':
|
||||
label_status.config(text= "Selected one isn't PAR2 file.")
|
||||
return
|
||||
|
||||
# Save directory of selected file
|
||||
#print("path=", file_path)
|
||||
folder_path = os.path.dirname(file_path)
|
||||
#print("path=", folder_path)
|
||||
if folder_path == "":
|
||||
folder_path = '.'
|
||||
|
||||
# Clear list of PAR files
|
||||
old_name.clear()
|
||||
listbox_list1.delete(0, tk.END)
|
||||
listbox_list2.delete(0, tk.END)
|
||||
button_name.config(state=tk.DISABLED)
|
||||
button_save.config(state=tk.DISABLED)
|
||||
|
||||
# Compare filename in case insensitive
|
||||
base_name = file_base.lower()
|
||||
# Remove ".vol#-#", ".vol#+#", or ".vol_#" at the last
|
||||
base_name = re.sub(r'[.]vol\d*[-+_]\d+$', "", base_name)
|
||||
#print("base=", base_name)
|
||||
listbox_list1.insert(tk.END, file_name)
|
||||
file_count = 1
|
||||
|
||||
# Search other PAR2 files of same base name
|
||||
for another_path in glob.glob(glob.escape(folder_path + "/" + base_name) + "*.par2"):
|
||||
#print("path=", another_path)
|
||||
another_name = os.path.basename(another_path)
|
||||
if another_name == file_name:
|
||||
continue
|
||||
listbox_list1.insert(tk.END, another_name)
|
||||
file_count += 1
|
||||
|
||||
# Ready to read PAR2 files
|
||||
button_read.config(state=tk.NORMAL)
|
||||
label_status.config(text= "There are " + str(file_count) + " PAR2 files of same base name.")
|
||||
|
||||
|
||||
# Select file to search other PAR files
|
||||
def button_file_clicked():
|
||||
global folder_path
|
||||
|
||||
# Show file selecting dialog
|
||||
file_type = [("PAR2 file", "*.par2"), ('All files', '*')]
|
||||
file_path = filedialog.askopenfilename(filetypes=file_type, initialdir=folder_path, title="Select a PAR2 file to search others")
|
||||
if file_path == "":
|
||||
return
|
||||
search_par_file(file_path)
|
||||
|
||||
|
||||
# Read filenames in a PAR file
|
||||
def button_read_clicked():
|
||||
global folder_path, set_id
|
||||
|
||||
# Clear list of source files
|
||||
set_id = None
|
||||
old_name.clear()
|
||||
listbox_list2.delete(0, tk.END)
|
||||
|
||||
# Get name of a selected file.
|
||||
# If not selected, get the first file.
|
||||
indices = listbox_list1.curselection()
|
||||
if len(indices) == 1:
|
||||
file_name = listbox_list1.get(indices[0])
|
||||
else:
|
||||
file_name = listbox_list1.get(0)
|
||||
label_status.config(text= "Reading " + file_name + " ...")
|
||||
|
||||
|
||||
# Open the selected PAR file and read the first 2 MB.
|
||||
# Main packet and File Description packet would be smaller than 1 MB.
|
||||
f = open(folder_path + "/" + file_name, 'rb')
|
||||
data = f.read(2097152)
|
||||
data_size = len(data)
|
||||
|
||||
# Initialize
|
||||
file_count = 0
|
||||
count_now = 0
|
||||
|
||||
# Search PAR2 packets
|
||||
offset = 0
|
||||
while offset + 64 < data_size:
|
||||
if data[offset : offset + 8] == b'PAR2\x00PKT':
|
||||
# Packet size
|
||||
packet_size = struct.unpack_from('Q', data, offset + 8)[0]
|
||||
#print("size=", packet_size)
|
||||
|
||||
# If a packet is larger than buffer size, just ignore it.
|
||||
if offset + packet_size > data_size:
|
||||
offset += 8
|
||||
continue
|
||||
|
||||
# Checksum of the packet
|
||||
hash = hashlib.md5(data[offset + 32 : offset + packet_size]).digest()
|
||||
#print(hash)
|
||||
if data[offset + 16 : offset + 32] == hash:
|
||||
#print("MD5 is same.")
|
||||
|
||||
# Set ID
|
||||
if set_id == None:
|
||||
set_id = data[offset + 32 : offset + 48]
|
||||
#print("Set ID=", set_id)
|
||||
elif set_id != data[offset + 32 : offset + 48]:
|
||||
#print("Set ID is different.")
|
||||
offset += packet_size
|
||||
continue
|
||||
|
||||
# Packet type
|
||||
if data[offset + 48 : offset + 64] == b'PAR 2.0\x00Main\x00\x00\x00\x00':
|
||||
#print("Main packet")
|
||||
file_count = struct.unpack_from('I', data, offset + 72)[0]
|
||||
#print("Number of source files=", file_count)
|
||||
if file_count == 0:
|
||||
break
|
||||
elif count_now == file_count:
|
||||
break
|
||||
|
||||
elif data[offset + 48 : offset + 64] == b'PAR 2.0\x00FileDesc':
|
||||
#print("File Description packet")
|
||||
# Remove padding null bytes
|
||||
name_end = packet_size
|
||||
while data[offset + name_end - 1] == 0:
|
||||
name_end -= 1
|
||||
#print("Filename length=", name_end - 64 - 56)
|
||||
file_name = data[offset + 120 : offset + name_end].decode("UTF-8")
|
||||
#print("Filename=", file_name)
|
||||
# Ignore same name, if the name exists in the list already.
|
||||
if file_name in old_name:
|
||||
offset += packet_size
|
||||
continue
|
||||
old_name.append(file_name)
|
||||
listbox_list2.insert(tk.END, file_name)
|
||||
count_now += 1
|
||||
if count_now == file_count:
|
||||
break
|
||||
|
||||
offset += packet_size
|
||||
else:
|
||||
offset += 8
|
||||
else:
|
||||
offset += 1
|
||||
|
||||
# When it reaches to half, read next 1 MB from the PAR file.
|
||||
if offset >= 1048576:
|
||||
#print("data_size=", data_size, "offset=", offset)
|
||||
data = data[offset : data_size] + f.read(1048576)
|
||||
data_size = len(data)
|
||||
offset = 0
|
||||
|
||||
# Close file
|
||||
f.close()
|
||||
|
||||
if file_count > 0:
|
||||
button_name.config(state=tk.NORMAL)
|
||||
else:
|
||||
button_name.config(state=tk.DISABLED)
|
||||
button_save.config(state=tk.DISABLED)
|
||||
label_status.config(text= "The PAR2 file includes " + str(file_count) + " source files.")
|
||||
|
||||
|
||||
# Edit a name of a source file
|
||||
def button_name_clicked():
|
||||
# Get current name of renaming file.
|
||||
# If not selected, show error message.
|
||||
indices = listbox_list2.curselection()
|
||||
if len(indices) == 1:
|
||||
current_name = listbox_list2.get(indices[0])
|
||||
else:
|
||||
label_status.config(text= "Select a source file to rename.")
|
||||
return
|
||||
|
||||
# Get new name and check invalid characters
|
||||
new_name = entry_edit.get()
|
||||
if new_name == "":
|
||||
label_status.config(text= "Enter new name in text box.")
|
||||
return
|
||||
elif new_name == current_name:
|
||||
label_status.config(text= "New name is same as old one.")
|
||||
return
|
||||
elif "'" + new_name + "'" in s_list2.get():
|
||||
label_status.config(text= "New name is same as another filename.")
|
||||
return
|
||||
elif '\\' in new_name:
|
||||
label_status.config(text= "Filename cannot include \\.")
|
||||
return
|
||||
elif ':' in new_name:
|
||||
label_status.config(text= "Filename cannot include :.")
|
||||
return
|
||||
elif '*' in new_name:
|
||||
label_status.config(text= "Filename cannot include *.")
|
||||
return
|
||||
elif '?' in new_name:
|
||||
label_status.config(text= "Filename cannot include ?.")
|
||||
return
|
||||
elif '\"' in new_name:
|
||||
label_status.config(text= "Filename cannot include \".")
|
||||
return
|
||||
elif '<' in new_name:
|
||||
label_status.config(text= "Filename cannot include <.")
|
||||
return
|
||||
elif '>' in new_name:
|
||||
label_status.config(text= "Filename cannot include >.")
|
||||
return
|
||||
elif '|' in new_name:
|
||||
label_status.config(text= "Filename cannot include |.")
|
||||
return
|
||||
|
||||
# Add new name and remove old one
|
||||
index = indices[0]
|
||||
listbox_list2.insert(index + 1, new_name)
|
||||
listbox_list2.delete(index)
|
||||
label_status.config(text= "Renamed from \"" + current_name + "\" to \"" + new_name + "\"")
|
||||
|
||||
# Only when names are changed, it's possible to save.
|
||||
index = 0
|
||||
for name_one in old_name:
|
||||
if listbox_list2.get(index) != name_one:
|
||||
index = -1
|
||||
break
|
||||
index += 1
|
||||
if index < 0:
|
||||
button_save.config(state=tk.NORMAL)
|
||||
else:
|
||||
button_save.config(state=tk.DISABLED)
|
||||
|
||||
|
||||
# Save as new PAR files
|
||||
def button_save_clicked():
|
||||
global folder_path, set_id
|
||||
|
||||
# Allocate 64 KB buffer for File Description packet
|
||||
buffer = bytearray(65536)
|
||||
rename_count = 0
|
||||
|
||||
# For each PAR file
|
||||
file_index = 0
|
||||
file_count = listbox_list1.size()
|
||||
while file_index < file_count:
|
||||
file_name = listbox_list1.get(file_index)
|
||||
file_index += 1
|
||||
#print("Target=", file_name)
|
||||
|
||||
# Open a PAR file
|
||||
fr = open(folder_path + "/" + file_name, 'rb')
|
||||
data = fr.read(2097152)
|
||||
data_size = len(data)
|
||||
|
||||
# Name of new PAR files have prefix "new_".
|
||||
fw = open(folder_path + "/new_" + file_name, 'wb')
|
||||
|
||||
# Search PAR2 packets
|
||||
offset = 0
|
||||
while offset + 64 < data_size:
|
||||
if data[offset : offset + 8] == b'PAR2\x00PKT':
|
||||
# Packet size
|
||||
packet_size = struct.unpack_from('Q', data, offset + 8)[0]
|
||||
#print("size=", packet_size)
|
||||
|
||||
# If a packet is larger than buffer size, just ignore it.
|
||||
if offset + packet_size > data_size:
|
||||
fw.write(data[offset : offset + 8])
|
||||
offset += 8
|
||||
continue
|
||||
|
||||
# Checksum of the packet
|
||||
hash = hashlib.md5(data[offset + 32 : offset + packet_size]).digest()
|
||||
#print(hash)
|
||||
if data[offset + 16 : offset + 32] == hash:
|
||||
#print("MD5 is same.")
|
||||
|
||||
# Set ID
|
||||
if set_id != data[offset + 32 : offset + 48]:
|
||||
#print("Set ID is different.")
|
||||
fw.write(data[offset : offset + packet_size])
|
||||
offset += packet_size
|
||||
continue
|
||||
|
||||
# Packet type
|
||||
index = -1
|
||||
if data[offset + 48 : offset + 64] == b'PAR 2.0\x00FileDesc':
|
||||
#print("File Description packet")
|
||||
# Remove padding null bytes
|
||||
name_end = packet_size
|
||||
while data[offset + name_end - 1] == 0:
|
||||
name_end -= 1
|
||||
#print("Filename length=", name_end - 64 - 56)
|
||||
name_one = data[offset + 120 : offset + name_end].decode("UTF-8")
|
||||
#print("Filename=", name_one)
|
||||
if name_one in old_name:
|
||||
index = old_name.index(name_one)
|
||||
#print("index=", index)
|
||||
new_name = listbox_list2.get(index)
|
||||
if new_name != name_one:
|
||||
# Copy from old packet
|
||||
buffer[0 : 120] = data[offset : offset + 120]
|
||||
|
||||
# Set new name
|
||||
#print("New name=", new_name, "old name=", name_one)
|
||||
name_byte = new_name.encode("UTF-8")
|
||||
name_len = len(name_byte)
|
||||
#print("byte=", name_byte, "len=", name_len)
|
||||
buffer[120 : 120 + name_len] = name_byte
|
||||
|
||||
# Padding null bytes
|
||||
while name_len % 4 != 0:
|
||||
buffer[120 + name_len] = 0
|
||||
name_len += 1
|
||||
#print("padded len=", name_len)
|
||||
|
||||
# Update packet size
|
||||
size_byte = struct.pack('Q', 120 + name_len)
|
||||
buffer[8 : 16] = size_byte
|
||||
#print("packet=", buffer[0 : 120 + name_len])
|
||||
|
||||
# Update checksum of packet
|
||||
hash = hashlib.md5(buffer[32 : 120 + name_len]).digest()
|
||||
buffer[16 : 32] = hash
|
||||
|
||||
# Write new packet
|
||||
fw.write(buffer[0 : 120 + name_len])
|
||||
rename_count += 1
|
||||
|
||||
# When filename isn't changed.
|
||||
else:
|
||||
index = -2
|
||||
|
||||
# Write packet with current data
|
||||
if index < 0:
|
||||
fw.write(data[offset : offset + packet_size])
|
||||
|
||||
offset += packet_size
|
||||
else:
|
||||
fw.write(data[offset : offset + 8])
|
||||
offset += 8
|
||||
else:
|
||||
fw.write(data[offset : offset + 1])
|
||||
offset += 1
|
||||
|
||||
# When it reaches to half, read next.
|
||||
if offset >= 1048576:
|
||||
#print("data_size=", data_size, "offset=", offset)
|
||||
data = data[offset : data_size] + fr.read(1048576)
|
||||
data_size = len(data)
|
||||
offset = 0
|
||||
|
||||
# Close file
|
||||
fr.close()
|
||||
fw.close()
|
||||
|
||||
label_status.config(text= "Modified " + str(rename_count) + " packets in " + str(file_count) + " PAR2 files.")
|
||||
|
||||
|
||||
# Window size and title
|
||||
root = tk.Tk()
|
||||
root.title('PAR2 Rename')
|
||||
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(0, weight=1)
|
||||
|
||||
# Body
|
||||
frame_body = ttk.Frame(root, padding=(2,6,2,2))
|
||||
frame_body.grid(row=0, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_body.rowconfigure(0, weight=1)
|
||||
frame_body.columnconfigure(0, weight=1)
|
||||
frame_body.columnconfigure(1, weight=1)
|
||||
|
||||
# List of PAR files
|
||||
frame_list1 = ttk.Frame(frame_body, padding=(6,2,6,6), relief='groove')
|
||||
frame_list1.grid(row=0, column=0, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list1.columnconfigure(0, weight=1)
|
||||
frame_list1.rowconfigure(1, weight=1)
|
||||
|
||||
frame_top1 = ttk.Frame(frame_list1, padding=(0,4,0,3))
|
||||
frame_top1.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
button_file = ttk.Button(frame_top1, text="File", width=9, command=button_file_clicked)
|
||||
button_file.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_read = ttk.Button(frame_top1, text="Read", width=9, command=button_read_clicked, state=tk.DISABLED)
|
||||
button_read.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_save = ttk.Button(frame_top1, text="Save", width=9, command=button_save_clicked, state=tk.DISABLED)
|
||||
button_save.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
s_list1 = tk.StringVar()
|
||||
listbox_list1 = tk.Listbox(frame_list1, listvariable=s_list1, activestyle=tk.NONE)
|
||||
listbox_list1.grid(row=1, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.VERTICAL, command=listbox_list1.yview)
|
||||
scrollbar_list1.grid(row=1, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list1["yscrollcommand"] = scrollbar_list1.set
|
||||
|
||||
# List of source files
|
||||
frame_list2 = ttk.Frame(frame_body, padding=(6,2,6,6), relief='groove')
|
||||
frame_list2.grid(row=0, column=1, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list2.columnconfigure(0, weight=1)
|
||||
frame_list2.rowconfigure(1, weight=1)
|
||||
|
||||
frame_top2 = ttk.Frame(frame_list2, padding=(0,4,0,3))
|
||||
frame_top2.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
s_edit2 = tk.StringVar()
|
||||
entry_edit = ttk.Entry(frame_top2,textvariable=s_edit2)
|
||||
entry_edit.grid(row=0, column=0, padx=2, sticky=(tk.E,tk.W))
|
||||
frame_top2.columnconfigure(0, weight=1)
|
||||
|
||||
button_name = ttk.Button(frame_top2, text="Rename", width=9, command=button_name_clicked, state=tk.DISABLED)
|
||||
button_name.grid(row=0, column=1, padx=2)
|
||||
|
||||
s_list2 = tk.StringVar()
|
||||
listbox_list2 = tk.Listbox(frame_list2, listvariable=s_list2, activestyle=tk.NONE)
|
||||
listbox_list2.grid(row=1, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.VERTICAL, command=listbox_list2.yview)
|
||||
scrollbar_list2.grid(row=1, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list2["yscrollcommand"] = scrollbar_list2.set
|
||||
|
||||
# Status text
|
||||
frame_foot = ttk.Frame(root)
|
||||
frame_foot.grid(row=1, column=0, sticky=(tk.E,tk.W))
|
||||
|
||||
label_status = ttk.Label(frame_foot, text='Select a PAR2 file to rename included files.', width=100)
|
||||
label_status.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
|
||||
# When file is specified in command-line
|
||||
if len(sys.argv) > 1:
|
||||
file_path = sys.argv[1]
|
||||
if os.path.isfile(file_path):
|
||||
#file_path = os.path.abspath(file_path)
|
||||
search_par_file(file_path)
|
||||
|
||||
|
||||
# Show window
|
||||
root.mainloop()
|
||||
703
alpha/tool/queue_create.py
Normal file
703
alpha/tool/queue_create.py
Normal file
@@ -0,0 +1,703 @@
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import stat
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import filedialog
|
||||
|
||||
|
||||
# Set path of MultiPar
|
||||
client_path = "../par2j64.exe"
|
||||
gui_path = "../MultiPar.exe"
|
||||
|
||||
# Set options for par2j
|
||||
# Because /fe option is set to exclude .PAR2 files by default, no need to set here.
|
||||
cmd_option = "/rr10 /rd2"
|
||||
|
||||
|
||||
# Initialize global variables
|
||||
current_dir = "./"
|
||||
sub_proc = None
|
||||
|
||||
|
||||
# Return zero for empty folder
|
||||
def check_empty(path='.'):
|
||||
total = 0
|
||||
with os.scandir(path) as it:
|
||||
for entry in it:
|
||||
if entry.is_file():
|
||||
# Ignore hidden file
|
||||
if entry.stat().st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
|
||||
continue
|
||||
# Ignore PAR file
|
||||
entry_ext = os.path.splitext(entry.name)[1]
|
||||
if entry_ext.lower() == ".par2":
|
||||
continue
|
||||
total += entry.stat().st_size
|
||||
elif entry.is_dir():
|
||||
total += check_empty(entry.path)
|
||||
if total > 0:
|
||||
break
|
||||
return total
|
||||
|
||||
|
||||
# Search children folders or files in a parent folder
|
||||
def search_child_item(parent_path):
|
||||
parent_name = os.path.basename(parent_path)
|
||||
|
||||
# Check the folder exists already
|
||||
item_count = listbox_list1.size()
|
||||
item_path = parent_path + "\\"
|
||||
item_index = 0
|
||||
while item_index < item_count:
|
||||
index_path = listbox_list1.get(item_index)
|
||||
if os.path.samefile(item_path, index_path):
|
||||
label_status.config(text= "The folder \"" + parent_name + "\" is selected already.")
|
||||
return
|
||||
common_path = os.path.commonpath([item_path, index_path]) + "\\"
|
||||
if os.path.samefile(common_path, item_path):
|
||||
label_status.config(text= "The folder \"" + parent_name + "\" is parent of another selected item.")
|
||||
return
|
||||
if os.path.samefile(common_path, index_path):
|
||||
label_status.config(text= "The folder \"" + parent_name + "\" is child of another selected item.")
|
||||
return
|
||||
item_index += 1
|
||||
|
||||
# Add found items
|
||||
error_text = ""
|
||||
add_count = 0
|
||||
for item_name in os.listdir(parent_path):
|
||||
# Ignore PAR files (extension ".par2")
|
||||
item_ext = os.path.splitext(item_name)[1]
|
||||
if item_ext.lower() == ".par2":
|
||||
error_text += " PAR file \"" + item_name + "\" is ignored."
|
||||
continue
|
||||
|
||||
# Ignore hidden item
|
||||
item_path = os.path.join(parent_path, item_name)
|
||||
if os.stat(item_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
|
||||
error_text += " Hidden \"" + item_name + "\" is ignored."
|
||||
continue
|
||||
|
||||
# Distinguish folder or file
|
||||
if os.path.isdir(item_path):
|
||||
# Ignore empty folder
|
||||
if check_empty(item_path) == 0:
|
||||
continue
|
||||
item_path += "\\"
|
||||
|
||||
listbox_list1.insert(tk.END, item_path)
|
||||
add_count += 1
|
||||
|
||||
item_count = listbox_list1.size()
|
||||
label_head1.config(text= str(item_count) + " items")
|
||||
if item_count == 0:
|
||||
label_status.config(text= "There are no items." + error_text)
|
||||
button_start.config(state=tk.DISABLED)
|
||||
elif add_count == 0:
|
||||
label_status.config(text= "No items were found in folder \"" + parent_name + "\"." + error_text)
|
||||
else:
|
||||
label_status.config(text= str(add_count) + " items were found in folder \"" + parent_name + "\"." + error_text)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
|
||||
|
||||
# Select a folder to search children folders or files
|
||||
def button_parent_clicked():
|
||||
global current_dir
|
||||
if os.path.exists(current_dir) == False:
|
||||
current_dir = "./"
|
||||
search_dir = filedialog.askdirectory(initialdir=current_dir)
|
||||
if search_dir == "":
|
||||
return
|
||||
current_dir = search_dir
|
||||
search_child_item(search_dir)
|
||||
|
||||
|
||||
# Reset lists and display status
|
||||
def button_reset_clicked():
|
||||
global current_dir
|
||||
current_dir = "./"
|
||||
|
||||
# Clear list-box at first
|
||||
listbox_list1.delete(0, tk.END)
|
||||
listbox_list2.delete(0, tk.END)
|
||||
|
||||
# Reset statues text
|
||||
label_head1.config(text= '0 items')
|
||||
label_head2.config(text= '0 finished items')
|
||||
label_status.config(text= 'Select folders and/or files to create PAR files.')
|
||||
|
||||
# Reset button state
|
||||
button_parent.config(state=tk.NORMAL)
|
||||
button_child.config(state=tk.NORMAL)
|
||||
button_file.config(state=tk.NORMAL)
|
||||
button_reset.config(state=tk.DISABLED)
|
||||
button_start.config(state=tk.DISABLED)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_remove.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.DISABLED)
|
||||
|
||||
|
||||
# Check and add items
|
||||
def add_argv_item():
|
||||
# Add specified items
|
||||
error_text = ""
|
||||
for one_path in sys.argv[1:]:
|
||||
# Make sure to be absolute path
|
||||
item_path = os.path.abspath(one_path)
|
||||
if os.path.exists(item_path) == False:
|
||||
error_text += " \"" + item_path + "\" doesn't exist."
|
||||
continue
|
||||
#print(item_path)
|
||||
|
||||
# Ignore PAR files (extension ".par2")
|
||||
item_name = os.path.basename(item_path)
|
||||
item_ext = os.path.splitext(item_name)[1]
|
||||
if item_ext.lower() == ".par2":
|
||||
error_text += " PAR file \"" + item_name + "\" is ignored."
|
||||
continue
|
||||
|
||||
# Ignore hidden item
|
||||
if os.stat(item_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
|
||||
error_text += " Hidden \"" + item_name + "\" is ignored."
|
||||
continue
|
||||
|
||||
# Distinguish folder or file
|
||||
if os.path.isdir(item_path):
|
||||
# Ignore empty folder
|
||||
if check_empty(item_path) == 0:
|
||||
continue
|
||||
item_path += "\\"
|
||||
|
||||
# Check the item exists already or duplicates
|
||||
item_count = listbox_list1.size()
|
||||
item_index = 0
|
||||
while item_index < item_count:
|
||||
index_path = listbox_list1.get(item_index)
|
||||
if os.path.samefile(item_path, index_path):
|
||||
error_text += " \"" + item_name + "\" is selected already."
|
||||
item_count = -1
|
||||
break
|
||||
common_path = os.path.commonpath([item_path, index_path]) + "\\"
|
||||
if os.path.samefile(common_path, item_path):
|
||||
error_text += " \"" + item_name + "\" is parent of another selected item."
|
||||
item_count = -1
|
||||
break
|
||||
if os.path.samefile(common_path, index_path):
|
||||
error_text += " \"" + item_name + "\" is child of another selected item."
|
||||
item_count = -1
|
||||
break
|
||||
item_index += 1
|
||||
if item_count < 0:
|
||||
continue
|
||||
|
||||
listbox_list1.insert(tk.END, item_path)
|
||||
|
||||
item_count = listbox_list1.size()
|
||||
label_head1.config(text= str(item_count) + " items")
|
||||
if item_count == 0:
|
||||
label_status.config(text= "There are no items." + error_text)
|
||||
else:
|
||||
label_status.config(text= str(item_count) + " items were selected at first." + error_text)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
|
||||
|
||||
# Verify the first PAR set
|
||||
def queue_run():
|
||||
global sub_proc
|
||||
|
||||
if sub_proc != None:
|
||||
return
|
||||
|
||||
if "disabled" in button_stop.state():
|
||||
button_parent.config(state=tk.NORMAL)
|
||||
button_child.config(state=tk.NORMAL)
|
||||
button_file.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Stopped queue")
|
||||
return
|
||||
|
||||
item_path = listbox_list1.get(0)
|
||||
if item_path == "":
|
||||
label_status.config(text= "There are no items.")
|
||||
return
|
||||
|
||||
# When it's a folder, create PAR2 files for inner files.
|
||||
if item_path[-1:] == "\\":
|
||||
base_name = os.path.basename(item_path[:-1]) + ".par2"
|
||||
source_path = item_path + "*"
|
||||
par_path = item_path + base_name
|
||||
# When it's a file, create PAR2 files for the file.
|
||||
else:
|
||||
base_name = os.path.basename(item_path) + ".par2"
|
||||
source_path = item_path
|
||||
par_path = item_path + ".par2"
|
||||
label_status.config(text= "Creating \"" + base_name + "\"")
|
||||
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
cmd = "\"" + client_path + "\" c /fe\"**.par2\" " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\""
|
||||
# If you want to see creating result only, use "t" command instead of "c".
|
||||
#print(cmd)
|
||||
|
||||
# Run PAR2 client
|
||||
sub_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
# Wait finish of creation
|
||||
root.after(300, queue_result)
|
||||
|
||||
|
||||
# Wait and read created result
|
||||
def queue_result():
|
||||
global sub_proc
|
||||
|
||||
# When sub-process was not started yet
|
||||
if sub_proc == None:
|
||||
return
|
||||
|
||||
# When sub-process is running still
|
||||
exit_code = sub_proc.poll()
|
||||
if exit_code == None:
|
||||
# Call self again
|
||||
root.after(300, queue_result)
|
||||
return
|
||||
|
||||
sub_proc = None
|
||||
item_path = listbox_list1.get(0)
|
||||
|
||||
# When fatal error happened in par2j
|
||||
if exit_code == 1:
|
||||
button_parent.config(state=tk.NORMAL)
|
||||
button_child.config(state=tk.NORMAL)
|
||||
button_file.config(state=tk.NORMAL)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Failed queue")
|
||||
return
|
||||
|
||||
# When you cancel par2j on Command Prompt
|
||||
elif exit_code == 2:
|
||||
button_parent.config(state=tk.NORMAL)
|
||||
button_child.config(state=tk.NORMAL)
|
||||
button_file.config(state=tk.NORMAL)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Canceled queue")
|
||||
return
|
||||
|
||||
# When par files were created successfully
|
||||
else:
|
||||
#print("exit code =", exit_code)
|
||||
# Add to list of finished items
|
||||
listbox_list2.insert(tk.END, item_path)
|
||||
item_count = listbox_list2.size()
|
||||
label_head2.config(text= str(item_count) + " finished items")
|
||||
|
||||
# Remove the first item from the list
|
||||
listbox_list1.delete(0)
|
||||
|
||||
# Process next set
|
||||
item_count = listbox_list1.size()
|
||||
if item_count == 0:
|
||||
button_parent.config(state=tk.NORMAL)
|
||||
button_child.config(state=tk.NORMAL)
|
||||
button_file.config(state=tk.NORMAL)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Created all items")
|
||||
|
||||
elif "disabled" in button_stop.state():
|
||||
button_parent.config(state=tk.NORMAL)
|
||||
button_child.config(state=tk.NORMAL)
|
||||
button_file.config(state=tk.NORMAL)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Interrupted queue")
|
||||
|
||||
else:
|
||||
root.after(100, queue_run)
|
||||
|
||||
|
||||
# Select a child folder to add
|
||||
def button_child_clicked():
|
||||
global current_dir
|
||||
if os.path.exists(current_dir) == False:
|
||||
current_dir = "./"
|
||||
one_path = filedialog.askdirectory(initialdir=current_dir)
|
||||
if one_path == "":
|
||||
return
|
||||
current_dir = os.path.dirname(one_path)
|
||||
|
||||
# Check the folder has content
|
||||
one_name = os.path.basename(one_path)
|
||||
if check_empty(one_path) == 0:
|
||||
label_status.config(text= "Selected folder \"" + one_name + "\" is empty.")
|
||||
return
|
||||
|
||||
# Check the folder is new
|
||||
one_path += "\\"
|
||||
item_count = listbox_list1.size()
|
||||
item_index = 0
|
||||
while item_index < item_count:
|
||||
index_path = listbox_list1.get(item_index)
|
||||
if os.path.samefile(one_path, index_path):
|
||||
label_status.config(text= "Folder \"" + one_name + "\" is selected already.")
|
||||
return
|
||||
common_path = os.path.commonpath([one_path, index_path]) + "\\"
|
||||
if os.path.samefile(common_path, one_path):
|
||||
label_status.config(text= "Folder \"" + one_name + "\" is parent of another selected item.")
|
||||
return
|
||||
if os.path.samefile(common_path, index_path):
|
||||
label_status.config(text= "Folder \"" + one_name + "\" is child of another selected item.")
|
||||
return
|
||||
item_index += 1
|
||||
listbox_list1.insert(tk.END, one_path)
|
||||
item_count += 1
|
||||
|
||||
label_head1.config(text= str(item_count) + " items")
|
||||
label_status.config(text= "Folder \"" + one_name + "\" was added.")
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
|
||||
|
||||
# Select multiple children files to add
|
||||
def button_file_clicked():
|
||||
global current_dir
|
||||
if os.path.exists(current_dir) == False:
|
||||
current_dir = "./"
|
||||
multi_path = filedialog.askopenfilenames(initialdir=current_dir)
|
||||
if len(multi_path) == 0:
|
||||
return
|
||||
one_path = multi_path[0]
|
||||
current_dir = os.path.dirname(one_path)
|
||||
|
||||
# Add selected items
|
||||
error_text = ""
|
||||
add_count = 0
|
||||
for one_path in multi_path:
|
||||
# Ignore PAR file (extension ".par2")
|
||||
one_name = os.path.basename(one_path)
|
||||
item_ext = os.path.splitext(one_name)[1]
|
||||
if item_ext.lower() == ".par2":
|
||||
error_text += " PAR file \"" + one_name + "\" is ignored."
|
||||
continue
|
||||
|
||||
# Ignore hidden file
|
||||
if os.stat(one_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
|
||||
error_text += " Hidden \"" + one_name + "\" is ignored."
|
||||
continue
|
||||
|
||||
# Check the file is new
|
||||
item_count = listbox_list1.size()
|
||||
item_index = 0
|
||||
while item_index < item_count:
|
||||
index_path = listbox_list1.get(item_index)
|
||||
if os.path.samefile(one_path, index_path):
|
||||
error_text += " \"" + one_name + "\" is selected already."
|
||||
item_count = -1
|
||||
break
|
||||
common_path = os.path.commonpath([one_path, index_path]) + "\\"
|
||||
if os.path.samefile(common_path, index_path):
|
||||
error_text += " \"" + one_name + "\" is child of another selected item."
|
||||
item_count = -1
|
||||
break
|
||||
item_index += 1
|
||||
if item_count < 0:
|
||||
continue
|
||||
|
||||
add_name = one_name
|
||||
listbox_list1.insert(tk.END, one_path)
|
||||
add_count += 1
|
||||
|
||||
item_count = listbox_list1.size()
|
||||
label_head1.config(text= str(item_count) + " items")
|
||||
if item_count == 0:
|
||||
label_status.config(text= "There are no items." + error_text)
|
||||
button_start.config(state=tk.DISABLED)
|
||||
elif add_count == 0:
|
||||
label_status.config(text= "No files were added." + error_text)
|
||||
else:
|
||||
if add_count == 1:
|
||||
label_status.config(text= "File \"" + add_name + "\" was added." + error_text)
|
||||
else:
|
||||
label_status.config(text= str(add_count) + " files were added." + error_text)
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
|
||||
|
||||
# Select a child file to add
|
||||
def button_file1_clicked():
|
||||
global current_dir
|
||||
if os.path.exists(current_dir) == False:
|
||||
current_dir = "./"
|
||||
one_path = filedialog.askopenfilename(initialdir=current_dir)
|
||||
if one_path == "":
|
||||
return
|
||||
current_dir = os.path.dirname(one_path)
|
||||
|
||||
# Ignore PAR file (extension ".par2")
|
||||
one_name = os.path.basename(one_path)
|
||||
item_ext = os.path.splitext(one_name)[1]
|
||||
# Compare filename in case insensitive
|
||||
item_ext = item_ext.lower()
|
||||
if item_ext == ".par2":
|
||||
label_status.config(text= "PAR file \"" + one_name + "\" is ignored.")
|
||||
return
|
||||
|
||||
# Ignore hidden file
|
||||
if os.stat(one_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
|
||||
label_status.config(text= "Hidden \"" + one_name + "\" is ignored.")
|
||||
return
|
||||
|
||||
# Check the file is new
|
||||
item_count = listbox_list1.size()
|
||||
item_index = 0
|
||||
while item_index < item_count:
|
||||
index_path = listbox_list1.get(item_index)
|
||||
if os.path.samefile(one_path, index_path):
|
||||
label_status.config(text= "File \"" + one_name + "\" is selected already.")
|
||||
return
|
||||
common_path = os.path.commonpath([one_path, index_path]) + "\\"
|
||||
if os.path.samefile(common_path, index_path):
|
||||
label_status.config(text= "File \"" + one_name + "\" is child of another selected item.")
|
||||
return
|
||||
item_index += 1
|
||||
|
||||
listbox_list1.insert(tk.END, one_path)
|
||||
item_count += 1
|
||||
|
||||
label_head1.config(text= str(item_count) + " items")
|
||||
label_status.config(text= "File \"" + one_name + "\" was added.")
|
||||
button_reset.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.NORMAL)
|
||||
|
||||
|
||||
# Resume stopped queue
|
||||
def button_start_clicked():
|
||||
global sub_proc
|
||||
|
||||
item_count = listbox_list1.size()
|
||||
if item_count == 0:
|
||||
label_status.config(text= "There are no items.")
|
||||
return
|
||||
|
||||
button_parent.config(state=tk.DISABLED)
|
||||
button_child.config(state=tk.DISABLED)
|
||||
button_file.config(state=tk.DISABLED)
|
||||
button_reset.config(state=tk.DISABLED)
|
||||
button_start.config(state=tk.DISABLED)
|
||||
button_stop.config(state=tk.NORMAL)
|
||||
button_remove.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.DISABLED)
|
||||
|
||||
if sub_proc == None:
|
||||
queue_run()
|
||||
else:
|
||||
queue_result()
|
||||
|
||||
|
||||
# Stop running queue
|
||||
def button_stop_clicked():
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
if sub_proc != None:
|
||||
label_status.config(text= "Waiting finish of current task")
|
||||
|
||||
|
||||
# Remove items from list
|
||||
def button_remove_clicked():
|
||||
# It's possible to select multiple items.
|
||||
selected_indices = listbox_list1.curselection()
|
||||
selected_count = len(selected_indices)
|
||||
if selected_count == 0:
|
||||
label_status.config(text= "Select items to remove at first.")
|
||||
return
|
||||
|
||||
label_status.config(text= "Removed " + str(selected_count) + " items.")
|
||||
while selected_count > 0:
|
||||
selected_count -= 1
|
||||
selected_index = selected_indices[selected_count]
|
||||
# Remove selected items at once
|
||||
listbox_list1.delete(selected_index)
|
||||
|
||||
item_count = listbox_list1.size()
|
||||
label_head1.config(text= str(item_count) + " items")
|
||||
if item_count == 0:
|
||||
button_start.config(state=tk.DISABLED)
|
||||
button_remove.config(state=tk.DISABLED)
|
||||
|
||||
|
||||
# Open a PAR set by MultiPar
|
||||
def button_open2_clicked():
|
||||
if os.path.exists(gui_path) == False:
|
||||
label_status.config(text= "Cannot call \"" + gui_path + "\". Set path correctly.")
|
||||
return
|
||||
|
||||
indices = listbox_list2.curselection()
|
||||
if len(indices) == 1:
|
||||
item_path = listbox_list2.get(indices[0])
|
||||
if item_path[-1:] == "\\":
|
||||
base_name = os.path.basename(item_path[:-1]) + ".par2"
|
||||
par_path = item_path + base_name
|
||||
else:
|
||||
base_name = os.path.basename(item_path) + ".par2"
|
||||
par_path = item_path + ".par2"
|
||||
label_status.config(text= "Opening \"" + base_name + "\"")
|
||||
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
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)
|
||||
|
||||
else:
|
||||
label_status.config(text= "Select one item to open at first.")
|
||||
|
||||
|
||||
# Window size and title
|
||||
root = tk.Tk()
|
||||
root.title('PAR Queue - Create')
|
||||
root.minsize(width=480, height=200)
|
||||
# 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_parent = ttk.Button(frame_top, text="Search inner folder", width=18, command=button_parent_clicked)
|
||||
button_parent.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_child = ttk.Button(frame_top, text="Add single folder", width=16, command=button_child_clicked)
|
||||
button_child.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_file = ttk.Button(frame_top, text="Add multi files", width=14, command=button_file_clicked)
|
||||
button_file.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_reset = ttk.Button(frame_top, text="Reset lists", width=11, command=button_reset_clicked, state=tk.DISABLED)
|
||||
button_reset.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
# List
|
||||
frame_middle = ttk.Frame(root, padding=(2,2,2,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)
|
||||
frame_middle.columnconfigure(1, weight=1)
|
||||
|
||||
# List of children items (folders and files)
|
||||
frame_list1 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove')
|
||||
frame_list1.grid(row=0, column=0, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list1.columnconfigure(0, weight=1)
|
||||
frame_list1.rowconfigure(2, weight=1)
|
||||
|
||||
frame_top1 = ttk.Frame(frame_list1, padding=(0,4,0,3))
|
||||
frame_top1.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
button_start = ttk.Button(frame_top1, text="Start", width=6, command=button_start_clicked, state=tk.DISABLED)
|
||||
button_start.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_stop = ttk.Button(frame_top1, text="Stop", width=6, command=button_stop_clicked, state=tk.DISABLED)
|
||||
button_stop.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_remove = ttk.Button(frame_top1, text="Remove", width=8, command=button_remove_clicked, state=tk.DISABLED)
|
||||
button_remove.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
label_head1 = ttk.Label(frame_list1, text='0 items')
|
||||
label_head1.grid(row=1, column=0, columnspan=2)
|
||||
|
||||
s_list1 = tk.StringVar()
|
||||
listbox_list1 = tk.Listbox(frame_list1, listvariable=s_list1, activestyle='none', selectmode='extended')
|
||||
listbox_list1.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.VERTICAL, command=listbox_list1.yview)
|
||||
scrollbar_list1.grid(row=2, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list1["yscrollcommand"] = scrollbar_list1.set
|
||||
|
||||
xscrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.HORIZONTAL, command=listbox_list1.xview)
|
||||
xscrollbar_list1.grid(row=3, column=0, sticky=(tk.E, tk.W))
|
||||
listbox_list1["xscrollcommand"] = xscrollbar_list1.set
|
||||
|
||||
# List of finished items
|
||||
frame_list2 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove')
|
||||
frame_list2.grid(row=0, column=1, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list2.columnconfigure(0, weight=1)
|
||||
frame_list2.rowconfigure(2, weight=1)
|
||||
|
||||
frame_top2 = ttk.Frame(frame_list2, padding=(0,4,0,3))
|
||||
frame_top2.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
button_open2 = ttk.Button(frame_top2, text="Open with MultiPar", width=20, command=button_open2_clicked, state=tk.DISABLED)
|
||||
button_open2.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
label_head2 = ttk.Label(frame_list2, text='0 finished items')
|
||||
label_head2.grid(row=1, column=0, columnspan=2)
|
||||
|
||||
s_list2 = tk.StringVar()
|
||||
listbox_list2 = tk.Listbox(frame_list2, listvariable=s_list2, activestyle='none')
|
||||
listbox_list2.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.VERTICAL, command=listbox_list2.yview)
|
||||
scrollbar_list2.grid(row=2, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list2["yscrollcommand"] = scrollbar_list2.set
|
||||
|
||||
xscrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.HORIZONTAL, command=listbox_list2.xview)
|
||||
xscrollbar_list2.grid(row=3, column=0, sticky=(tk.E, tk.W))
|
||||
listbox_list2["xscrollcommand"] = xscrollbar_list2.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 folders and/or files to create PAR files.')
|
||||
label_status.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
|
||||
# When a folder is specified in command-line
|
||||
if len(sys.argv) == 2:
|
||||
one_path = os.path.abspath(sys.argv[1])
|
||||
if os.path.isdir(one_path):
|
||||
search_child_item(one_path)
|
||||
else:
|
||||
add_argv_item()
|
||||
|
||||
# When multiple items are specified
|
||||
elif len(sys.argv) > 2:
|
||||
add_argv_item()
|
||||
|
||||
# If you want to start creation automatically, use below lines.
|
||||
#if listbox_list1.size() > 0:
|
||||
# button_parent.config(state=tk.DISABLED)
|
||||
# button_child.config(state=tk.DISABLED)
|
||||
# button_file.config(state=tk.DISABLED)
|
||||
# button_stop.config(state=tk.NORMAL)
|
||||
# root.after(100, queue_run)
|
||||
|
||||
|
||||
# Show window
|
||||
root.mainloop()
|
||||
400
alpha/tool/queue_verify.py
Normal file
400
alpha/tool/queue_verify.py
Normal file
@@ -0,0 +1,400 @@
|
||||
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 MultiPar
|
||||
client_path = "../par2j64.exe"
|
||||
gui_path = "../MultiPar.exe"
|
||||
save_path = "../save"
|
||||
|
||||
|
||||
# Initialize global variables
|
||||
folder_path = ""
|
||||
sub_proc = None
|
||||
|
||||
|
||||
# Search PAR2 sets in a folder
|
||||
def search_par_set(one_path):
|
||||
if one_path == "":
|
||||
return
|
||||
|
||||
# Clear list-box at first
|
||||
listbox_list1.delete(0, tk.END)
|
||||
listbox_list2.delete(0, tk.END)
|
||||
listbox_list3.delete(0, tk.END)
|
||||
button_start.config(state=tk.DISABLED)
|
||||
|
||||
# Add found PAR sets
|
||||
for par_path in glob.glob(glob.escape(one_path) + "/*.par2"):
|
||||
# Remove extension ".par2"
|
||||
one_name = os.path.splitext( os.path.basename(par_path) )[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)
|
||||
# Ignore same base, if the name exists in the list already.
|
||||
if "'" + base_name + "'" in s_list1.get():
|
||||
continue
|
||||
listbox_list1.insert(tk.END, base_name)
|
||||
|
||||
|
||||
# Add found PAR sets in sub-directories
|
||||
# This searches 1 level child only, because recursive search may be slow.
|
||||
for par_path in glob.glob(glob.escape(one_path) + "/*/*.par2"):
|
||||
# If you want to search recursively, use below line instead of above line.
|
||||
#for par_path in glob.glob(glob.escape(one_path) + "/**/*.par2", recursive=True):
|
||||
# Get relative path and convert to UNIX style directory mark
|
||||
rel_path = os.path.relpath(par_path, one_path)
|
||||
rel_path = rel_path.replace('\\', '/')
|
||||
# Remove extension ".par2"
|
||||
one_name = os.path.splitext(rel_path)[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)
|
||||
# Ignore same base, if the name exists in the list already.
|
||||
if "'" + base_name + "'" in s_list1.get():
|
||||
continue
|
||||
listbox_list1.insert(tk.END, base_name)
|
||||
|
||||
item_count = listbox_list1.size()
|
||||
one_name = os.path.basename(one_path)
|
||||
label_head1.config(text= str(item_count) + " sets in " + one_name)
|
||||
if item_count == 0:
|
||||
label_status.config(text= "There are no PAR sets in \"" + one_path + "\".")
|
||||
else:
|
||||
button_folder.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.DISABLED)
|
||||
button_open3.config(state=tk.DISABLED)
|
||||
# If you want to start manually, use these lines instead of below lines.
|
||||
#label_status.config(text= str(item_count) + " sets were found in \"" + one_path + "\".")
|
||||
button_start.config(state=tk.NORMAL)
|
||||
# If you want to start verification automatically, use these lines instead of above lines.
|
||||
#button_stop.config(state=tk.NORMAL)
|
||||
#root.after(100, queue_run)
|
||||
|
||||
|
||||
# Select folder to search PAR files
|
||||
def button_folder_clicked():
|
||||
global folder_path
|
||||
if folder_path == "":
|
||||
s_initialdir = "./"
|
||||
else:
|
||||
s_initialdir = os.path.dirname(folder_path)
|
||||
folder_path = filedialog.askdirectory(initialdir=s_initialdir)
|
||||
if folder_path == "":
|
||||
return
|
||||
search_par_set(folder_path)
|
||||
|
||||
|
||||
# Verify the first PAR set
|
||||
def queue_run():
|
||||
global folder_path, sub_proc
|
||||
|
||||
if sub_proc != None:
|
||||
return
|
||||
|
||||
if "disabled" in button_stop.state():
|
||||
button_folder.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
button_open3.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Stopped queue")
|
||||
return
|
||||
|
||||
if folder_path == "":
|
||||
label_status.config(text= "Select a folder at first.")
|
||||
return
|
||||
base_name = listbox_list1.get(0)
|
||||
if base_name == "":
|
||||
label_status.config(text= "There are no PAR sets.")
|
||||
return
|
||||
|
||||
one_path = folder_path + "\\" + base_name + ".par2"
|
||||
label_status.config(text= "Verifying " + base_name)
|
||||
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
cmd = "\"" + client_path + "\" v /fo /vs2 /vd\"" + save_path + "\" \"" + one_path + "\""
|
||||
# If you want to repair a damaged set automatically, use "r" command instead of "v".
|
||||
#print(cmd)
|
||||
|
||||
# Run PAR2 client
|
||||
sub_proc = subprocess.Popen(cmd, shell=True)
|
||||
|
||||
# Wait finish of verification
|
||||
root.after(300, queue_result)
|
||||
|
||||
|
||||
# Wait and read verification result
|
||||
def queue_result():
|
||||
global folder_path, sub_proc
|
||||
|
||||
# When sub-process was not started yet
|
||||
if sub_proc == None:
|
||||
return
|
||||
|
||||
# When sub-process is running still
|
||||
exit_code = sub_proc.poll()
|
||||
if exit_code == None:
|
||||
# Call self again
|
||||
root.after(300, queue_result)
|
||||
return
|
||||
|
||||
sub_proc = None
|
||||
base_name = listbox_list1.get(0)
|
||||
|
||||
# When all source files are complete
|
||||
if (exit_code == 0) or (exit_code == 256):
|
||||
# Add to list of complete set
|
||||
listbox_list3.insert(tk.END, base_name)
|
||||
item_count = listbox_list3.size()
|
||||
label_head3.config(text= str(item_count) + " complete sets")
|
||||
|
||||
# When fatal error happened in par2j
|
||||
elif exit_code == 1:
|
||||
button_folder.config(state=tk.NORMAL)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
button_open3.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Failed queue")
|
||||
return
|
||||
|
||||
# When you cancel par2j on Command Prompt
|
||||
elif exit_code == 2:
|
||||
button_folder.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
button_open3.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Canceled queue")
|
||||
return
|
||||
|
||||
# When source files are bad
|
||||
else:
|
||||
#print("exit code =", exit_code)
|
||||
# Add to list of bad set
|
||||
listbox_list2.insert(tk.END, base_name)
|
||||
item_count = listbox_list2.size()
|
||||
label_head2.config(text= str(item_count) + " bad sets")
|
||||
|
||||
# Remove the first item from the list
|
||||
listbox_list1.delete(0)
|
||||
|
||||
# Process next set
|
||||
item_count = listbox_list1.size()
|
||||
if item_count == 0:
|
||||
button_folder.config(state=tk.NORMAL)
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
button_open3.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Verified all PAR sets")
|
||||
|
||||
elif "disabled" in button_stop.state():
|
||||
button_folder.config(state=tk.NORMAL)
|
||||
button_start.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.NORMAL)
|
||||
button_open3.config(state=tk.NORMAL)
|
||||
label_status.config(text= "Interrupted queue")
|
||||
|
||||
else:
|
||||
root.after(100, queue_run)
|
||||
|
||||
|
||||
# Resume stopped queue
|
||||
def button_start_clicked():
|
||||
global sub_proc
|
||||
|
||||
button_folder.config(state=tk.DISABLED)
|
||||
button_start.config(state=tk.DISABLED)
|
||||
button_stop.config(state=tk.NORMAL)
|
||||
button_open2.config(state=tk.DISABLED)
|
||||
button_open3.config(state=tk.DISABLED)
|
||||
|
||||
if sub_proc == None:
|
||||
queue_run()
|
||||
else:
|
||||
queue_result()
|
||||
|
||||
|
||||
# Stop running queue
|
||||
def button_stop_clicked():
|
||||
button_stop.config(state=tk.DISABLED)
|
||||
if sub_proc != None:
|
||||
label_status.config(text= "Waiting finish of current task")
|
||||
|
||||
|
||||
# Open a PAR set by MultiPar
|
||||
def button_open2_clicked():
|
||||
if os.path.exists(gui_path) == False:
|
||||
label_status.config(text= "Cannot call \"" + gui_path + "\". Set path correctly.")
|
||||
return
|
||||
|
||||
indices = listbox_list2.curselection()
|
||||
if len(indices) == 1:
|
||||
base_name = listbox_list2.get(indices[0])
|
||||
one_path = folder_path + "\\" + base_name + ".par2"
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
cmd = "\"" + gui_path + "\" /verify \"" + one_path + "\""
|
||||
|
||||
# Open MultiPar GUI to see details
|
||||
# Because this doesn't wait finish of MultiPar, you may open some at once.
|
||||
subprocess.Popen(cmd)
|
||||
|
||||
|
||||
def button_open3_clicked():
|
||||
if os.path.exists(gui_path) == False:
|
||||
label_status.config(text= "Cannot call \"" + gui_path + "\". Set path correctly.")
|
||||
return
|
||||
|
||||
indices = listbox_list3.curselection()
|
||||
if len(indices) == 1:
|
||||
base_name = listbox_list3.get(indices[0])
|
||||
one_path = folder_path + "\\" + base_name + ".par2"
|
||||
# Set command-line
|
||||
# Cover path by " for possible space
|
||||
cmd = "\"" + gui_path + "\" /verify \"" + one_path + "\""
|
||||
|
||||
# Open MultiPar GUI to see details
|
||||
# Because this doesn't wait finish of MultiPar, you may open some at once.
|
||||
subprocess.Popen(cmd)
|
||||
|
||||
|
||||
# Window size and title
|
||||
root = tk.Tk()
|
||||
root.title('PAR Queue - Verify')
|
||||
root.minsize(width=520, height=200)
|
||||
# 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(0, weight=1)
|
||||
|
||||
# List
|
||||
frame_middle = ttk.Frame(root, padding=(2,6,2,2))
|
||||
frame_middle.grid(row=0, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_middle.rowconfigure(0, weight=1)
|
||||
frame_middle.columnconfigure(0, weight=1)
|
||||
frame_middle.columnconfigure(1, weight=1)
|
||||
frame_middle.columnconfigure(2, weight=1)
|
||||
|
||||
# List of PAR files
|
||||
frame_list1 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove')
|
||||
frame_list1.grid(row=0, column=0, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list1.columnconfigure(0, weight=1)
|
||||
frame_list1.rowconfigure(2, weight=1)
|
||||
|
||||
frame_top1 = ttk.Frame(frame_list1, padding=(0,4,0,3))
|
||||
frame_top1.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
button_folder = ttk.Button(frame_top1, text="Folder", width=7, command=button_folder_clicked)
|
||||
button_folder.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_start = ttk.Button(frame_top1, text="Start", width=6, command=button_start_clicked, state=tk.DISABLED)
|
||||
button_start.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
button_stop = ttk.Button(frame_top1, text="Stop", width=6, command=button_stop_clicked, state=tk.DISABLED)
|
||||
button_stop.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
label_head1 = ttk.Label(frame_list1, text='? sets in a folder')
|
||||
label_head1.grid(row=1, column=0, columnspan=2)
|
||||
|
||||
s_list1 = tk.StringVar()
|
||||
listbox_list1 = tk.Listbox(frame_list1, listvariable=s_list1, activestyle='none')
|
||||
listbox_list1.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.VERTICAL, command=listbox_list1.yview)
|
||||
scrollbar_list1.grid(row=2, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list1["yscrollcommand"] = scrollbar_list1.set
|
||||
|
||||
xscrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.HORIZONTAL, command=listbox_list1.xview)
|
||||
xscrollbar_list1.grid(row=3, column=0, sticky=(tk.E, tk.W))
|
||||
listbox_list1["xscrollcommand"] = xscrollbar_list1.set
|
||||
|
||||
# List of bad files
|
||||
frame_list2 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove')
|
||||
frame_list2.grid(row=0, column=1, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list2.columnconfigure(0, weight=1)
|
||||
frame_list2.rowconfigure(2, weight=1)
|
||||
|
||||
frame_top2 = ttk.Frame(frame_list2, padding=(0,4,0,3))
|
||||
frame_top2.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
button_open2 = ttk.Button(frame_top2, text="Open with MultiPar", command=button_open2_clicked, state=tk.DISABLED)
|
||||
button_open2.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
label_head2 = ttk.Label(frame_list2, text='0 bad sets')
|
||||
label_head2.grid(row=1, column=0, columnspan=2)
|
||||
|
||||
s_list2 = tk.StringVar()
|
||||
listbox_list2 = tk.Listbox(frame_list2, listvariable=s_list2, activestyle='none')
|
||||
listbox_list2.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.VERTICAL, command=listbox_list2.yview)
|
||||
scrollbar_list2.grid(row=2, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list2["yscrollcommand"] = scrollbar_list2.set
|
||||
|
||||
xscrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.HORIZONTAL, command=listbox_list2.xview)
|
||||
xscrollbar_list2.grid(row=3, column=0, sticky=(tk.E, tk.W))
|
||||
listbox_list2["xscrollcommand"] = xscrollbar_list2.set
|
||||
|
||||
# List of complete files
|
||||
frame_list3 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove')
|
||||
frame_list3.grid(row=0, column=2, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
frame_list3.columnconfigure(0, weight=1)
|
||||
frame_list3.rowconfigure(2, weight=1)
|
||||
|
||||
frame_top3 = ttk.Frame(frame_list3, padding=(0,4,0,3))
|
||||
frame_top3.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W))
|
||||
|
||||
button_open3 = ttk.Button(frame_top3, text="Open with MultiPar", command=button_open3_clicked, state=tk.DISABLED)
|
||||
button_open3.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
label_head3 = ttk.Label(frame_list3, text='0 complete sets')
|
||||
label_head3.grid(row=1, column=0, columnspan=2)
|
||||
|
||||
s_list3 = tk.StringVar()
|
||||
listbox_list3 = tk.Listbox(frame_list3, listvariable=s_list3, activestyle='none')
|
||||
listbox_list3.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N))
|
||||
|
||||
scrollbar_list3 = ttk.Scrollbar(frame_list3, orient=tk.VERTICAL, command=listbox_list3.yview)
|
||||
scrollbar_list3.grid(row=2, column=1, sticky=(tk.N, tk.S))
|
||||
listbox_list3["yscrollcommand"] = scrollbar_list3.set
|
||||
|
||||
xscrollbar_list3 = ttk.Scrollbar(frame_list3, orient=tk.HORIZONTAL, command=listbox_list3.xview)
|
||||
xscrollbar_list3.grid(row=3, column=0, sticky=(tk.E, tk.W))
|
||||
listbox_list3["xscrollcommand"] = xscrollbar_list3.set
|
||||
|
||||
# Status text
|
||||
frame_bottom = ttk.Frame(root)
|
||||
frame_bottom.grid(row=1, column=0, sticky=(tk.E,tk.W))
|
||||
|
||||
label_status = ttk.Label(frame_bottom, text='Select a folder to search PAR files.')
|
||||
label_status.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
|
||||
# When a folder is specified in command-line
|
||||
if len(sys.argv) > 1:
|
||||
folder_path = sys.argv[1]
|
||||
if os.path.isdir(folder_path):
|
||||
folder_name = os.path.basename(folder_path)
|
||||
label_head1.config(text="? sets in " + folder_name)
|
||||
else:
|
||||
label_status.config(text= "\"" + folder_path + "\" isn't a folder.")
|
||||
folder_path = ""
|
||||
search_par_set(folder_path)
|
||||
|
||||
|
||||
# Show window
|
||||
root.mainloop()
|
||||
75
alpha/tool/read_json.py
Normal file
75
alpha/tool/read_json.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Get path of JSON file from command-line.
|
||||
json_path = sys.argv[1]
|
||||
print("JSON file = " + json_path)
|
||||
|
||||
# Open the JSON file and read the contents.
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
json_dict = json.load(f)
|
||||
|
||||
# Get directory of recovery files.
|
||||
file_path = json_dict["SelectedFile"]
|
||||
recv_dir = os.path.dirname(file_path)
|
||||
print("\nRecovery files' directory = " + recv_dir)
|
||||
|
||||
# Get list of recovery files.
|
||||
recv_list = json_dict["RecoveryFile"]
|
||||
for file_name in recv_list:
|
||||
print(file_name)
|
||||
|
||||
# Get directory of source files.
|
||||
src_dir = json_dict["BaseDirectory"]
|
||||
print("\nSource files' directory = " + src_dir)
|
||||
|
||||
# Get list of source files.
|
||||
src_list = json_dict["SourceFile"]
|
||||
for file_name in src_list:
|
||||
print(file_name)
|
||||
|
||||
# Get list of found source files.
|
||||
if "FoundFile" in json_dict:
|
||||
find_list = json_dict["FoundFile"]
|
||||
print("\nFound files =")
|
||||
for file_name in find_list:
|
||||
print(file_name)
|
||||
|
||||
# Get list of external source files.
|
||||
if "ExternalFile" in json_dict:
|
||||
ext_list = json_dict["ExternalFile"]
|
||||
print("\nExternal files =")
|
||||
for file_name in ext_list:
|
||||
print(file_name)
|
||||
|
||||
# Get list of damaged source files.
|
||||
if "DamagedFile" in json_dict:
|
||||
damage_list = json_dict["DamagedFile"]
|
||||
print("\nDamaged files =")
|
||||
for file_name in damage_list:
|
||||
print(file_name)
|
||||
|
||||
# Get list of appended source files.
|
||||
if "AppendedFile" in json_dict:
|
||||
append_list = json_dict["AppendedFile"]
|
||||
print("\nAppended files =")
|
||||
for file_name in append_list:
|
||||
print(file_name)
|
||||
|
||||
# Get list of missing source files.
|
||||
if "MissingFile" in json_dict:
|
||||
miss_list = json_dict["MissingFile"]
|
||||
print("\nMissing files =")
|
||||
for file_name in miss_list:
|
||||
print(file_name)
|
||||
|
||||
# Get dict of misnamed source files.
|
||||
if "MisnamedFile" in json_dict:
|
||||
misname_dict = json_dict["MisnamedFile"]
|
||||
print("\nMisnamed files =")
|
||||
for file_names in misname_dict.items():
|
||||
print(file_names[0] + ", wrong name = " + file_names[1])
|
||||
|
||||
# If you don't confirm result, comment out below line.
|
||||
input('Press [Enter] key to continue . . .')
|
||||
Reference in New Issue
Block a user