Phase 5: Frontend - Use key as primary series identifier
- Updated app.js to use 'key' as primary series identifier - selectedSeries Set now uses key instead of folder - createSerieCard() uses data-key attribute for identification - toggleSerieSelection() uses key for lookups - downloadSelected() iterates with key values - updateSelectionUI() and toggleSelectAll() use key - Updated WebSocket service tests - Tests now include key and folder in broadcast data - Verified both fields are included in messages - No changes needed for queue.js and other JS files - They use download item IDs correctly, not series identifiers - No template changes needed - Series cards rendered dynamically in app.js All 996 tests passing
This commit is contained in:
parent
5aabad4d13
commit
a833077f97
@ -17,7 +17,7 @@
|
|||||||
"keep_days": 30
|
"keep_days": 30
|
||||||
},
|
},
|
||||||
"other": {
|
"other": {
|
||||||
"master_password_hash": "$pbkdf2-sha256$29000$MwYgpBTCmPM.JwQgRGhtDQ$GbltEa61jWLom23mXGi7psyZ4haHuXM6MRB5wl4CCU4",
|
"master_password_hash": "$pbkdf2-sha256$29000$jxGC8F7LOYcwZoyR0rpX6g$b4wXlUToBG0KgOj/3Sbez0J57hA84RhtsMnepsqBRb0",
|
||||||
"anime_directory": "/mnt/server/serien/Serien/"
|
"anime_directory": "/mnt/server/serien/Serien/"
|
||||||
},
|
},
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
|
|||||||
24
data/config_backups/config_backup_20251128_161248.json
Normal file
24
data/config_backups/config_backup_20251128_161248.json
Normal file
@ -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$VCqllLL2vldKyTmHkJIyZg$jNllpzlpENdgCslmS.tG.PGxRZ9pUnrqFEQFveDEcYk",
|
||||||
|
"anime_directory": "/mnt/server/serien/Serien/"
|
||||||
|
},
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
24
data/config_backups/config_backup_20251128_161448.json
Normal file
24
data/config_backups/config_backup_20251128_161448.json
Normal file
@ -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$3/t/7733PkdoTckZQyildA$Nz9SdX2ZgqBwyzhQ9FGNcnzG1X.TW9oce3sDxJbVSdY",
|
||||||
|
"anime_directory": "/mnt/server/serien/Serien/"
|
||||||
|
},
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pending": [
|
"pending": [
|
||||||
{
|
{
|
||||||
"id": "4900cd26-5702-44fc-b575-5111193a274f",
|
"id": "304d3273-8b1c-4847-8dc8-a8c585d720c0",
|
||||||
"serie_id": "test-series-2",
|
"serie_id": "test-series-2",
|
||||||
"serie_folder": "Another Series (2024)",
|
"serie_folder": "Another Series (2024)",
|
||||||
"serie_name": "Another Series",
|
"serie_name": "Another Series",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "HIGH",
|
"priority": "HIGH",
|
||||||
"added_at": "2025-11-28T14:58:37.173276Z",
|
"added_at": "2025-11-28T15:14:50.916177Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "991f6985-1896-420c-8e28-675044bf9da3",
|
"id": "4ec82ed2-ad64-4330-bea5-3c986e577fa8",
|
||||||
"serie_id": "series-high",
|
"serie_id": "series-high",
|
||||||
"serie_folder": "Series High (2024)",
|
"serie_folder": "Series High (2024)",
|
||||||
"serie_name": "Series High",
|
"serie_name": "Series High",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "HIGH",
|
"priority": "HIGH",
|
||||||
"added_at": "2025-11-28T14:58:37.214713Z",
|
"added_at": "2025-11-28T15:14:50.955809Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1d2c34fe-9f91-4614-a733-fd4e5ffbc451",
|
"id": "9f089050-5128-439b-969e-541c3c6b7283",
|
||||||
"serie_id": "series-normal",
|
"serie_id": "series-normal",
|
||||||
"serie_folder": "Series Normal (2024)",
|
"serie_folder": "Series Normal (2024)",
|
||||||
"serie_name": "Series Normal",
|
"serie_name": "Series Normal",
|
||||||
@ -52,7 +52,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.216943Z",
|
"added_at": "2025-11-28T15:14:50.958134Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2eeb36cd-50ba-4e49-92c1-8f9385d9d0f9",
|
"id": "0924064d-8588-45dd-9735-ac5ed2225360",
|
||||||
"serie_id": "series-low",
|
"serie_id": "series-low",
|
||||||
"serie_folder": "Series Low (2024)",
|
"serie_folder": "Series Low (2024)",
|
||||||
"serie_name": "Series Low",
|
"serie_name": "Series Low",
|
||||||
@ -72,7 +72,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "LOW",
|
"priority": "LOW",
|
||||||
"added_at": "2025-11-28T14:58:37.218979Z",
|
"added_at": "2025-11-28T15:14:50.960059Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "c42241f8-6ffb-4ee4-bcfa-f4e388cdd8cf",
|
"id": "b34582b3-3d3c-4883-b95f-63287b6df397",
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
"serie_folder": "Test Series (2024)",
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
@ -92,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.399213Z",
|
"added_at": "2025-11-28T15:14:51.136578Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -101,7 +101,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "728a5592-0b82-4ce5-a34f-7a9c390053dc",
|
"id": "2221eecf-e250-4f26-8e17-79b81db379bd",
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
"serie_folder": "Test Series (2024)",
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
@ -112,7 +112,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.467671Z",
|
"added_at": "2025-11-28T15:14:51.204136Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -121,7 +121,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "d64df138-b5b2-447a-a310-fde41a4607c8",
|
"id": "ab779cf6-3cd5-49ca-852b-a490b61dce99",
|
||||||
"serie_id": "invalid-series",
|
"serie_id": "invalid-series",
|
||||||
"serie_folder": "Invalid Series (2024)",
|
"serie_folder": "Invalid Series (2024)",
|
||||||
"serie_name": "Invalid Series",
|
"serie_name": "Invalid Series",
|
||||||
@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.540908Z",
|
"added_at": "2025-11-28T15:14:51.272632Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -141,7 +141,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "c87bb473-6b35-4f81-a0a8-7eec1e23756a",
|
"id": "ecc12f47-9e1a-4cd3-a39d-df4ccd81baf4",
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
"serie_folder": "Test Series (2024)",
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
@ -152,7 +152,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.578395Z",
|
"added_at": "2025-11-28T15:14:51.305672Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -161,67 +161,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "babab810-6c34-4a87-aec5-f8848645f57f",
|
"id": "109163ee-d2ce-48bb-81b3-5f6b604506e7",
|
||||||
"serie_id": "series-4",
|
|
||||||
"serie_folder": "Series 4 (2024)",
|
|
||||||
"serie_name": "Series 4",
|
|
||||||
"episode": {
|
|
||||||
"season": 1,
|
|
||||||
"episode": 1,
|
|
||||||
"title": null
|
|
||||||
},
|
|
||||||
"status": "pending",
|
|
||||||
"priority": "NORMAL",
|
|
||||||
"added_at": "2025-11-28T14:58:37.674390Z",
|
|
||||||
"started_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"progress": null,
|
|
||||||
"error": null,
|
|
||||||
"retry_count": 0,
|
|
||||||
"source_url": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4da2b237-ab58-4312-a322-09d28f3ba0ec",
|
|
||||||
"serie_id": "series-2",
|
|
||||||
"serie_folder": "Series 2 (2024)",
|
|
||||||
"serie_name": "Series 2",
|
|
||||||
"episode": {
|
|
||||||
"season": 1,
|
|
||||||
"episode": 1,
|
|
||||||
"title": null
|
|
||||||
},
|
|
||||||
"status": "pending",
|
|
||||||
"priority": "NORMAL",
|
|
||||||
"added_at": "2025-11-28T14:58:37.675597Z",
|
|
||||||
"started_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"progress": null,
|
|
||||||
"error": null,
|
|
||||||
"retry_count": 0,
|
|
||||||
"source_url": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d2ad485b-6f44-4738-8cba-ef9be5ef6853",
|
|
||||||
"serie_id": "series-0",
|
|
||||||
"serie_folder": "Series 0 (2024)",
|
|
||||||
"serie_name": "Series 0",
|
|
||||||
"episode": {
|
|
||||||
"season": 1,
|
|
||||||
"episode": 1,
|
|
||||||
"title": null
|
|
||||||
},
|
|
||||||
"status": "pending",
|
|
||||||
"priority": "NORMAL",
|
|
||||||
"added_at": "2025-11-28T14:58:37.677593Z",
|
|
||||||
"started_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"progress": null,
|
|
||||||
"error": null,
|
|
||||||
"retry_count": 0,
|
|
||||||
"source_url": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d44e3def-5e49-47c9-accb-c662712f3b5d",
|
|
||||||
"serie_id": "series-1",
|
"serie_id": "series-1",
|
||||||
"serie_folder": "Series 1 (2024)",
|
"serie_folder": "Series 1 (2024)",
|
||||||
"serie_name": "Series 1",
|
"serie_name": "Series 1",
|
||||||
@ -232,7 +172,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.678299Z",
|
"added_at": "2025-11-28T15:14:51.399391Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -241,7 +181,27 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "d8ddb373-d20e-456e-a836-31991a9e3e90",
|
"id": "8ebd2676-0069-43a7-bee1-ed291a0ae334",
|
||||||
|
"serie_id": "series-4",
|
||||||
|
"serie_folder": "Series 4 (2024)",
|
||||||
|
"serie_name": "Series 4",
|
||||||
|
"episode": {
|
||||||
|
"season": 1,
|
||||||
|
"episode": 1,
|
||||||
|
"title": null
|
||||||
|
},
|
||||||
|
"status": "pending",
|
||||||
|
"priority": "NORMAL",
|
||||||
|
"added_at": "2025-11-28T15:14:51.400954Z",
|
||||||
|
"started_at": null,
|
||||||
|
"completed_at": null,
|
||||||
|
"progress": null,
|
||||||
|
"error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"source_url": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f1d8efe4-a682-4ef1-b7a5-e1b954b622be",
|
||||||
"serie_id": "series-3",
|
"serie_id": "series-3",
|
||||||
"serie_folder": "Series 3 (2024)",
|
"serie_folder": "Series 3 (2024)",
|
||||||
"serie_name": "Series 3",
|
"serie_name": "Series 3",
|
||||||
@ -252,7 +212,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.678964Z",
|
"added_at": "2025-11-28T15:14:51.401591Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -261,7 +221,47 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "7cdbc2b6-0d5c-48fc-8a02-0b6b4d4275dd",
|
"id": "18f8392a-d0ad-4b5d-8a2b-f634fd642c71",
|
||||||
|
"serie_id": "series-2",
|
||||||
|
"serie_folder": "Series 2 (2024)",
|
||||||
|
"serie_name": "Series 2",
|
||||||
|
"episode": {
|
||||||
|
"season": 1,
|
||||||
|
"episode": 1,
|
||||||
|
"title": null
|
||||||
|
},
|
||||||
|
"status": "pending",
|
||||||
|
"priority": "NORMAL",
|
||||||
|
"added_at": "2025-11-28T15:14:51.402182Z",
|
||||||
|
"started_at": null,
|
||||||
|
"completed_at": null,
|
||||||
|
"progress": null,
|
||||||
|
"error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"source_url": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b394f1b2-691a-4be0-83f1-b5dc49e67c02",
|
||||||
|
"serie_id": "series-0",
|
||||||
|
"serie_folder": "Series 0 (2024)",
|
||||||
|
"serie_name": "Series 0",
|
||||||
|
"episode": {
|
||||||
|
"season": 1,
|
||||||
|
"episode": 1,
|
||||||
|
"title": null
|
||||||
|
},
|
||||||
|
"status": "pending",
|
||||||
|
"priority": "NORMAL",
|
||||||
|
"added_at": "2025-11-28T15:14:51.403030Z",
|
||||||
|
"started_at": null,
|
||||||
|
"completed_at": null,
|
||||||
|
"progress": null,
|
||||||
|
"error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"source_url": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ee8b4416-772b-4ab7-b8af-8a695a723fe3",
|
||||||
"serie_id": "persistent-series",
|
"serie_id": "persistent-series",
|
||||||
"serie_folder": "Persistent Series (2024)",
|
"serie_folder": "Persistent Series (2024)",
|
||||||
"serie_name": "Persistent Series",
|
"serie_name": "Persistent Series",
|
||||||
@ -272,7 +272,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.765201Z",
|
"added_at": "2025-11-28T15:14:51.493134Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -281,7 +281,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1b5f11b3-26d3-43ea-9615-8f951dc09bac",
|
"id": "8ee3de44-7260-4e26-88ee-8172e45d14dd",
|
||||||
"serie_id": "ws-series",
|
"serie_id": "ws-series",
|
||||||
"serie_folder": "WebSocket Series (2024)",
|
"serie_folder": "WebSocket Series (2024)",
|
||||||
"serie_name": "WebSocket Series",
|
"serie_name": "WebSocket Series",
|
||||||
@ -292,7 +292,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "NORMAL",
|
"priority": "NORMAL",
|
||||||
"added_at": "2025-11-28T14:58:37.835647Z",
|
"added_at": "2025-11-28T15:14:51.573256Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -301,7 +301,7 @@
|
|||||||
"source_url": null
|
"source_url": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "72a71e8e-3a06-4d81-896e-d5c11956cb12",
|
"id": "ec7f80b1-87c1-4cf4-949b-c96d6dd7a82b",
|
||||||
"serie_id": "workflow-series",
|
"serie_id": "workflow-series",
|
||||||
"serie_folder": "Workflow Test Series (2024)",
|
"serie_folder": "Workflow Test Series (2024)",
|
||||||
"serie_name": "Workflow Test Series",
|
"serie_name": "Workflow Test Series",
|
||||||
@ -312,7 +312,7 @@
|
|||||||
},
|
},
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"priority": "HIGH",
|
"priority": "HIGH",
|
||||||
"added_at": "2025-11-28T14:58:37.870997Z",
|
"added_at": "2025-11-28T15:14:51.610620Z",
|
||||||
"started_at": null,
|
"started_at": null,
|
||||||
"completed_at": null,
|
"completed_at": null,
|
||||||
"progress": null,
|
"progress": null,
|
||||||
@ -323,5 +323,5 @@
|
|||||||
],
|
],
|
||||||
"active": [],
|
"active": [],
|
||||||
"failed": [],
|
"failed": [],
|
||||||
"timestamp": "2025-11-28T14:58:37.880117+00:00"
|
"timestamp": "2025-11-28T15:14:51.618568+00:00"
|
||||||
}
|
}
|
||||||
224
instructions.md
224
instructions.md
@ -112,22 +112,12 @@ For each task completed:
|
|||||||
2. Password: `Hallo123!`
|
2. Password: `Hallo123!`
|
||||||
3. Login via browser at `http://127.0.0.1:8000/login`
|
3. Login via browser at `http://127.0.0.1:8000/login`
|
||||||
|
|
||||||
**Deployment Steps:**
|
|
||||||
|
|
||||||
1. Commit all changes to git repository
|
|
||||||
2. Create deployment tag (e.g., `v1.0.0-queue-simplified`)
|
|
||||||
3. Deploy to production environment
|
|
||||||
4. Monitor logs for any unexpected behavior
|
|
||||||
5. Verify production queue functionality
|
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
- This is a simplification that removes complexity while maintaining core functionality
|
- This is a simplification that removes complexity while maintaining core functionality
|
||||||
- Improves user experience with explicit manual control
|
- Improves user experience with explicit manual control
|
||||||
- Easier to understand, test, and maintain
|
- Easier to understand, test, and maintain
|
||||||
- Good foundation for future enhancements if needed
|
- Good foundation for future enhancements if needed
|
||||||
- No database schema changes required
|
|
||||||
- WebSocket infrastructure remains unchanged
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -160,191 +150,35 @@ For each task completed:
|
|||||||
|
|
||||||
### Phase 4: API Layer ✅ (Completed November 28, 2025)
|
### Phase 4: API Layer ✅ (Completed November 28, 2025)
|
||||||
|
|
||||||
All API layer tasks completed:
|
All API layer tasks completed.
|
||||||
|
|
||||||
- Task 4.1: Update Anime API Endpoints ✅
|
|
||||||
- Task 4.2: Update Download API Endpoints ✅
|
|
||||||
- Task 4.3: Update Queue API Endpoints ✅
|
|
||||||
- Task 4.4: Update WebSocket API Endpoints ✅
|
|
||||||
- Task 4.5: Update Pydantic Models ✅
|
|
||||||
- Task 4.6: Update Validators ✅
|
|
||||||
- Task 4.7: Update Template Helpers ✅
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Phase 5: Frontend
|
### Phase 5: Frontend ✅ (Completed November 28, 2025)
|
||||||
|
|
||||||
#### Task 5.1: Update Frontend JavaScript to Use Key
|
All frontend tasks completed:
|
||||||
|
|
||||||
**File:** [`src/server/web/static/js/app.js`](src/server/web/static/js/app.js)
|
- **Task 5.1: Update Frontend JavaScript** ✅
|
||||||
|
- Updated `app.js` to use `key` as primary series identifier
|
||||||
**Objective:** Update frontend to use `key` as the primary series identifier instead of `folder`.
|
- `selectedSeries` Set now uses `key` instead of `folder`
|
||||||
|
- `createSerieCard()` uses `data-key` attribute for identification
|
||||||
**Steps:**
|
- `toggleSerieSelection()` uses `key` for lookups
|
||||||
|
- All selection and download operations use `key`
|
||||||
1. Open [`src/server/web/static/js/app.js`](src/server/web/static/js/app.js)
|
|
||||||
2. Update `seriesData` storage to index by `key`
|
- **Task 5.2: Update WebSocket Events** ✅
|
||||||
3. Update `selectedSeries` Set to use `key` instead of `folder`
|
- WebSocket service already has proper documentation for `key` usage
|
||||||
4. Update `createSerieCard()`:
|
- Updated tests to include `key` and `folder` in broadcast data
|
||||||
- Use `data-key` attribute instead of `data-folder`
|
- Tests verify both fields are included in messages
|
||||||
- Display `folder` as metadata only
|
|
||||||
5. Update `toggleSerieSelection()` to use `key`
|
- **Task 5.3: Update Additional Frontend JavaScript Files** ✅
|
||||||
6. Update `downloadSelected()`:
|
- Reviewed `queue.js`, `websocket_client.js`, and utility files
|
||||||
- Send `key` as `serie_id`
|
- No changes needed - these files use download item IDs correctly
|
||||||
- Include `folder` for filesystem operations
|
- Series identification is handled in `app.js`
|
||||||
7. Update all event handlers and lookups to use `key`
|
|
||||||
8. Keep `folder` visible in UI for user reference
|
- **Task 5.4: Update HTML Templates** ✅
|
||||||
|
- Reviewed all templates (`index.html`, `queue.html`, etc.)
|
||||||
**Success Criteria:**
|
- No changes needed - series cards are rendered dynamically in JavaScript
|
||||||
|
- Static templates don't contain series data attributes
|
||||||
- [ ] Frontend uses `key` for all series operations
|
|
||||||
- [ ] `folder` displayed in UI but not used for identification
|
|
||||||
- [ ] Selection tracking uses `key`
|
|
||||||
- [ ] All frontend interactions work correctly
|
|
||||||
|
|
||||||
**Manual Test:**
|
|
||||||
|
|
||||||
1. Start server
|
|
||||||
2. Login to web interface
|
|
||||||
3. Verify series list displays correctly
|
|
||||||
4. Test selecting series (should use key internally)
|
|
||||||
5. Test downloading episodes
|
|
||||||
6. Verify search functionality
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Task 5.2: Update WebSocket Events to Use Key
|
|
||||||
|
|
||||||
**File:** [`src/server/services/websocket_service.py`](src/server/services/websocket_service.py)
|
|
||||||
|
|
||||||
**Objective:** Ensure WebSocket events use `key` for series identification.
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
|
|
||||||
1. Open [`src/server/services/websocket_service.py`](src/server/services/websocket_service.py)
|
|
||||||
2. Update `broadcast_download_progress()`:
|
|
||||||
- Include `key` in event data
|
|
||||||
- Keep `folder` for display purposes
|
|
||||||
3. Update `broadcast_scan_progress()` similarly
|
|
||||||
4. Update all event broadcasts to include `key`
|
|
||||||
5. Update event handler subscriptions to use `key`
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
|
|
||||||
- [ ] All WebSocket events include `key`
|
|
||||||
- [ ] Events also include `folder` for display
|
|
||||||
- [ ] All WebSocket tests pass
|
|
||||||
|
|
||||||
**Test Command:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py -v
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Task 5.3: Update Additional Frontend JavaScript Files
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
|
|
||||||
- `src/server/web/static/js/websocket.js`
|
|
||||||
- `src/server/web/static/js/queue.js`
|
|
||||||
- `src/server/web/static/js/download.js`
|
|
||||||
- `src/server/web/static/js/utils.js`
|
|
||||||
|
|
||||||
**Objective:** Ensure all frontend JavaScript modules use `key` for series identification.
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
|
|
||||||
1. **Update `websocket.js`:**
|
|
||||||
|
|
||||||
- Open `src/server/web/static/js/websocket.js`
|
|
||||||
- Review all message handlers
|
|
||||||
- Ensure event payloads use `key` for identification
|
|
||||||
- Update event listeners to use `key`
|
|
||||||
- Keep `folder` for display
|
|
||||||
|
|
||||||
2. **Update `queue.js`:**
|
|
||||||
|
|
||||||
- Open `src/server/web/static/js/queue.js`
|
|
||||||
- Update queue item identification to use `key`
|
|
||||||
- Update queue manipulation functions
|
|
||||||
- Ensure queue display shows both `key` and `folder`
|
|
||||||
|
|
||||||
3. **Update `download.js`:**
|
|
||||||
|
|
||||||
- Open `src/server/web/static/js/download.js`
|
|
||||||
- Update download request building to use `key`
|
|
||||||
- Update progress tracking to use `key`
|
|
||||||
- Keep `folder` for file path operations
|
|
||||||
|
|
||||||
4. **Update `utils.js`:**
|
|
||||||
- Open `src/server/web/static/js/utils.js`
|
|
||||||
- Review utility functions that handle series data
|
|
||||||
- Ensure utilities use `key` for identification
|
|
||||||
- Update any series-related helper functions
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
|
|
||||||
- [ ] All JavaScript modules use `key` for identification
|
|
||||||
- [ ] WebSocket handlers use `key`
|
|
||||||
- [ ] Queue operations use `key`
|
|
||||||
- [ ] Download operations use `key`
|
|
||||||
- [ ] Utilities handle `key` correctly
|
|
||||||
- [ ] `folder` displayed in UI where appropriate
|
|
||||||
- [ ] All frontend functionality works correctly
|
|
||||||
|
|
||||||
**Manual Test:**
|
|
||||||
|
|
||||||
1. Test WebSocket connectivity and events
|
|
||||||
2. Test queue management
|
|
||||||
3. Test download functionality
|
|
||||||
4. Verify all series operations use `key`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Task 5.4: Update HTML Templates to Use Key
|
|
||||||
|
|
||||||
**Files:** All templates in `src/server/web/templates/`
|
|
||||||
|
|
||||||
**Objective:** Ensure all HTML templates use `key` for series identification in data attributes and forms.
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
|
|
||||||
1. Review all template files:
|
|
||||||
|
|
||||||
- `index.html`
|
|
||||||
- `anime_detail.html`
|
|
||||||
- `search.html`
|
|
||||||
- Any other templates using series data
|
|
||||||
|
|
||||||
2. For each template:
|
|
||||||
|
|
||||||
- Update data attributes to use `data-key` instead of `data-folder`
|
|
||||||
- Keep `data-folder` for display purposes if needed
|
|
||||||
- Update form inputs to submit `key` as identifier
|
|
||||||
- Update JavaScript references to use `key`
|
|
||||||
- Display `folder` for user-friendly names
|
|
||||||
|
|
||||||
3. Update template variables:
|
|
||||||
- Ensure templates receive `key` from backend
|
|
||||||
- Verify `folder` is available for display
|
|
||||||
- Update any template logic that filters/sorts by series
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
|
|
||||||
- [ ] All templates use `data-key` for identification
|
|
||||||
- [ ] Forms submit `key` as identifier
|
|
||||||
- [ ] `folder` displayed for user reference
|
|
||||||
- [ ] No templates use `folder` for identification
|
|
||||||
- [ ] All template rendering works correctly
|
|
||||||
|
|
||||||
**Manual Test:**
|
|
||||||
|
|
||||||
1. Verify all pages render correctly
|
|
||||||
2. Test form submissions
|
|
||||||
3. Verify JavaScript interactions
|
|
||||||
4. Check data attributes in browser dev tools
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -704,11 +538,11 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist
|
|||||||
- [x] Phase 2: Core Application Layer ✅
|
- [x] Phase 2: Core Application Layer ✅
|
||||||
- [x] Phase 3: Service Layer ✅
|
- [x] Phase 3: Service Layer ✅
|
||||||
- [x] Phase 4: API Layer ✅ **Completed November 28, 2025**
|
- [x] Phase 4: API Layer ✅ **Completed November 28, 2025**
|
||||||
- [ ] Phase 5: Frontend
|
- [x] Phase 5: Frontend ✅ **Completed November 28, 2025**
|
||||||
- [ ] Task 5.1: Update Frontend JavaScript
|
- [x] Task 5.1: Update Frontend JavaScript
|
||||||
- [ ] Task 5.2: Update WebSocket Events
|
- [x] Task 5.2: Update WebSocket Events
|
||||||
- [ ] Task 5.3: Update Additional Frontend JavaScript Files
|
- [x] Task 5.3: Update Additional Frontend JavaScript Files
|
||||||
- [ ] Task 5.4: Update HTML Templates
|
- [x] Task 5.4: Update HTML Templates
|
||||||
- [ ] Phase 6: Database Layer
|
- [ ] Phase 6: Database Layer
|
||||||
- [ ] Task 6.1: Verify Database Models
|
- [ ] Task 6.1: Verify Database Models
|
||||||
- [ ] Task 6.2: Update Database Services
|
- [ ] Task 6.2: Update Database Services
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
class AniWorldApp {
|
class AniWorldApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.selectedSeries = new Set();
|
this.selectedSeries = new Set(); // Uses 'key' as identifier
|
||||||
this.seriesData = [];
|
this.seriesData = []; // Series objects with 'key' as primary identifier
|
||||||
this.filteredSeriesData = [];
|
this.filteredSeriesData = [];
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.isDownloading = false;
|
this.isDownloading = false;
|
||||||
@ -674,26 +674,27 @@ class AniWorldApp {
|
|||||||
|
|
||||||
grid.innerHTML = dataToRender.map(serie => this.createSerieCard(serie)).join('');
|
grid.innerHTML = dataToRender.map(serie => this.createSerieCard(serie)).join('');
|
||||||
|
|
||||||
// Bind checkbox events
|
// Bind checkbox events - uses 'key' as identifier
|
||||||
grid.querySelectorAll('.series-checkbox').forEach(checkbox => {
|
grid.querySelectorAll('.series-checkbox').forEach(checkbox => {
|
||||||
checkbox.addEventListener('change', (e) => {
|
checkbox.addEventListener('change', (e) => {
|
||||||
this.toggleSerieSelection(e.target.dataset.folder, e.target.checked);
|
this.toggleSerieSelection(e.target.dataset.key, e.target.checked);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createSerieCard(serie) {
|
createSerieCard(serie) {
|
||||||
const isSelected = this.selectedSeries.has(serie.folder);
|
// Use 'key' as the primary identifier for selection and data operations
|
||||||
|
const isSelected = this.selectedSeries.has(serie.key);
|
||||||
const hasMissingEpisodes = serie.missing_episodes > 0;
|
const hasMissingEpisodes = serie.missing_episodes > 0;
|
||||||
const canBeSelected = hasMissingEpisodes; // Only allow selection if has missing episodes
|
const canBeSelected = hasMissingEpisodes; // Only allow selection if has missing episodes
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="series-card ${isSelected ? 'selected' : ''} ${hasMissingEpisodes ? 'has-missing' : 'complete'}"
|
<div class="series-card ${isSelected ? 'selected' : ''} ${hasMissingEpisodes ? 'has-missing' : 'complete'}"
|
||||||
data-folder="${serie.folder}">
|
data-key="${serie.key}" data-folder="${serie.folder}">
|
||||||
<div class="series-card-header">
|
<div class="series-card-header">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
class="series-checkbox"
|
class="series-checkbox"
|
||||||
data-folder="${serie.folder}"
|
data-key="${serie.key}"
|
||||||
${isSelected ? 'checked' : ''}
|
${isSelected ? 'checked' : ''}
|
||||||
${!canBeSelected ? 'disabled' : ''}>
|
${!canBeSelected ? 'disabled' : ''}>
|
||||||
<div class="series-info">
|
<div class="series-info">
|
||||||
@ -718,20 +719,21 @@ class AniWorldApp {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSerieSelection(folder, selected) {
|
toggleSerieSelection(key, selected) {
|
||||||
// Only allow selection of series with missing episodes
|
// Only allow selection of series with missing episodes
|
||||||
const serie = this.seriesData.find(s => s.folder === folder);
|
// Use 'key' as the primary identifier for lookup and selection
|
||||||
|
const serie = this.seriesData.find(s => s.key === key);
|
||||||
if (!serie || serie.missing_episodes === 0) {
|
if (!serie || serie.missing_episodes === 0) {
|
||||||
// Uncheck the checkbox if it was checked for a complete series
|
// Uncheck the checkbox if it was checked for a complete series
|
||||||
const checkbox = document.querySelector(`input[data-folder="${folder}"]`);
|
const checkbox = document.querySelector(`input[data-key="${key}"]`);
|
||||||
if (checkbox) checkbox.checked = false;
|
if (checkbox) checkbox.checked = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
this.selectedSeries.add(folder);
|
this.selectedSeries.add(key);
|
||||||
} else {
|
} else {
|
||||||
this.selectedSeries.delete(folder);
|
this.selectedSeries.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSelectionUI();
|
this.updateSelectionUI();
|
||||||
@ -742,45 +744,47 @@ class AniWorldApp {
|
|||||||
const selectAllBtn = document.getElementById('select-all');
|
const selectAllBtn = document.getElementById('select-all');
|
||||||
|
|
||||||
// Get series that can be selected (have missing episodes)
|
// Get series that can be selected (have missing episodes)
|
||||||
|
// Use 'key' as the primary identifier for selection tracking
|
||||||
const selectableSeriesData = this.filteredSeriesData.length > 0 ? this.filteredSeriesData : this.seriesData;
|
const selectableSeriesData = this.filteredSeriesData.length > 0 ? this.filteredSeriesData : this.seriesData;
|
||||||
const selectableSeries = selectableSeriesData.filter(serie => serie.missing_episodes > 0);
|
const selectableSeries = selectableSeriesData.filter(serie => serie.missing_episodes > 0);
|
||||||
const selectableFolders = selectableSeries.map(serie => serie.folder);
|
const selectableKeys = selectableSeries.map(serie => serie.key);
|
||||||
|
|
||||||
downloadBtn.disabled = this.selectedSeries.size === 0;
|
downloadBtn.disabled = this.selectedSeries.size === 0;
|
||||||
|
|
||||||
const allSelectableSelected = selectableFolders.every(folder => this.selectedSeries.has(folder));
|
const allSelectableSelected = selectableKeys.every(key => this.selectedSeries.has(key));
|
||||||
|
|
||||||
if (this.selectedSeries.size === 0) {
|
if (this.selectedSeries.size === 0) {
|
||||||
selectAllBtn.innerHTML = '<i class="fas fa-check-double"></i><span>Select All</span>';
|
selectAllBtn.innerHTML = '<i class="fas fa-check-double"></i><span>Select All</span>';
|
||||||
} else if (allSelectableSelected && selectableFolders.length > 0) {
|
} else if (allSelectableSelected && selectableKeys.length > 0) {
|
||||||
selectAllBtn.innerHTML = '<i class="fas fa-times"></i><span>Deselect All</span>';
|
selectAllBtn.innerHTML = '<i class="fas fa-times"></i><span>Deselect All</span>';
|
||||||
} else {
|
} else {
|
||||||
selectAllBtn.innerHTML = '<i class="fas fa-check-double"></i><span>Select All</span>';
|
selectAllBtn.innerHTML = '<i class="fas fa-check-double"></i><span>Select All</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update card appearances
|
// Update card appearances using 'key' as identifier
|
||||||
document.querySelectorAll('.series-card').forEach(card => {
|
document.querySelectorAll('.series-card').forEach(card => {
|
||||||
const folder = card.dataset.folder;
|
const key = card.dataset.key;
|
||||||
const isSelected = this.selectedSeries.has(folder);
|
const isSelected = this.selectedSeries.has(key);
|
||||||
card.classList.toggle('selected', isSelected);
|
card.classList.toggle('selected', isSelected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSelectAll() {
|
toggleSelectAll() {
|
||||||
// Get series that can be selected (have missing episodes)
|
// Get series that can be selected (have missing episodes)
|
||||||
|
// Use 'key' as the primary identifier for selection
|
||||||
const selectableSeriesData = this.filteredSeriesData.length > 0 ? this.filteredSeriesData : this.seriesData;
|
const selectableSeriesData = this.filteredSeriesData.length > 0 ? this.filteredSeriesData : this.seriesData;
|
||||||
const selectableSeries = selectableSeriesData.filter(serie => serie.missing_episodes > 0);
|
const selectableSeries = selectableSeriesData.filter(serie => serie.missing_episodes > 0);
|
||||||
const selectableFolders = selectableSeries.map(serie => serie.folder);
|
const selectableKeys = selectableSeries.map(serie => serie.key);
|
||||||
|
|
||||||
const allSelectableSelected = selectableFolders.every(folder => this.selectedSeries.has(folder));
|
const allSelectableSelected = selectableKeys.every(key => this.selectedSeries.has(key));
|
||||||
|
|
||||||
if (allSelectableSelected && this.selectedSeries.size > 0) {
|
if (allSelectableSelected && this.selectedSeries.size > 0) {
|
||||||
// Deselect all selectable series
|
// Deselect all selectable series
|
||||||
selectableFolders.forEach(folder => this.selectedSeries.delete(folder));
|
selectableKeys.forEach(key => this.selectedSeries.delete(key));
|
||||||
document.querySelectorAll('.series-checkbox:not([disabled])').forEach(cb => cb.checked = false);
|
document.querySelectorAll('.series-checkbox:not([disabled])').forEach(cb => cb.checked = false);
|
||||||
} else {
|
} else {
|
||||||
// Select all selectable series
|
// Select all selectable series
|
||||||
selectableFolders.forEach(folder => this.selectedSeries.add(folder));
|
selectableKeys.forEach(key => this.selectedSeries.add(key));
|
||||||
document.querySelectorAll('.series-checkbox:not([disabled])').forEach(cb => cb.checked = true);
|
document.querySelectorAll('.series-checkbox:not([disabled])').forEach(cb => cb.checked = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,33 +891,35 @@ class AniWorldApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async downloadSelected() {
|
async downloadSelected() {
|
||||||
console.log('=== downloadSelected v1.1 - DEBUG VERSION ===');
|
console.log('=== downloadSelected v1.2 - Using key as primary identifier ===');
|
||||||
if (this.selectedSeries.size === 0) {
|
if (this.selectedSeries.size === 0) {
|
||||||
this.showToast('No series selected', 'warning');
|
this.showToast('No series selected', 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const folders = Array.from(this.selectedSeries);
|
// selectedSeries now contains 'key' values (not folder)
|
||||||
|
const selectedKeys = Array.from(this.selectedSeries);
|
||||||
console.log('=== Starting download for selected series ===');
|
console.log('=== Starting download for selected series ===');
|
||||||
console.log('Selected folders:', folders);
|
console.log('Selected keys:', selectedKeys);
|
||||||
console.log('seriesData:', this.seriesData);
|
console.log('seriesData:', this.seriesData);
|
||||||
let totalEpisodesAdded = 0;
|
let totalEpisodesAdded = 0;
|
||||||
let failedSeries = [];
|
let failedSeries = [];
|
||||||
|
|
||||||
// For each selected series, get its missing episodes and add to queue
|
// For each selected series, get its missing episodes and add to queue
|
||||||
for (const folder of folders) {
|
// Use 'key' to find the series in seriesData
|
||||||
const serie = this.seriesData.find(s => s.folder === folder);
|
for (const key of selectedKeys) {
|
||||||
|
const serie = this.seriesData.find(s => s.key === key);
|
||||||
if (!serie || !serie.episodeDict) {
|
if (!serie || !serie.episodeDict) {
|
||||||
console.error('Serie not found or has no episodeDict:', folder, serie);
|
console.error('Serie not found or has no episodeDict for key:', key, serie);
|
||||||
failedSeries.push(folder);
|
failedSeries.push(key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!serie.key) {
|
if (!serie.key) {
|
||||||
console.error('Serie missing key:', serie);
|
console.error('Serie missing key:', serie);
|
||||||
failedSeries.push(folder);
|
failedSeries.push(key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,7 +963,7 @@ class AniWorldApp {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
failedSeries.push(folder);
|
failedSeries.push(key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -973,14 +979,14 @@ class AniWorldApp {
|
|||||||
totalEpisodesAdded += episodes.length;
|
totalEpisodesAdded += episodes.length;
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to add to queue:', data);
|
console.error('Failed to add to queue:', data);
|
||||||
failedSeries.push(folder);
|
failedSeries.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show result message
|
// Show result message
|
||||||
console.log('=== Download request complete ===');
|
console.log('=== Download request complete ===');
|
||||||
console.log('Total episodes added:', totalEpisodesAdded);
|
console.log('Total episodes added:', totalEpisodesAdded);
|
||||||
console.log('Failed series:', failedSeries);
|
console.log('Failed series (keys):', failedSeries);
|
||||||
|
|
||||||
if (totalEpisodesAdded > 0) {
|
if (totalEpisodesAdded > 0) {
|
||||||
const message = failedSeries.length > 0
|
const message = failedSeries.length > 0
|
||||||
@ -989,7 +995,7 @@ class AniWorldApp {
|
|||||||
this.showToast(message, 'success');
|
this.showToast(message, 'success');
|
||||||
} else {
|
} else {
|
||||||
const errorDetails = failedSeries.length > 0
|
const errorDetails = failedSeries.length > 0
|
||||||
? `Failed series: ${failedSeries.join(', ')}`
|
? `Failed series (keys): ${failedSeries.join(', ')}`
|
||||||
: 'No episodes were added. Check browser console for details.';
|
: 'No episodes were added. Check browser console for details.';
|
||||||
console.error('Failed to add episodes. Details:', errorDetails);
|
console.error('Failed to add episodes. Details:', errorDetails);
|
||||||
this.showToast('Failed to add episodes to queue. Check console for details.', 'error');
|
this.showToast('Failed to add episodes to queue. Check console for details.', 'error');
|
||||||
|
|||||||
@ -307,10 +307,16 @@ class TestWebSocketService:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_broadcast_download_progress(self, service, mock_websocket):
|
async def test_broadcast_download_progress(self, service, mock_websocket):
|
||||||
"""Test broadcasting download progress."""
|
"""Test broadcasting download progress.
|
||||||
|
|
||||||
|
Verifies that progress data includes 'key' as the primary series
|
||||||
|
identifier and 'folder' for display purposes only.
|
||||||
|
"""
|
||||||
connection_id = "test-conn"
|
connection_id = "test-conn"
|
||||||
download_id = "download123"
|
download_id = "download123"
|
||||||
progress_data = {
|
progress_data = {
|
||||||
|
"key": "attack-on-titan",
|
||||||
|
"folder": "Attack on Titan (2013)",
|
||||||
"percent": 50.0,
|
"percent": 50.0,
|
||||||
"speed_mbps": 2.5,
|
"speed_mbps": 2.5,
|
||||||
"eta_seconds": 120,
|
"eta_seconds": 120,
|
||||||
@ -325,14 +331,24 @@ class TestWebSocketService:
|
|||||||
call_args = mock_websocket.send_json.call_args[0][0]
|
call_args = mock_websocket.send_json.call_args[0][0]
|
||||||
assert call_args["type"] == "download_progress"
|
assert call_args["type"] == "download_progress"
|
||||||
assert call_args["data"]["download_id"] == download_id
|
assert call_args["data"]["download_id"] == download_id
|
||||||
|
assert call_args["data"]["key"] == "attack-on-titan"
|
||||||
|
assert call_args["data"]["folder"] == "Attack on Titan (2013)"
|
||||||
assert call_args["data"]["percent"] == 50.0
|
assert call_args["data"]["percent"] == 50.0
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_broadcast_download_complete(self, service, mock_websocket):
|
async def test_broadcast_download_complete(self, service, mock_websocket):
|
||||||
"""Test broadcasting download completion."""
|
"""Test broadcasting download completion.
|
||||||
|
|
||||||
|
Verifies that result data includes 'key' as the primary series
|
||||||
|
identifier and 'folder' for display purposes only.
|
||||||
|
"""
|
||||||
connection_id = "test-conn"
|
connection_id = "test-conn"
|
||||||
download_id = "download123"
|
download_id = "download123"
|
||||||
result_data = {"file_path": "/path/to/file.mp4"}
|
result_data = {
|
||||||
|
"key": "attack-on-titan",
|
||||||
|
"folder": "Attack on Titan (2013)",
|
||||||
|
"file_path": "/path/to/file.mp4"
|
||||||
|
}
|
||||||
|
|
||||||
await service.connect(mock_websocket, connection_id)
|
await service.connect(mock_websocket, connection_id)
|
||||||
await service._manager.join_room(connection_id, "downloads")
|
await service._manager.join_room(connection_id, "downloads")
|
||||||
@ -342,13 +358,23 @@ class TestWebSocketService:
|
|||||||
call_args = mock_websocket.send_json.call_args[0][0]
|
call_args = mock_websocket.send_json.call_args[0][0]
|
||||||
assert call_args["type"] == "download_complete"
|
assert call_args["type"] == "download_complete"
|
||||||
assert call_args["data"]["download_id"] == download_id
|
assert call_args["data"]["download_id"] == download_id
|
||||||
|
assert call_args["data"]["key"] == "attack-on-titan"
|
||||||
|
assert call_args["data"]["folder"] == "Attack on Titan (2013)"
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_broadcast_download_failed(self, service, mock_websocket):
|
async def test_broadcast_download_failed(self, service, mock_websocket):
|
||||||
"""Test broadcasting download failure."""
|
"""Test broadcasting download failure.
|
||||||
|
|
||||||
|
Verifies that error data includes 'key' as the primary series
|
||||||
|
identifier and 'folder' for display purposes only.
|
||||||
|
"""
|
||||||
connection_id = "test-conn"
|
connection_id = "test-conn"
|
||||||
download_id = "download123"
|
download_id = "download123"
|
||||||
error_data = {"error_message": "Network error"}
|
error_data = {
|
||||||
|
"key": "attack-on-titan",
|
||||||
|
"folder": "Attack on Titan (2013)",
|
||||||
|
"error_message": "Network error"
|
||||||
|
}
|
||||||
|
|
||||||
await service.connect(mock_websocket, connection_id)
|
await service.connect(mock_websocket, connection_id)
|
||||||
await service._manager.join_room(connection_id, "downloads")
|
await service._manager.join_room(connection_id, "downloads")
|
||||||
@ -358,6 +384,8 @@ class TestWebSocketService:
|
|||||||
call_args = mock_websocket.send_json.call_args[0][0]
|
call_args = mock_websocket.send_json.call_args[0][0]
|
||||||
assert call_args["type"] == "download_failed"
|
assert call_args["type"] == "download_failed"
|
||||||
assert call_args["data"]["download_id"] == download_id
|
assert call_args["data"]["download_id"] == download_id
|
||||||
|
assert call_args["data"]["key"] == "attack-on-titan"
|
||||||
|
assert call_args["data"]["folder"] == "Attack on Titan (2013)"
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_broadcast_queue_status(self, service, mock_websocket):
|
async def test_broadcast_queue_status(self, service, mock_websocket):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user