Files
2026-07-28 03:21:34 +08:00

287 lines
11 KiB
Python

import os
import re
import json
import time
import threading
from datetime import datetime
from queue import Queue
import ctypes
from ctypes import wintypes
class FileInfo:
def __init__(self, Path, Name, Size, Extension, ModifyTime):
self.Path = Path
self.Name = Name
self.Size = Size
self.Extension = Extension
self.ModifyTime = ModifyTime
class FileInfoEncoder(json.JSONEncoder):
def default(self, Obj):
if isinstance(Obj, FileInfo):
return {
"__type__": "FileInfo",
"Path": Obj.Path,
"Name": Obj.Name,
"Size": Obj.Size,
"Extension": Obj.Extension,
"ModifyTime": Obj.ModifyTime
}
return super().default(Obj)
def FileInfoDecoder(Dct):
if "__type__" in Dct and Dct["__type__"] == "FileInfo":
return FileInfo(Dct["Path"], Dct["Name"], Dct["Size"], Dct["Extension"], Dct["ModifyTime"])
return Dct
def GetLogicalDrives():
drives = []
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
letter = ord('A')
while bitmask > 0:
if bitmask & 1:
drives.append(f"{chr(letter)}:\\")
bitmask >>= 1
letter += 1
return drives
def GetSubdirectories(Path):
subdirs = []
try:
with os.scandir(Path) as entries:
for entry in entries:
if entry.is_dir(follow_symlinks=False):
subdirs.append(entry.path)
except:
pass
return subdirs
class DiskIndexer:
def __init__(self, Drive):
self.Drive = Drive
self.IndexData = []
self.ProcessedFiles = 0
self.TotalFiles = 0
self.DiscoveredFiles = []
self.IsRunning = False
self.IsPaused = False
self.IsDiscovering = False
self.Threads = []
self.Queue = Queue()
self.DirQueue = Queue()
self.ProgressCallback = None
self.FileCallback = None
self.BatchSize = 100
self.ProgressUpdateInterval = 500
self.LastProgressUpdate = 0
self.Lock = threading.Lock()
self.ShouldStop = False
self.DiscoveryComplete = False
self.CompletionCheckThread = None
self.DiscoveryProgressCallback = None
def Start(self, ThreadCount=4, DiscoveryThreads=4):
if self.IsRunning: return
self.IsRunning = True
self.IsPaused = False
self.ShouldStop = False
self.DiscoveryComplete = False
self.LastProgressUpdate = 0
self._DiscoverFiles(DiscoveryThreads)
print("正在快速统计文件总数...")
while self.IsDiscovering and not self.ShouldStop:
time.sleep(0.5)
with self.Lock:
if self.DiscoveryProgressCallback and self.TotalFiles > 0:
self.DiscoveryProgressCallback(self.TotalFiles)
if self.ShouldStop: return
print(f"文件总数统计完成: {self.TotalFiles}个文件")
self._StartCompletionChecker()
for _ in range(ThreadCount):
Thread = threading.Thread(target=self._ProcessQueue)
Thread.daemon = True
Thread.start()
self.Threads.append(Thread)
def Pause(self):
self.IsPaused = True
def Resume(self):
self.IsPaused = False
def Stop(self):
self.ShouldStop = True
self.IsRunning = False
self.IsPaused = False
self.IsDiscovering = False
if self.CompletionCheckThread and self.CompletionCheckThread.is_alive():
self.CompletionCheckThread.join()
def ExportIndex(self, FilePath):
with open(FilePath, 'w') as F:
json.dump(self.IndexData, F, cls=FileInfoEncoder)
def ImportIndex(self, FilePath):
with open(FilePath, 'r') as F:
self.IndexData = json.load(F, object_hook=FileInfoDecoder)
self.ProcessedFiles = len(self.IndexData)
def CompareWith(self, OtherIndexer):
CurrentPaths = {F.Path for F in self.IndexData}
OtherPaths = {F.Path for F in OtherIndexer.IndexData}
Added = CurrentPaths - OtherPaths
Removed = OtherPaths - CurrentPaths
return {"Added": list(Added), "Removed": list(Removed)}
def Filter(self, MinSize=None, MaxSize=None, NamePattern=None, ExtPattern=None, MinModifyTime=None, MaxModifyTime=None):
Result = []
for File in self.IndexData:
if MinSize is not None and File.Size < MinSize: continue
if MaxSize is not None and File.Size > MaxSize: continue
if NamePattern and not re.search(NamePattern, File.Name): continue
if ExtPattern:
Ext = File.Extension.lower() if File.Extension else ''
if not re.search(ExtPattern, Ext, re.IGNORECASE): continue
if MinModifyTime and File.ModifyTime < MinModifyTime: continue
if MaxModifyTime and File.ModifyTime > MaxModifyTime: continue
Result.append(File)
return Result
def _DiscoverFiles(self, ThreadCount):
self.IsDiscovering = True
self.DiscoveredFiles = []
self.DirQueue.put(self.Drive)
def ProcessDirs():
while not self.ShouldStop and (not self.DirQueue.empty() or self.IsDiscovering):
try:
DirPath = self.DirQueue.get(timeout=1)
if not DirPath or not os.path.isdir(DirPath):
self.DirQueue.task_done()
continue
fileCount = 0
try:
with os.scandir(DirPath) as entries:
for entry in entries:
if self.ShouldStop: break
if entry.is_file(follow_symlinks=False):
fileCount += 1
self.DiscoveredFiles.append(entry.path)
except:
pass
with self.Lock:
self.TotalFiles += fileCount
subdirs = GetSubdirectories(DirPath)
for subdir in subdirs:
if self.ShouldStop: break
self.DirQueue.put(subdir)
self.DirQueue.task_done()
except:
if self.ShouldStop: break
for _ in range(ThreadCount):
Thread = threading.Thread(target=ProcessDirs, daemon=True)
Thread.start()
def MonitorDiscovery():
self.DirQueue.join()
with self.Lock:
self.DiscoveryComplete = True
self.IsDiscovering = False
for path in self.DiscoveredFiles:
self.Queue.put(path)
threading.Thread(target=MonitorDiscovery, daemon=True).start()
def _StartCompletionChecker(self):
def CheckCompletion():
while not self.ShouldStop and self.IsRunning:
time.sleep(1)
with self.Lock:
if self.DiscoveryComplete and self.Queue.empty() and self.ProcessedFiles >= self.TotalFiles:
self.IsRunning = False
break
self.CompletionCheckThread = threading.Thread(target=CheckCompletion, daemon=True)
self.CompletionCheckThread.start()
def _ProcessQueue(self):
while not self.ShouldStop:
while self.IsPaused and not self.ShouldStop: time.sleep(0.1)
try:
FilePath = self.Queue.get(timeout=1)
self._ProcessFile(FilePath)
self.Queue.task_done()
with self.Lock:
self.ProcessedFiles += 1
if (self.ProgressCallback and self.TotalFiles > 0 and
(self.ProcessedFiles - self.LastProgressUpdate >= self.ProgressUpdateInterval or
self.ProcessedFiles == self.TotalFiles)):
Progress = (self.ProcessedFiles / self.TotalFiles) * 100
self.ProgressCallback(Progress, self.ProcessedFiles, self.TotalFiles)
self.LastProgressUpdate = self.ProcessedFiles
if self.FileCallback and self.ProcessedFiles % self.BatchSize == 0:
self.FileCallback(self.ProcessedFiles, self.TotalFiles)
except: pass
def _ProcessFile(self, FilePath):
try:
Stat = os.stat(FilePath)
Size = Stat.st_size
ModifyTime = datetime.fromtimestamp(Stat.st_mtime).isoformat()
Name = os.path.basename(FilePath)
Ext = os.path.splitext(Name)[1][1:].lower() if '.' in Name else ''
File = FileInfo(FilePath, Name, Size, Ext, ModifyTime)
with self.Lock:
self.IndexData.append(File)
except: pass
def ProgressHandler(Progress, Processed, Total):
print(f"索引进度: {Progress:.2f}% ({Processed}/{Total}个文件)")
def FileCountHandler(Count, Total):
print(f"已索引文件: {Count}/{Total}个")
def DiscoveryProgressHandler(Count):
print(f"已发现文件: {Count}个", end='\r')
if __name__ == "__main__":
Indexer = DiskIndexer("C:\\")
Indexer.ProgressCallback = ProgressHandler
Indexer.FileCallback = FileCountHandler
Indexer.DiscoveryProgressCallback = DiscoveryProgressHandler
Indexer.BatchSize = 5000
Indexer.ProgressUpdateInterval = 1000
Indexer.Start(ThreadCount=8, DiscoveryThreads=8)
try:
while Indexer.IsRunning:
time.sleep(1)
except KeyboardInterrupt:
print("\n用户中断,暂停操作")
Indexer.Pause()
Save = input("是否保存当前索引? (y/n): ")
if Save.lower() == 'y':
Indexer.ExportIndex("c_drive_index.json")
print("索引已保存")
Resume = input("是否继续索引? (y/n): ")
if Resume.lower() == 'y':
print("继续索引...")
Indexer.Resume()
while Indexer.IsRunning: time.sleep(1)
Indexer.Stop()
print(f"索引完成,共处理{Indexer.ProcessedFiles}/{Indexer.TotalFiles}个文件")
Filtered = Indexer.Filter(MinSize=1024*1024, ExtPattern=r'^(docx|pdf)$')
print(f"大于1MB的DOCX和PDF文件共{len(Filtered)}个")
Indexer.ExportIndex("c_drive_final_index.json")
print("最终索引已导出")