mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
sync logs :D
This commit is contained in:
@@ -16,9 +16,9 @@ class AdultSyncService extends BaseSyncService
|
|||||||
private int $newCount = 0;
|
private int $newCount = 0;
|
||||||
private int $updatedCount = 0;
|
private int $updatedCount = 0;
|
||||||
|
|
||||||
public function __construct(PDO $pdo, array $source)
|
public function __construct(PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
parent::__construct($pdo, $source);
|
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||||
|
|
||||||
// Find XBVR and Stash sources
|
// Find XBVR and Stash sources
|
||||||
$this->xbvrSource = $this->findSourceByName('xbvr');
|
$this->xbvrSource = $this->findSourceByName('xbvr');
|
||||||
|
|||||||
@@ -2,20 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Models\SyncLog;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
abstract class BaseSyncService
|
abstract class BaseSyncService
|
||||||
{
|
{
|
||||||
protected \PDO $pdo;
|
protected \PDO $pdo;
|
||||||
protected array $source;
|
protected array $source;
|
||||||
protected SyncLog $syncLog;
|
|
||||||
protected int $sourceId;
|
protected int $sourceId;
|
||||||
|
|
||||||
protected $logFileHandle;
|
protected $logFileHandle;
|
||||||
protected $logFilePath;
|
protected $logFilePath;
|
||||||
|
|
||||||
public function __construct(\PDO $pdo, array $source)
|
public function __construct(\PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
$this->pdo = $pdo;
|
$this->pdo = $pdo;
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
@@ -25,6 +23,7 @@ abstract class BaseSyncService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->sourceId = (int) $source['id'];
|
$this->sourceId = (int) $source['id'];
|
||||||
|
$this->currentSyncLogId = $existingSyncLogId;
|
||||||
|
|
||||||
// Create log file for this sync operation
|
// Create log file for this sync operation
|
||||||
$this->initializeLogFile();
|
$this->initializeLogFile();
|
||||||
@@ -62,8 +61,19 @@ abstract class BaseSyncService
|
|||||||
ini_set('max_execution_time', 3600); // 1 hour
|
ini_set('max_execution_time', 3600); // 1 hour
|
||||||
ini_set('memory_limit', '512M');
|
ini_set('memory_limit', '512M');
|
||||||
|
|
||||||
|
// Use existing sync log ID if provided, otherwise create a new one
|
||||||
|
if ($this->currentSyncLogId) {
|
||||||
|
$syncLogId = $this->currentSyncLogId;
|
||||||
|
// Update the existing log to 'started' status
|
||||||
|
$this->updateSyncLog($syncLogId, 'started', [
|
||||||
|
'sync_type' => $syncType,
|
||||||
|
'started_at' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
// Create sync log entry
|
// Create sync log entry
|
||||||
$syncLogId = $this->createSyncLog($syncType, 'started');
|
$syncLogId = $this->createSyncLog($syncType, 'started');
|
||||||
|
}
|
||||||
|
|
||||||
$this->currentSyncLogId = $syncLogId;
|
$this->currentSyncLogId = $syncLogId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class ExophaseSyncService extends BaseSyncService
|
|||||||
private int $newCount = 0;
|
private int $newCount = 0;
|
||||||
private int $updatedCount = 0;
|
private int $updatedCount = 0;
|
||||||
|
|
||||||
public function __construct(\PDO $pdo, array $source)
|
public function __construct(\PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
parent::__construct($pdo, $source);
|
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||||
$this->httpClient = new Client([
|
$this->httpClient = new Client([
|
||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
'headers' => [
|
'headers' => [
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class JellyfinSyncService extends BaseSyncService
|
|||||||
private int $newCount = 0;
|
private int $newCount = 0;
|
||||||
private int $updatedCount = 0;
|
private int $updatedCount = 0;
|
||||||
|
|
||||||
public function __construct(\PDO $pdo, array $source)
|
public function __construct(\PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
parent::__construct($pdo, $source);
|
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||||
$this->httpClient = new Client([
|
$this->httpClient = new Client([
|
||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
'headers' => [
|
'headers' => [
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class StashSyncService extends BaseSyncService
|
|||||||
private int $newCount = 0;
|
private int $newCount = 0;
|
||||||
private int $updatedCount = 0;
|
private int $updatedCount = 0;
|
||||||
|
|
||||||
public function __construct(PDO $pdo, array $source)
|
public function __construct(PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
parent::__construct($pdo, $source);
|
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||||
|
|
||||||
// Initialize properties first before using them
|
// Initialize properties first before using them
|
||||||
$this->apiKey = $source['api_key'];
|
$this->apiKey = $source['api_key'];
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class SteamSyncService extends BaseSyncService
|
|||||||
private int $newCount = 0;
|
private int $newCount = 0;
|
||||||
private int $updatedCount = 0;
|
private int $updatedCount = 0;
|
||||||
|
|
||||||
public function __construct(\PDO $pdo, array $source)
|
public function __construct(\PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
parent::__construct($pdo, $source);
|
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||||
$this->httpClient = new Client([
|
$this->httpClient = new Client([
|
||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
'headers' => [
|
'headers' => [
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class XbvrSyncService extends BaseSyncService
|
|||||||
private int $newCount = 0;
|
private int $newCount = 0;
|
||||||
private int $updatedCount = 0;
|
private int $updatedCount = 0;
|
||||||
|
|
||||||
public function __construct(\PDO $pdo, array $source)
|
public function __construct(\PDO $pdo, array $source, ?int $existingSyncLogId = null)
|
||||||
{
|
{
|
||||||
parent::__construct($pdo, $source);
|
parent::__construct($pdo, $source, $existingSyncLogId);
|
||||||
|
|
||||||
// Initialize properties first before using them
|
// Initialize properties first before using them
|
||||||
$this->baseUrl = rtrim($source['api_url'], '/');
|
$this->baseUrl = rtrim($source['api_url'], '/');
|
||||||
|
|||||||
@@ -119,8 +119,11 @@
|
|||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
|
<th>Items</th>
|
||||||
<th>Started</th>
|
<th>Started</th>
|
||||||
<th>Duration</th>
|
<th>Duration</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -142,13 +145,27 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if sync.total_items > 0 %}
|
{% if sync.total_items > 0 %}
|
||||||
{{ sync.processed_items }} / {{ sync.total_items }}
|
{{ sync.processed_items }} / {{ sync.total_items }}
|
||||||
|
{% if sync.total_items > 0 %}
|
||||||
|
<div class="progress mt-1" style="height: 4px;">
|
||||||
|
<div class="progress-bar bg-primary" role="progressbar"
|
||||||
|
style="width: {{ (sync.processed_items / sync.total_items * 100)|round }}%"></div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>
|
||||||
|
{% if sync.new_items > 0 %}<span class="text-success">+{{ sync.new_items }}</span>{% endif %}
|
||||||
|
{% if sync.updated_items > 0 %} <span class="text-primary">{{ sync.updated_items }} updated</span>{% endif %}
|
||||||
|
{% if sync.deleted_items > 0 %} <span class="text-danger">{{ sync.deleted_items }} deleted</span>{% endif %}
|
||||||
|
{% if sync.new_items == 0 and sync.updated_items == 0 and sync.deleted_items == 0 %}No changes{% endif %}
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if sync.started_at %}
|
{% if sync.started_at %}
|
||||||
{{ sync.started_at|date('M j, H:i') }}
|
<small>{{ sync.started_at|date('M j, H:i') }}</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -157,16 +174,34 @@
|
|||||||
{% if sync.started_at and sync.completed_at %}
|
{% if sync.started_at and sync.completed_at %}
|
||||||
{% set duration = sync.completed_at|date('U') - sync.started_at|date('U') %}
|
{% set duration = sync.completed_at|date('U') - sync.started_at|date('U') %}
|
||||||
{% if duration < 60 %}
|
{% if duration < 60 %}
|
||||||
{{ duration }}s
|
<small>{{ duration }}s</small>
|
||||||
{% elseif duration < 3600 %}
|
{% elseif duration < 3600 %}
|
||||||
{{ (duration / 60)|round }}m
|
<small>{{ (duration / 60)|round }}m</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ (duration / 3600)|round }}h
|
<small>{{ (duration / 3600)|round }}h</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if sync.message %}
|
||||||
|
<small class="text-muted" title="{{ sync.message }}">
|
||||||
|
{{ sync.message|length > 50 ? sync.message|slice(0, 50) ~ '...' : sync.message }}
|
||||||
|
</small>
|
||||||
|
{% else %}
|
||||||
|
<small class="text-muted">-</small>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if sync.errors %}
|
||||||
|
<button class="btn btn-sm btn-outline-danger"
|
||||||
|
onclick="showSyncDetails({{ sync.id }}, '{{ sync.source_name }}', '{{ sync.sync_type }}')"
|
||||||
|
title="View errors and details">
|
||||||
|
Details
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -280,16 +315,118 @@
|
|||||||
|
|
||||||
function getStatusMessage(data) {
|
function getStatusMessage(data) {
|
||||||
if (data.status === 'completed') {
|
if (data.status === 'completed') {
|
||||||
return `Completed: ${data.new_items} new, ${data.updated_items} updated`;
|
let message = `Completed: ${data.new_items || 0} new`;
|
||||||
|
if (data.updated_items > 0) message += `, ${data.updated_items} updated`;
|
||||||
|
if (data.deleted_items > 0) message += `, ${data.deleted_items} deleted`;
|
||||||
|
return message;
|
||||||
} else if (data.status === 'failed') {
|
} else if (data.status === 'failed') {
|
||||||
return 'Failed: ' + (data.errors.join(', ') || 'Unknown error');
|
return 'Failed: ' + (data.message || 'Unknown error');
|
||||||
} else if (data.status === 'running') {
|
} else if (data.status === 'running') {
|
||||||
return `Processing: ${data.processed_items}/${data.total_items} items`;
|
let progress = 'Processing';
|
||||||
|
if (data.total_items > 0) {
|
||||||
|
progress += `: ${data.processed_items}/${data.total_items} items`;
|
||||||
|
}
|
||||||
|
if (data.message) {
|
||||||
|
progress += ` - ${data.message}`;
|
||||||
|
}
|
||||||
|
return progress;
|
||||||
} else {
|
} else {
|
||||||
return data.message || 'Unknown status';
|
return data.message || 'Unknown status';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showSyncDetails(syncId, sourceName, syncType) {
|
||||||
|
// Fetch detailed sync information
|
||||||
|
fetch(`/admin/sync/status/${syncId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'modal fade show';
|
||||||
|
modal.style.display = 'block';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Sync Details: ${sourceName} - ${syncType}</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="closeModal()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Status:</strong>
|
||||||
|
<span class="badge bg-${data.status === 'completed' ? 'success' : data.status === 'failed' ? 'danger' : 'warning'} ms-2">
|
||||||
|
${data.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Started:</strong> ${data.started_at ? new Date(data.started_at).toLocaleString() : 'N/A'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Completed:</strong> ${data.completed_at ? new Date(data.completed_at).toLocaleString() : 'N/A'}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Duration:</strong> ${data.started_at && data.completed_at ?
|
||||||
|
formatDuration(new Date(data.completed_at) - new Date(data.started_at)) : 'N/A'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-3"><strong>Total Items:</strong> ${data.total_items || 0}</div>
|
||||||
|
<div class="col-md-3"><strong>Processed:</strong> ${data.processed_items || 0}</div>
|
||||||
|
<div class="col-md-3"><strong>New:</strong> <span class="text-success">${data.new_items || 0}</span></div>
|
||||||
|
<div class="col-md-3"><strong>Updated:</strong> <span class="text-primary">${data.updated_items || 0}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Message:</strong>
|
||||||
|
<div class="alert alert-info">${data.message || 'No message available'}</div>
|
||||||
|
</div>
|
||||||
|
${data.errors && data.errors.length > 0 ? `
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Errors:</strong>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<ul class="mb-0">
|
||||||
|
${data.errors.map(error => `<li>${error}</li>`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching sync details:', error);
|
||||||
|
alert('Error loading sync details');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
const modal = document.querySelector('.modal.show');
|
||||||
|
if (modal) {
|
||||||
|
modal.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(ms) {
|
||||||
|
const seconds = Math.floor(ms / 1000);
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h ${minutes % 60}m`;
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return `${minutes}m ${seconds % 60}s`;
|
||||||
|
} else {
|
||||||
|
return `${seconds}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup intervals on page unload
|
// Cleanup intervals on page unload
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
Object.values(syncIntervals).forEach(interval => clearInterval(interval));
|
Object.values(syncIntervals).forEach(interval => clearInterval(interval));
|
||||||
|
|||||||
@@ -94,22 +94,22 @@ if ($source['name'] === 'jellyfin') {
|
|||||||
$syncService = null;
|
$syncService = null;
|
||||||
switch ($source['name']) {
|
switch ($source['name']) {
|
||||||
case 'steam':
|
case 'steam':
|
||||||
$syncService = new SteamSyncService($pdo, $sourceData);
|
$syncService = new SteamSyncService($pdo, $sourceData, $syncLogId);
|
||||||
break;
|
break;
|
||||||
case 'jellyfin':
|
case 'jellyfin':
|
||||||
$syncService = new JellyfinSyncService($pdo, $sourceData);
|
$syncService = new JellyfinSyncService($pdo, $sourceData, $syncLogId);
|
||||||
break;
|
break;
|
||||||
case 'stash':
|
case 'stash':
|
||||||
$syncService = new StashSyncService($pdo, $sourceData);
|
$syncService = new StashSyncService($pdo, $sourceData, $syncLogId);
|
||||||
break;
|
break;
|
||||||
case 'adult':
|
case 'adult':
|
||||||
$syncService = new AdultSyncService($pdo, $sourceData);
|
$syncService = new AdultSyncService($pdo, $sourceData, $syncLogId);
|
||||||
break;
|
break;
|
||||||
case 'xbvr':
|
case 'xbvr':
|
||||||
$syncService = new XbvrSyncService($pdo, $sourceData);
|
$syncService = new XbvrSyncService($pdo, $sourceData, $syncLogId);
|
||||||
break;
|
break;
|
||||||
case 'exophase':
|
case 'exophase':
|
||||||
$syncService = new ExophaseSyncService($pdo, $sourceData);
|
$syncService = new ExophaseSyncService($pdo, $sourceData, $syncLogId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
echo "Unsupported source type: " . $source['name'] . "\n";
|
echo "Unsupported source type: " . $source['name'] . "\n";
|
||||||
@@ -134,11 +134,11 @@ $syncLogModel->update($syncLogId, [
|
|||||||
|
|
||||||
// Execute the sync
|
// Execute the sync
|
||||||
try {
|
try {
|
||||||
$syncLogId = $syncService->startSync($syncType);
|
$returnedSyncLogId = $syncService->startSync($syncType);
|
||||||
|
|
||||||
if (!$noOutput) {
|
if (!$noOutput) {
|
||||||
echo "Sync started with log ID: {$syncLogId}\n";
|
echo "Sync started with log ID: {$returnedSyncLogId}\n";
|
||||||
echo "Monitor progress at: /admin/sync/status/{$syncLogId}\n";
|
echo "Monitor progress at: /admin/sync/status/{$returnedSyncLogId}\n";
|
||||||
echo "Sync completed successfully.\n";
|
echo "Sync completed successfully.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +150,10 @@ try {
|
|||||||
echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
|
echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update sync log as failed
|
// Update sync log as failed (use the returned ID or fall back to original)
|
||||||
$syncLogModel->update($syncLogId, [
|
$failedSyncLogId = $returnedSyncLogId ?? $syncLogId;
|
||||||
|
$syncLogModel = new SyncLog($pdo);
|
||||||
|
$syncLogModel->update($failedSyncLogId, [
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
'completed_at' => date('Y-m-d H:i:s'),
|
'completed_at' => date('Y-m-d H:i:s'),
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
|
|||||||
Reference in New Issue
Block a user