Add sync_single_series_after_scan with NFO metadata and WebSocket updates

- Implement sync_single_series_after_scan to persist scanned series to database
- Enhanced _broadcast_series_updated to include full NFO metadata (nfo_created_at, nfo_updated_at, tmdb_id, tvdb_id)
- Add immediate episode scanning in add_series endpoint when background loader isn't running
- Implement updateSingleSeries in frontend to handle series_updated WebSocket events
- Add SERIES_UPDATED event constant to WebSocket event definitions
- Update background loader to use sync_single_series_after_scan method
- Simplified background loader initialization in FastAPI app
- Add comprehensive tests for series update WebSocket payload and episode counting logic
- Import reorganization: move get_background_loader_service to dependencies module
This commit is contained in:
2026-02-06 18:36:39 +01:00
parent d74c181556
commit d72b8cb1ab
9 changed files with 1078 additions and 21 deletions

View File

@@ -338,6 +338,18 @@ AniWorld.SeriesManager = (function() {
const canBeSelected = hasMissingEpisodes;
const hasNfo = serie.has_nfo || false;
const isLoading = serie.loading_status && serie.loading_status !== 'completed' && serie.loading_status !== 'failed';
// Debug logging for troubleshooting
if (serie.key === 'so-im-a-spider-so-what') {
console.log('[createSerieCard] Spider series:', {
key: serie.key,
missing_episodes: serie.missing_episodes,
missing_episodes_type: typeof serie.missing_episodes,
episodeDict: serie.episodeDict,
hasMissingEpisodes: hasMissingEpisodes,
has_missing: serie.has_missing
});
}
return '<div class="series-card ' + (isSelected ? 'selected' : '') + ' ' +
(hasMissingEpisodes ? 'has-missing' : 'complete') + ' ' +
@@ -455,6 +467,76 @@ AniWorld.SeriesManager = (function() {
}
}
/**
* Update a single series from WebSocket data
* @param {Object} updatedData - Updated series data from WebSocket
*/
function updateSingleSeries(updatedData) {
if (!updatedData || !updatedData.key) {
console.warn('Invalid series update data:', updatedData);
return;
}
console.log('[updateSingleSeries] Received data:', updatedData);
console.log('[updateSingleSeries] missing_episodes type:', typeof updatedData.missing_episodes);
console.log('[updateSingleSeries] missing_episodes value:', updatedData.missing_episodes);
// Count total missing episodes from the episode dictionary
const episodeDict = updatedData.missing_episodes || {};
console.log('[updateSingleSeries] episodeDict:', episodeDict);
const totalMissing = Object.values(episodeDict).reduce(
function(sum, episodes) {
return sum + (Array.isArray(episodes) ? episodes.length : 0);
},
0
);
console.log('[updateSingleSeries] totalMissing calculated:', totalMissing);
// Transform WebSocket data to match our internal format
const transformedSerie = {
key: updatedData.key,
name: updatedData.name,
site: updatedData.site || 'aniworld.to',
folder: updatedData.folder,
episodeDict: episodeDict,
missing_episodes: totalMissing,
has_missing: updatedData.has_missing || totalMissing > 0,
has_nfo: updatedData.has_nfo || false,
nfo_created_at: updatedData.nfo_created_at || null,
nfo_updated_at: updatedData.nfo_updated_at || null,
tmdb_id: updatedData.tmdb_id || null,
tvdb_id: updatedData.tvdb_id || null,
loading_status: updatedData.loading_status || 'completed',
episodes_loaded: updatedData.episodes_loaded !== false,
nfo_loaded: updatedData.nfo_loaded !== false,
logo_loaded: updatedData.logo_loaded !== false,
images_loaded: updatedData.images_loaded !== false
};
console.log('[updateSingleSeries] Transformed serie:', transformedSerie);
// Find existing series in our data
const existingIndex = seriesData.findIndex(function(s) {
return s.key === updatedData.key;
});
if (existingIndex >= 0) {
// Update existing series
seriesData[existingIndex] = transformedSerie;
console.log('Updated existing series:', updatedData.key, transformedSerie);
} else {
// Add new series
seriesData.push(transformedSerie);
console.log('Added new series:', updatedData.key, transformedSerie);
}
// Reapply filters and re-render
applyFiltersAndSort();
renderSeries();
}
// Public API
return {
init: init,
@@ -464,6 +546,7 @@ AniWorld.SeriesManager = (function() {
getSeriesData: getSeriesData,
getFilteredSeriesData: getFilteredSeriesData,
findByKey: findByKey,
updateSeriesLoadingStatus: updateSeriesLoadingStatus
updateSeriesLoadingStatus: updateSeriesLoadingStatus,
updateSingleSeries: updateSingleSeries
};
})();

View File

@@ -133,6 +133,22 @@ AniWorld.IndexSocketHandler = (function() {
AniWorld.ScanManager.updateProcessStatus('download', false, true);
});
// Series events
socket.on(WS_EVENTS.SERIES_UPDATED, function(data) {
console.log('Series updated:', data);
// Use the data directly to update the series instead of full refresh
if (data && data.data && AniWorld.SeriesManager && AniWorld.SeriesManager.updateSingleSeries) {
AniWorld.SeriesManager.updateSingleSeries(data.data);
} else {
// Fallback to full reload if data is incomplete
console.warn('Incomplete series update data, falling back to full reload');
if (AniWorld.SeriesManager && AniWorld.SeriesManager.loadSeries) {
AniWorld.SeriesManager.loadSeries();
}
}
});
// Series loading events
socket.on(WS_EVENTS.SERIES_LOADING_UPDATE, function(data) {
console.log('Series loading update:', data);