mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
- added option to set radarr/sonarr specific scores that profilarr's compiler will handle on import - revise design for arr settings container - now styled as a table - completely rewrote import module. Now uses connection pooling to reuse connections. - fixed import progress bug where 1 failed format causes all other formats to be labelled as failed (even if they succeeded) - fixed bug where on pull sync wasn't working - improve styling for link / unlink database modals - fixed issue where 0 score formats were removed in selective mode
326 lines
10 KiB
Python
326 lines
10 KiB
Python
"""Main import module entry point."""
|
|
import sys
|
|
import logging
|
|
from typing import Dict, Any, List
|
|
from .strategies import FormatStrategy, ProfileStrategy
|
|
from .logger import reset_import_logger
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def handle_import_request(request: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Handle an import request.
|
|
|
|
Args:
|
|
request: Request dictionary containing:
|
|
- arrID: ID of the arr_config to use
|
|
- strategy: 'format' or 'profile'
|
|
- filenames: List of filenames to import
|
|
- dryRun: Optional boolean for dry-run mode (default: false)
|
|
|
|
Returns:
|
|
Import results with added/updated/failed counts
|
|
"""
|
|
from ..db import get_db
|
|
|
|
try:
|
|
# Extract request parameters
|
|
arr_id = request.get('arrID')
|
|
strategy_type = request.get('strategy')
|
|
filenames = request.get('filenames', [])
|
|
dry_run = request.get('dryRun', False)
|
|
|
|
# Validate inputs
|
|
if not arr_id:
|
|
return {'success': False, 'error': 'arrID is required'}
|
|
|
|
if strategy_type not in ['format', 'profile']:
|
|
return {
|
|
'success': False,
|
|
'error': 'strategy must be "format" or "profile"'
|
|
}
|
|
|
|
if not filenames:
|
|
return {'success': False, 'error': 'filenames list is required'}
|
|
|
|
# Load arr_config from database
|
|
with get_db() as conn:
|
|
cursor = conn.execute("SELECT * FROM arr_config WHERE id = ?",
|
|
(arr_id, ))
|
|
arr_config = cursor.fetchone()
|
|
|
|
if not arr_config:
|
|
return {
|
|
'success': False,
|
|
'error': f'arr_config {arr_id} not found'
|
|
}
|
|
|
|
# Select strategy
|
|
strategy_map = {'format': FormatStrategy, 'profile': ProfileStrategy}
|
|
|
|
strategy_class = strategy_map[strategy_type]
|
|
strategy = strategy_class(arr_config)
|
|
|
|
# Execute import with new logger
|
|
import_logger = reset_import_logger()
|
|
|
|
# Show start message
|
|
dry_run_text = " [DRY RUN]" if dry_run else ""
|
|
print(f"Starting {strategy_type} import for {arr_config['name']} ({arr_config['type']}): {len(filenames)} items{dry_run_text}", file=sys.stderr)
|
|
|
|
result = strategy.execute(filenames, dry_run=dry_run)
|
|
|
|
added = result.get('added', 0)
|
|
updated = result.get('updated', 0)
|
|
failed = result.get('failed', 0)
|
|
|
|
# Determine status
|
|
is_partial = failed > 0 and (added > 0 or updated > 0)
|
|
is_success = failed == 0
|
|
|
|
result['success'] = is_success or is_partial
|
|
if is_partial:
|
|
result['status'] = "partial"
|
|
elif is_success:
|
|
result['status'] = "success"
|
|
else:
|
|
result['status'] = "failed"
|
|
|
|
result['arr_config_id'] = arr_id
|
|
result['arr_config_name'] = arr_config['name']
|
|
result['strategy'] = strategy_type
|
|
|
|
# Complete logging
|
|
import_logger.complete()
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.exception("Import request failed")
|
|
return {'success': False, 'error': str(e)}
|
|
|
|
|
|
def handle_scheduled_import(task_id: int) -> Dict[str, Any]:
|
|
"""
|
|
Handle a scheduled import task.
|
|
|
|
Args:
|
|
task_id: ID from scheduled_tasks table
|
|
|
|
Returns:
|
|
Import results
|
|
"""
|
|
from ..db import get_db
|
|
import json
|
|
|
|
try:
|
|
# Find arr_config for this task
|
|
with get_db() as conn:
|
|
cursor = conn.execute(
|
|
"SELECT * FROM arr_config WHERE import_task_id = ?",
|
|
(task_id, ))
|
|
arr_config = cursor.fetchone()
|
|
|
|
if not arr_config:
|
|
return {
|
|
'success': False,
|
|
'error': f'No arr_config found for task {task_id}'
|
|
}
|
|
|
|
# Parse data_to_sync
|
|
data_to_sync = json.loads(arr_config['data_to_sync'] or '{}')
|
|
|
|
# Build import requests
|
|
results = []
|
|
|
|
# Import custom formats
|
|
format_names = data_to_sync.get('customFormats', [])
|
|
if format_names:
|
|
# Remove .yml extension if present
|
|
format_names = [f.replace('.yml', '') for f in format_names]
|
|
|
|
request = {
|
|
'arrID': arr_config['id'],
|
|
'strategy': 'format',
|
|
'filenames': format_names
|
|
}
|
|
result = handle_import_request(request)
|
|
results.append(result)
|
|
|
|
# Import profiles
|
|
profile_names = data_to_sync.get('profiles', [])
|
|
if profile_names:
|
|
# Remove .yml extension if present
|
|
profile_names = [p.replace('.yml', '') for p in profile_names]
|
|
|
|
request = {
|
|
'arrID': arr_config['id'],
|
|
'strategy': 'profile',
|
|
'filenames': profile_names
|
|
}
|
|
result = handle_import_request(request)
|
|
results.append(result)
|
|
|
|
# Combine results
|
|
total_added = sum(r.get('added', 0) for r in results)
|
|
total_updated = sum(r.get('updated', 0) for r in results)
|
|
total_failed = sum(r.get('failed', 0) for r in results)
|
|
|
|
is_partial = total_failed > 0 and (total_added > 0
|
|
or total_updated > 0)
|
|
is_success = total_failed == 0
|
|
|
|
status = "failed"
|
|
if is_partial:
|
|
status = "partial"
|
|
elif is_success:
|
|
status = "success"
|
|
|
|
combined_result = {
|
|
'success': is_success or is_partial,
|
|
'status': status,
|
|
'task_id': task_id,
|
|
'arr_config_id': arr_config['id'],
|
|
'arr_config_name': arr_config['name'],
|
|
'added': total_added,
|
|
'updated': total_updated,
|
|
'failed': total_failed,
|
|
'results': results
|
|
}
|
|
|
|
# Update sync status
|
|
_update_sync_status(arr_config['id'], combined_result)
|
|
|
|
return combined_result
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Scheduled import {task_id} failed")
|
|
return {'success': False, 'error': str(e)}
|
|
|
|
|
|
def handle_pull_import(arr_config_id: int) -> Dict[str, Any]:
|
|
"""
|
|
Handle an on-pull import for a specific ARR config.
|
|
|
|
This mirrors scheduled import behavior but is triggered immediately
|
|
during a git pull (not scheduled).
|
|
"""
|
|
from ..db import get_db
|
|
import json
|
|
|
|
try:
|
|
# Load arr_config by id
|
|
with get_db() as conn:
|
|
cursor = conn.execute("SELECT * FROM arr_config WHERE id = ?",
|
|
(arr_config_id, ))
|
|
arr_config = cursor.fetchone()
|
|
if not arr_config:
|
|
return {
|
|
'success': False,
|
|
'error': f'arr_config {arr_config_id} not found'
|
|
}
|
|
|
|
# Parse data_to_sync
|
|
data_to_sync = json.loads(arr_config['data_to_sync'] or '{}')
|
|
|
|
results: List[Dict[str, Any]] = []
|
|
|
|
# Import custom formats
|
|
format_names = data_to_sync.get('customFormats', [])
|
|
if format_names:
|
|
format_names = [f.replace('.yml', '') for f in format_names]
|
|
request = {
|
|
'arrID': arr_config['id'],
|
|
'strategy': 'format',
|
|
'filenames': format_names,
|
|
}
|
|
result = handle_import_request(request)
|
|
results.append(result)
|
|
|
|
# Import profiles
|
|
profile_names = data_to_sync.get('profiles', [])
|
|
if profile_names:
|
|
profile_names = [p.replace('.yml', '') for p in profile_names]
|
|
request = {
|
|
'arrID': arr_config['id'],
|
|
'strategy': 'profile',
|
|
'filenames': profile_names,
|
|
}
|
|
result = handle_import_request(request)
|
|
results.append(result)
|
|
|
|
# Combine results
|
|
total_added = sum(r.get('added', 0) for r in results)
|
|
total_updated = sum(r.get('updated', 0) for r in results)
|
|
total_failed = sum(r.get('failed', 0) for r in results)
|
|
|
|
is_partial = total_failed > 0 and (total_added > 0
|
|
or total_updated > 0)
|
|
is_success = total_failed == 0
|
|
|
|
status = "failed"
|
|
if is_partial:
|
|
status = "partial"
|
|
elif is_success:
|
|
status = "success"
|
|
|
|
combined_result = {
|
|
'success': is_success or is_partial,
|
|
'status': status,
|
|
'arr_config_id': arr_config['id'],
|
|
'arr_config_name': arr_config['name'],
|
|
'added': total_added,
|
|
'updated': total_updated,
|
|
'failed': total_failed,
|
|
'results': results,
|
|
}
|
|
|
|
# Update sync status
|
|
_update_sync_status(arr_config['id'], combined_result)
|
|
|
|
return combined_result
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Pull import for arr_config {arr_config_id} failed")
|
|
return {
|
|
'success': False,
|
|
'error': str(e),
|
|
}
|
|
|
|
|
|
def _update_sync_status(config_id: int, result: Dict[str, Any]) -> None:
|
|
"""Update arr_config sync status after scheduled import."""
|
|
from ..db import get_db
|
|
from datetime import datetime
|
|
|
|
try:
|
|
total = result.get('added', 0) + result.get('updated', 0) + result.get(
|
|
'failed', 0)
|
|
successful = result.get('added', 0) + result.get('updated', 0)
|
|
|
|
sync_percentage = int((successful / total * 100) if total > 0 else 0)
|
|
|
|
with get_db() as conn:
|
|
conn.execute(
|
|
"""
|
|
UPDATE arr_config
|
|
SET last_sync_time = ?,
|
|
sync_percentage = ?
|
|
WHERE id = ?
|
|
""", (datetime.now(), sync_percentage, config_id))
|
|
conn.commit()
|
|
|
|
logger.info(
|
|
f"Updated sync status for arr_config #{config_id}: {sync_percentage}%"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to update sync status: {e}")
|
|
|
|
|
|
# Export main functions
|
|
__all__ = [
|
|
'handle_import_request', 'handle_scheduled_import', 'handle_pull_import'
|
|
]
|