mirror of
https://github.com/CodeSeeed/sbackup.git
synced 2026-10-05 02:42:58 +08:00
90 lines
2.5 KiB
Python
90 lines
2.5 KiB
Python
"""Reed-Solomon 纠错码:备份自修复"""
|
|
|
|
import os
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 默认纠错参数:每 128 字节数据生成 8 字节纠错码
|
|
DEFAULT_DATA_SYMBOLS = 128
|
|
DEFAULT_PARITY_SYMBOLS = 8
|
|
|
|
|
|
def generate_parity(
|
|
file_path: str,
|
|
parity_path: str | None = None,
|
|
data_symbols: int = DEFAULT_DATA_SYMBOLS,
|
|
parity_symbols: int = DEFAULT_PARITY_SYMBOLS,
|
|
) -> str | None:
|
|
"""为文件生成 Reed-Solomon 纠错码
|
|
|
|
:returns: 纠错码文件路径,失败返回 None
|
|
"""
|
|
try:
|
|
from reedsolo import RSCodec
|
|
except ImportError:
|
|
logger.warning("reedsolo not installed, skipping parity generation")
|
|
return None
|
|
|
|
if parity_path is None:
|
|
parity_path = file_path + ".par2"
|
|
|
|
try:
|
|
coder = RSCodec(parity_symbols)
|
|
with open(file_path, "rb") as f:
|
|
data = f.read()
|
|
encoded = coder.encode(data)
|
|
with open(parity_path, "wb") as f:
|
|
f.write(encoded)
|
|
return parity_path
|
|
except Exception:
|
|
logger.exception("Failed to generate parity for %s", file_path)
|
|
return None
|
|
|
|
|
|
def verify_and_repair(
|
|
file_path: str,
|
|
parity_path: str | None = None,
|
|
data_symbols: int = DEFAULT_DATA_SYMBOLS,
|
|
parity_symbols: int = DEFAULT_PARITY_SYMBOLS,
|
|
) -> tuple[bool, str]:
|
|
"""验证并修复文件
|
|
|
|
:returns: (success, message)
|
|
"""
|
|
try:
|
|
from reedsolo import RSCodec
|
|
except ImportError:
|
|
return False, "reedsolo not installed"
|
|
|
|
if parity_path is None:
|
|
parity_path = file_path + ".par2"
|
|
|
|
if not os.path.exists(parity_path):
|
|
# 没有纠错码文件,做常规校验
|
|
return _verify_integrity(file_path)
|
|
|
|
try:
|
|
coder = RSCodec(parity_symbols)
|
|
with open(parity_path, "rb") as f:
|
|
encoded = f.read()
|
|
decoded, _, errata = coder.decode(encoded)
|
|
if errata:
|
|
# 修复成功,写回文件
|
|
with open(file_path, "wb") as f:
|
|
f.write(bytes(decoded))
|
|
return True, f"Repaired {errata} errors"
|
|
return True, "Integrity OK"
|
|
except Exception as e:
|
|
return False, f"Repair failed: {e}"
|
|
|
|
|
|
def _verify_integrity(file_path: str) -> tuple[bool, str]:
|
|
"""没有纠错码时的简单完整性检查"""
|
|
try:
|
|
with open(file_path, "rb") as f:
|
|
f.read()
|
|
return True, "File readable"
|
|
except Exception as e:
|
|
return False, f"File corrupted: {e}"
|