fix timezone handling and logging fallback robustness
This commit is contained in:
+5
-4
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user