# --*--coding:utf-8--*--
# updatedtime:20220507
import inspect
import ctypes
import re
import shutil
import threading
import os
import configparser
import webbrowser
import PySimpleGUI as sg
list_ths = [] # 多线程列表
list_path = [] # 存放选过的路径
list_newpath = [] # 存放选过的新路径
class utils(object):
def __init__(self):
self.result_display = None
# 处理文件
def search_path_method(self, search_path, ignore_folder: list, filter_suffix, newPath_this="", reString=""):
"""
根据search_path路径下的的所有后缀名为filter_suffix的文件,输出时筛选掉ignore_path目录下的文件
:param search_path: 待搜索的路径。字符串
:param newPath_this: 放置文件的路径。字符串
:param ignore_folder:忽略的文件夹的绝对路径。列表
:param filter_suffix:需要查出来的后缀名。元组
:return:路径结果
"""
"""
def print_result(future):
global jump_num_ths, jump_success_ths
jump_num_ths += 1
if future.result():
jump_success_ths += 1
# ThreadPoolExecutor适用于处理I/O 密集型任务。对于CPU密集型任务还不如单线程快。
with ThreadPoolExecutor(max_workers=MaxWorkers) as pool:
input_flag = False # 真完结标志
while True: # 提单成功列表尽数跳转
try:
dataDict = q.get(timeout=120)
while dataDict.get("flag"):
if jump_num_ths == submit_num_ths:
if destination == destination_list[2]: # 下一步执行,才塞结束标记,不执行,就等执行那次再塞
boe_jump_ths_queue.put(True)
input_flag = True
break
time.sleep(2)
if input_flag:
break
boeNo = dataDict.get("boeNo")
taskId = dataDict.get("taskId")
future1 = pool.submit(jump, cookie, taskId, boeNo)
future1.add_done_callback(print_result)
except BaseException:
print("jump_ths方法出现一次error\n", end='')
# raise error
"""
failnum, successnum = 0, 0
for o, value in enumerate(ignore_folder): # 进不去了
ignore_folder[o] = os.path.normcase(value.replace('\\\\', '\\'))
list_file_path = "" # 文件路径
if not newPath_this:
newPath_this = search_path
if search_path:
for root, dirs, files in os.walk(search_path):
if os.path.normcase(root).replace('\\\\', '\\') in ignore_folder: # 被忽略的文件夹路径 # 进不去了
for i, j, k in os.walk(root):
pathsadads = os.path.normcase(str(i).replace('\\\\', '\\'))
ignore_folder.append(pathsadads)
continue
list_all = ['', '.', ".*"]
result = list(set(list_all).intersection(set(list(filter_suffix))))
if not len(result):
files = [f for f in files if f.endswith(filter_suffix)] # 选择后缀名 # filter_suffix后缀
for name in files:
list_file_path = os.path.join(root, name).replace('\\', '/').strip('\n').strip('\t').strip('\r') # 路径和文件名连接构成完整路径
try:
newPathName = self.ElementFilter(name, reString, '1')
if not newPathName:
# reString = '.*?(?=\\.[jpg|jpeg|gif|bmp|png])'
print(f"图片 {list_file_path} 处理失败:正则过滤目录名失败")
failnum += 1
continue
except:
sg.popup_quick_message("正则有误")
return
newPath_True = os.path.join(newPath_this, newPathName)
if not os.path.exists(newPath_True):
os.makedirs(newPath_True)
try:
shutil.move(list_file_path, os.path.join(newPath_True, name))
except shutil.Error as error:
try:
os.remove('F:\PythonProject\CHN_TGtools\\newPath212.jpg')
except:
pass
finally:
successnum += 1
# 保存历史路径
# self.save_his_path(search_path)
print(f"处理完成!\n共{successnum + failnum}次处理。成功{successnum},失败{failnum}")
sg.popup_quick_message("处理完成!", background_color='green', auto_close_duration=7)
return list_file_path
# 正则匹配
def ElementFilter(self, string, re_string, return_num="1"):
"""
正则匹配搜索string,以列表形式返回某个能匹配的子串,默认取列表第一个,
:param string: 总的字符串,string
:param re_string: 目标字符串的匹配规则,string。
如想提取首尾字符串中间的最短内容,ride填:(?<=首字符串).*?(?=尾字符串)
如想提取首尾字符串中间的最长内容,ride填:(?<=首字符串).*(?=尾字符串)
如想提取首尾字符串中间的数字内容,ride填:(?<=首字符串)\d+(?=尾字符串)
如想提取首尾字符串中间的字符串内容,ride填:(?<=首字符串).*(?=尾字符串)
:param return_num: 返回匹配的第几个值,默认1返回第一个,int。传空返回全部匹配结果。
:return: result_list 匹配结果以列表形式返回全部能匹配的子串,默认返回全部匹配结果,string。
使用参考:https://www.runoob.com/regexp/regexp-tutorial.html
找出连续的数字并且最后一个数字跟着至少一个a-z里面的字符序列: r"\d+(?=[a-z]+)"。
找出连续的数字,并且最后一个数字后面不能跟任何一个a-z里面的字符序列: r"\d+(?![a-z]+)"。
找出连续的数字,并且第一个数字的前面要是a-z中的一个字符:r"(?<=[a-z])\d+"。
找出连续的数字,并且第一个数字的前面不能是a-z中的一个字符:r"(?![a-z])\d+"。
"""
return_num = eval(return_num)
if isinstance(return_num, int) is False:
print(">>>return_num只支持传入数字,ride填写时不带引号。", "\n>>>type(return_num):", type(return_num))
# 正则过滤出目标规则字符串,存储成元组列表
reObj = re.compile(re_string)
result_list = reObj.findall(string)
# print(">>>匹配结果列表:", result_list)
return_num_new = return_num - 1
if len(result_list) == 0:
# print(">>>未匹配到内容,请检查正则表达式书写格式")
return ""
if len(result_list) > return_num_new and return_num > 0:
# print(">>>返回第%s个结果:" % (return_num), result_list[return_num_new])
return result_list[return_num_new]
else:
# print(">>>参数return_num大小超出了匹配出来的结果数量,返回全部匹配结果:,", result_list)
return result_list
# 中止执行
def suspend_btn_onclick(self):
"""中止线程"""
global list_ths
def stop_thread(thread):
def _async_raise(tid, exctype):
"""线程停止"""
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
_async_raise(thread.ident, SystemExit)
if not len(list_ths):
return True
for key, value in enumerate(list_ths):
print(key, value)
try:
stop_thread(value)
except:
pass
list_ths.clear()
return True
# 执行按钮
def loading_btn_onclick(self, value, windows, text_choice=None):
"""执行按钮函数
value: 界面值
window: 操作窗口
text_choice:忽略列表
"""
global list_ths
search_path = value.get('basePath') # 取出目标路径输入框的值
newPath_done = value.get('newPath') # 取出移动至输入框的值
fileEndname = value.get('fileEndname') # 取出文件后缀输入框的值
reString = value.get('reString') # 取出文件后缀输入框的值
ignore_folder = [] # 取出忽略文件夹输入框的值,暂时不做此功能
filter_suffix = tuple(fileEndname.replace(',', ',').replace(' ', '').split(",")) # 取出筛选后缀输入框的值写入元组
if not newPath_done:
newPath_done = search_path
if not os.path.isdir(search_path) or not os.path.isdir(newPath_done):
sg.popup_auto_close("目标路径 或 移动至的路径 不存在,请检查", title="提示")
return
if search_path not in list_path:
list_path.append(search_path)
else:
pass
if newPath_done not in list_newpath:
list_newpath.append(newPath_done)
else:
pass
windows['basePath'].update(value=search_path, values=list_path)
windows['newPath'].update(value=newPath_done, values=list_newpath)
windows['debug_result'].Update(value='')
# 输出加载结果
# def search_path_method(self, search_path, ignore_folder: list, filter_suffix, newPath_this="", reString=""):
t2 = threading.Thread(
target=self.search_path_method, args=(search_path, ignore_folder, filter_suffix, newPath_done, reString))
t2.daemon = True
t2.start()
list_ths.append(t2)
class WindowsTool(utils):
def __init__(self):
super().__init__()
self.right_button_menu = ['正直诚信', '专业敬业', '开放合作', '持续成长']
# 数据工厂UI
def tool_data_ui(self, **kwargs):
global list_path
if isinstance(kwargs.get('list_hisPath'), list):
list_path.extend(kwargs.get('list_hisPath'))
layout_title = [
[
sg.Text('* 目标路径:', size=10, justification='right'),
sg.InputCombo(key='basePath', size=40, default_value=os.path.join(os.path.expanduser('~'),"Desktop"), values=list_path,
enable_events=True, tooltip="存放路径"),
sg.FolderBrowse(button_text='选择', auto_size_button=True, tooltip="选择待处理照片存放路径"),
sg.Text('移动至:', size=10, justification='right'),
sg.InputCombo(key='newPath', size=40, default_value='', values=list_newpath, enable_events=True, tooltip="移放路径"),
sg.FolderBrowse(button_text='选择', auto_size_button=True, tooltip="选择移放路径,不填默认同目录"),
],
[
sg.Text('* 文件后缀:', size=10, justification='right'),
sg.Input(key='fileEndname', size=20, default_text=".jpg", tooltip="需要处理的文件后缀,多个用逗号隔开\n如.jpg,.png"),
sg.Text('* 目录正则:', size=10, justification='right'), # .*?(?=\.[jpg|jpeg|gif|bmp|png])
sg.Input(key='reString', size=35, default_text="(?<=_)[a-zA-Z0-9]+?_[a-zA-Z0-9]+?(?=_)", tooltip="从文件绝对路径中匹配目录名的正则表达式\n请经过测试后再填写使用"),
sg.Text(' ', size=1, justification='left', right_click_menu=['&Right', self.right_button_menu]), # 空白区域
sg.Button(
"执行",
bind_return_key=True,
size=10,
mouseover_colors='#1d953f',
use_ttk_buttons=True,
right_click_menu=['&Right', ['源码']]),
sg.Button(
"中止",
bind_return_key=True,
size=10,
mouseover_colors='#1d953f',
use_ttk_buttons=True,
right_click_menu=['&Right', self.right_button_menu]),
sg.Button(
"重置",
bind_return_key=True,
size=10,
mouseover_colors='#1d953f',
use_ttk_buttons=True,
right_click_menu=['&Right', self.right_button_menu]),
],
]
layout_result = [
[
sg.Output(key='debug_result', size=(120, 20), right_click_menu=['&Right', self.right_button_menu])
]
]
layout = [
[layout_title],
[sg.Frame('', layout_result, border_width=1, right_click_menu=['&Right', self.right_button_menu])],
]
return layout
"""数据工厂"""
def windowDataTools(self):
"""数据工厂"""
layout = self.tool_data_ui()
windows = sg.Window("图像分家", layout, finalize=True, enable_close_attempted_event=True) # 加载布局生成窗口
while True:
event, value = windows.read(300)
if event == "执行":
self.loading_btn_onclick(value, windows)
elif event == "中止":
self.suspend_btn_onclick()
elif event == "重置":
windows['debug_result'].Update(value='')
windows['basePath'].Update(value='')
windows['newPath'].Update(value='')
sg.popup_quick_message("已重置", auto_close_duration=1)
elif event == "源码":
if sg.popup_yes_no("是否打开存放源码的博客?", title='确认') == 'Yes':
url = 'http://t.csdn.cn/g1Ajz'
webbrowser.open(url)
elif event in [sg.WINDOW_CLOSE_ATTEMPTED_EVENT, sg.WIN_X_EVENT] and sg.popup_yes_no('退出程序?',
title='确认') == 'Yes':
windows.close()
elif event in [sg.WINDOW_CLOSED, sg.WIN_CLOSED]:
try:
windows.close()
break
except:
return
if __name__ == '__main__':
WindowsTool = WindowsTool()
WindowsTool.windowDataTools()
图像分家GUI
于 2022-05-07 15:57:16 首次发布