import
os
import
random
import
sys
import
threading
import
shutil
import
tkinter as tk
from
tkinter
import
ttk, filedialog, messagebox, END
from
tkinterdnd2
import
TkinterDnD, DND_FILES
def
secure_delete(file_path, passes
=
3
, progress_callback
=
None
):
try
:
if
not
os.path.isfile(file_path):
raise
ValueError(f
"文件 {file_path} 不存在"
)
file_size
=
os.path.getsize(file_path)
with
open
(file_path,
'br+'
) as f:
for
pass_num
in
range
(passes):
patterns
=
[
lambda
n: os.urandom(n),
lambda
n: bytes([
0xFF
]
*
n),
lambda
n: bytes([
0x00
]
*
n),
lambda
n: bytes([
0xAA
]
*
n),
lambda
n: bytes([
0x55
]
*
n)
]
pattern
=
patterns[pass_num
%
len
(patterns)]
f.seek(
0
)
chunk_size
=
1024
*
1024
written
=
0
while
written < file_size:
write_size
=
min
(chunk_size, file_size
-
written)
data
=
pattern(write_size)
f.write(data)
written
+
=
write_size
if
progress_callback:
progress_callback(written, file_size)
f.flush()
os.fsync(f.fileno())
temp_name
=
None
for
i
in
range
(
3
):
try
:
temp_name
=
os.path.join(os.path.dirname(file_path), f
".{random.randint(0, 9999999999)}.tmp"
)
shutil.move(file_path, temp_name)
break
except
Exception as e:
print
(f
"重命名失败: {str(e)}"
,
file
=
sys.stderr)
continue
if
temp_name
and
os.path.exists(temp_name):
try
:
os.remove(temp_name)
except
Exception as e:
print
(f
"删除临时文件失败: {str(e)}"
,
file
=
sys.stderr)
return
False
else
:
print
(
"临时文件不存在,可能已被删除"
,
file
=
sys.stderr)
return
False
return
True
except
Exception as e:
print
(f
"删除失败: {str(e)}"
,
file
=
sys.stderr)
return
False
class
SecureDeleterGUI(TkinterDnD.Tk):
def
__init__(
self
):
super
().__init__()
self
.title(
"文件(夹)永久删除-防恢复"
)
self
.geometry(
"600x400"
)
self
.configure(bg
=
'#f0f0f0'
)
self
.drop_target_color
=
'#e0e0e0'
self
.normal_bg_color
=
'#f0f0f0'
self
.create_widgets()
self
.abort_flag
=
False
self
.target_paths
=
[]
def
create_widgets(
self
):
self
.drop_label
=
ttk.Label(
self
,
text
=
"拖拽文件或文件夹至此区域"
,
font
=
(
'微软雅黑'
,
14
),
relief
=
'ridge'
,
anchor
=
'center'
)
self
.drop_label.pack(pady
=
20
, padx
=
40
, fill
=
tk.X)
self
.drop_label.drop_target_register(DND_FILES)
self
.drop_label.dnd_bind(
'<<Drop>>'
,
self
.on_drop)
self
.drop_label.dnd_bind(
'<<Enter>>'
,
self
.on_drag_enter)
self
.drop_label.dnd_bind(
'<<Leave>>'
,
self
.on_drag_leave)
self
.progress
=
ttk.Progressbar(
self
,
orient
=
tk.HORIZONTAL,
length
=
400
,
mode
=
'determinate'
)
self
.progress.pack(pady
=
15
)
self
.log_text
=
tk.Text(
self
,
height
=
12
,
wrap
=
tk.WORD,
bg
=
'white'
,
font
=
(
'Consolas'
,
9
)
)
self
.log_text.pack(pady
=
10
, fill
=
tk.BOTH, expand
=
True
, padx
=
20
)
control_frame
=
ttk.Frame(
self
)
control_frame.pack(pady
=
10
)
ttk.Button(
control_frame,
text
=
"选择文件"
,
command
=
self
.select_file
).grid(row
=
0
, column
=
0
, padx
=
5
)
ttk.Button(
control_frame,
text
=
"选择文件夹"
,
command
=
self
.select_folder
).grid(row
=
0
, column
=
1
, padx
=
5
)
ttk.Button(
control_frame,
text
=
"开始删除"
,
command
=
self
.start_deletion
).grid(row
=
0
, column
=
2
, padx
=
5
)
ttk.Button(
control_frame,
text
=
"中止操作"
,
command
=
self
.abort_operation
).grid(row
=
0
, column
=
3
, padx
=
5
)
def
on_drag_enter(
self
, event):
self
.drop_label.configure(background
=
self
.drop_target_color)
def
on_drag_leave(
self
, event):
self
.drop_label.configure(background
=
self
.normal_bg_color)
def
on_drop(
self
, event):
self
.drop_label.configure(background
=
self
.normal_bg_color)
data
=
event.data
paths
=
self
.parse_dropped_paths(data)
if
not
paths:
self
.log(
"错误:无效的拖拽内容"
)
return
valid_paths
=
[]
for
path
in
paths:
path
=
path.strip()
if
os.path.isfile(path)
or
os.path.isdir(path):
valid_paths.append(path)
if
os.path.isfile(path):
self
.log(f
"已拖入文件: {path}"
)
else
:
self
.log(f
"已拖入文件夹: {path}"
)
else
:
self
.log(f
"错误:无效路径 {path}"
)
self
.target_paths.extend(valid_paths)
@staticmethod
def
parse_dropped_paths(data):
if
data.startswith(
'{'
)
and
data.endswith(
'}'
):
return
data[
1
:
-
1
].split(
'} {'
)
else
:
return
data.split()
def
select_file(
self
):
file_paths
=
filedialog.askopenfilenames(title
=
"选择要删除的文件"
)
if
file_paths:
self
.target_paths.extend(file_paths)
for
file_path
in
file_paths:
self
.log(f
"已选择文件: {file_path}"
)
def
select_folder(
self
):
folder_paths
=
filedialog.askdirectory(title
=
"选择要删除的文件夹"
)
if
folder_paths:
self
.target_paths.extend(folder_paths)
for
folder_path
in
folder_paths:
self
.log(f
"已选择文件夹: {folder_path}"
)
def
log(
self
, message):
self
.log_text.insert(END, message
+
"\n"
)
self
.log_text.see(END)
self
.update_idletasks()
def
start_deletion(
self
):
if
not
self
.target_paths:
messagebox.showwarning(
"警告"
,
"请先选择文件或文件夹"
)
return
confirm
=
messagebox.askyesno(
"确认删除"
,f
"即将永久删除以下内容:\n{' '.join(self.target_paths)}\n此操作不可恢复!确认继续?"
)
if
not
confirm:
return
self
.toggle_controls(
False
)
self
.abort_flag
=
False
self
.progress[
'value'
]
=
0
threading.Thread(target
=
self
.process_deletion, daemon
=
True
).start()
def
process_deletion(
self
):
try
:
total_items
=
len
(
self
.target_paths)
current_item
=
0
for
path
in
self
.target_paths:
if
self
.abort_flag:
self
.log(
"用户中止操作"
)
break
if
os.path.isfile(path):
self
.process_single_file(path)
else
:
self
.process_folder(path)
current_item
+
=
1
self
.progress[
'value'
]
=
(current_item
/
total_items)
*
100
except
Exception as e:
self
.log(f
"发生错误: {str(e)}"
)
finally
:
self
.toggle_controls(
True
)
self
.target_paths
=
[]
def
process_single_file(
self
, file_path):
self
.log(f
"开始处理文件: {os.path.basename(file_path)}"
)
success
=
secure_delete(file_path, progress_callback
=
self
.update_progress)
self
.log(
"文件删除完成"
if
success
else
"文件删除失败"
)
def
process_folder(
self
, folder_path):
total_files
=
0
for
root, dirs, files
in
os.walk(folder_path):
total_files
+
=
len
(files)
if
total_files
=
=
0
:
self
.log(
"文件夹为空,直接删除该文件夹"
)
try
:
shutil.rmtree(folder_path)
self
.log(f
"文件夹 {folder_path} 删除完成"
)
except
Exception as e:
self
.log(f
"删除文件夹 {folder_path} 失败: {str(e)}"
)
return
self
.log(f
"发现 {total_files} 个文件,开始处理..."
)
current_count
=
0
for
root, dirs, files
in
os.walk(folder_path):
for
filename
in
files:
if
self
.abort_flag:
self
.log(
"用户中止操作"
)
return
file_path
=
os.path.join(root, filename)
self
.log(f
"正在处理: {file_path}"
)
secure_delete(file_path, progress_callback
=
self
.update_progress)
current_count
+
=
1
self
.progress[
'value'
]
=
(current_count
/
total_files)
*
100
try
:
shutil.rmtree(folder_path)
self
.log(f
"文件夹 {folder_path} 及其内容删除完成"
)
self
.log(f
"已完成所有文件处理,共删除 {current_count} 个文件"
)
except
Exception as e:
self
.log(f
"删除文件夹 {folder_path} 失败: {str(e)}"
)
def
update_progress(
self
, current, total):
def
_update():
if
total >
0
:
percent
=
(current
/
total)
*
100
self
.progress[
'value'
]
=
percent
self
.after(
0
, _update)
def
toggle_controls(
self
, enabled):
state
=
"normal"
if
enabled
else
"disabled"
for
child
in
self
.winfo_children():
if
isinstance
(child, ttk.Button):
child[
'state'
]
=
state
def
abort_operation(
self
):
self
.abort_flag
=
True
self
.log(
"正在中止当前操作..."
)
if
__name__
=
=
"__main__"
:
try
:
app
=
SecureDeleterGUI()
app.mainloop()
except
ImportError:
print
(
"错误:需要安装 tkinterdnd2 库"
)
print
(
"安装命令: pip install tkinterdnd2"
)