mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
snyc...
This commit is contained in:
@@ -54,7 +54,7 @@ class AdminController extends Controller
|
|||||||
|
|
||||||
// Validate sync type based on source type
|
// Validate sync type based on source type
|
||||||
if ($source['name'] === 'jellyfin') {
|
if ($source['name'] === 'jellyfin') {
|
||||||
$validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows'];
|
$validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows', 'cleanup'];
|
||||||
if (!in_array($syncType, $validSyncTypes)) {
|
if (!in_array($syncType, $validSyncTypes)) {
|
||||||
return $this->json($response, [
|
return $this->json($response, [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Models\TvShow;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Slim\Views\Twig;
|
use Slim\Views\Twig;
|
||||||
@@ -30,14 +31,16 @@ class TvShowController extends Controller
|
|||||||
// Get view mode
|
// Get view mode
|
||||||
$viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers
|
$viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers
|
||||||
|
|
||||||
// For now, return empty arrays since TV Shows aren't implemented yet
|
// Get TV shows with pagination and search
|
||||||
$tvshows = [];
|
$tvshows = TvShow::getAllWithPagination($this->pdo, $page, $perPage, $search);
|
||||||
$totalCount = 0;
|
|
||||||
|
// Get total count for pagination
|
||||||
|
$totalCount = TvShow::getTotalCount($this->pdo, $search);
|
||||||
|
|
||||||
// Calculate pagination info
|
// Calculate pagination info
|
||||||
$totalPages = 0;
|
$totalPages = ceil($totalCount / $perPage);
|
||||||
$hasNextPage = false;
|
$hasNextPage = $page < $totalPages;
|
||||||
$hasPrevPage = false;
|
$hasPrevPage = $page > 1;
|
||||||
|
|
||||||
return $this->view->render($response, 'tvshows/index.twig', [
|
return $this->view->render($response, 'tvshows/index.twig', [
|
||||||
'title' => 'TV Shows',
|
'title' => 'TV Shows',
|
||||||
@@ -91,31 +94,9 @@ class TvShowController extends Controller
|
|||||||
");
|
");
|
||||||
$stmt->execute(['tv_show_id' => $tvShowId]);
|
$stmt->execute(['tv_show_id' => $tvShowId]);
|
||||||
$actors = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
$actors = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
/*
|
// Get seasons and episodes for this TV show
|
||||||
// Get seasons for this TV show
|
$tvShowModel = new TvShow($this->pdo, $tvShow);
|
||||||
$stmt = $this->pdo->prepare("
|
$seasons = $tvShowModel->getSeasonsWithEpisodes();
|
||||||
SELECT * FROM tv_seasons
|
|
||||||
WHERE tv_show_id = :tv_show_id
|
|
||||||
ORDER BY season_number ASC
|
|
||||||
");
|
|
||||||
$stmt->execute(['tv_show_id' => $tvShowId]);
|
|
||||||
$seasons = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
||||||
*//*
|
|
||||||
// Get episodes for each season
|
|
||||||
foreach ($seasons as &$season) {
|
|
||||||
$stmt = $this->pdo->prepare("
|
|
||||||
SELECT * FROM tv_episodes
|
|
||||||
WHERE tv_show_id = :tv_show_id AND season_number = :season_number
|
|
||||||
ORDER BY episode_number ASC
|
|
||||||
");
|
|
||||||
$stmt->execute([
|
|
||||||
'tv_show_id' => $tvShowId,
|
|
||||||
'season_number' => $season['season_number']
|
|
||||||
]);
|
|
||||||
$season['episodes'] = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
unset($season); // Unset reference
|
|
||||||
*/
|
|
||||||
return $this->view->render($response, 'tvshows/show.twig', [
|
return $this->view->render($response, 'tvshows/show.twig', [
|
||||||
'title' => $tvShow['title'],
|
'title' => $tvShow['title'],
|
||||||
'tvshow' => $tvShow,
|
'tvshow' => $tvShow,
|
||||||
|
|||||||
@@ -51,10 +51,20 @@ abstract class BaseSyncService
|
|||||||
{
|
{
|
||||||
if ($this->logFileHandle) {
|
if ($this->logFileHandle) {
|
||||||
$this->logProgress("=== Sync completed at " . date('Y-m-d H:i:s') . " ===");
|
$this->logProgress("=== Sync completed at " . date('Y-m-d H:i:s') . " ===");
|
||||||
|
$this->updateSyncLog($this->currentSyncLogId, 'completed', [
|
||||||
|
'processed_items' => $this->getProcessedCount(),
|
||||||
|
'new_items' => $this->getNewCount(),
|
||||||
|
'updated_items' => $this->getUpdatedCount(),
|
||||||
|
'deleted_items' => $this->getDeletedCount(),
|
||||||
|
'message' => $this->getCompletionMessage()
|
||||||
|
]);
|
||||||
fclose($this->logFileHandle);
|
fclose($this->logFileHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected $deletedCount = 0;
|
||||||
|
protected $totalItems = 0;
|
||||||
|
|
||||||
public function startSync(string $syncType = 'full'): int
|
public function startSync(string $syncType = 'full'): int
|
||||||
{
|
{
|
||||||
// Set higher limits for long-running syncs
|
// Set higher limits for long-running syncs
|
||||||
@@ -79,18 +89,31 @@ abstract class BaseSyncService
|
|||||||
try {
|
try {
|
||||||
$this->logProgress("Starting {$syncType} sync for source: " . ($this->source['display_name'] ?? $this->source['name']));
|
$this->logProgress("Starting {$syncType} sync for source: " . ($this->source['display_name'] ?? $this->source['name']));
|
||||||
|
|
||||||
|
// Execute cleanup if requested
|
||||||
|
if ($syncType === 'cleanup') {
|
||||||
|
$this->executeCleanup();
|
||||||
|
} else {
|
||||||
$this->executeSync($syncType);
|
$this->executeSync($syncType);
|
||||||
|
|
||||||
|
// Optionally run cleanup after regular sync
|
||||||
|
if (in_array($syncType, ['full', 'incremental'])) {
|
||||||
|
$this->logProgress("Running cleanup after sync...");
|
||||||
|
$this->executeCleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update sync log as completed
|
// Update sync log as completed
|
||||||
$this->updateSyncLog($syncLogId, 'completed', [
|
$this->updateSyncLog($syncLogId, 'completed', [
|
||||||
'processed_items' => $this->getProcessedCount(),
|
'processed_items' => $this->getProcessedCount(),
|
||||||
'new_items' => $this->getNewCount(),
|
'new_items' => $this->getNewCount(),
|
||||||
'updated_items' => $this->getUpdatedCount(),
|
'updated_items' => $this->getUpdatedCount(),
|
||||||
'deleted_items' => $this->getDeletedCount(),
|
'deleted_items' => $this->getDeletedCount(),
|
||||||
'message' => "Successfully completed sync"
|
'total_items' => $this->getTotalItems(),
|
||||||
|
'message' => $this->getCompletionMessage()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->logProgress("Sync completed successfully");
|
// Log completion to file but don't update database (already completed above)
|
||||||
|
$this->logProgressToFile("Sync completed successfully");
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// Log the full error details
|
// Log the full error details
|
||||||
@@ -119,6 +142,12 @@ abstract class BaseSyncService
|
|||||||
return $syncLogId;
|
return $syncLogId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function executeCleanup(): void
|
||||||
|
{
|
||||||
|
// Override in subclasses to implement cleanup logic
|
||||||
|
$this->logProgress("Cleanup not implemented for this sync service");
|
||||||
|
}
|
||||||
|
|
||||||
private function createSyncLog(string $syncType, string $status): int
|
private function createSyncLog(string $syncType, string $status): int
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
@@ -162,6 +191,10 @@ abstract class BaseSyncService
|
|||||||
$data['message'] = $stats['message'];
|
$data['message'] = $stats['message'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($stats['total_items'])) {
|
||||||
|
$data['total_items'] = $stats['total_items'];
|
||||||
|
}
|
||||||
|
|
||||||
$setClause = array_map(fn($col) => "$col = :$col", array_keys($data));
|
$setClause = array_map(fn($col) => "$col = :$col", array_keys($data));
|
||||||
$sql = "UPDATE sync_logs SET " . implode(', ', $setClause) . " WHERE id = :id";
|
$sql = "UPDATE sync_logs SET " . implode(', ', $setClause) . " WHERE id = :id";
|
||||||
$data['id'] = $syncLogId;
|
$data['id'] = $syncLogId;
|
||||||
@@ -189,11 +222,53 @@ abstract class BaseSyncService
|
|||||||
|
|
||||||
protected function getDeletedCount(): int
|
protected function getDeletedCount(): int
|
||||||
{
|
{
|
||||||
return 0; // Override in subclasses
|
return $this->deletedCount; // Return the deleted count
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTotalItems(): int
|
||||||
|
{
|
||||||
|
return $this->totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setTotalItems(int $total): void
|
||||||
|
{
|
||||||
|
$this->totalItems = $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCompletionMessage(): string
|
||||||
|
{
|
||||||
|
$new = $this->getNewCount();
|
||||||
|
$updated = $this->getUpdatedCount();
|
||||||
|
$deleted = $this->getDeletedCount();
|
||||||
|
|
||||||
|
$message = "Sync completed";
|
||||||
|
if ($new > 0 || $updated > 0 || $deleted > 0) {
|
||||||
|
$parts = [];
|
||||||
|
if ($new > 0) $parts[] = "{$new} new";
|
||||||
|
if ($updated > 0) $parts[] = "{$updated} updated";
|
||||||
|
if ($deleted > 0) $parts[] = "{$deleted} deleted";
|
||||||
|
$message .= ": " . implode(", ", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $currentSyncLogId = null;
|
protected $currentSyncLogId = null;
|
||||||
|
|
||||||
|
protected function logProgressToFile(string $message): void
|
||||||
|
{
|
||||||
|
$timestamp = date('H:i:s');
|
||||||
|
$logMessage = "[{$timestamp}] {$message}\n";
|
||||||
|
|
||||||
|
// Write to log file if available
|
||||||
|
if ($this->logFileHandle) {
|
||||||
|
fwrite($this->logFileHandle, $logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also write to error log for immediate visibility
|
||||||
|
error_log($message);
|
||||||
|
}
|
||||||
|
|
||||||
protected function logProgress(string $message): void
|
protected function logProgress(string $message): void
|
||||||
{
|
{
|
||||||
$timestamp = date('H:i:s');
|
$timestamp = date('H:i:s');
|
||||||
@@ -209,9 +284,23 @@ abstract class BaseSyncService
|
|||||||
|
|
||||||
// Update sync log with progress message if we have a current sync log
|
// Update sync log with progress message if we have a current sync log
|
||||||
if ($this->currentSyncLogId) {
|
if ($this->currentSyncLogId) {
|
||||||
$this->updateSyncLog($this->currentSyncLogId, 'running', [
|
$updateData = [
|
||||||
'message' => $message
|
'message' => $message,
|
||||||
]);
|
'processed_items' => $this->getProcessedCount(),
|
||||||
|
'new_items' => $this->getNewCount(),
|
||||||
|
'updated_items' => $this->getUpdatedCount(),
|
||||||
|
'deleted_items' => $this->getDeletedCount()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Only update total_items if it's greater than 0 (to avoid overwriting with 0)
|
||||||
|
if ($this->getTotalItems() > 0) {
|
||||||
|
$updateData['total_items'] = $this->getTotalItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't update status for completion messages - status should remain as set by completion logic
|
||||||
|
$newStatus = 'running';
|
||||||
|
|
||||||
|
$this->updateSyncLog($this->currentSyncLogId, $newStatus, $updateData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ class JellyfinSyncService extends BaseSyncService
|
|||||||
try {
|
try {
|
||||||
$this->logProgress('Fetching movies from Jellyfin...');
|
$this->logProgress('Fetching movies from Jellyfin...');
|
||||||
$movies = $this->getJellyfinItems('Movie');
|
$movies = $this->getJellyfinItems('Movie');
|
||||||
$this->logProgress("Found " . count($movies) . " movies in Jellyfin");
|
$movieCount = count($movies);
|
||||||
|
$this->setTotalItems($movieCount);
|
||||||
|
$this->logProgress("Found {$movieCount} movies in Jellyfin");
|
||||||
|
|
||||||
if (empty($movies)) {
|
if (empty($movies)) {
|
||||||
$this->logProgress('No movies found in Jellyfin library');
|
$this->logProgress('No movies found in Jellyfin library');
|
||||||
@@ -118,7 +120,9 @@ class JellyfinSyncService extends BaseSyncService
|
|||||||
$this->logProgress('=== Starting TV Shows Sync ===');
|
$this->logProgress('=== Starting TV Shows Sync ===');
|
||||||
$this->logProgress('Fetching TV shows from Jellyfin...');
|
$this->logProgress('Fetching TV shows from Jellyfin...');
|
||||||
$tvShows = $this->getJellyfinItems('Series');
|
$tvShows = $this->getJellyfinItems('Series');
|
||||||
$this->logProgress("Found " . count($tvShows) . " TV shows in Jellyfin");
|
$tvShowCount = count($tvShows);
|
||||||
|
$this->setTotalItems($tvShowCount);
|
||||||
|
$this->logProgress("Found {$tvShowCount} TV shows in Jellyfin");
|
||||||
|
|
||||||
if (empty($tvShows)) {
|
if (empty($tvShows)) {
|
||||||
$this->logProgress('No TV shows found in Jellyfin library');
|
$this->logProgress('No TV shows found in Jellyfin library');
|
||||||
@@ -131,7 +135,8 @@ class JellyfinSyncService extends BaseSyncService
|
|||||||
|
|
||||||
foreach ($tvShows as $showData) {
|
foreach ($tvShows as $showData) {
|
||||||
$processedShows++;
|
$processedShows++;
|
||||||
$this->logProgress("Processing TV show {$processedShows}/" . count($tvShows) . ": {$showData['Name']} (ID: {$showData['Id']})");
|
$this->processedCount++; // Increment processed count for each TV show
|
||||||
|
$this->logProgress("Processing TV show {$processedShows}/{$tvShowCount}: {$showData['Name']} (ID: {$showData['Id']})");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->syncTvShow($showData);
|
$this->syncTvShow($showData);
|
||||||
@@ -858,8 +863,196 @@ class JellyfinSyncService extends BaseSyncService
|
|||||||
return $this->updatedCount;
|
return $this->updatedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDeletedCount(): int
|
protected function executeCleanup(): void
|
||||||
{
|
{
|
||||||
return 0; // Jellyfin doesn't provide deletion info in this context
|
$this->logProgress("Starting cleanup - detecting deleted media in Jellyfin...");
|
||||||
|
|
||||||
|
// Clean up movies
|
||||||
|
$this->cleanupMovies();
|
||||||
|
|
||||||
|
// Clean up TV shows and episodes
|
||||||
|
$this->cleanupTvShows();
|
||||||
|
|
||||||
|
$this->logProgress("Cleanup completed. Deleted {$this->deletedCount} items.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupMovies(): void
|
||||||
|
{
|
||||||
|
$this->logProgress("Checking for deleted movies...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all movies from Jellyfin
|
||||||
|
$jellyfinMovies = $this->getJellyfinItems('Movie');
|
||||||
|
$jellyfinMovieIds = array_column($jellyfinMovies, 'Id');
|
||||||
|
$this->logProgress("Found " . count($jellyfinMovieIds) . " movies in Jellyfin");
|
||||||
|
|
||||||
|
// Get all movies from local database for this source
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT id, metadata FROM movies WHERE source_id = :source_id
|
||||||
|
");
|
||||||
|
$stmt->execute(['source_id' => $this->source['id']]);
|
||||||
|
$localMovies = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->logProgress("Found " . count($localMovies) . " movies in local database");
|
||||||
|
|
||||||
|
$deletedCount = 0;
|
||||||
|
foreach ($localMovies as $localMovie) {
|
||||||
|
$metadata = json_decode($localMovie['metadata'], true);
|
||||||
|
$jellyfinId = $metadata['jellyfin_id'] ?? null;
|
||||||
|
|
||||||
|
if ($jellyfinId && !in_array($jellyfinId, $jellyfinMovieIds)) {
|
||||||
|
// Movie exists in local DB but not in Jellyfin - delete it
|
||||||
|
$this->deleteMovie($localMovie['id']);
|
||||||
|
$deletedCount++;
|
||||||
|
$this->deletedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logProgress("Deleted {$deletedCount} movies that no longer exist in Jellyfin");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error during movie cleanup: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupTvShows(): void
|
||||||
|
{
|
||||||
|
$this->logProgress("Checking for deleted TV shows and episodes...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all TV shows from Jellyfin
|
||||||
|
$jellyfinShows = $this->getJellyfinItems('Series');
|
||||||
|
$jellyfinShowIds = array_column($jellyfinShows, 'Id');
|
||||||
|
$this->logProgress("Found " . count($jellyfinShowIds) . " TV shows in Jellyfin");
|
||||||
|
|
||||||
|
// Get all TV shows from local database for this source
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT id, metadata FROM tv_shows WHERE source_id = :source_id
|
||||||
|
");
|
||||||
|
$stmt->execute(['source_id' => $this->source['id']]);
|
||||||
|
$localShows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->logProgress("Found " . count($localShows) . " TV shows in local database");
|
||||||
|
|
||||||
|
// Check for deleted shows
|
||||||
|
$deletedShows = 0;
|
||||||
|
foreach ($localShows as $localShow) {
|
||||||
|
$metadata = json_decode($localShow['metadata'], true);
|
||||||
|
$jellyfinId = $metadata['jellyfin_id'] ?? null;
|
||||||
|
|
||||||
|
if ($jellyfinId && !in_array($jellyfinId, $jellyfinShowIds)) {
|
||||||
|
// Show exists in local DB but not in Jellyfin - delete it
|
||||||
|
$this->deleteTvShow($localShow['id']);
|
||||||
|
$deletedShows++;
|
||||||
|
$this->deletedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logProgress("Deleted {$deletedShows} TV shows that no longer exist in Jellyfin");
|
||||||
|
|
||||||
|
// Also clean up episodes that might be orphaned (show deleted but episodes remain)
|
||||||
|
$this->cleanupOrphanedEpisodes();
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error during TV show cleanup: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupOrphanedEpisodes(): void
|
||||||
|
{
|
||||||
|
$this->logProgress("Checking for orphaned episodes...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all episode IDs that belong to shows from this source
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT te.id, te.metadata, ts.metadata as show_metadata
|
||||||
|
FROM tv_episodes te
|
||||||
|
JOIN tv_shows ts ON te.tv_show_id = ts.id
|
||||||
|
WHERE ts.source_id = :source_id
|
||||||
|
");
|
||||||
|
$stmt->execute(['source_id' => $this->source['id']]);
|
||||||
|
$episodes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$deletedEpisodes = 0;
|
||||||
|
foreach ($episodes as $episode) {
|
||||||
|
$episodeMetadata = json_decode($episode['metadata'], true);
|
||||||
|
$showMetadata = json_decode($episode['show_metadata'], true);
|
||||||
|
|
||||||
|
$episodeJellyfinId = $episodeMetadata['jellyfin_id'] ?? null;
|
||||||
|
$showJellyfinId = $showMetadata['jellyfin_id'] ?? null;
|
||||||
|
|
||||||
|
// If either the episode or its parent show doesn't exist in Jellyfin, delete the episode
|
||||||
|
if (!$episodeJellyfinId || !$showJellyfinId) {
|
||||||
|
$this->deleteTvEpisode($episode['id']);
|
||||||
|
$deletedEpisodes++;
|
||||||
|
$this->deletedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deletedEpisodes > 0) {
|
||||||
|
$this->logProgress("Deleted {$deletedEpisodes} orphaned episodes");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error during orphaned episode cleanup: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteMovie(int $movieId): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Delete actor relationships first
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM actor_movie WHERE movie_id = :movie_id");
|
||||||
|
$stmt->execute(['movie_id' => $movieId]);
|
||||||
|
|
||||||
|
// Delete the movie
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM movies WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $movieId]);
|
||||||
|
|
||||||
|
$this->logProgress("Deleted movie with ID: {$movieId}");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error deleting movie {$movieId}: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteTvShow(int $showId): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Delete actor relationships first
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM actor_tv_show WHERE tv_show_id = :tv_show_id");
|
||||||
|
$stmt->execute(['tv_show_id' => $showId]);
|
||||||
|
|
||||||
|
// Delete episodes (which will also delete episode-actor relationships via CASCADE)
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM tv_episodes WHERE tv_show_id = :tv_show_id");
|
||||||
|
$stmt->execute(['tv_show_id' => $showId]);
|
||||||
|
|
||||||
|
// Delete the show
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM tv_shows WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $showId]);
|
||||||
|
|
||||||
|
$this->logProgress("Deleted TV show with ID: {$showId}");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error deleting TV show {$showId}: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteTvEpisode(int $episodeId): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Delete actor relationships first
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM actor_tv_episode WHERE tv_episode_id = :tv_episode_id");
|
||||||
|
$stmt->execute(['tv_episode_id' => $episodeId]);
|
||||||
|
|
||||||
|
// Delete the episode
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM tv_episodes WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $episodeId]);
|
||||||
|
|
||||||
|
$this->logProgress("Deleted TV episode with ID: {$episodeId}");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error deleting TV episode {$episodeId}: " . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -750,8 +750,112 @@ class StashSyncService extends BaseSyncService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDeletedCount(): int
|
protected function executeCleanup(): void
|
||||||
{
|
{
|
||||||
return 0; // Stash doesn't provide deletion info in this context
|
$this->logProgress("Starting cleanup - detecting deleted media in Stash...");
|
||||||
|
|
||||||
|
// Clean up scenes
|
||||||
|
$this->cleanupScenes();
|
||||||
|
|
||||||
|
// Clean up movies
|
||||||
|
$this->cleanupMovies();
|
||||||
|
|
||||||
|
$this->logProgress("Cleanup completed. Deleted {$this->deletedCount} items.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupScenes(): void
|
||||||
|
{
|
||||||
|
$this->logProgress("Checking for deleted scenes...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all scenes from Stash
|
||||||
|
$stashScenes = $this->getStashScenes(0, 1000); // Get up to 1000 scenes for cleanup
|
||||||
|
$stashSceneIds = array_column($stashScenes, 'id');
|
||||||
|
$this->logProgress("Found " . count($stashSceneIds) . " scenes in Stash");
|
||||||
|
|
||||||
|
// Get all scenes from local database for this source
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT id, metadata FROM adult_videos WHERE source_id = :source_id
|
||||||
|
");
|
||||||
|
$stmt->execute(['source_id' => $this->source['id']]);
|
||||||
|
$localScenes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->logProgress("Found " . count($localScenes) . " scenes in local database");
|
||||||
|
|
||||||
|
$deletedCount = 0;
|
||||||
|
foreach ($localScenes as $localScene) {
|
||||||
|
$metadata = json_decode($localScene['metadata'], true);
|
||||||
|
$stashId = $metadata['stash_id'] ?? null;
|
||||||
|
|
||||||
|
if ($stashId && !in_array($stashId, $stashSceneIds)) {
|
||||||
|
// Scene exists in local DB but not in Stash - delete it
|
||||||
|
$this->deleteAdultVideo($localScene['id']);
|
||||||
|
$deletedCount++;
|
||||||
|
$this->deletedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logProgress("Deleted {$deletedCount} scenes that no longer exist in Stash");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error during scene cleanup: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupMovies(): void
|
||||||
|
{
|
||||||
|
$this->logProgress("Checking for deleted movies...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all movies from Stash
|
||||||
|
$stashMovies = $this->getStashMovies();
|
||||||
|
$stashMovieIds = array_column($stashMovies, 'id');
|
||||||
|
$this->logProgress("Found " . count($stashMovieIds) . " movies in Stash");
|
||||||
|
|
||||||
|
// Get all movies from local database for this source
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT id, metadata FROM adult_videos WHERE source_id = :source_id
|
||||||
|
");
|
||||||
|
$stmt->execute(['source_id' => $this->source['id']]);
|
||||||
|
$localVideos = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->logProgress("Found " . count($localVideos) . " videos in local database");
|
||||||
|
|
||||||
|
$deletedCount = 0;
|
||||||
|
foreach ($localVideos as $localVideo) {
|
||||||
|
$metadata = json_decode($localVideo['metadata'], true);
|
||||||
|
$stashMovieId = $metadata['stash_movie_id'] ?? null;
|
||||||
|
|
||||||
|
if ($stashMovieId && !in_array($stashMovieId, $stashMovieIds)) {
|
||||||
|
// Movie exists in local DB but not in Stash - delete it
|
||||||
|
$this->deleteAdultVideo($localVideo['id']);
|
||||||
|
$deletedCount++;
|
||||||
|
$this->deletedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logProgress("Deleted {$deletedCount} movies that no longer exist in Stash");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error during movie cleanup: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteAdultVideo(int $videoId): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Delete actor relationships first
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM actor_adult_video WHERE adult_video_id = :adult_video_id");
|
||||||
|
$stmt->execute(['adult_video_id' => $videoId]);
|
||||||
|
|
||||||
|
// Delete the video
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM adult_videos WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $videoId]);
|
||||||
|
|
||||||
|
$this->logProgress("Deleted adult video with ID: {$videoId}");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error deleting adult video {$videoId}: " . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,9 +493,91 @@ class XbvrSyncService extends BaseSyncService
|
|||||||
return $this->updatedCount;
|
return $this->updatedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDeletedCount(): int
|
protected function executeCleanup(): void
|
||||||
{
|
{
|
||||||
return 0; // XBVR doesn't provide deletion info in this context
|
$this->logProgress("Starting cleanup - detecting deleted VR scenes in XBVR...");
|
||||||
|
|
||||||
|
// Clean up VR scenes
|
||||||
|
$this->cleanupScenes();
|
||||||
|
|
||||||
|
$this->logProgress("Cleanup completed. Deleted {$this->deletedCount} VR scenes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupScenes(): void
|
||||||
|
{
|
||||||
|
$this->logProgress("Checking for deleted VR scenes...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all scenes from XBVR
|
||||||
|
$xbvrScenes = $this->getXbvrScenesForCleanup();
|
||||||
|
$xbvrSceneIds = array_column($xbvrScenes, 'id');
|
||||||
|
$this->logProgress("Found " . count($xbvrSceneIds) . " VR scenes in XBVR");
|
||||||
|
|
||||||
|
// Get all scenes from local database for this source
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT id, metadata FROM adult_videos WHERE source_id = :source_id
|
||||||
|
");
|
||||||
|
$stmt->execute(['source_id' => $this->source['id']]);
|
||||||
|
$localScenes = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$this->logProgress("Found " . count($localScenes) . " VR scenes in local database");
|
||||||
|
|
||||||
|
$deletedCount = 0;
|
||||||
|
foreach ($localScenes as $localScene) {
|
||||||
|
$metadata = json_decode($localScene['metadata'], true);
|
||||||
|
$xbvrId = $metadata['xbvr_id'] ?? null;
|
||||||
|
|
||||||
|
if ($xbvrId && !in_array($xbvrId, $xbvrSceneIds)) {
|
||||||
|
// Scene exists in local DB but not in XBVR - delete it
|
||||||
|
$this->deleteAdultVideo($localScene['id']);
|
||||||
|
$deletedCount++;
|
||||||
|
$this->deletedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logProgress("Deleted {$deletedCount} VR scenes that no longer exist in XBVR");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error during VR scene cleanup: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getXbvrScenesForCleanup(): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = $this->httpClient->get("{$this->baseUrl}/api/scene/list", [
|
||||||
|
'timeout' => 30,
|
||||||
|
'connect_timeout' => 10
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->getStatusCode() === 200) {
|
||||||
|
$data = json_decode($response->getBody(), true);
|
||||||
|
return $data['scenes'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error fetching XBVR scenes: " . $e->getMessage());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteAdultVideo(int $videoId): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Delete actor relationships first
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM actor_adult_video WHERE adult_video_id = :adult_video_id");
|
||||||
|
$stmt->execute(['adult_video_id' => $videoId]);
|
||||||
|
|
||||||
|
// Delete the video
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM adult_videos WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $videoId]);
|
||||||
|
|
||||||
|
$this->logProgress("Deleted adult video with ID: {$videoId}");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logProgress("Error deleting adult video {$videoId}: " . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createActorRelationships(int $adultVideoId, array $actors): void
|
private function createActorRelationships(int $adultVideoId, array $actors): void
|
||||||
|
|||||||
@@ -34,37 +34,49 @@
|
|||||||
<!-- Jellyfin-specific sync options -->
|
<!-- Jellyfin-specific sync options -->
|
||||||
<div class="d-flex gap-1 mb-2 flex-wrap">
|
<div class="d-flex gap-1 mb-2 flex-wrap">
|
||||||
<button onclick="startSync({{ source.id }}, 'all')"
|
<button onclick="startSync({{ source.id }}, 'all')"
|
||||||
class="btn btn-primary btn-sm flex-fill">
|
class="btn btn-primary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
All Content
|
All Content
|
||||||
</button>
|
</button>
|
||||||
<button onclick="startSync({{ source.id }}, 'movies')"
|
<button onclick="startSync({{ source.id }}, 'movies')"
|
||||||
class="btn btn-outline-primary btn-sm flex-fill">
|
class="btn btn-outline-primary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
Movies Only
|
Movies Only
|
||||||
</button>
|
</button>
|
||||||
<button onclick="startSync({{ source.id }}, 'tvshows')"
|
<button onclick="startSync({{ source.id }}, 'tvshows')"
|
||||||
class="btn btn-outline-primary btn-sm flex-fill">
|
class="btn btn-outline-primary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
TV Shows Only
|
TV Shows Only
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-1 mb-2">
|
<div class="d-flex gap-1 mb-2">
|
||||||
<button onclick="startSync({{ source.id }}, 'full')"
|
<button onclick="startSync({{ source.id }}, 'full')"
|
||||||
class="btn btn-secondary btn-sm flex-fill">
|
class="btn btn-secondary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
Full Sync
|
Full Sync
|
||||||
</button>
|
</button>
|
||||||
<button onclick="startSync({{ source.id }}, 'incremental')"
|
<button onclick="startSync({{ source.id }}, 'incremental')"
|
||||||
class="btn btn-outline-secondary btn-sm flex-fill">
|
class="btn btn-outline-secondary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
Incremental
|
Incremental
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="startSync({{ source.id }}, 'cleanup')"
|
||||||
|
class="btn btn-outline-warning btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
|
Cleanup
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Standard sync options for other sources -->
|
<!-- Standard sync options for other sources -->
|
||||||
<div class="d-flex gap-2 mb-2">
|
<div class="d-flex gap-2 mb-2">
|
||||||
<button onclick="startSync({{ source.id }}, 'full')"
|
<button onclick="startSync({{ source.id }}, 'full')"
|
||||||
class="btn btn-primary btn-sm flex-fill">
|
class="btn btn-primary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
Full Sync
|
Full Sync
|
||||||
</button>
|
</button>
|
||||||
<button onclick="startSync({{ source.id }}, 'incremental')"
|
<button onclick="startSync({{ source.id }}, 'incremental')"
|
||||||
class="btn btn-secondary btn-sm flex-fill">
|
class="btn btn-secondary btn-sm flex-fill"
|
||||||
|
data-source-id="{{ source.id }}">
|
||||||
Incremental
|
Incremental
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,11 +235,12 @@
|
|||||||
progressBar.style.width = '0%';
|
progressBar.style.width = '0%';
|
||||||
statusDiv.textContent = 'Starting sync...';
|
statusDiv.textContent = 'Starting sync...';
|
||||||
|
|
||||||
// Disable all sync buttons for this source
|
// Disable all sync buttons for this source - use a more reliable selector
|
||||||
const buttons = document.querySelectorAll(`[onclick*="startSync(${sourceId},"]`);
|
const buttons = document.querySelectorAll(`[data-source-id="${sourceId}"]`);
|
||||||
buttons.forEach(button => {
|
buttons.forEach(button => {
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
button.textContent = 'Syncing...';
|
button.textContent = 'Syncing...';
|
||||||
|
button.dataset.originalText = button.textContent;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start sync via API
|
// Start sync via API
|
||||||
@@ -249,7 +262,7 @@
|
|||||||
// Re-enable buttons on error
|
// Re-enable buttons on error
|
||||||
buttons.forEach(button => {
|
buttons.forEach(button => {
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.textContent = button.textContent.replace('Syncing...', '').trim();
|
button.textContent = button.dataset.originalText || button.textContent.replace('Syncing...', '').trim();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -259,7 +272,7 @@
|
|||||||
// Re-enable buttons on error
|
// Re-enable buttons on error
|
||||||
buttons.forEach(button => {
|
buttons.forEach(button => {
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.textContent = button.textContent.replace('Syncing...', '').trim();
|
button.textContent = button.dataset.originalText || button.textContent.replace('Syncing...', '').trim();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -269,31 +282,47 @@
|
|||||||
fetch(`/admin/sync/status/${syncLogId}`)
|
fetch(`/admin/sync/status/${syncLogId}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Update progress
|
// Update progress bar using percentage from backend
|
||||||
if (data.total_items > 0) {
|
if (data.progress_percentage !== undefined && data.progress_percentage > 0) {
|
||||||
const progress = (data.processed_items / data.total_items) * 100;
|
progressBar.style.width = data.progress_percentage + '%';
|
||||||
progressBar.style.width = progress + '%';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status
|
// Update status
|
||||||
statusDiv.textContent = getStatusMessage(data);
|
statusDiv.textContent = getStatusMessage(data);
|
||||||
|
|
||||||
|
// Debug logging for troubleshooting
|
||||||
|
console.log('Sync status check:', {
|
||||||
|
syncLogId: syncLogId,
|
||||||
|
status: data.status,
|
||||||
|
message: data.message,
|
||||||
|
progress_percentage: data.progress_percentage,
|
||||||
|
processed_items: data.processed_items,
|
||||||
|
total_items: data.total_items,
|
||||||
|
completed_at: data.completed_at
|
||||||
|
});
|
||||||
|
|
||||||
// Stop monitoring if sync is complete or failed
|
// Stop monitoring if sync is complete or failed
|
||||||
if (['completed', 'failed'].includes(data.status)) {
|
if (['completed', 'failed'].includes(data.status)) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
delete syncIntervals[sourceId];
|
delete syncIntervals[sourceId];
|
||||||
|
|
||||||
if (data.status === 'completed') {
|
if (data.status === 'completed') {
|
||||||
|
// Show completion message briefly before hiding
|
||||||
|
statusDiv.textContent = 'Sync completed successfully!';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.getElementById(`sync-progress-${sourceId}`).classList.add('d-none');
|
document.getElementById(`sync-progress-${sourceId}`).classList.add('d-none');
|
||||||
// Refresh page to show updated sync log
|
// Re-enable buttons immediately on completion
|
||||||
location.reload();
|
buttons.forEach(button => {
|
||||||
}, 2000);
|
button.disabled = false;
|
||||||
|
button.textContent = button.dataset.originalText || button.textContent.replace('Syncing...', '').trim();
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
// Re-enable buttons on failure
|
// Re-enable buttons on failure
|
||||||
buttons.forEach(button => {
|
buttons.forEach(button => {
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.textContent = button.textContent.replace('Syncing...', '').trim();
|
button.textContent = button.dataset.originalText || button.textContent.replace('Syncing...', '').trim();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,7 +334,7 @@
|
|||||||
// Re-enable buttons on error
|
// Re-enable buttons on error
|
||||||
buttons.forEach(button => {
|
buttons.forEach(button => {
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.textContent = button.textContent.replace('Syncing...', '').trim();
|
button.textContent = button.dataset.originalText || button.textContent.replace('Syncing...', '').trim();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -318,6 +347,12 @@
|
|||||||
let message = `Completed: ${data.new_items || 0} new`;
|
let message = `Completed: ${data.new_items || 0} new`;
|
||||||
if (data.updated_items > 0) message += `, ${data.updated_items} updated`;
|
if (data.updated_items > 0) message += `, ${data.updated_items} updated`;
|
||||||
if (data.deleted_items > 0) message += `, ${data.deleted_items} deleted`;
|
if (data.deleted_items > 0) message += `, ${data.deleted_items} deleted`;
|
||||||
|
|
||||||
|
// If we have a cleaner message from the completion handler, use it
|
||||||
|
if (data.message && !data.message.includes('===')) {
|
||||||
|
return data.message;
|
||||||
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
} else if (data.status === 'failed') {
|
} else if (data.status === 'failed') {
|
||||||
return 'Failed: ' + (data.message || 'Unknown error');
|
return 'Failed: ' + (data.message || 'Unknown error');
|
||||||
@@ -326,7 +361,13 @@
|
|||||||
if (data.total_items > 0) {
|
if (data.total_items > 0) {
|
||||||
progress += `: ${data.processed_items}/${data.total_items} items`;
|
progress += `: ${data.processed_items}/${data.total_items} items`;
|
||||||
}
|
}
|
||||||
if (data.message) {
|
if (data.progress_percentage !== undefined && data.progress_percentage > 0) {
|
||||||
|
progress += ` (${Math.round(data.progress_percentage)}%)`;
|
||||||
|
}
|
||||||
|
if (data.sync_type === 'cleanup') {
|
||||||
|
progress = 'Cleanup: ' + progress;
|
||||||
|
}
|
||||||
|
if (data.message && !data.message.includes('===')) {
|
||||||
progress += ` - ${data.message}`;
|
progress += ` - ${data.message}`;
|
||||||
}
|
}
|
||||||
return progress;
|
return progress;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ $sourceData = [
|
|||||||
|
|
||||||
// Validate sync type
|
// Validate sync type
|
||||||
if ($source['name'] === 'jellyfin') {
|
if ($source['name'] === 'jellyfin') {
|
||||||
$validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows'];
|
$validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows', 'cleanup'];
|
||||||
if (!in_array($syncType, $validSyncTypes)) {
|
if (!in_array($syncType, $validSyncTypes)) {
|
||||||
echo "Invalid sync type for Jellyfin source. Valid types: " . implode(', ', $validSyncTypes) . "\n";
|
echo "Invalid sync type for Jellyfin source. Valid types: " . implode(', ', $validSyncTypes) . "\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user