复查修复(三): 调度与扫描

- scan: .tmp/.part 过滤 + list_files 分批遍历(消除 10 万条截断误判)
- Scheduler: 持锁只做标记, 锁外异步执行任务(长任务不再冻结调度)
- cron 范围/步进边界校验;FTP 下载后大小校验防静默截断
This commit is contained in:
HYC Fixer
2026-09-02 00:40:09 +08:00
parent 82875b710a
commit ccd66b806f
3 changed files with 533 additions and 502 deletions
+6
View File
@@ -1307,6 +1307,12 @@ class MirrorSyncManager:
ftp.retrbinary(f'RETR {remote_filename}', f.write, rest=start_pos) ftp.retrbinary(f'RETR {remote_filename}', f.write, rest=start_pos)
else: else:
ftp.retrbinary(f'RETR {remote_filename}', f.write) ftp.retrbinary(f'RETR {remote_filename}', f.write)
# 校验下载大小与远端一致,防止静默截断
actual_size = os.path.getsize(local_path)
expected = file_info.get('size')
if expected is not None and expected > 0 and actual_size != expected:
raise Exception(f"文件大小不匹配: {actual_size} != {expected}")
return True return True
except Exception as e: except Exception as e:
+25 -9
View File
@@ -103,16 +103,27 @@ class ScheduledTask:
result.extend(self._parse_cron_part(sub.strip(), min_val, max_val)) result.extend(self._parse_cron_part(sub.strip(), min_val, max_val))
return result return result
# 处理范围: "1-5" # 处理范围: "1-5"(校验边界,越界返回空)
if '-' in part: if '-' in part:
start, end = part.split('-') try:
return list(range(int(start), int(end) + 1)) start, end = part.split('-')
start, end = int(start), int(end)
if not (min_val <= start <= end <= max_val):
return []
return list(range(start, end + 1))
except ValueError:
return []
# 处理步进: "*/5" # 处理步进: "*/5"
if '/' in part: if '/' in part:
base, step = part.split('/') base, step = part.split('/')
base_list = self._parse_cron_part(base or '*', min_val, max_val) base_list = self._parse_cron_part(base or '*', min_val, max_val)
step = int(step) try:
step = int(step)
if step <= 0:
return []
except ValueError:
return []
return base_list[::step] return base_list[::step]
# 单个值 # 单个值
@@ -301,18 +312,23 @@ class Scheduler:
logger.info("定时任务调度器已停止") logger.info("定时任务调度器已停止")
def _run_loop(self): def _run_loop(self):
"""运行循环""" """运行循环(持锁只做标记,锁外异步执行任务,避免长任务冻结调度)"""
while self._running: while self._running:
try: try:
now = datetime.now() now = datetime.now()
due = []
with self._lock: with self._lock:
for name, task in self.tasks.items(): for name, task in self.tasks.items():
if task.should_run_now(): if task.should_run_now():
# 使用线程池执行任务 due.append(task)
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=1) as executor: # 锁外异步执行,不阻塞后续任务的调度
executor.submit(task.run) for task in due:
try:
threading.Thread(target=task.run, daemon=True).start()
except Exception as e:
logger.error(f"调度任务 {task.name} 启动失败: {e}")
time.sleep(self.check_interval) time.sleep(self.check_interval)
+502 -493
View File
@@ -1,493 +1,502 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
定时同步调度器 定时同步调度器
负责本地数据和数据库之间的定时同步 负责本地数据和数据库之间的定时同步
""" """
import os import os
import sys import sys
import time import time
import json import json
import hashlib import hashlib
import threading import threading
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Dict, List, Optional, Callable from typing import Dict, List, Optional, Callable
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
# 添加项目根目录到路径 # 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.database import DatabaseManager, get_db from core.database import DatabaseManager, get_db
from core.scheduler import Scheduler, ScheduledTask from core.scheduler import Scheduler, ScheduledTask
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SyncScheduler: class SyncScheduler:
"""同步调度器""" """同步调度器"""
def __init__(self, config: dict, db: DatabaseManager = None): def __init__(self, config: dict, db: DatabaseManager = None):
self.config = config self.config = config
self.db = db self.db = db
self._running = False self._running = False
self._executor = ThreadPoolExecutor(max_workers=4) self._executor = ThreadPoolExecutor(max_workers=4)
# 同步配置 # 同步配置
self.sync_interval = config.get('database', {}).get('sync_interval', 60) self.sync_interval = config.get('database', {}).get('sync_interval', 60)
self.auto_scan = config.get('auto_scan', True) self.auto_scan = config.get('auto_scan', True)
self.scan_interval = config.get('scan_interval', 300) # 5分钟扫描一次 self.scan_interval = config.get('scan_interval', 300) # 5分钟扫描一次
# 同步状态 # 同步状态
self.last_sync_time = 0 self.last_sync_time = 0
self.last_scan_time = 0 self.last_scan_time = 0
self.sync_in_progress = False self.sync_in_progress = False
self.scan_in_progress = False self.scan_in_progress = False
# 回调函数 # 回调函数
self.on_file_added: Optional[Callable] = None self.on_file_added: Optional[Callable] = None
self.on_file_deleted: Optional[Callable] = None self.on_file_deleted: Optional[Callable] = None
self.on_file_updated: Optional[Callable] = None self.on_file_updated: Optional[Callable] = None
self.on_sync_complete: Optional[Callable] = None self.on_sync_complete: Optional[Callable] = None
# 定时同步执行器(注入 MirrorSyncManager.start_sync 或等效 callable) # 定时同步执行器(注入 MirrorSyncManager.start_sync 或等效 callable)
self.sync_starter: Optional[Callable] = None self.sync_starter: Optional[Callable] = None
# 待同步队列 # 待同步队列
self._pending_add = [] # 待添加的文件 self._pending_add = [] # 待添加的文件
self._pending_update = [] # 待更新的文件 self._pending_update = [] # 待更新的文件
self._pending_delete = [] # 待删除的文件 self._pending_delete = [] # 待删除的文件
# 定时任务调度器 # 定时任务调度器
self.task_scheduler = None self.task_scheduler = None
self.scheduled_syncs: Dict[str, dict] = {} self.scheduled_syncs: Dict[str, dict] = {}
def _init_scheduled_syncs(self): def _init_scheduled_syncs(self):
"""初始化定时同步任务""" """初始化定时同步任务"""
if not self.config.get('enable_sync', True): if not self.config.get('enable_sync', True):
return return
# 从配置加载定时同步设置 # 从配置加载定时同步设置
sync_sources = self.config.get('sync_sources', {}) sync_sources = self.config.get('sync_sources', {})
scheduled_sources = {} scheduled_sources = {}
for name, source_config in sync_sources.items(): for name, source_config in sync_sources.items():
schedule = source_config.get('schedule', {}) schedule = source_config.get('schedule', {})
if schedule.get('enabled', False): if schedule.get('enabled', False):
scheduled_sources[name] = { scheduled_sources[name] = {
'type': schedule.get('type', 'interval'), # 'cron' 或 'interval' 'type': schedule.get('type', 'interval'), # 'cron' 或 'interval'
'config': { 'config': {
'cron': schedule.get('cron'), 'cron': schedule.get('cron'),
'interval': schedule.get('interval', {}), 'interval': schedule.get('interval', {}),
'enabled': True 'enabled': True
} }
} }
if scheduled_sources: if scheduled_sources:
self.task_scheduler = Scheduler() self.task_scheduler = Scheduler()
for name, sched_config in scheduled_sources.items(): for name, sched_config in scheduled_sources.items():
self.task_scheduler.add_task( self.task_scheduler.add_task(
name=f"sync_{name}", name=f"sync_{name}",
task_type=sched_config['type'], task_type=sched_config['type'],
config=sched_config['config'], config=sched_config['config'],
callback=self._create_sync_callback(name) callback=self._create_sync_callback(name)
) )
self.scheduled_syncs = scheduled_sources self.scheduled_syncs = scheduled_sources
def _create_sync_callback(self, source_name: str): def _create_sync_callback(self, source_name: str):
"""创建同步回调函数""" """创建同步回调函数"""
def sync_callback(task_name: str, config: dict): def sync_callback(task_name: str, config: dict):
logger.info(f"定时同步任务触发: {source_name}") logger.info(f"定时同步任务触发: {source_name}")
# 委托给注入的同步执行器(MirrorSyncManager.start_sync) # 委托给注入的同步执行器(MirrorSyncManager.start_sync)
starter = self.sync_starter or self._default_sync_starter() starter = self.sync_starter or self._default_sync_starter()
if starter: if starter:
try: try:
return bool(starter(source_name)) return bool(starter(source_name))
except Exception as e: except Exception as e:
logger.error(f"触发同步 {source_name} 失败: {e}") logger.error(f"触发同步 {source_name} 失败: {e}")
return False return False
logger.error(f"无法触发同步 {source_name}: 未配置同步执行器(sync_starter)") logger.error(f"无法触发同步 {source_name}: 未配置同步执行器(sync_starter)")
return False return False
return sync_callback return sync_callback
def _default_sync_starter(self): def _default_sync_starter(self):
"""尝试从配置获取 MirrorSyncManager 实例(server.py 启动时注入)""" """尝试从配置获取 MirrorSyncManager 实例(server.py 启动时注入)"""
mgr = self.config.get('_sync_manager') mgr = self.config.get('_sync_manager')
if mgr and hasattr(mgr, 'start_sync'): if mgr and hasattr(mgr, 'start_sync'):
return mgr.start_sync return mgr.start_sync
return None return None
def start(self): def start(self):
"""启动同步调度器""" """启动同步调度器"""
if self._running: if self._running:
logger.warning("SyncScheduler 已经运行中") logger.warning("SyncScheduler 已经运行中")
return return
self._running = True self._running = True
self._executor.submit(self._sync_loop) self._executor.submit(self._sync_loop)
self._executor.submit(self._scan_loop) self._executor.submit(self._scan_loop)
# 初始化并启动定时同步 # 初始化并启动定时同步
self._init_scheduled_syncs() self._init_scheduled_syncs()
if self.task_scheduler: if self.task_scheduler:
self.task_scheduler.start() self.task_scheduler.start()
logger.info(f"同步调度器已启动,间隔: {self.sync_interval}秒") logger.info(f"同步调度器已启动,间隔: {self.sync_interval}秒")
def stop(self): def stop(self):
"""停止同步调度器""" """停止同步调度器"""
self._running = False self._running = False
# 停止定时任务调度器 # 停止定时任务调度器
if self.task_scheduler: if self.task_scheduler:
self.task_scheduler.stop() self.task_scheduler.stop()
self.task_scheduler = None self.task_scheduler = None
self._executor.shutdown(wait=True) self._executor.shutdown(wait=True)
logger.info("同步调度器已停止") logger.info("同步调度器已停止")
def _sync_loop(self): def _sync_loop(self):
"""同步循环""" """同步循环"""
while self._running: while self._running:
try: try:
if time.time() - self.last_sync_time >= self.sync_interval: if time.time() - self.last_sync_time >= self.sync_interval:
self.perform_sync() self.perform_sync()
time.sleep(1) time.sleep(1)
except Exception as e: except Exception as e:
logger.error(f"同步循环错误: {e}") logger.error(f"同步循环错误: {e}")
time.sleep(5) time.sleep(5)
def _scan_loop(self): def _scan_loop(self):
"""扫描循环 - 检测本地文件变化""" """扫描循环 - 检测本地文件变化"""
while self._running: while self._running:
try: try:
if self.auto_scan and time.time() - self.last_scan_time >= self.scan_interval: if self.auto_scan and time.time() - self.last_scan_time >= self.scan_interval:
self.scan_local_files() self.scan_local_files()
time.sleep(5) time.sleep(5)
except Exception as e: except Exception as e:
logger.error(f"扫描循环错误: {e}") logger.error(f"扫描循环错误: {e}")
time.sleep(10) time.sleep(10)
def queue_add(self, file_info: dict): def queue_add(self, file_info: dict):
"""队列添加文件""" """队列添加文件"""
self._pending_add.append(file_info) self._pending_add.append(file_info)
def queue_update(self, file_info: dict): def queue_update(self, file_info: dict):
"""队列更新文件""" """队列更新文件"""
self._pending_update.append(file_info) self._pending_update.append(file_info)
def queue_delete(self, file_id: str): def queue_delete(self, file_id: str):
"""队列删除文件""" """队列删除文件"""
self._pending_delete.append(file_id) self._pending_delete.append(file_id)
def perform_sync(self): def perform_sync(self):
"""执行同步""" """执行同步"""
if self.sync_in_progress: if self.sync_in_progress:
logger.warning("同步已在进行中,跳过") logger.warning("同步已在进行中,跳过")
return return
self.sync_in_progress = True self.sync_in_progress = True
start_time = time.time() start_time = time.time()
try: try:
logger.info("开始执行数据库同步...") logger.info("开始执行数据库同步...")
# 同步待添加的文件 # 同步待添加的文件
added = 0 added = 0
for file_info in self._pending_add[:]: for file_info in self._pending_add[:]:
try: try:
self._sync_add_file(file_info) self._sync_add_file(file_info)
self._pending_add.remove(file_info) self._pending_add.remove(file_info)
added += 1 added += 1
except Exception as e: except Exception as e:
logger.error(f"同步添加文件失败: {e}") logger.error(f"同步添加文件失败: {e}")
# 同步待更新的文件 # 同步待更新的文件
updated = 0 updated = 0
for file_info in self._pending_update[:]: for file_info in self._pending_update[:]:
try: try:
self._sync_update_file(file_info) self._sync_update_file(file_info)
self._pending_update.remove(file_info) self._pending_update.remove(file_info)
updated += 1 updated += 1
except Exception as e: except Exception as e:
logger.error(f"同步更新文件失败: {e}") logger.error(f"同步更新文件失败: {e}")
# 同步待删除的文件 # 同步待删除的文件
deleted = 0 deleted = 0
for file_id in self._pending_delete[:]: for file_id in self._pending_delete[:]:
try: try:
self._sync_delete_file(file_id) self._sync_delete_file(file_id)
self._pending_delete.remove(file_id) self._pending_delete.remove(file_id)
deleted += 1 deleted += 1
except Exception as e: except Exception as e:
logger.error(f"同步删除文件失败: {e}") logger.error(f"同步删除文件失败: {e}")
# 同步统计 # 同步统计
self.db.reset_pending_count() self.db.reset_pending_count()
self.last_sync_time = time.time() self.last_sync_time = time.time()
duration = time.time() - start_time duration = time.time() - start_time
logger.info(f"同步完成: 添加{added}, 更新{updated}, 删除{deleted}, 耗时{duration:.2f}秒") logger.info(f"同步完成: 添加{added}, 更新{updated}, 删除{deleted}, 耗时{duration:.2f}秒")
# 回调 # 回调
if self.on_sync_complete: if self.on_sync_complete:
self.on_sync_complete({ self.on_sync_complete({
'added': added, 'added': added,
'updated': updated, 'updated': updated,
'deleted': deleted, 'deleted': deleted,
'duration': duration 'duration': duration
}) })
except Exception as e: except Exception as e:
logger.error(f"同步过程错误: {e}") logger.error(f"同步过程错误: {e}")
finally: finally:
self.sync_in_progress = False self.sync_in_progress = False
def scan_local_files(self): def scan_local_files(self):
"""扫描本地文件""" """扫描本地文件"""
if self.scan_in_progress: if self.scan_in_progress:
return return
self.scan_in_progress = True self.scan_in_progress = True
try: try:
base_dir = self.config.get('base_dir', './downloads') base_dir = self.config.get('base_dir', './downloads')
if not os.path.exists(base_dir): if not os.path.exists(base_dir):
self.last_scan_time = time.time() self.last_scan_time = time.time()
return return
# 扫描文件 # 扫描文件(过滤传输中的临时文件)
scanned_files = [] scanned_files = []
for root, dirs, files in os.walk(base_dir): for root, dirs, files in os.walk(base_dir):
for filename in files: for filename in files:
full_path = os.path.join(root, filename) if filename.endswith(('.tmp', '.part')):
rel_path = os.path.relpath(full_path, base_dir).replace("\\", "/") continue
full_path = os.path.join(root, filename)
stat = os.stat(full_path) rel_path = os.path.relpath(full_path, base_dir).replace("\\", "/")
file_info = {
'path': rel_path, stat = os.stat(full_path)
'name': filename, file_info = {
'size': stat.st_size, 'path': rel_path,
'mtime': stat.st_mtime, 'name': filename,
'ctime': stat.st_ctime 'size': stat.st_size,
} 'mtime': stat.st_mtime,
scanned_files.append(file_info) 'ctime': stat.st_ctime
}
# 与数据库对比 scanned_files.append(file_info)
db_files = self.db.list_files(limit=100000)
db_paths = {f.path for f in db_files if not f.is_dir} # 与数据库对比(分批遍历,避免 limit 截断导致误判新增/删除)
db_paths = set()
# 检测新增 offset = 0
local_paths = {f['path'] for f in scanned_files} BATCH = 5000
new_paths = local_paths - db_paths while True:
batch = self.db.list_files(limit=BATCH, offset=offset)
for path in new_paths: if not batch:
file_info = next((f for f in scanned_files if f['path'] == path), None) break
if file_info: db_paths.update(f.path for f in batch if not f.is_dir)
file_id = hashlib.md5(path.encode()).hexdigest() offset += BATCH
self._sync_add_file({
'file_id': file_id, # 检测新增
'path': path, local_paths = {f['path'] for f in scanned_files}
'name': file_info['name'], new_paths = local_paths - db_paths
'size': file_info['size'],
'updated_at': file_info['mtime'] for path in new_paths:
}) file_info = next((f for f in scanned_files if f['path'] == path), None)
if file_info:
# 检测删除 file_id = hashlib.md5(path.encode()).hexdigest()
deleted_paths = db_paths - local_paths self._sync_add_file({
for path in deleted_paths: 'file_id': file_id,
record = self.db.get_file_by_path(path) 'path': path,
if record: 'name': file_info['name'],
self.db.delete_file(record.file_id) 'size': file_info['size'],
'updated_at': file_info['mtime']
self.last_scan_time = time.time() })
except Exception as e: # 检测删除
logger.error(f"扫描本地文件错误: {e}") deleted_paths = db_paths - local_paths
finally: for path in deleted_paths:
self.scan_in_progress = False record = self.db.get_file_by_path(path)
if record:
def _sync_add_file(self, file_info: dict): self.db.delete_file(record.file_id)
"""同步添加文件"""
existing = self.db.get_file_by_path(file_info['path']) self.last_scan_time = time.time()
if existing:
# 已存在,更新 except Exception as e:
self.db.update_file( logger.error(f"扫描本地文件错误: {e}")
existing.file_id, finally:
size=file_info.get('size', 0), self.scan_in_progress = False
updated_at=file_info.get('updated_at', time.time()),
hash=file_info.get('hash'), def _sync_add_file(self, file_info: dict):
sync_status='synced' """同步添加文件"""
) existing = self.db.get_file_by_path(file_info['path'])
else: if existing:
# 新增 # 已存在,更新
file_id = file_info.get('file_id') or hashlib.md5( self.db.update_file(
file_info['path'].encode() existing.file_id,
).hexdigest() size=file_info.get('size', 0),
updated_at=file_info.get('updated_at', time.time()),
self.db.add_file( hash=file_info.get('hash'),
file_id=file_id, sync_status='synced'
path=file_info['path'], )
name=file_info['name'], else:
size=file_info.get('size', 0), # 新增
hash=file_info.get('hash'), file_id = file_info.get('file_id') or hashlib.md5(
is_dir=False, file_info['path'].encode()
created_at=file_info.get('created_at'), ).hexdigest()
updated_at=file_info.get('updated_at', time.time())
) self.db.add_file(
file_id=file_id,
if self.on_file_added: path=file_info['path'],
self.on_file_added(file_info) name=file_info['name'],
size=file_info.get('size', 0),
def _sync_update_file(self, file_info: dict): hash=file_info.get('hash'),
"""同步更新文件""" is_dir=False,
file_id = file_info.get('file_id') created_at=file_info.get('created_at'),
if file_id: updated_at=file_info.get('updated_at', time.time())
self.db.update_file( )
file_id,
size=file_info.get('size'), if self.on_file_added:
updated_at=file_info.get('updated_at', time.time()), self.on_file_added(file_info)
hash=file_info.get('hash'),
sync_status='synced' def _sync_update_file(self, file_info: dict):
) """同步更新文件"""
file_id = file_info.get('file_id')
if self.on_file_updated: if file_id:
self.on_file_updated(file_info) self.db.update_file(
file_id,
def _sync_delete_file(self, file_id: str): size=file_info.get('size'),
"""同步删除文件""" updated_at=file_info.get('updated_at', time.time()),
self.db.delete_file(file_id) hash=file_info.get('hash'),
sync_status='synced'
if self.on_file_deleted: )
self.on_file_deleted({'file_id': file_id})
if self.on_file_updated:
def get_status(self) -> dict: self.on_file_updated(file_info)
"""获取同步状态"""
return { def _sync_delete_file(self, file_id: str):
'running': self._running, """同步删除文件"""
'last_sync_time': self.last_sync_time, self.db.delete_file(file_id)
'last_scan_time': self.last_scan_time,
'sync_in_progress': self.sync_in_progress, if self.on_file_deleted:
'scan_in_progress': self.scan_in_progress, self.on_file_deleted({'file_id': file_id})
'pending_add': len(self._pending_add),
'pending_update': len(self._pending_update), def get_status(self) -> dict:
'pending_delete': len(self._pending_delete), """获取同步状态"""
'pending_operations': self.db.get_pending_operations() if self.db else 0 return {
} 'running': self._running,
'last_sync_time': self.last_sync_time,
def force_sync(self): 'last_scan_time': self.last_scan_time,
"""强制立即同步""" 'sync_in_progress': self.sync_in_progress,
self.last_sync_time = 0 'scan_in_progress': self.scan_in_progress,
self.perform_sync() 'pending_add': len(self._pending_add),
'pending_update': len(self._pending_update),
'pending_delete': len(self._pending_delete),
# ==================== 文件操作包装器 ==================== 'pending_operations': self.db.get_pending_operations() if self.db else 0
}
class DatabaseBackedFileOperations:
"""数据库支持的文件操作""" def force_sync(self):
"""强制立即同步"""
def __init__(self, config: dict, db: DatabaseManager, scheduler: SyncScheduler = None): self.last_sync_time = 0
self.config = config self.perform_sync()
self.db = db
self.scheduler = scheduler
self.base_dir = config.get('base_dir', './downloads') # ==================== 文件操作包装器 ====================
def add_file_record(self, path: str, name: str, size: int = 0, class DatabaseBackedFileOperations:
hash: str = None, is_dir: bool = False) -> dict: """数据库支持的文件操作"""
"""添加文件记录到数据库"""
import hashlib def __init__(self, config: dict, db: DatabaseManager, scheduler: SyncScheduler = None):
self.config = config
file_id = hashlib.md5(path.encode()).hexdigest() self.db = db
self.scheduler = scheduler
file_info = { self.base_dir = config.get('base_dir', './downloads')
'file_id': file_id,
'path': path, def add_file_record(self, path: str, name: str, size: int = 0,
'name': name, hash: str = None, is_dir: bool = False) -> dict:
'size': size, """添加文件记录到数据库"""
'hash': hash, import hashlib
'is_dir': is_dir,
'created_at': time.time(), file_id = hashlib.md5(path.encode()).hexdigest()
'updated_at': time.time()
} file_info = {
'file_id': file_id,
if self.scheduler: 'path': path,
self.scheduler.queue_add(file_info) 'name': name,
else: 'size': size,
self.db.add_file( 'hash': hash,
file_id=file_id, 'is_dir': is_dir,
path=path, 'created_at': time.time(),
name=name, 'updated_at': time.time()
size=size, }
hash=hash,
is_dir=is_dir, if self.scheduler:
created_at=time.time(), self.scheduler.queue_add(file_info)
updated_at=time.time() else:
) self.db.add_file(
file_id=file_id,
return file_info path=path,
name=name,
def update_file_record(self, file_id: str, **kwargs): size=size,
"""更新文件记录""" hash=hash,
if self.scheduler: is_dir=is_dir,
self.scheduler.queue_update({'file_id': file_id, **kwargs}) created_at=time.time(),
else: updated_at=time.time()
self.db.update_file(file_id, **kwargs) )
def delete_file_record(self, file_id: str, hard: bool = False): return file_info
"""删除文件记录"""
if self.scheduler: def update_file_record(self, file_id: str, **kwargs):
self.scheduler.queue_delete(file_id) """更新文件记录"""
else: if self.scheduler:
self.db.delete_file(file_id, hard=hard) self.scheduler.queue_update({'file_id': file_id, **kwargs})
else:
def record_download(self, file_path: str, file_size: int = 0, self.db.update_file(file_id, **kwargs)
client_ip: str = None, duration: float = 0,
success: bool = True, error_message: str = None): def delete_file_record(self, file_id: str, hard: bool = False):
"""记录下载""" """删除文件记录"""
self.db.add_download_record( if self.scheduler:
file_path=file_path, self.scheduler.queue_delete(file_id)
file_size=file_size, else:
client_ip=client_ip, self.db.delete_file(file_id, hard=hard)
duration=duration,
success=success, def record_download(self, file_path: str, file_size: int = 0,
error_message=error_message client_ip: str = None, duration: float = 0,
) success: bool = True, error_message: str = None):
"""记录下载"""
# 更新下载计数 self.db.add_download_record(
record = self.db.get_file_by_path(file_path) file_path=file_path,
if record: file_size=file_size,
self.db.increment_download_count(record.file_id) client_ip=client_ip,
duration=duration,
def record_cache_hit(self, cache_key: str, cache_type: str): success=success,
"""记录缓存命中""" error_message=error_message
record = self.db.get_cache_record(cache_key) )
if record:
self.db.increment_cache_hits(cache_key) # 更新下载计数
else: record = self.db.get_file_by_path(file_path)
self.db.add_cache_record( if record:
cache_key=cache_key, self.db.increment_download_count(record.file_id)
cache_type=cache_type,
hits=1, def record_cache_hit(self, cache_key: str, cache_type: str):
last_hit=time.time() """记录缓存命中"""
) record = self.db.get_cache_record(cache_key)
if record:
self.db.increment_cache_hits(cache_key)
# ==================== 便捷函数 ==================== else:
self.db.add_cache_record(
def get_sync_scheduler(config: dict) -> SyncScheduler: cache_key=cache_key,
"""获取同步调度器""" cache_type=cache_type,
db = get_db(config) hits=1,
return SyncScheduler(config, db) last_hit=time.time()
)
def init_database_sync(config: dict, db=None) -> tuple:
"""初始化数据库和同步""" # ==================== 便捷函数 ====================
if db is None:
db = get_db(config) def get_sync_scheduler(config: dict) -> SyncScheduler:
scheduler = SyncScheduler(config, db) """获取同步调度器"""
file_ops = DatabaseBackedFileOperations(config, db, scheduler) db = get_db(config)
return SyncScheduler(config, db)
return db, scheduler, file_ops
def init_database_sync(config: dict, db=None) -> tuple:
"""初始化数据库和同步"""
if db is None:
db = get_db(config)
scheduler = SyncScheduler(config, db)
file_ops = DatabaseBackedFileOperations(config, db, scheduler)
return db, scheduler, file_ops