diff --git a/data/aniworld.db-shm b/data/aniworld.db-shm new file mode 100644 index 0000000..d8e7159 Binary files /dev/null and b/data/aniworld.db-shm differ diff --git a/data/aniworld.db-wal b/data/aniworld.db-wal new file mode 100644 index 0000000..b3d323d Binary files /dev/null and b/data/aniworld.db-wal differ diff --git a/data/config.json b/data/config.json index f37aea1..41d69dd 100644 --- a/data/config.json +++ b/data/config.json @@ -16,6 +16,9 @@ "path": "data/backups", "keep_days": 30 }, - "other": {}, + "other": { + "master_password_hash": "$pbkdf2-sha256$29000$hdC6t/b.f885Z0xprfXeWw$7K3TmeKN2jtTZq8/xiQjm3Y5DCLx8s0Nj9mIZbs/XUM", + "anime_directory": "/mnt/server/serien/Serien/" + }, "version": "1.0.0" } \ No newline at end of file diff --git a/docs/instructions.md b/docs/instructions.md index 73e5e8e..5c0980e 100644 --- a/docs/instructions.md +++ b/docs/instructions.md @@ -120,3 +120,148 @@ For each task completed: - Good foundation for future enhancements if needed --- + +## 🔧 Current Task: Make MP4 Scanning Progress Visible in UI + +### Problem Statement + +When users trigger a library rescan (via the "Rescan Library" button on the anime page), the MP4 file scanning happens silently in the background. Users only see a brief toast message, but there's no visual feedback showing: + +1. That scanning is actively happening +2. How many files/directories have been scanned +3. The progress through the scan operation +4. When scanning is complete with results + +Currently, the only indication is in server logs: + +``` +INFO: Starting directory rescan +INFO: Scanning for .mp4 files +``` + +### Desired Outcome + +Users should see real-time progress in the UI during library scanning with: + +1. **Progress overlay** showing scan is active with a spinner animation +2. **Live counters** showing directories scanned and files found +3. **Current directory display** showing which folder is being scanned (truncated if too long) +4. **Completion summary** showing total files found, directories scanned, and elapsed time +5. **Auto-dismiss** the overlay after showing completion summary + +### Files to Modify + +#### 1. `src/server/services/websocket_service.py` + +Add three new broadcast methods for scan events: + +- **broadcast_scan_started**: Notify clients that a scan has begun, include the root directory path +- **broadcast_scan_progress**: Send periodic updates with directories scanned count, files found count, and current directory name +- **broadcast_scan_completed**: Send final summary with total directories, total files, and elapsed time in seconds + +Follow the existing pattern used by `broadcast_download_progress` for message structure consistency. + +#### 2. `src/server/services/scanner_service.py` + +Modify the scanning logic to emit progress via WebSocket: + +- Inject `WebSocketService` dependency into the scanner service +- At scan start, call `broadcast_scan_started` +- During directory traversal, track directories scanned and files found +- Every 10 directories (to avoid WebSocket spam), call `broadcast_scan_progress` +- Track elapsed time using `time.time()` +- At scan completion, call `broadcast_scan_completed` with summary statistics +- Ensure the scan still works correctly even if WebSocket broadcast fails (wrap in try/except) + +#### 3. `src/server/static/css/style.css` + +Add styles for the scan progress overlay: + +- Full-screen semi-transparent overlay (z-index high enough to be on top) +- Centered container with background matching theme (use CSS variables) +- Spinner animation using CSS keyframes +- Styling for current directory text (truncated with ellipsis) +- Styling for statistics display +- Success state styling for completion +- Ensure it works in both light and dark mode themes + +#### 4. `src/server/static/js/anime.js` + +Add WebSocket message handlers and UI functions: + +- Handle `scan_started` message: Create and show progress overlay with spinner +- Handle `scan_progress` message: Update directory count, file count, and current directory text +- Handle `scan_completed` message: Show completion summary, then auto-remove overlay after 3 seconds +- Ensure overlay is properly cleaned up if page navigates away +- Update the existing rescan button handler to work with the new progress system + +### WebSocket Message Types + +Define three new message types following the existing project patterns: + +1. **scan_started**: type, directory path, timestamp +2. **scan_progress**: type, directories_scanned, files_found, current_directory, timestamp +3. **scan_completed**: type, total_directories, total_files, elapsed_seconds, timestamp + +### Implementation Steps + +1. First modify `websocket_service.py` to add the three new broadcast methods +2. Add unit tests for the new WebSocket methods +3. Modify `scanner_service.py` to use the new broadcast methods during scanning +4. Add CSS styles to `style.css` for the progress overlay +5. Update `anime.js` to handle the new WebSocket messages and display the UI +6. Test the complete flow manually +7. Verify all existing tests still pass + +### Testing Requirements + +**Unit Tests:** + +- Test each new WebSocket broadcast method +- Test that scanner service calls WebSocket methods at appropriate times +- Mock WebSocket service in scanner tests + +**Manual Testing:** + +- Start server and login +- Navigate to anime page +- Click "Rescan Library" button +- Verify overlay appears immediately with spinner +- Verify counters update during scan +- Verify current directory updates +- Verify completion summary appears +- Verify overlay auto-dismisses after 3 seconds +- Test in both light and dark mode +- Verify no JavaScript console errors + +### Acceptance Criteria + +- [ ] Progress overlay appears immediately when scan starts +- [ ] Spinner animation is visible during scanning +- [ ] Directory counter updates periodically (every ~10 directories) +- [ ] Files found counter updates as MP4 files are discovered +- [ ] Current directory name is displayed (truncated if path is too long) +- [ ] Scan completion shows total directories, files, and elapsed time +- [ ] Overlay auto-dismisses 3 seconds after completion +- [ ] Works correctly in both light and dark mode +- [ ] No JavaScript errors in browser console +- [ ] All existing tests continue to pass +- [ ] New unit tests added and passing +- [ ] Code follows project coding standards + +### Edge Cases to Handle + +- Empty directory with no MP4 files +- Very large directory structure (ensure UI remains responsive) +- WebSocket connection lost during scan (scan should still complete) +- User navigates away during scan (cleanup overlay properly) +- Rapid consecutive scan requests (debounce or queue) + +### Notes + +- Keep progress updates throttled to avoid overwhelming the WebSocket connection +- Use existing CSS variables for colors to maintain theme consistency +- Follow existing JavaScript patterns in the codebase +- The scan functionality must continue to work even if WebSocket fails + +--- diff --git a/src/server/api/anime.py b/src/server/api/anime.py index 4d67127..698ef9e 100644 --- a/src/server/api/anime.py +++ b/src/server/api/anime.py @@ -81,6 +81,7 @@ class AnimeSummary(BaseModel): site: Provider site URL folder: Filesystem folder name (metadata only) missing_episodes: Episode dictionary mapping seasons to episode numbers + has_missing: Boolean flag indicating if series has missing episodes link: Optional link to the series page (used when adding new series) """ key: str = Field( @@ -103,6 +104,10 @@ class AnimeSummary(BaseModel): ..., description="Episode dictionary: {season: [episode_numbers]}" ) + has_missing: bool = Field( + default=False, + description="Whether the series has any missing episodes" + ) link: Optional[str] = Field( default="", description="Link to the series page (for adding new series)" @@ -117,6 +122,7 @@ class AnimeSummary(BaseModel): "site": "aniworld.to", "folder": "beheneko the elf girls cat (2025)", "missing_episodes": {"1": [1, 2, 3, 4]}, + "has_missing": True, "link": "https://aniworld.to/anime/stream/beheneko" } } @@ -181,11 +187,14 @@ async def list_anime( _auth: dict = Depends(require_auth), series_app: Any = Depends(get_series_app), ) -> List[AnimeSummary]: - """List library series that still have missing episodes. + """List all library series with their missing episodes status. Returns AnimeSummary objects where `key` is the primary identifier used for all operations. The `folder` field is metadata only and should not be used for lookups. + + All series are returned, with `has_missing` flag indicating whether + a series has any missing episodes. Args: page: Page number for pagination (must be positive) @@ -204,6 +213,7 @@ async def list_anime( - site: Provider site - folder: Filesystem folder name (metadata only) - missing_episodes: Dict mapping seasons to episode numbers + - has_missing: Whether the series has any missing episodes Raises: HTTPException: When the underlying lookup fails or params invalid. @@ -264,11 +274,11 @@ async def list_anime( ) try: - # Get missing episodes from series app + # Get all series from series app if not hasattr(series_app, "list"): return [] - series = series_app.list.GetMissingEpisode() + series = series_app.list.GetList() summaries: List[AnimeSummary] = [] for serie in series: # Get all properties from the serie object @@ -281,6 +291,9 @@ async def list_anime( # Convert episode dict keys to strings for JSON serialization missing_episodes = {str(k): v for k, v in episode_dict.items()} + # Determine if series has missing episodes + has_missing = bool(episode_dict) + summaries.append( AnimeSummary( key=key, @@ -288,6 +301,7 @@ async def list_anime( site=site, folder=folder, missing_episodes=missing_episodes, + has_missing=has_missing, ) ) diff --git a/src/server/web/static/js/app.js b/src/server/web/static/js/app.js index 64babaf..647fb4e 100644 --- a/src/server/web/static/js/app.js +++ b/src/server/web/static/js/app.js @@ -565,7 +565,8 @@ class AniWorldApp { site: anime.site, folder: anime.folder, episodeDict: episodeDict, - missing_episodes: totalMissing + missing_episodes: totalMissing, + has_missing: anime.has_missing || totalMissing > 0 }; }); } else if (data.status === 'success') { diff --git a/tests/frontend/test_existing_ui_integration.py b/tests/frontend/test_existing_ui_integration.py index ca3c85b..1b8a39b 100644 --- a/tests/frontend/test_existing_ui_integration.py +++ b/tests/frontend/test_existing_ui_integration.py @@ -162,6 +162,7 @@ class TestFrontendAuthentication: mock_app = AsyncMock() mock_list = AsyncMock() mock_list.GetMissingEpisode = AsyncMock(return_value=[]) + mock_list.GetList = AsyncMock(return_value=[]) mock_app.List = mock_list mock_get_app.return_value = mock_app diff --git a/tests/performance/test_api_load.py b/tests/performance/test_api_load.py index 685a2cb..f3be66c 100644 --- a/tests/performance/test_api_load.py +++ b/tests/performance/test_api_load.py @@ -29,6 +29,7 @@ class TestAPILoadTesting: mock_app = MagicMock() mock_app.list = MagicMock() mock_app.list.GetMissingEpisode = MagicMock(return_value=[]) + mock_app.list.GetList = MagicMock(return_value=[]) mock_app.search = AsyncMock(return_value=[]) app.dependency_overrides[get_series_app] = lambda: mock_app diff --git a/tests/security/test_sql_injection.py b/tests/security/test_sql_injection.py index 565587d..96483eb 100644 --- a/tests/security/test_sql_injection.py +++ b/tests/security/test_sql_injection.py @@ -27,6 +27,7 @@ class TestSQLInjection: mock_app = MagicMock() mock_app.list = MagicMock() mock_app.list.GetMissingEpisode = MagicMock(return_value=[]) + mock_app.list.GetList = MagicMock(return_value=[]) mock_app.search = AsyncMock(return_value=[]) # Override dependency @@ -287,6 +288,7 @@ class TestDatabaseSecurity: mock_app = MagicMock() mock_app.list = MagicMock() mock_app.list.GetMissingEpisode = MagicMock(return_value=[]) + mock_app.list.GetList = MagicMock(return_value=[]) mock_app.search = AsyncMock(return_value=[]) # Override dependency