仿everything制作搞笑检索设计(是的,我没有打错字)
everything的确是一个很好的工具,所以仿照开发一个
# -*- coding: utf-8 -*-
import sys
import os
import sqlite3
import threading
from pathlib import Path
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLineEdit, QListWidget, QLabel, QCheckBox, QProgressBar,
QMessageBox, QGridLayout, QGroupBox
)
from PyQt5.QtCore import Qt, pyqtSignal, QObject
class IndexerSignals(QObject):
count_update = pyqtSignal(int)
status = pyqtSignal(str)
current_file = pyqtSignal(str)
finished = pyqtSignal(int)
error = pyqtSignal(str)
class FileIndexer(QObject):
def __init__(self):
super().__init__()
self.signals = IndexerSignals()
self.stop_requested = False
def stop(self):
self.stop_requested = True
def build_index(self, roots):
db_path = Path.home() / ".file_searcher_db.sqlite"
# 删除旧库,确保干净重建
if db_path.exists():
try:
os.remove(db_path)
except:
pass
try:
conn = sqlite3.connect(str(db_path))
cur = conn.cursor()
cur.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS files USING fts5(
name,
path UNINDEXED, -- UNINDEXED 表示该字段存储但不参与全文索引(节省空间且避免误搜)
is_dir UNINDEXED,
tokenize = 'unicode61'
);
""")
conn.commit()
processed_count = 0
batch = []
self.signals.status.emit("正在极速扫描中...")
for root in roots:
if self.stop_requested:
break
for dirpath, dirnames, filenames in os.walk(root):
if self.stop_requested:
break
# 1. 处理文件夹
for dname in dirnames:
full_path = os.path.join(dirpath, dname)
batch.append((dname, full_path, 1)) # 1 代表文件夹
processed_count += 1
# 2. 处理文件
for fname in filenames:
full_path = os.path.join(dirpath, fname)
batch.append((fname, full_path, 0)) # 0 代表文件
processed_count += 1
if len(batch) >= 2000:
cur.executemany("INSERT INTO files(name, path, is_dir) VALUES (?, ?, ?)", batch)
conn.commit()
batch.clear()
# 发送信号更新界面
self.signals.count_update.emit(processed_count)
self.signals.current_file.emit(dirpath) # 显示当前扫描到的目录即可
# 写入剩余的数据
if batch:
cur.executemany("INSERT INTO files(name, path, is_dir) VALUES (?, ?, ?)", batch)
conn.commit()
conn.close()
if not self.stop_requested:
self.signals.finished.emit(processed_count)
else:
self.signals.status.emit("索引已停止")
except Exception as e:
self.signals.error.emit(str(e))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("极速文件搜索器 v3.0 (逻辑修正版)")
self.setGeometry(200, 100, 950, 700)
self.db_path = Path.home() / ".file_searcher_db.sqlite"
self.indexer = FileIndexer()
self.thread = None
self.init_ui()
def init_ui(self):
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout(central)
# === 1. 盘符选择 ===
group_disk = QGroupBox("1. 索引设置")
h_disk = QHBoxLayout()
self.drives_layout = QGridLayout()
self.checkboxes = {}
self.refresh_drives() # 初始化盘符
btn_refresh = QPushButton("刷新盘符")
btn_refresh.clicked.connect(self.refresh_drives)
h_disk.addLayout(self.drives_layout)
h_disk.addWidget(btn_refresh)
group_disk.setLayout(h_disk)
# === 2. 控制台与进度 ===
group_ctrl = QGroupBox("2. 索引控制")
v_ctrl = QVBoxLayout()
h_btns = QHBoxLayout()
self.btn_update = QPushButton("开始建立索引")
self.btn_update.setStyleSheet("background-color: #2E7D32; color: white; font-weight: bold; padding: 8px;")
self.btn_update.clicked.connect(self.start_indexing)
self.btn_stop = QPushButton("停止")
self.btn_stop.setStyleSheet("background-color: #C62828; color: white; padding: 8px;")
self.btn_stop.clicked.connect(self.stop_indexing)
self.btn_stop.setEnabled(False)
h_btns.addWidget(self.btn_update)
h_btns.addWidget(self.btn_stop)
self.lbl_status = QLabel("准备就绪")
self.progress_bar = QProgressBar()
self.progress_bar.setTextVisible(False) # 不显示百分比,因为没有总数
self.progress_bar.setRange(0, 0)
self.progress_bar.hide()
self.lbl_count = QLabel("已收录: 0")
self.lbl_current = QLabel("...")
self.lbl_current.setStyleSheet("color: gray; font-size: 10px;")
v_ctrl.addLayout(h_btns)
v_ctrl.addWidget(self.lbl_status)
v_ctrl.addWidget(self.progress_bar)
h_info = QHBoxLayout()
h_info.addWidget(self.lbl_count)
h_info.addWidget(self.lbl_current)
v_ctrl.addLayout(h_info)
group_ctrl.setLayout(v_ctrl)
# === 3. 搜索区域 ===
group_search = QGroupBox("3. 极速搜索")
v_search = QVBoxLayout()
h_filter = QHBoxLayout()
self.chk_search_file = QCheckBox("搜文件")
self.chk_search_file.setChecked(True)
self.chk_search_file.stateChanged.connect(lambda: self.on_search(self.edit_search.text()))
self.chk_search_dir = QCheckBox("搜文件夹")
self.chk_search_dir.setChecked(True)
self.chk_search_dir.stateChanged.connect(lambda: self.on_search(self.edit_search.text()))
h_filter.addWidget(QLabel("过滤类型:"))
h_filter.addWidget(self.chk_search_file)
h_filter.addWidget(self.chk_search_dir)
h_filter.addStretch()
self.edit_search = QLineEdit()
self.edit_search.setPlaceholderText("输入文件名关键词(空格隔开表示“与”,例如:合同 2024)...")
self.edit_search.setStyleSheet("font-size: 14pt; padding: 6px;")
self.edit_search.textChanged.connect(self.on_search)
self.list_result = QListWidget()
self.list_result.setStyleSheet("font-size: 11pt;")
self.list_result.itemDoubleClicked.connect(self.open_item)
v_search.addLayout(h_filter)
v_search.addWidget(self.edit_search)
v_search.addWidget(self.list_result)
group_search.setLayout(v_search)
# 添加到主布局
layout.addWidget(group_disk)
layout.addWidget(group_ctrl)
layout.addWidget(group_search)
def get_drives(self):
drives = []
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
d = f"{letter}:\\"
if os.path.exists(d):
drives.append(d)
return drives
def refresh_drives(self):
# 清除旧的
for i in reversed(range(self.drives_layout.count())):
self.drives_layout.itemAt(i).widget().setParent(None)
self.checkboxes.clear()
drives = self.get_drives()
for i, d in enumerate(drives):
cb = QCheckBox(d)
# 默认只勾选 C 和 D,避免全部勾选太慢
if d.startswith("C") or d.startswith("D"):
cb.setChecked(True)
self.checkboxes[d] = cb
self.drives_layout.addWidget(cb, i // 6, i % 6)
def start_indexing(self):
roots = [d for d, cb in self.checkboxes.items() if cb.isChecked()]
if not roots:
QMessageBox.warning(self, "提示", "请至少选择一个盘符!")
return
self.btn_update.setEnabled(False)
self.btn_stop.setEnabled(True)
self.progress_bar.show()
self.list_result.clear()
self.list_result.addItem("正在建立索引,建立过程中也可以尝试搜索...")
self.indexer = FileIndexer()
self.thread = threading.Thread(target=self.indexer.build_index, args=(roots,), daemon=True)
self.indexer.signals.count_update.connect(lambda n: self.lbl_count.setText(f"已收录: {n:,}"))
self.indexer.signals.status.connect(self.lbl_status.setText)
self.indexer.signals.current_file.connect(self.lbl_current.setText)
self.indexer.signals.finished.connect(self.on_finished)
self.thread.start()
def stop_indexing(self):
self.indexer.stop()
self.btn_stop.setEnabled(False)
self.lbl_status.setText("正在停止...")
def on_finished(self, total):
self.progress_bar.hide()
self.btn_update.setEnabled(True)
self.btn_stop.setEnabled(False)
self.lbl_status.setText("索引完成")
self.lbl_current.setText("")
QMessageBox.information(self, "完成", f"索引更新完毕!\n本次共收录 {total:,} 个项目。")
self.list_result.clear()
def on_search(self, text):
text = text.strip()
if not self.db_path.exists():
return
# 检查过滤条件
show_files = self.chk_search_file.isChecked()
show_dirs = self.chk_search_dir.isChecked()
if not text:
self.list_result.clear()
return
if not show_files and not show_dirs:
self.list_result.clear()
self.list_result.addItem("请至少勾选一种类型(文件或文件夹)")
return
tokens = text.split()
fts_query_parts = []
for t in tokens:
# 加上 * 使得搜索 "con" 能匹配 "config"
# 语法:name : "keyword*"
fts_query_parts.append(f'name : "{t}*"')
fts_query = " AND ".join(fts_query_parts)
# 构建类型过滤 SQL
type_filters = []
if show_files: type_filters.append(0)
if show_dirs: type_filters.append(1)
type_sql = ",".join(map(str, type_filters))
try:
conn = sqlite3.connect(str(self.db_path))
cur = conn.cursor()
# 核心查询语句
sql = f"""
SELECT name, path, is_dir FROM files
WHERE files MATCH ?
AND is_dir IN ({type_sql})
ORDER BY rank
LIMIT 500
"""
cur.execute(sql, (fts_query,))
rows = cur.fetchall()
conn.close()
self.list_result.clear()
if not rows:
self.list_result.addItem("未找到匹配项")
return
self.list_result.addItem(f"--- 找到 {len(rows)} 个结果 ---")
for name, path, is_dir in rows:
icon = "📁" if is_dir else "📄"
item_text = f"{icon} {name} → {path}"
self.list_result.addItem(item_text)
except Exception as e:
# 输入特殊字符时可能导致 FTS 语法错误,忽略即可
pass
def open_item(self, item):
text = item.text()
if "→" in text:
path = text.split("→", 1)[1].strip()
try:
os.startfile(path)
except Exception as e:
QMessageBox.warning(self, "错误", f"无法打开路径:\n{path}\n错误信息:{e}")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
有什么改进思路希望多多指出

浙公网安备 33010602011771号