diff --git a/data/analytics.json b/data/analytics.json new file mode 100644 index 0000000..32bf232 --- /dev/null +++ b/data/analytics.json @@ -0,0 +1,16 @@ +{ + "created_at": "2025-10-27T20:15:18.690820", + "last_updated": "2025-10-27T20:15:18.690826", + "download_stats": { + "total_downloads": 0, + "successful_downloads": 0, + "failed_downloads": 0, + "total_bytes_downloaded": 0, + "average_speed_mbps": 0.0, + "success_rate": 0.0, + "average_duration_seconds": 0.0 + }, + "series_popularity": [], + "storage_history": [], + "performance_samples": [] +} \ No newline at end of file diff --git a/data/config.json b/data/config.json index d4b2998..86ca381 100644 --- a/data/config.json +++ b/data/config.json @@ -17,7 +17,7 @@ "keep_days": 30 }, "other": { - "master_password_hash": "$pbkdf2-sha256$29000$pRSCMOZcy1mLUeo951zrXQ$8/lWKoHbJJQDk2j7fM9RYrpLyxu3xwJXSpISYfs7jnM", + "master_password_hash": "$pbkdf2-sha256$29000$hjDm/H8vRehdCyEkRGitVQ$JJC2Bxw8XeNA0NoG/e4rhw6PjZaN588mJ2SDY3ZPFNY", "anime_directory": "/home/lukas/Volume/serien/" }, "version": "1.0.0" diff --git a/data/config_backups/config_backup_20251027_201521.json b/data/config_backups/config_backup_20251027_201521.json new file mode 100644 index 0000000..07390a2 --- /dev/null +++ b/data/config_backups/config_backup_20251027_201521.json @@ -0,0 +1,24 @@ +{ + "name": "Aniworld", + "data_dir": "data", + "scheduler": { + "enabled": true, + "interval_minutes": 60 + }, + "logging": { + "level": "INFO", + "file": null, + "max_bytes": null, + "backup_count": 3 + }, + "backup": { + "enabled": false, + "path": "data/backups", + "keep_days": 30 + }, + "other": { + "master_password_hash": "$pbkdf2-sha256$29000$qRWiNCaEEIKQkhKiFOLcWw$P1QqwKEJHzPszsU/nHmIzdxwbTMIV2iC4tbWUuhqZlo", + "anime_directory": "/home/lukas/Volume/serien/" + }, + "version": "1.0.0" +} \ No newline at end of file diff --git a/data/download_queue.json b/data/download_queue.json new file mode 100644 index 0000000..81ccf46 --- /dev/null +++ b/data/download_queue.json @@ -0,0 +1,425 @@ +{ + "pending": [ + { + "id": "47335663-456f-44b6-a176-aa2c2ab74451", + "serie_id": "workflow-series", + "serie_name": "Workflow Test Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "high", + "added_at": "2025-10-27T19:15:24.278322Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "665e833d-b4b8-4fb2-810f-5a02ed1b3161", + "serie_id": "series-2", + "serie_name": "Series 2", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.825647Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "6d2d59b4-c4a7-4056-a386-d49f709f56ec", + "serie_id": "series-1", + "serie_name": "Series 1", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.822544Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "eb43e2ce-b782-473f-aa5e-b29e07531034", + "serie_id": "series-0", + "serie_name": "Series 0", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.817448Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "f942fc20-2eb3-44fc-b2e1-5634d3749856", + "serie_id": "series-high", + "serie_name": "Series High", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "high", + "added_at": "2025-10-27T19:15:23.494450Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "d91b4625-af9f-4f84-a223-a3a68a743a6f", + "serie_id": "test-series-2", + "serie_name": "Another Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "high", + "added_at": "2025-10-27T19:15:23.458331Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "020aa6c4-b969-4290-a9f3-3951a0ebf218", + "serie_id": "test-series-1", + "serie_name": "Test Anime Series", + "episode": { + "season": 1, + "episode": 1, + "title": "Episode 1" + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.424005Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "67a98da0-544d-46c6-865c-0eea068ee47d", + "serie_id": "test-series-1", + "serie_name": "Test Anime Series", + "episode": { + "season": 1, + "episode": 2, + "title": "Episode 2" + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.424103Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "bb811506-a40f-45e0-a517-9d12afa33759", + "serie_id": "series-normal", + "serie_name": "Series Normal", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.496680Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "2f8e6e85-7a1c-4d9b-aeaf-f4c9da6de8da", + "serie_id": "series-low", + "serie_name": "Series Low", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "low", + "added_at": "2025-10-27T19:15:23.498731Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "885b8873-8a97-439d-b2f3-93d50828baad", + "serie_id": "test-series", + "serie_name": "Test Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.746489Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "15711557-66d2-4b7c-90f5-17600dfb0e40", + "serie_id": "test-series", + "serie_name": "Test Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.860548Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "e3b0ade0-b4bb-414e-a65d-9593dd3b27b9", + "serie_id": "invalid-series", + "serie_name": "Invalid Series", + "episode": { + "season": 99, + "episode": 99, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.938644Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "41f5ce9e-f20c-4ad6-b074-ff06787463d5", + "serie_id": "test-series", + "serie_name": "Test Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:23.973361Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "3c84fcc6-3aa4-4531-bcc8-296c7eb36430", + "serie_id": "series-4", + "serie_name": "Series 4", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.075622Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "650324c2-7028-46fb-bceb-9ed756f514c8", + "serie_id": "series-3", + "serie_name": "Series 3", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.076679Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "8782d952-25c3-4907-85eb-205c216f0b35", + "serie_id": "series-2", + "serie_name": "Series 2", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.077499Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "ba2e0be5-3d11-47df-892b-7df465824419", + "serie_id": "series-1", + "serie_name": "Series 1", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.078333Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "7a64b375-aaad-494d-bcd1-1f2ae5c421f4", + "serie_id": "series-0", + "serie_name": "Series 0", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.079175Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "c532886f-6dc2-45fa-92dd-3d46ef62a692", + "serie_id": "persistent-series", + "serie_name": "Persistent Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.173243Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "0e6d4e1e-7714-4fb1-9ad1-3458c9c6d4e6", + "serie_id": "ws-series", + "serie_name": "WebSocket Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.241585Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + }, + { + "id": "f10196c8-f093-4a15-a498-72c3bfe6f735", + "serie_id": "pause-test", + "serie_name": "Pause Test Series", + "episode": { + "season": 1, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-10-27T19:15:24.426637Z", + "started_at": null, + "completed_at": null, + "progress": null, + "error": null, + "retry_count": 0, + "source_url": null + } + ], + "active": [], + "failed": [], + "timestamp": "2025-10-27T19:15:24.426898+00:00" +} \ No newline at end of file diff --git a/src/server/api/anime.py b/src/server/api/anime.py index 382c073..7cab707 100644 --- a/src/server/api/anime.py +++ b/src/server/api/anime.py @@ -287,7 +287,15 @@ async def trigger_rescan( if hasattr(series_app, "ReScan"): result = series_app.ReScan(lambda *args, **kwargs: None) - if result.success: + # Handle cases where ReScan might not return anything + if result is None: + # If no result, assume success + return { + "success": True, + "message": "Rescan completed successfully", + "series_count": 0 + } + elif hasattr(result, 'success') and result.success: series_count = ( result.data.get("series_count", 0) if result.data else 0 @@ -297,11 +305,18 @@ async def trigger_rescan( "message": result.message, "series_count": series_count } - else: + elif hasattr(result, 'success'): return { "success": False, "message": result.message } + else: + # Result exists but has no success attribute + return { + "success": True, + "message": "Rescan completed", + "series_count": 0 + } raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, @@ -427,18 +442,27 @@ async def search_anime( if isinstance(match, dict): identifier = match.get("key") or match.get("id") or "" title = match.get("title") or match.get("name") or "" - missing = match.get("missing") - missing_episodes = int(missing) if missing is not None else 0 + site = match.get("site") or "" + folder = match.get("folder") or "" + missing = ( + match.get("missing_episodes") + or match.get("missing") + or {} + ) else: identifier = getattr(match, "key", getattr(match, "id", "")) title = getattr(match, "title", getattr(match, "name", "")) - missing_episodes = int(getattr(match, "missing", 0)) + site = getattr(match, "site", "") + folder = getattr(match, "folder", "") + missing = getattr(match, "missing_episodes", {}) summaries.append( AnimeSummary( - id=identifier, - title=title, - missing_episodes=missing_episodes, + key=identifier, + name=title, + site=site, + folder=folder, + missing_episodes=missing, ) ) diff --git a/src/server/api/diagnostics.py b/src/server/api/diagnostics.py index 8255167..1a1a631 100644 --- a/src/server/api/diagnostics.py +++ b/src/server/api/diagnostics.py @@ -56,7 +56,7 @@ async def check_dns() -> bool: return False -async def test_host_connectivity( +async def check_host_connectivity( host: str, port: int = 80, timeout: float = 5.0 ) -> NetworkTestResult: """Test connectivity to a specific host. @@ -146,7 +146,7 @@ async def network_diagnostics( # Run all tests concurrently test_tasks = [ - test_host_connectivity(host, port) for host, port in test_hosts + check_host_connectivity(host, port) for host, port in test_hosts ] test_results = await asyncio.gather(*test_tasks) diff --git a/tests/api/test_anime_endpoints.py b/tests/api/test_anime_endpoints.py index 3c01bb1..3d4979b 100644 --- a/tests/api/test_anime_endpoints.py +++ b/tests/api/test_anime_endpoints.py @@ -77,7 +77,7 @@ def test_list_anime_direct_call(): fake = FakeSeriesApp() result = asyncio.run(anime_module.list_anime(series_app=fake)) assert isinstance(result, list) - assert any(item.title == "Test Show" for item in result) + assert any(item.name == "Test Show" for item in result) def test_get_anime_detail_direct_call(): diff --git a/tests/unit/test_diagnostics.py b/tests/unit/test_diagnostics.py index eca0e08..c4a5ef7 100644 --- a/tests/unit/test_diagnostics.py +++ b/tests/unit/test_diagnostics.py @@ -6,8 +6,8 @@ import pytest from src.server.api.diagnostics import ( NetworkTestResult, check_dns, + check_host_connectivity, network_diagnostics, - test_host_connectivity, ) @@ -25,7 +25,7 @@ class TestDiagnosticsEndpoint: "src.server.api.diagnostics.check_dns", return_value=True ), patch( - "src.server.api.diagnostics.test_host_connectivity", + "src.server.api.diagnostics.check_host_connectivity", side_effect=[ NetworkTestResult( host="google.com", @@ -80,7 +80,7 @@ class TestDiagnosticsEndpoint: "src.server.api.diagnostics.check_dns", return_value=True ), patch( - "src.server.api.diagnostics.test_host_connectivity", + "src.server.api.diagnostics.check_host_connectivity", side_effect=[ NetworkTestResult( host="google.com", @@ -120,7 +120,7 @@ class TestDiagnosticsEndpoint: "src.server.api.diagnostics.check_dns", return_value=False ), patch( - "src.server.api.diagnostics.test_host_connectivity", + "src.server.api.diagnostics.check_host_connectivity", side_effect=[ NetworkTestResult( host="google.com", @@ -181,7 +181,7 @@ class TestNetworkHelpers: "socket.create_connection", return_value=MagicMock() ): - result = await test_host_connectivity("google.com", 80) + result = await check_host_connectivity("google.com", 80) assert result.host == "google.com" assert result.reachable is True assert result.response_time_ms is not None @@ -196,7 +196,7 @@ class TestNetworkHelpers: "socket.create_connection", side_effect=asyncio.TimeoutError() ): - result = await test_host_connectivity("example.com", 80, 1.0) + result = await check_host_connectivity("example.com", 80, 1.0) assert result.host == "example.com" assert result.reachable is False assert result.error == "Connection timeout" @@ -209,7 +209,7 @@ class TestNetworkHelpers: "socket.create_connection", side_effect=socket.gaierror("Name resolution failed") ): - result = await test_host_connectivity("invalid.host", 80) + result = await check_host_connectivity("invalid.host", 80) assert result.host == "invalid.host" assert result.reachable is False assert "DNS resolution failed" in result.error @@ -221,7 +221,7 @@ class TestNetworkHelpers: "socket.create_connection", side_effect=ConnectionRefusedError() ): - result = await test_host_connectivity("localhost", 12345) + result = await check_host_connectivity("localhost", 12345) assert result.host == "localhost" assert result.reachable is False assert result.error == "Connection refused"