- Add logger.py with TimedRotatingFileHandler (daily rotation, 30-day retention) - Replace all print() in migrate.py with logging calls - Add logging to sync.py for sync status tracking - Refactor watch.py to use logger.py, remove duplicate first_run branch - All logs now go to logs/app.log consistently - Add logging config section to config.yaml, add logs/ to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
"""
|
|
LAN file sync module.
|
|
Detects file changes, copies from LAN UNC paths to local, and determines which tables need migration.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import shutil
|
|
|
|
logger = logging.getLogger('app')
|
|
|
|
|
|
def sync_file(local_path, lan_path):
|
|
"""Sync a single file from LAN to local.
|
|
|
|
Returns:
|
|
(status, error_msg)
|
|
status: 'copied' | 'updated' | 'skipped' | 'error'
|
|
"""
|
|
try:
|
|
if not os.path.exists(lan_path):
|
|
logger.error(f"LAN 路径不可达: {lan_path}")
|
|
return 'error', f'LAN path not accessible: {lan_path}'
|
|
|
|
lan_mtime = os.path.getmtime(lan_path)
|
|
local_existed = os.path.exists(local_path)
|
|
|
|
if local_existed:
|
|
local_mtime = os.path.getmtime(local_path)
|
|
if local_mtime == lan_mtime:
|
|
logger.debug(f"跳过 (mtime一致): {os.path.basename(local_path)}")
|
|
return 'skipped', None
|
|
|
|
# Ensure parent directory exists
|
|
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
|
|
|
# Atomic copy: write to .tmp first, then os.replace
|
|
tmp_path = local_path + '.tmp'
|
|
try:
|
|
shutil.copy2(lan_path, tmp_path)
|
|
os.replace(tmp_path, local_path)
|
|
except Exception:
|
|
# Clean up temp file on failure
|
|
if os.path.exists(tmp_path):
|
|
os.remove(tmp_path)
|
|
raise
|
|
|
|
if not local_existed:
|
|
logger.info(f"copied: {os.path.basename(local_path)}")
|
|
return 'copied', None
|
|
else:
|
|
logger.info(f"updated: {os.path.basename(local_path)}")
|
|
return 'updated', None
|
|
|
|
except Exception as e:
|
|
logger.error(f"同步失败: {local_path} - {e}")
|
|
return 'error', str(e)
|
|
|
|
|
|
def sync_all_files(cfg, excel_dir):
|
|
"""Sync all LAN source files to local.
|
|
|
|
Returns:
|
|
{rel_path: (status, error_msg)}
|
|
"""
|
|
lan_sources = cfg.get('lan_sources', {})
|
|
results = {}
|
|
|
|
for rel_path, lan_path in lan_sources.items():
|
|
local_path = os.path.join(excel_dir, rel_path)
|
|
status, error_msg = sync_file(local_path, lan_path)
|
|
results[rel_path] = (status, error_msg)
|
|
|
|
return results
|
|
|
|
|
|
def determine_tables_to_migrate(cfg, sync_results):
|
|
"""Determine which tables need migration based on sync results.
|
|
|
|
A table needs migration if ANY of its source files were copied or updated.
|
|
|
|
Returns:
|
|
set of table names that need migration
|
|
"""
|
|
tables_to_migrate = set()
|
|
|
|
for table_cfg in cfg.get('tables', []):
|
|
table_name = table_cfg['table']
|
|
for src in table_cfg['sources']:
|
|
file_rel = src['file']
|
|
if file_rel in sync_results:
|
|
status, _ = sync_results[file_rel]
|
|
if status in ('copied', 'updated'):
|
|
tables_to_migrate.add(table_name)
|
|
break
|
|
|
|
return tables_to_migrate
|