diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index aff1235..bd1aeef 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -132,26 +132,65 @@ class AdminController extends Controller private function startSync(array $source, string $syncType): int { - // Create appropriate sync service based on source type - switch ($source['name']) { - case 'steam': - $syncService = new SteamSyncService($this->pdo, $source); - break; - case 'jellyfin': - $syncService = new JellyfinSyncService($this->pdo, $source); - break; - case 'stash': - $syncService = new StashSyncService($this->pdo, $source); - break; - case 'adult': - $syncService = new AdultSyncService($this->pdo, $source); - break; - case 'xbvr': - $syncService = new XbvrSyncService($this->pdo, $source); - break; + // Create sync log entry first + $syncLogId = $this->createSyncLog($source, $syncType); + + // Start sync in background process + $this->startBackgroundSync($source['id'], $syncType, $syncLogId); + + return $syncLogId; + } + + private function createSyncLog(array $source, string $syncType): int + { + $data = [ + 'source_id' => $source['id'], + 'sync_type' => $syncType, + 'status' => 'started', + 'total_items' => 0, + 'processed_items' => 0, + 'new_items' => 0, + 'updated_items' => 0, + 'deleted_items' => 0, + 'started_at' => date('Y-m-d H:i:s') + ]; + + $columns = array_keys($data); + $placeholders = array_map(fn($col) => ":$col", $columns); + $sql = "INSERT INTO sync_logs (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")"; + + $stmt = $this->pdo->prepare($sql); + $stmt->execute($data); + + return (int) $this->pdo->lastInsertId(); + } + + private function startBackgroundSync(int $sourceId, string $syncType, int $syncLogId): void + { + $scriptPath = __DIR__ . '/../../sync-runner.php'; + $command = sprintf( + 'php %s %d %s %d > /dev/null 2>&1 &', + escapeshellarg($scriptPath), + $sourceId, + escapeshellarg($syncType), + $syncLogId + ); + + // Execute the command in background + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + // Windows + pclose(popen('start /B ' . $command, 'r')); + } else { + // Unix-like systems + exec($command); } - // Start sync (this would typically be queued in production) - return $syncService->startSync($syncType); + // */ + // Update sync log to indicate it's running (this will be updated again by the script) + $syncLogModel = new SyncLog($this->pdo); + $syncLogModel->update($syncLogId, [ + 'status' => 'running', + 'message' => 'Sync process starting in background' + ]); } } diff --git a/resources/views/layouts/app.twig b/resources/views/layouts/app.twig index e60e3c4..7bb3412 100644 --- a/resources/views/layouts/app.twig +++ b/resources/views/layouts/app.twig @@ -6,14 +6,26 @@ {{ title }} - Media Collector + {% if app_env == 'production' %} - + {% if manifest['resources/css/app.css'] is defined %} + + {% endif %} {% else %} - + {% endif %} + + + + +{% if app_env == 'production' and manifest['resources/js/app.js'] is defined %} + +{% else %} + +{% endif %} @@ -88,15 +100,5 @@
{% block content %}{% endblock %}
- - - {% if app_env == 'production' %} - - {% else %} - - {% endif %} - - - diff --git a/run_migrations.bat b/run_migrations.bat new file mode 100644 index 0000000..4a72c0e --- /dev/null +++ b/run_migrations.bat @@ -0,0 +1,3 @@ +@echo off +cd /d "%~dp0" +php run_migrations.php diff --git a/sync-runner.php b/sync-runner.php new file mode 100644 index 0000000..d3f558c --- /dev/null +++ b/sync-runner.php @@ -0,0 +1,165 @@ +load(); + +// Load database configuration +$dbConfig = require __DIR__ . '/config/database.php'; +\App\Database\Database::setConfig($dbConfig); + +// Initialize database +try { + $pdo = \App\Database\Database::getInstance(); +} catch (Exception $e) { + echo "Database connection failed: " . $e->getMessage() . "\n"; + exit(1); +} + +use App\Models\Source; +use App\Models\SyncLog; +use App\Services\SteamSyncService; +use App\Services\JellyfinSyncService; +use App\Services\StashSyncService; +use App\Services\XbvrSyncService; +use App\Services\AdultSyncService; +use App\Services\ExophaseSyncService; + +// Get command line arguments +$args = $argv; +array_shift($args); // Remove script name + +if (count($args) < 3) { + echo "Usage: php sync-runner.php [--no-output]\n"; + echo "Example: php sync-runner.php 1 full 123\n"; + echo "Example: php sync-runner.php 2 incremental 124 --no-output\n"; + exit(1); +} + +$sourceId = (int) $args[0]; +$syncType = $args[1]; +$syncLogId = (int) $args[2]; +$noOutput = in_array('--no-output', $args); + +// Initialize PDO connection +try { + $pdo = \App\Database\Database::getInstance(); +} catch (Exception $e) { + echo "Database connection failed: " . $e->getMessage() . "\n"; + exit(1); +} + +// Get source information +$sourceModel = new Source($pdo); +$source = $sourceModel->find($sourceId); + +if (!$source) { + echo "Source with ID {$sourceId} not found.\n"; + exit(1); +} + +// Convert source array to expected format for sync services +$sourceData = [ + 'id' => $source['id'], + 'name' => $source['name'], + 'display_name' => $source['display_name'], + 'api_url' => $source['api_url'], + 'api_key' => $source['api_key'], + 'config' => $source['config'], + 'is_active' => $source['is_active'], + 'last_sync_at' => $source['last_sync_at'] +]; + +// Validate sync type +if ($source['name'] === 'jellyfin') { + $validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows']; + if (!in_array($syncType, $validSyncTypes)) { + echo "Invalid sync type for Jellyfin source. Valid types: " . implode(', ', $validSyncTypes) . "\n"; + exit(1); + } +} else { + $validSyncTypes = ['full', 'incremental']; + if (!in_array($syncType, $validSyncTypes)) { + echo "Invalid sync type. Valid types: " . implode(', ', $validSyncTypes) . "\n"; + exit(1); + } +} + +// Create appropriate sync service based on source type +$syncService = null; +switch ($source['name']) { + case 'steam': + $syncService = new SteamSyncService($pdo, $sourceData); + break; + case 'jellyfin': + $syncService = new JellyfinSyncService($pdo, $sourceData); + break; + case 'stash': + $syncService = new StashSyncService($pdo, $sourceData); + break; + case 'adult': + $syncService = new AdultSyncService($pdo, $sourceData); + break; + case 'xbvr': + $syncService = new XbvrSyncService($pdo, $sourceData); + break; + case 'exophase': + $syncService = new ExophaseSyncService($pdo, $sourceData); + break; + default: + echo "Unsupported source type: " . $source['name'] . "\n"; + exit(1); +} + +if (!$noOutput) { + echo "Starting {$syncType} sync for source: " . ($source['display_name'] ?? $source['name']) . "\n"; + echo "Source ID: {$sourceId}\n"; + echo "Sync Type: {$syncType}\n"; + echo "Sync Log ID: {$syncLogId}\n"; + echo "Timestamp: " . date('Y-m-d H:i:s') . "\n"; + echo "----------------------------------------\n"; +} + +// Update sync log to running status first +$syncLogModel = new SyncLog($pdo); +$syncLogModel->update($syncLogId, [ + 'status' => 'running', + 'message' => 'Sync process started in background' +]); + +// Execute the sync +try { + $syncLogId = $syncService->startSync($syncType); + + if (!$noOutput) { + echo "Sync started with log ID: {$syncLogId}\n"; + echo "Monitor progress at: /admin/sync/status/{$syncLogId}\n"; + echo "Sync completed successfully.\n"; + } + + exit(0); + +} catch (Exception $e) { + if (!$noOutput) { + echo "ERROR: Sync failed - " . $e->getMessage() . "\n"; + echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n"; + } + + // Update sync log as failed + $syncLogModel->update($syncLogId, [ + 'status' => 'failed', + 'completed_at' => date('Y-m-d H:i:s'), + 'message' => $e->getMessage(), + 'errors' => json_encode([ + $e->getMessage(), + "File: " . $e->getFile() . ":" . $e->getLine() + ]) + ]); + + exit(1); +}