fix timezone handling and logging fallback robustness

This commit is contained in:
windyboy
2026-02-09 20:16:39 +08:00
parent 976a1fa1d0
commit aa0dc8ec47
13 changed files with 492 additions and 86 deletions
+5 -4
View File
@@ -15,8 +15,9 @@ from vlm.config import Config, load_config, create_default_config, validate_conf
from vlm.logging_config import setup_logging, get_logger
# Default configuration path
DEFAULT_CONFIG_PATH = Path.home() / ".vlm" / "config.yaml"
def default_config_path() -> Path:
"""Return the default config path resolved at runtime."""
return Path.home() / ".vlm" / "config.yaml"
class CLIContext:
@@ -34,7 +35,7 @@ pass_context = click.make_pass_decorator(CLIContext)
@click.option(
'--config',
type=click.Path(path_type=Path),
default=DEFAULT_CONFIG_PATH,
default=default_config_path,
help='Path to configuration file (default: ~/.vlm/config.yaml)'
)
@click.option(
@@ -1962,7 +1963,7 @@ def config_cmd(ctx: CLIContext):
@click.option(
'--path',
type=click.Path(path_type=Path),
default=DEFAULT_CONFIG_PATH,
default=default_config_path,
help='Path where configuration file should be created'
)
def config_init(path: Path):
+23 -17
View File
@@ -13,13 +13,16 @@ from pathlib import Path
from typing import Optional
# Default log directory
DEFAULT_LOG_DIR = Path.home() / ".vlm" / "logs"
DEFAULT_LOG_FILE = "vlm.log"
MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB in bytes
BACKUP_COUNT = 5 # Keep 5 backup log files
def default_log_dir() -> Path:
"""Return the default log directory resolved at runtime."""
return Path.home() / ".vlm" / "logs"
class OperationContextFilter(logging.Filter):
"""Filter to add operation context to log records."""
@@ -57,10 +60,7 @@ def setup_logging(
# Use default log directory if not specified
if log_dir is None:
log_dir = DEFAULT_LOG_DIR
# Create log directory if it doesn't exist
log_dir.mkdir(parents=True, exist_ok=True)
log_dir = default_log_dir()
# Get root logger
logger = logging.getLogger("vlm")
@@ -83,17 +83,23 @@ def setup_logging(
logger.addHandler(console_handler)
# File handler with rotation (DEBUG+)
log_file_path = log_dir / log_file
file_handler = logging.handlers.RotatingFileHandler(
filename=log_file_path,
maxBytes=MAX_LOG_SIZE,
backupCount=BACKUP_COUNT,
encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
file_handler.addFilter(OperationContextFilter())
logger.addHandler(file_handler)
try:
log_dir.mkdir(parents=True, exist_ok=True)
log_file_path = log_dir / log_file
file_handler = logging.handlers.RotatingFileHandler(
filename=log_file_path,
maxBytes=MAX_LOG_SIZE,
backupCount=BACKUP_COUNT,
encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
file_handler.addFilter(OperationContextFilter())
logger.addHandler(file_handler)
except (OSError, PermissionError) as exc:
logger.warning(
f"File logging disabled (cannot write to {log_dir}): {exc}"
)
# Prevent propagation to root logger
logger.propagate = False
+9 -10
View File
@@ -19,6 +19,13 @@ from vlm.models import SeasonCompleteness, DuplicateGroup, VideoFile, MovieIdent
logger = logging.getLogger(__name__)
def _normalize_to_utc(timestamp: datetime) -> datetime:
"""Normalize a datetime to a UTC instant."""
if timestamp.tzinfo is None:
timestamp = timestamp.astimezone()
return timestamp.astimezone(timezone.utc)
def generate_inventory_report(
files: list[VideoFile],
format: str,
@@ -87,11 +94,7 @@ def _generate_inventory_csv(
# Write each file
for video_file in files:
# Format timestamp as ISO 8601 in UTC
if video_file.modified_timestamp.tzinfo is None:
# Assume local time, convert to UTC
modified_utc = video_file.modified_timestamp.replace(tzinfo=timezone.utc)
else:
modified_utc = video_file.modified_timestamp.astimezone(timezone.utc)
modified_utc = _normalize_to_utc(video_file.modified_timestamp)
row = {
'path': str(video_file.path),
@@ -127,11 +130,7 @@ def _generate_inventory_json(
# Add each file
for video_file in files:
# Format timestamp as ISO 8601 in UTC
if video_file.modified_timestamp.tzinfo is None:
# Assume local time, convert to UTC
modified_utc = video_file.modified_timestamp.replace(tzinfo=timezone.utc)
else:
modified_utc = video_file.modified_timestamp.astimezone(timezone.utc)
modified_utc = _normalize_to_utc(video_file.modified_timestamp)
file_data = {
'path': str(video_file.path),
+10 -16
View File
@@ -20,6 +20,13 @@ from vlm.models import VideoFile
logger = logging.getLogger(__name__)
def _normalize_to_utc(timestamp: datetime) -> datetime:
"""Normalize a datetime to a UTC instant."""
if timestamp.tzinfo is None:
timestamp = timestamp.astimezone()
return timestamp.astimezone(timezone.utc)
def scan_library(root: Path, config: Config) -> list[VideoFile]:
"""Recursively scan library for video files.
@@ -50,8 +57,6 @@ def scan_library(root: Path, config: Config) -> list[VideoFile]:
video_files = []
file_count = 0
error_count = 0
# Recursively scan directory tree
for video_file in _scan_directory_recursive(root, config, root):
video_files.append(video_file)
@@ -61,9 +66,6 @@ def scan_library(root: Path, config: Config) -> list[VideoFile]:
logger.debug(f"Scanned {file_count} files so far...")
logger.info(f"Scan complete. Found {file_count} video files")
if error_count > 0:
logger.warning(f"Encountered {error_count} errors during scan (see log for details)")
return video_files
@@ -145,7 +147,7 @@ def _create_video_file(file_path: Path, library_root: Path) -> Optional[VideoFil
# Get file stats
stat = file_path.stat()
size_bytes = stat.st_size
modified_timestamp = datetime.fromtimestamp(stat.st_mtime)
modified_timestamp = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
# Categorize based on directory structure
category = categorize_file(file_path, library_root)
@@ -376,11 +378,7 @@ def save_inventory_csv(files: list[VideoFile], output: Path, library_root: Path)
# Write each file
for video_file in files:
# Format timestamp as ISO 8601 in UTC
if video_file.modified_timestamp.tzinfo is None:
# Assume local time, convert to UTC
modified_utc = video_file.modified_timestamp.replace(tzinfo=timezone.utc)
else:
modified_utc = video_file.modified_timestamp.astimezone(timezone.utc)
modified_utc = _normalize_to_utc(video_file.modified_timestamp)
row = {
'path': str(video_file.path),
@@ -434,11 +432,7 @@ def save_inventory_json(files: list[VideoFile], output: Path, library_root: Path
# Add each file
for video_file in files:
# Format timestamp as ISO 8601 in UTC
if video_file.modified_timestamp.tzinfo is None:
# Assume local time, convert to UTC
modified_utc = video_file.modified_timestamp.replace(tzinfo=timezone.utc)
else:
modified_utc = video_file.modified_timestamp.astimezone(timezone.utc)
modified_utc = _normalize_to_utc(video_file.modified_timestamp)
file_data = {
'path': str(video_file.path),