Files
Haocode/ui/views/system_tools/file_reader.py
T
sorrow404null a7412824e0 chore: import original project baseline
Import the pre-repair source tree as the history baseline.
Runtime data (data/), virtualenvs, bytecode caches and logs are
gitignored so local secrets and user state stay out of the repo.
2026-09-17 16:40:01 +08:00

72 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
文本/代码文件读取工具 (file_reader)
编码探测 + 二进制探测 + 大小守卫,供附件系统(拖拽/粘贴/上传文本与代码文件)复用。
用法:read_text_file(path) -> (content, encoding, size_kb, lines),失败抛 ValueError。
说明:纯函数模块,不依赖 Qt,便于单元测试。
"""
import os
# 明确拒绝的二进制/文档格式(Word/Excel/PPT/PDF/压缩包/可执行/媒体等)
BINARY_EXTS = frozenset({
# Office 文档(明确排除 Word/Excel
'.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.odt', '.ods', '.odp', '.rtf',
# 电子书(.pdf 已交由 tools/builtin_tools/pdf_reader.py 专门解析,不在此拦截)
'.epub', '.mobi',
# 压缩包 / 磁盘镜像
'.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.iso', '.dmg',
# 可执行 / 编译产物
'.exe', '.dll', '.so', '.dylib', '.msi', '.bin', '.apk', '.jar',
'.class', '.pyc', '.pyd', '.o', '.a',
# 音视频
'.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg',
'.mp4', '.avi', '.mov', '.mkv', '.webm', '.wmv',
# 字体
'.ttf', '.otf', '.woff', '.woff2', '.eot',
# 数据库
'.db', '.sqlite', '.sqlite3', '.mdb',
# 设计稿
'.psd', '.ai', '.sketch', '.fig',
})
# 单文件大小上限(字节):超过则拒绝,避免内存与上下文爆炸
MAX_ATTACH_FILE_BYTES = 2 * 1024 * 1024 # 2 MB
# 编码降级链:UTF-8(含BOM) → GB18030(⊇GBK/GB2312) → latin-1(永不失败)
_ENCODINGS = ('utf-8-sig', 'gb18030', 'latin-1')
def read_text_file(path, max_bytes=MAX_ATTACH_FILE_BYTES):
"""读取一个文本/代码文件。
返回 (content, encoding, size_kb, lines)。
文件过大或判定为二进制时抛出 ValueError(附带可读原因)。
"""
size = os.path.getsize(path)
if size > max_bytes:
raise ValueError(
f"文件过大({size / 1024 / 1024:.1f} MB > {max_bytes / 1024 / 1024:.0f} MB"
)
with open(path, 'rb') as f:
raw = f.read()
# 二进制探测:前 8KB 含 NUL 字节即判定为二进制(git 同款启发式)
if b'\x00' in raw[:8192]:
raise ValueError("二进制文件,无法作为文本读取")
text, used_enc = None, 'utf-8'
for enc in _ENCODINGS:
try:
text = raw.decode(enc)
used_enc = enc
break
except UnicodeDecodeError:
continue
if text is None: # 理论上 latin-1 兜底永不失败
raise ValueError("无法识别文件编码")
lines = text.count('\n') + 1
return text, used_enc, round(size / 1024, 2), lines