- Add sync.py for LAN file sync with atomic writes and mtime-based change detection - Add watch.py daemon that periodically syncs files and migrates only changed tables - Add TRUNCATE mode to migrate.py (--truncate flag) to preserve table structure - Update config.yaml schema with check_interval_minutes and lan_sources - Update README with new features documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
2.5 KiB
Python
90 lines
2.5 KiB
Python
"""
|
|
LAN file sync module.
|
|
Detects file changes, copies from LAN UNC paths to local, and determines which tables need migration.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
|
|
|
|
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):
|
|
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:
|
|
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:
|
|
return 'copied', None
|
|
else:
|
|
return 'updated', None
|
|
|
|
except Exception as 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
|