From 9b071fe3706e1a7ca1feacdc7c3947d71687d3a3 Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 23 Dec 2025 18:13:10 +0100 Subject: [PATCH] backup --- data/aniworld.db-shm | Bin 0 -> 32768 bytes data/aniworld.db-wal | Bin 0 -> 94792 bytes data/config.json | 5 +- docs/instructions.md | 145 ++++++++++++++++++ src/server/api/anime.py | 20 ++- src/server/web/static/js/app.js | 3 +- .../frontend/test_existing_ui_integration.py | 1 + tests/performance/test_api_load.py | 1 + tests/security/test_sql_injection.py | 2 + 9 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 data/aniworld.db-shm create mode 100644 data/aniworld.db-wal diff --git a/data/aniworld.db-shm b/data/aniworld.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..d8e7159640b2c1029ee8413306c53e892047e6fa GIT binary patch literal 32768 zcmeI*J4%B=6b4Y^`)z#3XO~8>bO-i=Zo&nKi*QpxY_kHf5-cq&t@K`$@Rws_F>^ln z$e%DIxn}`)2CwH+nch$P`|)}jy}Q4>eR~`~zYniJAFf8d@yqD!;^XFN@W=D(Py9Ke`#+if%`DqPx+(X#VFx^dNc|J&GPjPog=C5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0Rja6TOju(NQpphoshZ&a#M(u2;{yMsY@VtoBSS-Q- literal 0 HcmV?d00001 diff --git a/data/aniworld.db-wal b/data/aniworld.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..b3d323d97ad49a4edd6ebbe666cda8f7c802908b GIT binary patch literal 94792 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 X%JBv&H$eWzy?qW{<1bUg-^l+TP?}&V literal 0 HcmV?d00001 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