mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
yay
This commit is contained in:
@@ -127,6 +127,7 @@ class ActorController extends Controller
|
||||
LEFT JOIN tv_shows ts ON ats.tv_show_id = ts.id
|
||||
GROUP BY a.id
|
||||
ORDER BY total_media_count DESC, a.name ASC
|
||||
LIMIT 50
|
||||
");
|
||||
$stmt->execute();
|
||||
$actors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -243,7 +243,9 @@ class GameController extends Controller
|
||||
'playtime_desc' => 'Most Played',
|
||||
'completion_desc' => 'Highest Completion',
|
||||
'added_desc' => 'Recently Added',
|
||||
'last_played_desc' => 'Last Played'
|
||||
'last_played_desc' => 'Last Played',
|
||||
'platforms_desc' => 'Most Platforms',
|
||||
'platforms_asc' => 'Fewest Platforms'
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -273,6 +275,7 @@ class GameController extends Controller
|
||||
$gameModel->game_key = $mainGame['game_key'];
|
||||
$platformVersions = $gameModel->getPlatformVersions();
|
||||
|
||||
|
||||
return $this->view->render($response, 'games/show.twig', [
|
||||
'title' => $mainGame['title'],
|
||||
'main_game' => $mainGame,
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Services\PlayniteImportService;
|
||||
use App\Services\PlayniteSyncService;
|
||||
use Slim\Views\Twig;
|
||||
|
||||
class PlayniteImportController extends Controller
|
||||
@@ -133,10 +134,25 @@ class PlayniteImportController extends Controller
|
||||
$queryParams = $request->getQueryParams();
|
||||
$updateExisting = ($queryParams['update_existing'] ?? 'false') === 'true';
|
||||
|
||||
try {
|
||||
// Execute the import
|
||||
$importResult = $this->importService->importGames($previewData['games'], $updateExisting);
|
||||
// Create a source entry for Playnite if it doesn't exist
|
||||
$source = $this->getOrCreatePlayniteSource();
|
||||
|
||||
// Initialize the sync service with logging
|
||||
$syncService = new PlayniteSyncService($this->pdo, $source);
|
||||
|
||||
try {
|
||||
// Start the sync process (this will create a sync log entry)
|
||||
$syncLogId = $syncService->startSync('import');
|
||||
|
||||
// Store the sync log ID in the session so we can update it later
|
||||
$_SESSION['playnite_import']['sync_log_id'] = $syncLogId;
|
||||
|
||||
// Execute the import through the sync service
|
||||
$importResult = $syncService->importGames($previewData['games'], $updateExisting);
|
||||
|
||||
// Get the log file path to show to the user
|
||||
$logFilePath = $syncService->getLogFilePath();
|
||||
|
||||
// Clean up temp file
|
||||
if (file_exists($tempPath)) {
|
||||
unlink($tempPath);
|
||||
@@ -148,10 +164,18 @@ class PlayniteImportController extends Controller
|
||||
return $this->view->render($response, 'admin/playnite/result.twig', [
|
||||
'title' => 'Import Complete',
|
||||
'import_result' => $importResult,
|
||||
'preview_data' => $previewData
|
||||
'preview_data' => $previewData,
|
||||
'log_file' => $logFilePath,
|
||||
'sync_log_id' => $syncLogId
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Log the error
|
||||
if (isset($syncService)) {
|
||||
$syncService->logProgress("ERROR: " . $e->getMessage());
|
||||
$logFilePath = $syncService->getLogFilePath();
|
||||
}
|
||||
|
||||
// Clean up temp file
|
||||
if (file_exists($tempPath)) {
|
||||
unlink($tempPath);
|
||||
@@ -163,11 +187,44 @@ class PlayniteImportController extends Controller
|
||||
return $this->view->render($response->withStatus(500), 'admin/playnite/import.twig', [
|
||||
'title' => 'Import Playnite Games',
|
||||
'error' => 'Import failed: ' . $e->getMessage(),
|
||||
'log_file' => $logFilePath ?? null,
|
||||
'csrf_token' => $this->generateCSRFToken()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a source for Playnite
|
||||
*/
|
||||
private function getOrCreatePlayniteSource(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, name, display_name
|
||||
FROM sources
|
||||
WHERE name = 'playnite' OR display_name = 'Playnite'
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute();
|
||||
|
||||
$source = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$source) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO sources (name, display_name, created_at, updated_at)
|
||||
VALUES ('playnite', 'Playnite', NOW(), NOW())
|
||||
");
|
||||
$stmt->execute();
|
||||
|
||||
$source = [
|
||||
'id' => $this->pdo->lastInsertId(),
|
||||
'name' => 'playnite',
|
||||
'display_name' => 'Playnite'
|
||||
];
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the import (cleanup)
|
||||
*/
|
||||
|
||||
@@ -358,7 +358,9 @@ class Game extends Model
|
||||
'added_asc' => 'added_at ASC NULLS LAST',
|
||||
'added_desc' => 'added_at DESC NULLS LAST',
|
||||
'last_played_asc' => 'last_played_at ASC NULLS LAST',
|
||||
'last_played_desc' => 'last_played_at DESC NULLS LAST'
|
||||
'last_played_desc' => 'last_played_at DESC NULLS LAST',
|
||||
'platforms_asc' => 'platform_count ASC',
|
||||
'platforms_desc' => 'platform_count DESC'
|
||||
];
|
||||
|
||||
$sortClause = $sortOptions[$sort] ?? 'title ASC';
|
||||
|
||||
@@ -304,8 +304,13 @@ class PlayniteImportService
|
||||
|
||||
/**
|
||||
* Import games to database
|
||||
*
|
||||
* @param array $games Array of games to import
|
||||
* @param bool $updateExisting Whether to update existing games
|
||||
* @param callable|null $logCallback Optional callback for logging progress
|
||||
* @return array Results of the import
|
||||
*/
|
||||
public function importGames(array $games, bool $updateExisting = true): array
|
||||
public function importGames(array $games, bool $updateExisting = true, ?callable $logCallback = null): array
|
||||
{
|
||||
$results = [
|
||||
'imported' => 0,
|
||||
@@ -314,24 +319,56 @@ class PlayniteImportService
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
foreach ($games as $gameData) {
|
||||
$totalGames = count($games);
|
||||
$log = function(string $message) use ($logCallback) {
|
||||
if (is_callable($logCallback)) {
|
||||
$logCallback($message);
|
||||
}
|
||||
};
|
||||
|
||||
$log(sprintf("Starting import of %d games", $totalGames));
|
||||
|
||||
foreach ($games as $index => $gameData) {
|
||||
$gameTitle = $gameData['title'] ?? 'Untitled';
|
||||
$log(sprintf("Processing game %d/%d: %s", $index + 1, $totalGames, $gameTitle));
|
||||
|
||||
try {
|
||||
$existingGame = $this->findExistingGame($gameData);
|
||||
|
||||
if ($existingGame && $updateExisting) {
|
||||
$log(sprintf("Updating existing game: %s (ID: %d)", $gameTitle, $existingGame['id']));
|
||||
$this->updateGame($existingGame['id'], $gameData);
|
||||
$results['updated']++;
|
||||
$log(sprintf("Successfully updated game: %s", $gameTitle));
|
||||
} elseif (!$existingGame) {
|
||||
$log(sprintf("Importing new game: %s", $gameTitle));
|
||||
$this->insertGame($gameData);
|
||||
$results['imported']++;
|
||||
$log(sprintf("Successfully imported game: %s", $gameTitle));
|
||||
} else {
|
||||
$log(sprintf("Skipping unchanged game: %s", $gameTitle));
|
||||
$results['skipped']++;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$results['errors'][] = "Failed to import {$gameData['title']}: " . $e->getMessage();
|
||||
$errorMsg = sprintf("Failed to import %s: %s", $gameTitle, $e->getMessage());
|
||||
$results['errors'][] = $errorMsg;
|
||||
$log("ERROR: " . $errorMsg);
|
||||
}
|
||||
|
||||
// Log progress every 10 games or if it's the last game
|
||||
if (($index + 1) % 10 === 0 || ($index + 1) === $totalGames) {
|
||||
$log(sprintf("Progress: %d/%d games processed", $index + 1, $totalGames));
|
||||
}
|
||||
}
|
||||
|
||||
$log(sprintf(
|
||||
"Import completed: %d imported, %d updated, %d skipped, %d errors",
|
||||
$results['imported'],
|
||||
$results['updated'],
|
||||
$results['skipped'],
|
||||
count($results['errors'])
|
||||
));
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
117
app/Services/PlayniteSyncService.php
Normal file
117
app/Services/PlayniteSyncService.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class PlayniteSyncService extends BaseSyncService
|
||||
{
|
||||
private PlayniteImportService $importService;
|
||||
private array $importResults = [
|
||||
'imported' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
public function __construct(\PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||
{
|
||||
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||
$this->importService = new PlayniteImportService($pdo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the sync process
|
||||
*/
|
||||
protected function executeSync(string $syncType): void
|
||||
{
|
||||
$this->logProgress("Starting Playnite import process");
|
||||
|
||||
try {
|
||||
// Get the import data from session
|
||||
if (!isset($_SESSION['playnite_import'])) {
|
||||
$this->logProgress($_SESSION);
|
||||
$this->logProgress("No Playnite import data found in session");
|
||||
throw new \Exception('No Playnite import data found in session');
|
||||
}
|
||||
|
||||
$importData = $_SESSION['playnite_import'];
|
||||
$games = $importData['preview_data']['games'] ?? [];
|
||||
|
||||
$this->logProgress(sprintf('Found %d games to import', count($games)));
|
||||
$this->totalItems = count($games);
|
||||
|
||||
// Import the games
|
||||
$this->importResults = $this->importService->importGames(
|
||||
$games,
|
||||
true, // Always update existing
|
||||
function($message) {
|
||||
$this->logProgress($message);
|
||||
}
|
||||
);
|
||||
|
||||
$this->logProgress(sprintf(
|
||||
'Import completed: %d imported, %d updated, %d skipped, %d errors',
|
||||
$this->importResults['imported'] ?? 0,
|
||||
$this->importResults['updated'] ?? 0,
|
||||
$this->importResults['skipped'] ?? 0,
|
||||
count($this->importResults['errors'] ?? [])
|
||||
));
|
||||
|
||||
// Log any errors
|
||||
foreach ($this->importResults['errors'] as $error) {
|
||||
$this->logProgress("ERROR: $error");
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logProgress("ERROR: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of processed items
|
||||
*/
|
||||
public function getProcessedCount(): int
|
||||
{
|
||||
return ($this->importResults['imported'] ?? 0) +
|
||||
($this->importResults['updated'] ?? 0) +
|
||||
($this->importResults['skipped'] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of new items
|
||||
*/
|
||||
public function getNewCount(): int
|
||||
{
|
||||
return $this->importResults['imported'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of updated items
|
||||
*/
|
||||
public function getUpdatedCount(): int
|
||||
{
|
||||
return $this->importResults['updated'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of deleted items
|
||||
*/
|
||||
public function getDeletedCount(): int
|
||||
{
|
||||
return 0; // Playnite import doesn't handle deletions
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a completion message
|
||||
*/
|
||||
public function getCompletionMessage(): string
|
||||
{
|
||||
return sprintf(
|
||||
'Playnite import completed: %d imported, %d updated, %d skipped, %d errors',
|
||||
$this->importResults['imported'] ?? 0,
|
||||
$this->importResults['updated'] ?? 0,
|
||||
$this->importResults['skipped'] ?? 0,
|
||||
count($this->importResults['errors'] ?? [])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
{% if game.image_url %}
|
||||
<img class="h-12 w-12 rounded-lg object-cover" src="{{ game.image_url }}" alt="{{ game.title }}">
|
||||
<img class="h-12 w-12 rounded-lg object-cover" src="/images/playnite/{{ game.image_url }}" alt="{{ game.title }}">
|
||||
{% else %}
|
||||
<div class="h-12 w-12 rounded-lg bg-gray-200 flex items-center justify-center">
|
||||
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
{% if main_game.image_url %}
|
||||
<img class="h-16 w-16 rounded-lg object-cover mr-4" src="{{ main_game.image_url }}" alt="{{ main_game.title }}">
|
||||
<img class="h-16 w-16 rounded-lg object-cover mr-4" src="images/playnite/{{ main_game.image_url }}" alt="{{ main_game.title }}">
|
||||
{% else %}
|
||||
<div class="h-16 w-16 rounded-lg bg-gray-200 flex items-center justify-center mr-4">
|
||||
<svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
{% if game.image_url %}
|
||||
<img class="rounded me-3" style="width: 64px; height: 64px; object-fit: cover;" src="{{ game.image_url }}" alt="{{ game.title }}">
|
||||
<img class="rounded me-3" style="width: 64px; height: 64px; object-fit: cover;" src="/images/playnite/{{ game.image_url }}" alt="{{ game.title }}">
|
||||
{% else %}
|
||||
<div class="bg-light rounded me-3 d-flex align-items-center justify-content-center" style="width: 64px; height: 64px;">
|
||||
<svg class="text-muted" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -251,11 +251,11 @@
|
||||
<!-- Cover grid view -->
|
||||
<div class="row g-3">
|
||||
{% for game in games %}
|
||||
<div class="col-6 col-sm-4 col-md-3 col-lg-2">
|
||||
<div class="col-6 col-sm-4 col-md-3 col-lg-1">
|
||||
<div class="card h-100">
|
||||
{% if game.image_url %}
|
||||
<div class="position-relative" style="aspect-ratio: 3/4; overflow: hidden;">
|
||||
<img src="{{ game.image_url }}" alt="{{ game.title }}" class="card-img-top" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<img src="/images/playnite/{{ game.image_url }}" alt="{{ game.title }}" class="card-img-top" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="d-flex align-items-center justify-content-center bg-light" style="aspect-ratio: 3/4; min-height: 200px;">
|
||||
@@ -285,7 +285,7 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
{% if game.image_url %}
|
||||
<img class="rounded" style="width: 64px; height: 64px; object-fit: cover;" src="{{ game.image_url }}" alt="{{ game.title }}">
|
||||
<img class="rounded" style="width: 64px; height: 64px; object-fit: cover;" src="/images/playnite/{{ game.image_url }}" alt="{{ game.title }}">
|
||||
{% else %}
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 64px; height: 64px;">
|
||||
<svg class="text-muted" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }} - Media Collector</title>
|
||||
<!-- Iconify Icons -->
|
||||
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
|
||||
<!-- Bootstrap CSS CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Select2 CSS -->
|
||||
@@ -18,6 +20,9 @@
|
||||
{% endif %}
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="{{ base_url() }}/favicon.svg">
|
||||
|
||||
<!-- Iconify Icons -->
|
||||
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
{# DebugBar Assets #}
|
||||
|
||||
@@ -10,7 +10,8 @@ $app->group('/api', function (RouteCollectorProxy $apiGroup) {
|
||||
// Playnite API endpoints
|
||||
$apiGroup->group('/playnite', function (RouteCollectorProxy $playniteGroup) {
|
||||
// Game management
|
||||
$playniteGroup->post('/media', 'App\Controllers\Api\PlayniteController:insertGames')->setName('api.playnite.games');
|
||||
$playniteGroup->post('/insert', 'App\Controllers\Api\PlayniteController:insertGames')->setName('api.playnite.insert');
|
||||
$playniteGroup->post('/media', 'App\Controllers\Api\PlayniteController:updateMedia')->setName('api.playnite.media');
|
||||
$playniteGroup->put('/update/games/', 'App\Controllers\Api\PlayniteController:updateGames')->setName('api.playnite.update');
|
||||
$playniteGroup->put('/v1/games/delete', 'App\Controllers\Api\PlayniteController:deleteGames')->setName('api.playnite.delete');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user