1032 lines
43 KiB
Python
1032 lines
43 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk, messagebox, filedialog, simpledialog
|
|
import DISCAPI
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import subprocess
|
|
import platform
|
|
from tkinterdnd2 import *
|
|
from datetime import datetime
|
|
import hashlib
|
|
import time
|
|
import json
|
|
|
|
class DISCManager:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.root.title("DISC文件管理器")
|
|
self.root.geometry("1200x600")
|
|
|
|
# 当前打开的DISC文件
|
|
self.current_DISC = None
|
|
self.current_path = [] # 当前在DISC中的路径
|
|
self.selected_items = []
|
|
self.clipboard_items = [] # 用于复制/粘贴操作的剪贴板
|
|
self.clipboard_operation = None # 'copy' 或 'move'
|
|
|
|
# 创建菜单栏
|
|
self.create_menu()
|
|
|
|
# 创建工具栏
|
|
self.create_toolbar()
|
|
|
|
# 创建导航栏
|
|
self.create_navigation()
|
|
|
|
# 创建文件列表
|
|
self.create_file_list()
|
|
|
|
# 创建状态栏
|
|
self.create_status_bar()
|
|
|
|
# 绑定拖拽事件
|
|
self.setup_drag_drop()
|
|
|
|
def create_menu(self):
|
|
menubar = tk.Menu(self.root)
|
|
self.root.config(menu=menubar)
|
|
|
|
# 文件菜单
|
|
file_menu = tk.Menu(menubar, tearoff=0)
|
|
menubar.add_cascade(label="文件", menu=file_menu)
|
|
file_menu.add_command(label="新建压缩包", command=self.new_archive)
|
|
file_menu.add_command(label="打开压缩包", command=self.open_archive)
|
|
file_menu.add_separator()
|
|
file_menu.add_command(label="重命名", command=self.rename_selected)
|
|
file_menu.add_command(label="删除", command=self.delete_selected)
|
|
file_menu.add_separator()
|
|
file_menu.add_command(label="解压到", command=self.extract_to)
|
|
file_menu.add_command(label="CRC校验", command=self.check_crc)
|
|
|
|
# 编辑菜单
|
|
edit_menu = tk.Menu(menubar, tearoff=0)
|
|
menubar.add_cascade(label="编辑", menu=edit_menu)
|
|
edit_menu.add_command(label="全选", command=self.select_all)
|
|
edit_menu.add_command(label="全部取消", command=self.deselect_all)
|
|
edit_menu.add_command(label="反选", command=self.invert_selection)
|
|
edit_menu.add_separator()
|
|
edit_menu.add_command(label="选择(掩码)", command=self.select_by_mask)
|
|
edit_menu.add_command(label="取消选择(掩码)", command=self.deselect_by_mask)
|
|
edit_menu.add_separator()
|
|
edit_menu.add_command(label="复制", command=self.copy_selected)
|
|
edit_menu.add_command(label="粘贴", command=self.paste_items)
|
|
|
|
# 工具菜单
|
|
tools_menu = tk.Menu(menubar, tearoff=0)
|
|
menubar.add_cascade(label="工具", menu=tools_menu)
|
|
tools_menu.add_command(label="基准测试", command=self.benchmark)
|
|
|
|
# 帮助菜单
|
|
help_menu = tk.Menu(menubar, tearoff=0)
|
|
menubar.add_cascade(label="帮助", menu=help_menu)
|
|
help_menu.add_command(label="关于", command=self.show_about)
|
|
|
|
def create_toolbar(self):
|
|
toolbar = ttk.Frame(self.root)
|
|
toolbar.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
|
|
|
|
# 工具栏按钮
|
|
ttk.Button(toolbar, text="添加", command=self.add_files).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(toolbar, text="解压", command=self.extract_selected).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(toolbar, text="测试", command=self.test_archive).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(toolbar, text="复制", command=self.copy_selected).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(toolbar, text="移动", command=self.move_selected).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(toolbar, text="删除", command=self.delete_selected).pack(side=tk.LEFT, padx=2)
|
|
ttk.Button(toolbar, text="信息", command=self.show_info).pack(side=tk.LEFT, padx=2)
|
|
|
|
def create_navigation(self):
|
|
nav_frame = ttk.Frame(self.root)
|
|
nav_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
|
|
|
|
# 返回上层按钮
|
|
ttk.Button(nav_frame, text="↑", width=3, command=self.go_up).pack(side=tk.LEFT, padx=2)
|
|
|
|
# 路径显示
|
|
self.path_label = ttk.Label(nav_frame, text="", anchor=tk.W)
|
|
self.path_label.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
|
|
|
def create_file_list(self):
|
|
# 创建一个主Frame来容纳Treeview和滚动条
|
|
self.tree_frame = ttk.Frame(self.root)
|
|
self.tree_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
|
|
# 创建Treeview
|
|
columns = ('名称', '大小', '类型', '修改时间')
|
|
self.tree = ttk.Treeview(self.tree_frame, columns=columns, show='tree headings')
|
|
|
|
# 设置列
|
|
self.tree.heading('#0', text='')
|
|
self.tree.heading('名称', text='名称')
|
|
self.tree.heading('大小', text='大小')
|
|
self.tree.heading('类型', text='类型')
|
|
self.tree.heading('修改时间', text='修改时间')
|
|
|
|
# 设置列宽
|
|
self.tree.column('#0', width=0, stretch=False)
|
|
self.tree.column('名称', width=200)
|
|
self.tree.column('大小', width=100)
|
|
self.tree.column('类型', width=100)
|
|
self.tree.column('修改时间', width=150)
|
|
|
|
# 滚动条
|
|
scrollbar = ttk.Scrollbar(self.tree_frame, orient=tk.VERTICAL, command=self.tree.yview)
|
|
self.tree.configure(yscrollcommand=scrollbar.set)
|
|
|
|
# 布局Treeview和滚动条
|
|
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
|
|
# 绑定事件
|
|
self.tree.bind('<Double-1>', self.on_double_click)
|
|
self.tree.bind('<Button-3>', self.on_right_click)
|
|
|
|
# 创建右键菜单
|
|
self.create_context_menu()
|
|
|
|
def create_context_menu(self):
|
|
self.context_menu = tk.Menu(self.root, tearoff=0)
|
|
self.context_menu.add_command(label="重命名", command=self.rename_selected)
|
|
self.context_menu.add_command(label="删除", command=self.delete_selected)
|
|
self.context_menu.add_command(label="查看属性", command=self.show_properties)
|
|
self.context_menu.add_command(label="创建链接", command=self.create_link)
|
|
self.context_menu.add_command(label="CRC校验", command=self.check_crc)
|
|
self.context_menu.add_separator()
|
|
self.context_menu.add_command(label="复制", command=self.copy_selected)
|
|
self.context_menu.add_command(label="移动", command=self.move_selected)
|
|
|
|
# 空白区域右键菜单
|
|
self.empty_context_menu = tk.Menu(self.root, tearoff=0)
|
|
self.empty_context_menu.add_command(label="新建文件", command=self.new_file)
|
|
self.empty_context_menu.add_command(label="新建文件夹", command=self.new_folder)
|
|
self.empty_context_menu.add_separator()
|
|
if self.clipboard_items and self.clipboard_operation:
|
|
self.empty_context_menu.add_command(label="粘贴", command=self.paste_items)
|
|
|
|
def create_status_bar(self):
|
|
# 状态栏单独放在窗口底部
|
|
self.status_bar = ttk.Label(self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
|
|
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
|
|
|
def setup_drag_drop(self):
|
|
# 注册拖放目标
|
|
self.tree.drop_target_register(DND_FILES)
|
|
self.tree.dnd_bind('<<Drop>>', self.on_drop)
|
|
|
|
def on_drop(self, event):
|
|
files = self.root.tk.splitlist(event.data)
|
|
self.add_files_to_archive(files)
|
|
|
|
def new_archive(self):
|
|
filename = filedialog.asksaveasfilename(
|
|
defaultextension=".DISC",
|
|
filetypes=[("DISC文件", "*.DISC"), ("所有文件", "*.*")]
|
|
)
|
|
if filename:
|
|
try:
|
|
# 使用最高压缩等级创建DISC文件
|
|
with DISCAPI.DISCFile(filename, 'w', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf:
|
|
pass
|
|
self.current_DISC = filename
|
|
self.current_path = []
|
|
self.update_file_list()
|
|
self.update_status(f"新建压缩包: {filename} (最高压缩等级)")
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"创建压缩包失败: {str(e)}")
|
|
|
|
def open_archive(self):
|
|
filename = filedialog.askopenfilename(
|
|
filetypes=[("DISC文件", "*.DISC"), ("所有文件", "*.*")]
|
|
)
|
|
if filename:
|
|
try:
|
|
with DISCAPI.DISCFile(filename, 'r') as zf:
|
|
# 测试文件是否有效
|
|
zf.testDISC()
|
|
self.current_DISC = filename
|
|
self.current_path = []
|
|
self.update_file_list()
|
|
self.update_status(f"打开压缩包: {filename}")
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"打开压缩包失败: {str(e)}")
|
|
|
|
def update_file_list(self):
|
|
# 清空列表
|
|
for item in self.tree.get_children():
|
|
self.tree.delete(item)
|
|
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
try:
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
# 添加返回上层按钮
|
|
if self.current_path:
|
|
self.tree.insert('', 'end', values=('..', '', '文件夹', ''), tags=('folder',))
|
|
|
|
# 获取当前路径的文件和文件夹
|
|
current_path_str = '/'.join(self.current_path) + '/' if self.current_path else ''
|
|
|
|
folders = set()
|
|
files = []
|
|
|
|
for info in zf.infolist():
|
|
filename = info.filename
|
|
if filename.startswith(current_path_str):
|
|
relative_path = filename[len(current_path_str):]
|
|
if '/' in relative_path:
|
|
# 这是一个文件夹
|
|
folder_name = relative_path.split('/')[0]
|
|
folders.add(folder_name)
|
|
else:
|
|
# 这是一个文件
|
|
files.append((relative_path, info))
|
|
|
|
# 添加文件夹
|
|
for folder in sorted(folders):
|
|
self.tree.insert('', 'end', values=(folder, '', '文件夹', ''), tags=('folder',))
|
|
|
|
# 添加文件
|
|
print(files)
|
|
for filename, info in sorted(files):
|
|
size = self.format_size(info.file_size)
|
|
date = datetime(*info.date_time).strftime('%Y-%m-%d %H:%M:%S')
|
|
file_type = self.get_file_type(filename)
|
|
self.tree.insert('', 'end', values=(filename, size, file_type, date), tags=('file',))
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
messagebox.showerror("错误", f"读取文件列表失败: {str(e)}")
|
|
|
|
# 更新路径显示
|
|
path_str = '/'.join(self.current_path)
|
|
self.path_label.config(text=path_str if path_str else '根目录')
|
|
|
|
# 更新右键菜单,显示或隐藏粘贴选项
|
|
self.create_context_menu()
|
|
|
|
def get_file_type(self, filename):
|
|
"""根据文件扩展名返回文件类型描述"""
|
|
_, ext = os.path.splitext(filename.lower())
|
|
file_types = {
|
|
'.txt': '文本文档',
|
|
'.doc': 'Word文档',
|
|
'.docx': 'Word文档',
|
|
'.xls': 'Excel表格',
|
|
'.xlsx': 'Excel表格',
|
|
'.ppt': 'PowerPoint',
|
|
'.pptx': 'PowerPoint',
|
|
'.pdf': 'PDF文档',
|
|
'.jpg': 'JPEG图像',
|
|
'.jpeg': 'JPEG图像',
|
|
'.png': 'PNG图像',
|
|
'.gif': 'GIF图像',
|
|
'.bmp': '位图',
|
|
'.mp3': 'MP3音频',
|
|
'.wav': 'WAV音频',
|
|
'.mp4': 'MP4视频',
|
|
'.avi': 'AVI视频',
|
|
'.mkv': 'MKV视频',
|
|
'.DISC': 'DISC压缩包',
|
|
'.rar': 'RAR压缩包',
|
|
'.7z': '7-DISC压缩包',
|
|
'.exe': '应用程序',
|
|
'.dll': '动态链接库',
|
|
'.py': 'Python脚本',
|
|
'.js': 'JavaScript文件',
|
|
'.html': 'HTML文档',
|
|
'.css': '样式表',
|
|
'.json': 'JSON数据',
|
|
'.xml': 'XML文档',
|
|
}
|
|
return file_types.get(ext, f'{ext}文件')
|
|
|
|
def format_size(self, size):
|
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
if size < 1024:
|
|
return f"{size:.1f} {unit}"
|
|
size /= 1024
|
|
return f"{size:.1f} TB"
|
|
|
|
def on_double_click(self, event):
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
item = self.tree.item(selection[0])
|
|
name = item['values'][0]
|
|
|
|
if name == '..':
|
|
self.go_up()
|
|
elif item['tags'] and 'folder' in item['tags']:
|
|
# 进入文件夹
|
|
self.current_path.append(str(name))
|
|
self.update_file_list()
|
|
else:
|
|
# 打开文件
|
|
self.open_file(name)
|
|
|
|
def go_up(self):
|
|
if self.current_path:
|
|
self.current_path.pop()
|
|
self.update_file_list()
|
|
|
|
def open_file(self, filename):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
try:
|
|
# 创建临时文件
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_file = os.path.join(temp_dir, str(filename))
|
|
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
file_path = '/'.join(self.current_path + [str(filename)])
|
|
with open(temp_file, 'wb') as f:
|
|
f.write(zf.read(file_path))
|
|
|
|
# 用系统默认程序打开
|
|
if platform.system() == 'Windows':
|
|
os.startfile(temp_file)
|
|
elif platform.system() == 'Darwin':
|
|
subprocess.run(['open', temp_file])
|
|
else:
|
|
subprocess.run(['xdg-open', temp_file])
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"打开文件失败: {str(e)}")
|
|
|
|
def on_right_click(self, event):
|
|
# 获取点击位置的项目
|
|
item = self.tree.identify('item', event.x, event.y)
|
|
|
|
if item:
|
|
# 右键点击文件/文件夹
|
|
self.tree.selection_set(item)
|
|
self.context_menu.post(event.x_root, event.y_root)
|
|
else:
|
|
# 右键点击空白区域
|
|
self.empty_context_menu.post(event.x_root, event.y_root)
|
|
|
|
def add_files(self):
|
|
if not self.current_DISC:
|
|
messagebox.showwarning("警告", "请先打开或创建一个压缩包")
|
|
return
|
|
|
|
filenames = filedialog.askopenfilenames()
|
|
if filenames:
|
|
self.add_files_to_archive(filenames)
|
|
|
|
def add_files_to_archive(self, filenames):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
try:
|
|
# 使用最高压缩等级添加文件
|
|
with DISCAPI.DISCFile(self.current_DISC, 'a', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf:
|
|
for filename in filenames:
|
|
if os.path.isdir(filename):
|
|
# 添加文件夹及其内容
|
|
self.add_folder_to_DISC(zf, filename, '')
|
|
else:
|
|
# 添加单个文件
|
|
arcname = '/'.join(self.current_path + [os.path.basename(filename)])
|
|
zf.write(filename, arcname)
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已添加 {len(filenames)} 个文件 (最高压缩等级)")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"添加文件失败: {str(e)}")
|
|
|
|
def add_folder_to_DISC(self, zf, folder_path, arcpath):
|
|
for root, dirs, files in os.walk(folder_path):
|
|
for file in files:
|
|
file_path = os.path.join(str(root), str(file))
|
|
rel_path = os.path.relpath(file_path, folder_path)
|
|
arcname = '/'.join(self.current_path + [str(arcpath), str(rel_path)])
|
|
zf.write(file_path, arcname)
|
|
|
|
def extract_selected(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
folder = filedialog.askdirectory()
|
|
if not folder:
|
|
return
|
|
|
|
try:
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
for item in selection:
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..':
|
|
continue
|
|
|
|
if 'folder' in self.tree.item(item)['tags']:
|
|
# 提取文件夹
|
|
folder_path = '/'.join(self.current_path + [str(name)])
|
|
for info in zf.infolist():
|
|
if info.filename.startswith(folder_path + '/'):
|
|
zf.extract(info, folder)
|
|
else:
|
|
# 提取文件
|
|
file_path = '/'.join(self.current_path + [str(name)])
|
|
zf.extract(file_path, folder)
|
|
|
|
self.update_status(f"已提取 {len(selection)} 个项目")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"提取失败: {str(e)}")
|
|
|
|
def extract_to(self):
|
|
self.extract_selected()
|
|
|
|
def delete_selected(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
if not messagebox.askyesno("确认", "确定要删除选中的项目吗?"):
|
|
return
|
|
|
|
try:
|
|
# 创建临时文件
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_DISC = os.path.join(temp_dir, 'temp.DISC')
|
|
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf_in:
|
|
# 使用最高压缩等级创建临时DISC
|
|
with DISCAPI.DISCFile(temp_DISC, 'w', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf_out:
|
|
# 复制不需要删除的文件
|
|
for info in zf_in.infolist():
|
|
should_delete = False
|
|
|
|
for item in selection:
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..':
|
|
continue
|
|
|
|
if 'folder' in self.tree.item(item)['tags']:
|
|
# 检查是否在要删除的文件夹中
|
|
folder_path = '/'.join(self.current_path + [str(name)])
|
|
if info.filename.startswith(folder_path + '/'):
|
|
should_delete = True
|
|
break
|
|
else:
|
|
# 检查是否是要删除的文件
|
|
file_path = '/'.join(self.current_path + [str(name)])
|
|
if info.filename == file_path:
|
|
should_delete = True
|
|
break
|
|
|
|
if not should_delete:
|
|
zf_out.writestr(info, zf_in.read(info.filename))
|
|
|
|
# 替换原文件
|
|
shutil.move(temp_DISC, self.current_DISC)
|
|
shutil.rmtree(temp_dir)
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已删除 {len(selection)} 个项目")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"删除失败: {str(e)}")
|
|
|
|
def rename_selected(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if len(selection) != 1:
|
|
messagebox.showwarning("警告", "请选择一个项目进行重命名")
|
|
return
|
|
|
|
item = selection[0]
|
|
values = self.tree.item(item)['values']
|
|
old_name = values[0]
|
|
|
|
if old_name == '..':
|
|
return
|
|
|
|
new_name = simpledialog.askstring("重命名", "请输入新名称:", initialvalue=old_name)
|
|
if not new_name or new_name == old_name:
|
|
return
|
|
|
|
try:
|
|
# 创建临时文件
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_DISC = os.path.join(temp_dir, 'temp.DISC')
|
|
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf_in:
|
|
# 使用最高压缩等级创建临时DISC
|
|
with DISCAPI.DISCFile(temp_DISC, 'w', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf_out:
|
|
for info in zf_in.infolist():
|
|
if 'folder' in self.tree.item(item)['tags']:
|
|
# 重命名文件夹
|
|
old_path = '/'.join(self.current_path + [str(old_name)])
|
|
new_path = '/'.join(self.current_path + [str(new_name)])
|
|
|
|
if info.filename.startswith(old_path + '/'):
|
|
new_filename = info.filename.replace(old_path + '/', new_path + '/')
|
|
zf_out.writestr(new_filename, zf_in.read(info.filename))
|
|
else:
|
|
zf_out.writestr(info, zf_in.read(info.filename))
|
|
else:
|
|
# 重命名文件
|
|
old_path = '/'.join(self.current_path + [str(old_name)])
|
|
new_path = '/'.join(self.current_path + [str(new_name)])
|
|
|
|
if info.filename == old_path:
|
|
zf_out.writestr(new_path, zf_in.read(info.filename))
|
|
else:
|
|
zf_out.writestr(info, zf_in.read(info.filename))
|
|
|
|
# 替换原文件
|
|
shutil.move(temp_DISC, self.current_DISC)
|
|
shutil.rmtree(temp_dir)
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已重命名: {old_name} -> {new_name}")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"重命名失败: {str(e)}")
|
|
|
|
def select_all(self):
|
|
for item in self.tree.get_children():
|
|
self.tree.selection_add(item)
|
|
|
|
def deselect_all(self):
|
|
for item in self.tree.get_children():
|
|
self.tree.selection_remove(item)
|
|
|
|
def invert_selection(self):
|
|
for item in self.tree.get_children():
|
|
if item in self.tree.selection():
|
|
self.tree.selection_remove(item)
|
|
else:
|
|
self.tree.selection_add(item)
|
|
|
|
def select_by_mask(self):
|
|
mask = simpledialog.askstring("选择", "请输入选择掩码 (例如: *.txt):")
|
|
if mask:
|
|
for item in self.tree.get_children():
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
if self.match_mask(name, mask):
|
|
self.tree.selection_add(item)
|
|
|
|
def deselect_by_mask(self):
|
|
mask = simpledialog.askstring("取消选择", "请输入取消选择掩码 (例如: *.txt):")
|
|
if mask:
|
|
for item in self.tree.get_children():
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
if self.match_mask(name, mask):
|
|
self.tree.selection_remove(item)
|
|
|
|
def match_mask(self, name, mask):
|
|
import fnmatch
|
|
return fnmatch.fnmatch(name, mask)
|
|
|
|
def test_archive(self):
|
|
if not self.current_DISC:
|
|
messagebox.showwarning("警告", "请先打开一个压缩包")
|
|
return
|
|
|
|
try:
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
bad_file = zf.testDISC()
|
|
if bad_file:
|
|
messagebox.showerror("测试结果", f"发现损坏的文件: {bad_file}")
|
|
else:
|
|
messagebox.showinfo("测试结果", "压缩包测试通过,没有发现损坏的文件")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"测试失败: {str(e)}")
|
|
|
|
def check_crc(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
try:
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
results = []
|
|
for item in selection:
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..' or 'folder' in self.tree.item(item)['tags']:
|
|
continue
|
|
|
|
file_path = '/'.join(self.current_path + [str(name)])
|
|
data = zf.read(file_path)
|
|
crc = hashlib.crc32(data) & 0xffffffff
|
|
results.append(f"{name}: CRC32 = {crc:08X}")
|
|
|
|
if results:
|
|
messagebox.showinfo("CRC校验结果", '\n'.join(results))
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"CRC校验失败: {str(e)}")
|
|
|
|
def show_properties(self):
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
item = selection[0]
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..':
|
|
return
|
|
|
|
try:
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
if 'folder' in self.tree.item(item)['tags']:
|
|
# 文件夹属性
|
|
folder_path = '/'.join(self.current_path + [str(name)])
|
|
file_count = 0
|
|
total_size = 0
|
|
compressed_size = 0
|
|
|
|
for info in zf.infolist():
|
|
if info.filename.startswith(folder_path + '/'):
|
|
file_count += 1
|
|
total_size += info.file_size
|
|
compressed_size += info.compress_size
|
|
|
|
props = f"名称: {name}\n"
|
|
props += f"类型: 文件夹\n"
|
|
props += f"文件数: {file_count}\n"
|
|
props += f"总大小: {self.format_size(total_size)}\n"
|
|
props += f"压缩后大小: {self.format_size(compressed_size)}\n"
|
|
if total_size > 0:
|
|
props += f"压缩率: {(1 - compressed_size/total_size)*100:.1f}%"
|
|
else:
|
|
# 文件属性
|
|
file_path = '/'.join(self.current_path + [str(name)])
|
|
info = zf.getinfo(file_path)
|
|
|
|
props = f"名称: {name}\n"
|
|
props += f"类型: 文件\n"
|
|
props += f"大小: {self.format_size(info.file_size)}\n"
|
|
props += f"压缩后: {self.format_size(info.compress_size)}\n"
|
|
props += f"压缩率: {(1 - info.compress_size/info.file_size)*100:.1f}%\n"
|
|
props += f"修改时间: {datetime(*info.date_time).strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
props += f"CRC32: {info.CRC:08X}\n"
|
|
props += f"压缩方法: {info.compress_type}"
|
|
|
|
messagebox.showinfo("属性", props)
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"获取属性失败: {str(e)}")
|
|
|
|
def create_link(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
messagebox.showwarning("警告", "请选择要创建链接的文件")
|
|
return
|
|
|
|
item = selection[0]
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..' or 'folder' in self.tree.item(item)['tags']:
|
|
messagebox.showwarning("警告", "只能为文件创建链接")
|
|
return
|
|
|
|
link_name = simpledialog.askstring("创建链接", "请输入链接名称:", initialvalue=f"链接到_{name}")
|
|
if not link_name:
|
|
return
|
|
|
|
try:
|
|
# 创建链接信息
|
|
file_path = '/'.join(self.current_path + [str(name)])
|
|
link_info = {
|
|
"type": "link",
|
|
"target": file_path,
|
|
"created": datetime.now().isoformat()
|
|
}
|
|
|
|
# 创建临时文件
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_DISC = os.path.join(temp_dir, 'temp.DISC')
|
|
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf_in:
|
|
# 使用最高压缩等级创建临时DISC
|
|
with DISCAPI.DISCFile(temp_DISC, 'w', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf_out:
|
|
# 复制所有文件
|
|
for info in zf_in.infolist():
|
|
zf_out.writestr(info, zf_in.read(info.filename))
|
|
|
|
# 添加链接文件
|
|
link_path = '/'.join(self.current_path + [str(link_name)])
|
|
zf_out.writestr(link_path, json.dumps(link_info))
|
|
|
|
# 替换原文件
|
|
shutil.move(temp_DISC, self.current_DISC)
|
|
shutil.rmtree(temp_dir)
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已创建链接: {link_name}")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"创建链接失败: {str(e)}")
|
|
|
|
def new_file(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
filename = simpledialog.askstring("新建文件", "请输入文件名:")
|
|
if filename:
|
|
try:
|
|
# 使用最高压缩等级
|
|
with DISCAPI.DISCFile(self.current_DISC, 'a', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf:
|
|
file_path = '/'.join(self.current_path + [str(filename)])
|
|
zf.writestr(file_path, '')
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已创建文件: {filename}")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"创建文件失败: {str(e)}")
|
|
|
|
def new_folder(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
foldername = simpledialog.askstring("新建文件夹", "请输入文件夹名:")
|
|
if foldername:
|
|
try:
|
|
# 使用最高压缩等级
|
|
with DISCAPI.DISCFile(self.current_DISC, 'a', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf:
|
|
folder_path = '/'.join(self.current_path + [str(foldername)]) + '/'
|
|
zf.writestr(folder_path, '')
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已创建文件夹: {foldername}")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"创建文件夹失败: {str(e)}")
|
|
|
|
def copy_selected(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
# 保存选中的项目到剪贴板
|
|
self.clipboard_items = []
|
|
self.clipboard_operation = 'copy'
|
|
|
|
for item in selection:
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..':
|
|
continue
|
|
|
|
is_folder = 'folder' in self.tree.item(item)['tags']
|
|
self.clipboard_items.append({
|
|
'name': name,
|
|
'is_folder': is_folder,
|
|
'path': '/'.join(self.current_path + [str(name)])
|
|
})
|
|
|
|
self.update_status(f"已复制 {len(self.clipboard_items)} 个项目")
|
|
# 更新右键菜单,显示粘贴选项
|
|
self.create_context_menu()
|
|
|
|
def move_selected(self):
|
|
if not self.current_DISC:
|
|
return
|
|
|
|
selection = self.tree.selection()
|
|
if not selection:
|
|
return
|
|
|
|
# 保存选中的项目到剪贴板
|
|
self.clipboard_items = []
|
|
self.clipboard_operation = 'move'
|
|
|
|
for item in selection:
|
|
values = self.tree.item(item)['values']
|
|
name = values[0]
|
|
|
|
if name == '..':
|
|
continue
|
|
|
|
is_folder = 'folder' in self.tree.item(item)['tags']
|
|
self.clipboard_items.append({
|
|
'name': name,
|
|
'is_folder': is_folder,
|
|
'path': '/'.join(self.current_path + [str(name)])
|
|
})
|
|
|
|
self.update_status(f"已移动 {len(self.clipboard_items)} 个项目")
|
|
# 更新右键菜单,显示粘贴选项
|
|
self.create_context_menu()
|
|
|
|
def paste_items(self):
|
|
if not self.current_DISC or not self.clipboard_items:
|
|
return
|
|
|
|
try:
|
|
# 创建临时文件
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_DISC = os.path.join(temp_dir, 'temp.DISC')
|
|
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf_in:
|
|
# 使用最高压缩等级创建临时DISC
|
|
with DISCAPI.DISCFile(temp_DISC, 'w', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf_out:
|
|
# 复制所有文件,除了要移动的文件
|
|
for info in zf_in.infolist():
|
|
should_skip = False
|
|
|
|
if self.clipboard_operation == 'move':
|
|
for item in self.clipboard_items:
|
|
if item['is_folder']:
|
|
# 检查是否在要移动的文件夹中
|
|
if info.filename.startswith(item['path'] + '/'):
|
|
should_skip = True
|
|
break
|
|
else:
|
|
# 检查是否是要移动的文件
|
|
if info.filename == item['path']:
|
|
should_skip = True
|
|
break
|
|
|
|
if not should_skip:
|
|
zf_out.writestr(info, zf_in.read(info.filename))
|
|
|
|
# 粘贴剪贴板中的项目
|
|
for item in self.clipboard_items:
|
|
new_path = '/'.join(self.current_path + [item['name']])
|
|
|
|
if item['is_folder']:
|
|
# 粘贴文件夹
|
|
folder_path = item['path']
|
|
|
|
# 创建文件夹标记
|
|
zf_out.writestr(new_path + '/', '')
|
|
|
|
# 复制文件夹内容
|
|
for info in zf_in.infolist():
|
|
if info.filename.startswith(folder_path + '/'):
|
|
# 计算新的路径
|
|
rel_path = info.filename[len(folder_path)+1:]
|
|
new_file_path = new_path + '/' + rel_path
|
|
zf_out.writestr(new_file_path, zf_in.read(info.filename))
|
|
else:
|
|
# 粘贴文件
|
|
zf_out.writestr(new_path, zf_in.read(item['path']))
|
|
|
|
# 替换原文件
|
|
shutil.move(temp_DISC, self.current_DISC)
|
|
shutil.rmtree(temp_dir)
|
|
|
|
# 如果是移动操作,清空剪贴板
|
|
if self.clipboard_operation == 'move':
|
|
self.clipboard_items = []
|
|
self.clipboard_operation = None
|
|
|
|
self.update_file_list()
|
|
self.update_status(f"已粘贴 {len(self.clipboard_items)} 个项目")
|
|
|
|
# 更新右键菜单
|
|
self.create_context_menu()
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"粘贴失败: {str(e)}")
|
|
|
|
def show_info(self):
|
|
if not self.current_DISC:
|
|
messagebox.showwarning("警告", "请先打开一个压缩包")
|
|
return
|
|
|
|
try:
|
|
with DISCAPI.DISCFile(self.current_DISC, 'r') as zf:
|
|
file_count = 0
|
|
folder_count = 0
|
|
total_size = 0
|
|
compressed_size = 0
|
|
|
|
for info in zf.infolist():
|
|
if info.filename.endswith('/'):
|
|
folder_count += 1
|
|
else:
|
|
file_count += 1
|
|
total_size += info.file_size
|
|
compressed_size += info.compress_size
|
|
|
|
info_text = f"文件名: {os.path.basename(self.current_DISC)}\n"
|
|
info_text += f"路径: {os.path.dirname(self.current_DISC)}\n"
|
|
info_text += f"文件夹数: {folder_count}\n"
|
|
info_text += f"文件数: {file_count}\n"
|
|
info_text += f"原始大小: {self.format_size(total_size)}\n"
|
|
info_text += f"压缩后大小: {self.format_size(compressed_size)}\n"
|
|
if total_size > 0:
|
|
info_text += f"压缩率: {(1 - compressed_size/total_size)*100:.1f}%"
|
|
|
|
messagebox.showinfo("压缩包信息", info_text)
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"获取信息失败: {str(e)}")
|
|
|
|
def benchmark(self):
|
|
if not self.current_DISC:
|
|
messagebox.showwarning("警告", "请先打开一个压缩包")
|
|
return
|
|
|
|
try:
|
|
# 创建临时文件用于测试
|
|
temp_dir = tempfile.mkdtemp()
|
|
temp_file = os.path.join(temp_dir, 'benchmark_test.txt')
|
|
|
|
# 创建测试文件
|
|
with open(temp_file, 'w') as f:
|
|
f.write('A' * 1000000) # 1MB测试文件
|
|
|
|
# 测试压缩速度
|
|
start_time = time.time()
|
|
temp_DISC = os.path.join(temp_dir, 'benchmark_test.DISC')
|
|
with DISCAPI.DISCFile(temp_DISC, 'w', compression=DISCAPI.DISC_DEFLATED, compresslevel=9) as zf:
|
|
zf.write(temp_file, 'test.txt')
|
|
compress_time = time.time() - start_time
|
|
|
|
# 测试解压速度
|
|
start_time = time.time()
|
|
with DISCAPI.DISCFile(temp_DISC, 'r') as zf:
|
|
zf.extract('test.txt', temp_dir)
|
|
extract_time = time.time() - start_time
|
|
|
|
# 获取文件大小
|
|
original_size = os.path.getsize(temp_file)
|
|
compressed_size = os.path.getsize(temp_DISC)
|
|
|
|
# 计算速度
|
|
compress_speed = original_size / (1024 * 1024) / compress_time # MB/s
|
|
extract_speed = original_size / (1024 * 1024) / extract_time # MB/s
|
|
|
|
# 显示结果
|
|
result = f"基准测试结果:\n\n"
|
|
result += f"原始大小: {self.format_size(original_size)}\n"
|
|
result += f"压缩后大小: {self.format_size(compressed_size)}\n"
|
|
result += f"压缩率: {(1 - compressed_size/original_size)*100:.1f}%\n\n"
|
|
result += f"压缩速度: {compress_speed:.2f} MB/s\n"
|
|
result += f"解压速度: {extract_speed:.2f} MB/s"
|
|
|
|
messagebox.showinfo("基准测试结果", result)
|
|
|
|
# 清理临时文件
|
|
shutil.rmtree(temp_dir)
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("错误", f"基准测试失败: {str(e)}")
|
|
|
|
def show_about(self):
|
|
about_text = "DISC文件管理器 v1.0\n\n"
|
|
about_text += "一个仿照7-DISC的DISC文件管理器\n"
|
|
about_text += "使用Python tkinter开发\n\n"
|
|
about_text += "功能包括:\n"
|
|
about_text += "- 创建和打开DISC文件\n"
|
|
about_text += "- 添加、删除、重命名文件\n"
|
|
about_text += "- 文件夹浏览\n"
|
|
about_text += "- 拖拽添加文件\n"
|
|
about_text += "- CRC校验\n"
|
|
about_text += "- 文件属性查看\n"
|
|
about_text += "- 文件复制和移动\n"
|
|
about_text += "- 创建链接\n"
|
|
about_text += "- 基准测试"
|
|
|
|
messagebox.showinfo("关于", about_text)
|
|
|
|
def update_status(self, message):
|
|
self.status_bar.config(text=message)
|
|
|
|
if __name__ == "__main__":
|
|
root = TkinterDnD.Tk()
|
|
app = DISCManager(root)
|
|
root.mainloop() |