Compare commits
5 Commits
7286b9b3e8
...
423b77033c
| Author | SHA1 | Date | |
|---|---|---|---|
| 423b77033c | |||
| b73210a3c9 | |||
| b2d77a099b | |||
| f9102d7bcd | |||
| 7cc0d7c7a5 |
5
.vscode/launch.json
vendored
5
.vscode/launch.json
vendored
@ -9,12 +9,13 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"FLASK_APP": "app.py",
|
"FLASK_APP": "app.py",
|
||||||
"FLASK_ENV": "development",
|
"FLASK_ENV": "development",
|
||||||
"PYTHONPATH": "${workspaceFolder}"
|
"PYTHONPATH": "${workspaceFolder}/src;${workspaceFolder}"
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
"jinja": true,
|
"jinja": true,
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"cwd": "${workspaceFolder}/src/server"
|
"cwd": "${workspaceFolder}/src",
|
||||||
|
"python": "C:/Users/lukas/anaconda3/envs/AniWorld/python.exe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Python: CLI Tool",
|
"name": "Python: CLI Tool",
|
||||||
|
|||||||
0
errors.log
Normal file
0
errors.log
Normal file
0
noGerFound.log
Normal file
0
noGerFound.log
Normal file
49
src/config.json
Normal file
49
src/config.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"security": {
|
||||||
|
"master_password_hash": "1353f6d9db7090c302864c2d6437dc11cc96cd66d59d7737d1b345603fdbdfda",
|
||||||
|
"salt": "a25e23440d681cef2d75c0adb6de0913359a1d8b9f98f9747fc75f53c79c4bd4",
|
||||||
|
"session_timeout_hours": 24,
|
||||||
|
"max_failed_attempts": 5,
|
||||||
|
"lockout_duration_minutes": 30
|
||||||
|
},
|
||||||
|
"anime": {
|
||||||
|
"directory": "\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien",
|
||||||
|
"download_threads": 3,
|
||||||
|
"download_speed_limit": null,
|
||||||
|
"auto_rescan_time": "03:00",
|
||||||
|
"auto_download_after_rescan": false
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"level": "INFO",
|
||||||
|
"enable_console_logging": true,
|
||||||
|
"enable_console_progress": false,
|
||||||
|
"enable_fail2ban_logging": true,
|
||||||
|
"log_file": "aniworld.log",
|
||||||
|
"max_log_size_mb": 10,
|
||||||
|
"log_backup_count": 5
|
||||||
|
},
|
||||||
|
"providers": {
|
||||||
|
"default_provider": "aniworld.to",
|
||||||
|
"preferred_language": "German Dub",
|
||||||
|
"fallback_providers": [
|
||||||
|
"aniworld.to"
|
||||||
|
],
|
||||||
|
"provider_timeout": 30,
|
||||||
|
"retry_attempts": 3,
|
||||||
|
"provider_settings": {
|
||||||
|
"aniworld.to": {
|
||||||
|
"enabled": true,
|
||||||
|
"priority": 1,
|
||||||
|
"quality_preference": "720p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"max_concurrent_downloads": 3,
|
||||||
|
"download_buffer_size": 8192,
|
||||||
|
"connection_timeout": 30,
|
||||||
|
"read_timeout": 300,
|
||||||
|
"enable_debug_mode": false,
|
||||||
|
"cache_duration_minutes": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
464
src/logs/aniworld.log
Normal file
464
src/logs/aniworld.log
Normal file
@ -0,0 +1,464 @@
|
|||||||
|
2025-09-29 12:38:25 - INFO - __main__ - <module> - Enhanced logging system initialized
|
||||||
|
2025-09-29 12:38:25 - INFO - __main__ - <module> - Starting Aniworld Flask server...
|
||||||
|
2025-09-29 12:38:25 - INFO - __main__ - <module> - Anime directory: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien
|
||||||
|
2025-09-29 12:38:25 - INFO - __main__ - <module> - Log level: INFO
|
||||||
|
2025-09-29 12:38:25 - INFO - __main__ - <module> - Scheduled operations disabled
|
||||||
|
2025-09-29 12:38:25 - INFO - __main__ - <module> - Server will be available at http://localhost:5000
|
||||||
|
2025-09-29 12:38:30 - INFO - __main__ - <module> - Enhanced logging system initialized
|
||||||
|
2025-09-29 12:38:30 - INFO - __main__ - <module> - Starting Aniworld Flask server...
|
||||||
|
2025-09-29 12:38:30 - INFO - __main__ - <module> - Anime directory: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien
|
||||||
|
2025-09-29 12:38:30 - INFO - __main__ - <module> - Log level: INFO
|
||||||
|
2025-09-29 12:38:30 - INFO - __main__ - <module> - Scheduled operations disabled
|
||||||
|
2025-09-29 12:38:30 - INFO - __main__ - <module> - Server will be available at http://localhost:5000
|
||||||
|
2025-09-29 12:38:30 - WARNING - werkzeug - _log - * Debugger is active!
|
||||||
|
2025-09-29 12:38:40 - INFO - root - __init__ - Initialized Loader with base path: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Scanning anime folders in: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien
|
||||||
|
2025-09-29 12:38:40 - WARNING - root - load_series - Skipping .deletedByTMM - No data folder found
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\2.5 Dimensional Seduction (2024)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\2.5 Dimensional Seduction (2024)\data for 2.5 Dimensional Seduction (2024)
|
||||||
|
2025-09-29 12:38:40 - WARNING - root - load_series - Skipping 25-dimensional-seduction - No data folder found
|
||||||
|
2025-09-29 12:38:40 - WARNING - root - load_series - Skipping 25-sai no Joshikousei (2018) - No data folder found
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\7th Time Loop The Villainess Enjoys a Carefree Life Married to Her Worst Enemy! (2024)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\7th Time Loop The Villainess Enjoys a Carefree Life Married to Her Worst Enemy! (2024)\data for 7th Time Loop The Villainess Enjoys a Carefree Life Married to Her Worst Enemy! (2024)
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\9-nine-rulers-crown\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\9-nine-rulers-crown\data for 9-nine-rulers-crown
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\A Couple of Cuckoos (2022)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\A Couple of Cuckoos (2022)\data for A Couple of Cuckoos (2022)
|
||||||
|
2025-09-29 12:38:40 - WARNING - root - load_series - Skipping A Time Called You (2023) - No data folder found
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\A.I.C.O. Incarnation (2018)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\A.I.C.O. Incarnation (2018)\data for A.I.C.O. Incarnation (2018)
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Aesthetica of a Rogue Hero (2012)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Aesthetica of a Rogue Hero (2012)\data for Aesthetica of a Rogue Hero (2012)
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Alya Sometimes Hides Her Feelings in Russian (2024)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Alya Sometimes Hides Her Feelings in Russian (2024)\data for Alya Sometimes Hides Her Feelings in Russian (2024)
|
||||||
|
2025-09-29 12:38:40 - WARNING - root - load_series - Skipping American Horror Story (2011) - No data folder found
|
||||||
|
2025-09-29 12:38:40 - WARNING - root - load_series - Skipping Andor (2022) - No data folder found
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Angels of Death (2018)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Angels of Death (2018)\data for Angels of Death (2018)
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Aokana Four Rhythm Across the Blue (2016)\data
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Aokana Four Rhythm Across the Blue (2016)\data for Aokana Four Rhythm Across the Blue (2016)
|
||||||
|
2025-09-29 12:38:40 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Arifureta (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Arifureta (2019)\data for Arifureta (2019)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\As a Reincarnated Aristocrat, I'll Use My Appraisal Skill to Rise in the World (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\As a Reincarnated Aristocrat, I'll Use My Appraisal Skill to Rise in the World (2024)\data for As a Reincarnated Aristocrat, I'll Use My Appraisal Skill to Rise in the World (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\BOFURI I Don't Want to Get Hurt, so I'll Max Out My Defense. (2020)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\BOFURI I Don't Want to Get Hurt, so I'll Max Out My Defense. (2020)\data for BOFURI I Don't Want to Get Hurt, so I'll Max Out My Defense. (2020)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Black Butler (2008)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Black Butler (2008)\data for Black Butler (2008)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Black Clover (2017)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Black Clover (2017)\data for Black Clover (2017)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blast of Tempest (2012)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blast of Tempest (2012)\data for Blast of Tempest (2012)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blood Lad (2013)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blood Lad (2013)\data for Blood Lad (2013)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blue Box (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blue Box (2024)\data for Blue Box (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blue Exorcist (2011)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Blue Exorcist (2011)\data for Blue Exorcist (2011)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Bogus Skill Fruitmaster About That Time I Became Able to Eat Unlimited Numbers of Skill Fruits (That Kill You) (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Bogus Skill Fruitmaster About That Time I Became Able to Eat Unlimited Numbers of Skill Fruits (That Kill You) (2025)\data for Bogus Skill Fruitmaster About That Time I Became Able to Eat Unlimited Numbers of Skill Fruits (That Kill You) (2025)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Boys Over Flowers (2009) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Burst Angel (2004)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Burst Angel (2004)\data for Burst Angel (2004)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\By the Grace of the Gods (2020)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\By the Grace of the Gods (2020)\data for By the Grace of the Gods (2020)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Call of the Night (2022)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Call of the Night (2022)\data for Call of the Night (2022)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Campfire Cooking in Another World with My Absurd Skill (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Campfire Cooking in Another World with My Absurd Skill (2023)\data for Campfire Cooking in Another World with My Absurd Skill (2023)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Celebrity (2023) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Chainsaw Man (2022)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Chainsaw Man (2022)\data for Chainsaw Man (2022)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Charlotte (2015)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Charlotte (2015)\data for Charlotte (2015)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Cherish the Day (2020) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Chernobyl (2019) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Chillin’ in Another World with Level 2 Super Cheat Powers (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Chillin’ in Another World with Level 2 Super Cheat Powers (2024)\data for Chillin’ in Another World with Level 2 Super Cheat Powers (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Clannad (2007)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Clannad (2007)\data for Clannad (2007)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Classroom of the Elite (2017)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Classroom of the Elite (2017)\data for Classroom of the Elite (2017)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Clevatess (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Clevatess (2025)\data for Clevatess (2025)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\DAN DA DAN (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\DAN DA DAN (2024)\data for DAN DA DAN (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Danmachi Is It Wrong to Try to Pick Up Girls in a Dungeon (2015)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Danmachi Is It Wrong to Try to Pick Up Girls in a Dungeon (2015)\data for Danmachi Is It Wrong to Try to Pick Up Girls in a Dungeon (2015)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Das Buch von Boba Fett (2021) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Date a Live (2013)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Date a Live (2013)\data for Date a Live (2013)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dead Mount Death Play (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dead Mount Death Play (2023)\data for Dead Mount Death Play (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Deadman Wonderland (2011)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Deadman Wonderland (2011)\data for Deadman Wonderland (2011)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dealing with Mikadono Sisters Is a Breeze (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dealing with Mikadono Sisters Is a Breeze (2025)\data for Dealing with Mikadono Sisters Is a Breeze (2025)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Delicious in Dungeon (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Delicious in Dungeon (2024)\data for Delicious in Dungeon (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Demon Lord, Retry! (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Demon Lord, Retry! (2019)\data for Demon Lord, Retry! (2019)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Demon Slave - The Chained Soldier (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Demon Slave - The Chained Soldier (2024)\data for Demon Slave - The Chained Soldier (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Demon Slayer Kimetsu no Yaiba (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Demon Slayer Kimetsu no Yaiba (2019)\data for Demon Slayer Kimetsu no Yaiba (2019)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Der Herr der Ringe Die Ringe der Macht (2022) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Devil in Ohio (2022) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Die Bibel (2013) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Die Tagebücher der Apothekerin (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Die Tagebücher der Apothekerin (2023)\data for Die Tagebücher der Apothekerin (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Domestic Girlfriend (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Domestic Girlfriend (2019)\data for Domestic Girlfriend (2019)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Doona! (2023) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dr. STONE (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dr. STONE (2019)\data for Dr. STONE (2019)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dragonball Super (2015)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Dragonball Super (2015)\data for Dragonball Super (2015)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Failure Frame I Became the Strongest and Annihilated Everything With Low-Level Spells (2024) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Fallout (2024) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Farming Life in Another World (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Farming Life in Another World (2023)\data for Farming Life in Another World (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Frieren - Nach dem Ende der Reise (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Frieren - Nach dem Ende der Reise (2023)\data for Frieren - Nach dem Ende der Reise (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Fruits Basket (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Fruits Basket (2019)\data for Fruits Basket (2019)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Gachiakuta (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Gachiakuta (2025)\data for Gachiakuta (2025)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Gate (2015)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Gate (2015)\data for Gate (2015)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Generation der Verdammten (2014) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Girls und Panzer (2012)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Girls und Panzer (2012)\data for Girls und Panzer (2012)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Gleipnir (2020)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Gleipnir (2020)\data for Gleipnir (2020)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Golden Time (2013)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Golden Time (2013)\data for Golden Time (2013)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Grimgar, Ashes and Illusions (2016)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Grimgar, Ashes and Illusions (2016)\data for Grimgar, Ashes and Illusions (2016)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Harem in the Labyrinth of Another World (2022)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Harem in the Labyrinth of Another World (2022)\data for Harem in the Labyrinth of Another World (2022)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Highschool D×D (2012) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Hinamatsuri (2018)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Hinamatsuri (2018)\data for Hinamatsuri (2018)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I Got a Cheat Skill in Another World and Became Unrivaled in The Real World Too (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I Got a Cheat Skill in Another World and Became Unrivaled in The Real World Too (2023)\data for I Got a Cheat Skill in Another World and Became Unrivaled in The Real World Too (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I Parry Everything What Do You Mean I’m the Strongest I’m Not Even an Adventurer Yet! (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I Parry Everything What Do You Mean I’m the Strongest I’m Not Even an Adventurer Yet! (2024)\data for I Parry Everything What Do You Mean I’m the Strongest I’m Not Even an Adventurer Yet! (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I'm the Evil Lord of an Intergalactic Empire! (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I'm the Evil Lord of an Intergalactic Empire! (2025)\data for I'm the Evil Lord of an Intergalactic Empire! (2025)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I've Been Killing Slimes for 300 Years and Maxed Out My Level (2021)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I've Been Killing Slimes for 300 Years and Maxed Out My Level (2021)\data for I've Been Killing Slimes for 300 Years and Maxed Out My Level (2021)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\In the Land of Leadale (2022)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\In the Land of Leadale (2022)\data for In the Land of Leadale (2022)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Ishura (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Ishura (2024)\data for Ishura (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I’ll Become a Villainess Who Goes Down in History (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\I’ll Become a Villainess Who Goes Down in History (2024)\data for I’ll Become a Villainess Who Goes Down in History (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\JUJUTSU KAISEN (2020)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\JUJUTSU KAISEN (2020)\data for JUJUTSU KAISEN (2020)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kaguya-sama Love is War (2019)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kaguya-sama Love is War (2019)\data for Kaguya-sama Love is War (2019)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kaiju No. 8 (20200)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kaiju No. 8 (20200)\data for Kaiju No. 8 (20200)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\KamiKatsu Meine Arbeit als Missionar in einer gottlosen Welt (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\KamiKatsu Meine Arbeit als Missionar in einer gottlosen Welt (2023)\data for KamiKatsu Meine Arbeit als Missionar in einer gottlosen Welt (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Knight's & Magic (2017)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Knight's & Magic (2017)\data for Knight's & Magic (2017)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kombattanten werden entsandt! (2021)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kombattanten werden entsandt! (2021)\data for Kombattanten werden entsandt! (2021)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\KonoSuba – An Explosion on This Wonderful World! (2023)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\KonoSuba – An Explosion on This Wonderful World! (2023)\data for KonoSuba – An Explosion on This Wonderful World! (2023)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Konosuba God's Blessing on This Wonderful World! (2016)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Konosuba God's Blessing on This Wonderful World! (2016)\data for Konosuba God's Blessing on This Wonderful World! (2016)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Krieg der Welten (2019) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kuma Kuma Kuma Bear (2020)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Kuma Kuma Kuma Bear (2020)\data for Kuma Kuma Kuma Bear (2020)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Log Horizon (2013)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Log Horizon (2013)\data for Log Horizon (2013)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Loki (2021) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Loner Life in Another World (2024)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Loner Life in Another World (2024)\data for Loner Life in Another World (2024)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Lord of Mysteries (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Lord of Mysteries (2025)\data for Lord of Mysteries (2025)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Lycoris Recoil (2022)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Lycoris Recoil (2022)\data for Lycoris Recoil (2022)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Magic Maker How to Make Magic in Another World (2025)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Magic Maker How to Make Magic in Another World (2025)\data for Magic Maker How to Make Magic in Another World (2025)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Magical Girl Site (2018)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Magical Girl Site (2018)\data for Magical Girl Site (2018)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Management of a Novice Alchemist (2022)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Management of a Novice Alchemist (2022)\data for Management of a Novice Alchemist (2022)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Marianne (2019) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Meine Wiedergeburt als Schleim in einer anderen Welt (2018)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Meine Wiedergeburt als Schleim in einer anderen Welt (2018)\data for Meine Wiedergeburt als Schleim in einer anderen Welt (2018)
|
||||||
|
2025-09-29 12:38:41 - WARNING - root - load_series - Skipping Midnight Mass (2021) - No data folder found
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Mirai Nikki (2011)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Mirai Nikki (2011)\data for Mirai Nikki (2011)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Miss Kobayashi's Dragon Maid (2017)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Miss Kobayashi's Dragon Maid (2017)\data for Miss Kobayashi's Dragon Maid (2017)
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Mob Psycho 100 (2016)\data
|
||||||
|
2025-09-29 12:38:41 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Mob Psycho 100 (2016)\data for Mob Psycho 100 (2016)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\More than a Married Couple, but Not Lovers (2022)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\More than a Married Couple, but Not Lovers (2022)\data for More than a Married Couple, but Not Lovers (2022)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Mushoku Tensei Jobless Reincarnation (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Mushoku Tensei Jobless Reincarnation (2021)\data for Mushoku Tensei Jobless Reincarnation (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Hero Academia Vigilantes (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Hero Academia Vigilantes (2025)\data for My Hero Academia Vigilantes (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Instant Death Ability Is So Overpowered, No One in This Other World Stands a Chance Against Me! (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Instant Death Ability Is So Overpowered, No One in This Other World Stands a Chance Against Me! (2024)\data for My Instant Death Ability Is So Overpowered, No One in This Other World Stands a Chance Against Me! (2024)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Isekai Life (2022)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Isekai Life (2022)\data for My Isekai Life (2022)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Life as Inukai-san's Dog (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Life as Inukai-san's Dog (2023)\data for My Life as Inukai-san's Dog (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Unique Skill Makes Me OP even at Level 1 (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\My Unique Skill Makes Me OP even at Level 1 (2023)\data for My Unique Skill Makes Me OP even at Level 1 (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\New Saga (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\New Saga (2025)\data for New Saga (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Nina the Starry Bride (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Nina the Starry Bride (2024)\data for Nina the Starry Bride (2024)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Nisekoi Liebe, Lügen & Yakuza (2014)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Nisekoi Liebe, Lügen & Yakuza (2014)\data for Nisekoi Liebe, Lügen & Yakuza (2014)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\No Game No Life (2014)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\No Game No Life (2014)\data for No Game No Life (2014)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Obi-Wan Kenobi (2022) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Orange (2016)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Orange (2016)\data for Orange (2016)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Peach Boy Riverside (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Peach Boy Riverside (2021)\data for Peach Boy Riverside (2021)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Penny Dreadful (2014) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Planet Erde II Eine Erde - viele Welten (2016) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Plastic Memories (2015)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Plastic Memories (2015)\data for Plastic Memories (2015)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Ragna Crimson (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Ragna Crimson (2023)\data for Ragna Crimson (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Rascal Does Not Dream of Bunny Girl Senpai (2018)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Rascal Does Not Dream of Bunny Girl Senpai (2018)\data for Rascal Does Not Dream of Bunny Girl Senpai (2018)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\ReMonster (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\ReMonster (2024)\data for ReMonster (2024)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\ReZERO - Starting Life in Another World (2016)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\ReZERO - Starting Life in Another World (2016)\data for ReZERO - Starting Life in Another World (2016)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Reborn as a Vending Machine, I Now Wander the Dungeon (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Reborn as a Vending Machine, I Now Wander the Dungeon (2023)\data for Reborn as a Vending Machine, I Now Wander the Dungeon (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Redo of Healer (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Redo of Healer (2021)\data for Redo of Healer (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Rick and Morty (2013)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Rick and Morty (2013)\data for Rick and Morty (2013)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Rocket & Groot (2017) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Romulus (2020) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Saga of Tanya the Evil (2017)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Saga of Tanya the Evil (2017)\data for Saga of Tanya the Evil (2017)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Seirei Gensouki Spirit Chronicles (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Seirei Gensouki Spirit Chronicles (2021)\data for Seirei Gensouki Spirit Chronicles (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Shangri-La Frontier (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Shangri-La Frontier (2023)\data for Shangri-La Frontier (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\She Professed Herself Pupil of the Wise Man (2022)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\She Professed Herself Pupil of the Wise Man (2022)\data for She Professed Herself Pupil of the Wise Man (2022)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping She-Hulk Die Anwältin (2022) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Solo Leveling (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Solo Leveling (2024)\data for Solo Leveling (2024)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Spice and Wolf (2008)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Spice and Wolf (2008)\data for Spice and Wolf (2008)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Star Trek Discovery (2017) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Stargate (1997) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Stargate Atlantis (2004) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Steins;Gate (2011)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Steins;Gate (2011)\data for Steins;Gate (2011)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Sweet Tooth (2021) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Sword of the Demon Hunter Kijin Gen (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Sword of the Demon Hunter Kijin Gen (2025)\data for Sword of the Demon Hunter Kijin Gen (2025)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Tales from the Loop (2020) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tamako Market (2013)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tamako Market (2013)\data for Tamako Market (2013)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Ancient Magus' Bride (2017)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Ancient Magus' Bride (2017)\data for The Ancient Magus' Bride (2017)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Demon Sword Master of Excalibur Academy (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Demon Sword Master of Excalibur Academy (2023)\data for The Demon Sword Master of Excalibur Academy (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Devil is a Part-Timer! (2013)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Devil is a Part-Timer! (2013)\data for The Devil is a Part-Timer! (2013)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Dreaming Boy is a Realist (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Dreaming Boy is a Realist (2023)\data for The Dreaming Boy is a Realist (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Dungeon of Black Company (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Dungeon of Black Company (2021)\data for The Dungeon of Black Company (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Eminence in Shadow (2022)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Eminence in Shadow (2022)\data for The Eminence in Shadow (2022)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Familiar of Zero (2006)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Familiar of Zero (2006)\data for The Familiar of Zero (2006)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Faraway Paladin (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Faraway Paladin (2021)\data for The Faraway Paladin (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Gorilla God’s Go-To Girl (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Gorilla God’s Go-To Girl (2025)\data for The Gorilla God’s Go-To Girl (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Hidden Dungeon Only I Can Enter (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Hidden Dungeon Only I Can Enter (2021)\data for The Hidden Dungeon Only I Can Enter (2021)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping The Last of Us (2023) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping The Man in the High Castle (2015) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping The Mandalorian (2019) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Quintessential Quintuplets (2019)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Quintessential Quintuplets (2019)\data for The Quintessential Quintuplets (2019)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Saint’s Magic Power is Omnipotent (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Saint’s Magic Power is Omnipotent (2021)\data for The Saint’s Magic Power is Omnipotent (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Too-Perfect Saint Tossed Aside by My Fiance and Sold to Another Kingdom (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Too-Perfect Saint Tossed Aside by My Fiance and Sold to Another Kingdom (2025)\data for The Too-Perfect Saint Tossed Aside by My Fiance and Sold to Another Kingdom (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Unaware Atelier Meister (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Unaware Atelier Meister (2025)\data for The Unaware Atelier Meister (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Weakest Tamer Began a Journey to Pick Up Trash (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\The Weakest Tamer Began a Journey to Pick Up Trash (2024)\data for The Weakest Tamer Began a Journey to Pick Up Trash (2024)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping The Witcher (2019) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping The World's Finest Assassin Gets Reincarnated in Another World as an Aristocrat (2021) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\To Your Eternity (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\To Your Eternity (2021)\data for To Your Eternity (2021)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tomo-chan Is a Girl! (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tomo-chan Is a Girl! (2023)\data for Tomo-chan Is a Girl! (2023)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tonikawa Over the Moon for You (2020)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tonikawa Over the Moon for You (2020)\data for Tonikawa Over the Moon for You (2020)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tsukimichi Moonlit Fantasy (2021)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Tsukimichi Moonlit Fantasy (2021)\data for Tsukimichi Moonlit Fantasy (2021)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping Unidentified - Die wahren X-Akten (2019) - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Unnamed Memory (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Unnamed Memory (2024)\data for Unnamed Memory (2024)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Vom Landei zum Schwertheiligen (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Vom Landei zum Schwertheiligen (2025)\data for Vom Landei zum Schwertheiligen (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\WIND BREAKER (2024)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\WIND BREAKER (2024)\data for WIND BREAKER (2024)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\WITCH WATCH (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\WITCH WATCH (2025)\data for WITCH WATCH (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Wolf Girl & Black Prince (2014)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Wolf Girl & Black Prince (2014)\data for Wolf Girl & Black Prince (2014)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\World’s End Harem (2022)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\World’s End Harem (2022)\data for World’s End Harem (2022)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Zom 100 Bucket List of the Dead (2023)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Zom 100 Bucket List of the Dead (2023)\data for Zom 100 Bucket List of the Dead (2023)
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping a-couple-of-cuckoos - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\a-ninja-and-an-assassin-under-one-roof\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\a-ninja-and-an-assassin-under-one-roof\data for a-ninja-and-an-assassin-under-one-roof
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\a-nobodys-way-up-to-an-exploration-hero\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\a-nobodys-way-up-to-an-exploration-hero\data for a-nobodys-way-up-to-an-exploration-hero
|
||||||
|
2025-09-29 12:38:42 - WARNING - root - load_series - Skipping a-silent-voice - No data folder found
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\am-i-actually-the-strongest\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\am-i-actually-the-strongest\data for am-i-actually-the-strongest
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\anne-shirley\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\anne-shirley\data for anne-shirley
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\apocalypse-bringer-mynoghra\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\apocalypse-bringer-mynoghra\data for apocalypse-bringer-mynoghra
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\banished-from-the-heros-party-i-decided-to-live-a-quiet-life-in-the-countryside\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\banished-from-the-heros-party-i-decided-to-live-a-quiet-life-in-the-countryside\data for banished-from-the-heros-party-i-decided-to-live-a-quiet-life-in-the-countryside
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)\data for beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\berserk-of-gluttony\data
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\berserk-of-gluttony\data for berserk-of-gluttony
|
||||||
|
2025-09-29 12:38:42 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\black-summoner\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\black-summoner\data for black-summoner
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\boarding-school-juliet\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\boarding-school-juliet\data for boarding-school-juliet
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\buddy-daddies\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\buddy-daddies\data for buddy-daddies
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\can-a-boy-girl-friendship-survive\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\can-a-boy-girl-friendship-survive\data for can-a-boy-girl-friendship-survive
|
||||||
|
2025-09-29 12:38:43 - WARNING - root - load_series - Skipping chillin-in-another-world-with-level-2-super-cheat-powers - No data folder found
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\chillin-in-my-30s-after-getting-fired-from-the-demon-kings-army\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\chillin-in-my-30s-after-getting-fired-from-the-demon-kings-army\data for chillin-in-my-30s-after-getting-fired-from-the-demon-kings-army
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\choujin koukousei tachi wa isekai de mo yoyuu de ikinuku you desu\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\choujin koukousei tachi wa isekai de mo yoyuu de ikinuku you desu\data for choujin koukousei tachi wa isekai de mo yoyuu de ikinuku you desu
|
||||||
|
2025-09-29 12:38:43 - WARNING - root - load_series - Skipping clevatess - No data folder found
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\compass-20-animation-project\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\compass-20-animation-project\data for compass-20-animation-project
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\dragon-raja-the-blazing-dawn\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\dragon-raja-the-blazing-dawn\data for dragon-raja-the-blazing-dawn
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\dragonar-academy\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\dragonar-academy\data for dragonar-academy
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\drugstore-in-another-world-the-slow-life-of-a-cheat-pharmacist\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\drugstore-in-another-world-the-slow-life-of-a-cheat-pharmacist\data for drugstore-in-another-world-the-slow-life-of-a-cheat-pharmacist
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\fluffy-paradise\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\fluffy-paradise\data for fluffy-paradise
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\food-for-the-soul\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\food-for-the-soul\data for food-for-the-soul
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\handyman-saitou-in-another-world\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\handyman-saitou-in-another-world\data for handyman-saitou-in-another-world
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\i-shall-survive-using-potions\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\i-shall-survive-using-potions\data for i-shall-survive-using-potions
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\im-giving-the-disgraced-noble-lady-i-rescued-a-crash-course-in-naughtiness\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\im-giving-the-disgraced-noble-lady-i-rescued-a-crash-course-in-naughtiness\data for im-giving-the-disgraced-noble-lady-i-rescued-a-crash-course-in-naughtiness
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\killing-bites\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\killing-bites\data for killing-bites
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\love-flops\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\love-flops\data for love-flops
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\magic-maker-how-to-make-magic-in-another-world\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\magic-maker-how-to-make-magic-in-another-world\data for magic-maker-how-to-make-magic-in-another-world
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\muhyo-rojis-bureau-of-supernatural-investigation\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\muhyo-rojis-bureau-of-supernatural-investigation\data for muhyo-rojis-bureau-of-supernatural-investigation
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\my-roommate-is-a-cat\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\my-roommate-is-a-cat\data for my-roommate-is-a-cat
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\nukitashi-the-animation\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\nukitashi-the-animation\data for nukitashi-the-animation
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\outbreak-company\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\outbreak-company\data for outbreak-company
|
||||||
|
2025-09-29 12:38:43 - WARNING - root - load_series - Skipping plastic-memories - No data folder found
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\pseudo-harem\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\pseudo-harem\data for pseudo-harem
|
||||||
|
2025-09-29 12:38:43 - WARNING - root - load_series - Skipping rent-a-girlfriend - No data folder found
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\sasaki-and-peeps\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\sasaki-and-peeps\data for sasaki-and-peeps
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\scooped-up-by-an-s-rank-adventurer\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\scooped-up-by-an-s-rank-adventurer\data for scooped-up-by-an-s-rank-adventurer
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\secrets-of-the-silent-witch\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\secrets-of-the-silent-witch\data for secrets-of-the-silent-witch
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\seton-academy-join-the-pack\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\seton-academy-join-the-pack\data for seton-academy-join-the-pack
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\shachibato-president-its-time-for-battle\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\shachibato-president-its-time-for-battle\data for shachibato-president-its-time-for-battle
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\skeleton-knight-in-another-world\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\skeleton-knight-in-another-world\data for skeleton-knight-in-another-world
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\sugar-apple-fairy-tale\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\sugar-apple-fairy-tale\data for sugar-apple-fairy-tale
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\summer-pockets\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\summer-pockets\data for summer-pockets
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\suppose-a-kid-from-the-last-dungeon-boonies-moved-to-a-starter-town\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\suppose-a-kid-from-the-last-dungeon-boonies-moved-to-a-starter-town\data for suppose-a-kid-from-the-last-dungeon-boonies-moved-to-a-starter-town
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-beginning-after-the-end\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-beginning-after-the-end\data for the-beginning-after-the-end
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-brilliant-healers-new-life-in-the-shadows\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-brilliant-healers-new-life-in-the-shadows\data for the-brilliant-healers-new-life-in-the-shadows
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-daily-life-of-a-middle-aged-online-shopper-in-another-world\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-daily-life-of-a-middle-aged-online-shopper-in-another-world\data for the-daily-life-of-a-middle-aged-online-shopper-in-another-world
|
||||||
|
2025-09-29 12:38:43 - WARNING - root - load_series - Skipping the-familiar-of-zero - No data folder found
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-fragrant-flower-blooms-with-dignity\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-fragrant-flower-blooms-with-dignity\data for the-fragrant-flower-blooms-with-dignity
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-great-cleric\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-great-cleric\data for the-great-cleric
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-new-chronicles-of-extraordinary-beings-preface\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-new-chronicles-of-extraordinary-beings-preface\data for the-new-chronicles-of-extraordinary-beings-preface
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-shiunji-family-children\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-shiunji-family-children\data for the-shiunji-family-children
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-shy-hero-and-the-assassin-princesses\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-shy-hero-and-the-assassin-princesses\data for the-shy-hero-and-the-assassin-princesses
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-testament-of-sister-new-devil\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-testament-of-sister-new-devil\data for the-testament-of-sister-new-devil
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-unwanted-undead-adventurer\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-unwanted-undead-adventurer\data for the-unwanted-undead-adventurer
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-water-magician\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-water-magician\data for the-water-magician
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat\data for the-worlds-finest-assassin-gets-reincarnated-in-another-world-as-an-aristocrat
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-wrong-way-to-use-healing-magic\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\the-wrong-way-to-use-healing-magic\data for the-wrong-way-to-use-healing-magic
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\theres-no-freaking-way-ill-be-your-lover-unless\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\theres-no-freaking-way-ill-be-your-lover-unless\data for theres-no-freaking-way-ill-be-your-lover-unless
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\to-be-hero-x\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\to-be-hero-x\data for to-be-hero-x
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\tougen-anki\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\tougen-anki\data for tougen-anki
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\uglymug-epicfighter\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\uglymug-epicfighter\data for uglymug-epicfighter
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\valkyrie-drive-mermaid\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\valkyrie-drive-mermaid\data for valkyrie-drive-mermaid
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\wandering-witch-the-journey-of-elaina\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\wandering-witch-the-journey-of-elaina\data for wandering-witch-the-journey-of-elaina
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\war-god-system-im-counting-on-you\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\war-god-system-im-counting-on-you\data for war-god-system-im-counting-on-you
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\welcome-to-japan-ms-elf\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\welcome-to-japan-ms-elf\data for welcome-to-japan-ms-elf
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\welcome-to-the-outcasts-restaurant\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\welcome-to-the-outcasts-restaurant\data for welcome-to-the-outcasts-restaurant
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\yandere-dark-elf-she-chased-me-all-the-way-from-another-world\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\yandere-dark-elf-she-chased-me-all-the-way-from-another-world\data for yandere-dark-elf-she-chased-me-all-the-way-from-another-world
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_series - Found data folder: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Übel Blatt (2025)\data
|
||||||
|
2025-09-29 12:38:43 - INFO - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Übel Blatt (2025)\data for Übel Blatt (2025)
|
||||||
0
src/logs/auth_failures.log
Normal file
0
src/logs/auth_failures.log
Normal file
0
src/logs/downloads.log
Normal file
0
src/logs/downloads.log
Normal file
109
src/server/ROUTE_ORGANIZATION.md
Normal file
109
src/server/ROUTE_ORGANIZATION.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# Route Organization Summary
|
||||||
|
|
||||||
|
This document describes the reorganization of routes from a single `app.py` file into separate blueprint files for better organization and maintainability.
|
||||||
|
|
||||||
|
## New File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/server/web/routes/
|
||||||
|
├── __init__.py # Package initialization with graceful imports
|
||||||
|
├── main_routes.py # Main page routes (index)
|
||||||
|
├── auth_routes.py # Authentication routes (login, setup, API auth)
|
||||||
|
├── api_routes.py # Core API routes (series, search, download, rescan)
|
||||||
|
├── static_routes.py # Static file routes (JS/CSS for UX features)
|
||||||
|
├── diagnostic_routes.py # Diagnostic and monitoring routes
|
||||||
|
├── config_routes.py # Configuration management routes
|
||||||
|
└── websocket_handlers.py # WebSocket event handlers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Route Categories
|
||||||
|
|
||||||
|
### 1. Main Routes (`main_routes.py`)
|
||||||
|
- `/` - Main index page
|
||||||
|
|
||||||
|
### 2. Authentication Routes (`auth_routes.py`)
|
||||||
|
Contains two blueprints:
|
||||||
|
- **auth_bp**: Page routes (`/login`, `/setup`)
|
||||||
|
- **auth_api_bp**: API routes (`/api/auth/*`)
|
||||||
|
|
||||||
|
### 3. API Routes (`api_routes.py`)
|
||||||
|
- `/api/series` - Get series data
|
||||||
|
- `/api/search` - Search for series
|
||||||
|
- `/api/add_series` - Add new series
|
||||||
|
- `/api/rescan` - Rescan series directory
|
||||||
|
- `/api/download` - Add to download queue
|
||||||
|
- `/api/queue/start` - Start download queue
|
||||||
|
- `/api/queue/stop` - Stop download queue
|
||||||
|
- `/api/status` - Get system status
|
||||||
|
- `/api/process/locks/status` - Get process lock status
|
||||||
|
- `/api/config/directory` - Update directory configuration
|
||||||
|
|
||||||
|
### 4. Static Routes (`static_routes.py`)
|
||||||
|
- `/static/js/*` - JavaScript files for UX features
|
||||||
|
- `/static/css/*` - CSS files for styling
|
||||||
|
|
||||||
|
### 5. Diagnostic Routes (`diagnostic_routes.py`)
|
||||||
|
- `/api/diagnostics/network` - Network diagnostics
|
||||||
|
- `/api/diagnostics/errors` - Error history
|
||||||
|
- `/api/diagnostics/system-status` - System status summary
|
||||||
|
- `/api/diagnostics/recovery/*` - Recovery endpoints
|
||||||
|
|
||||||
|
### 6. Config Routes (`config_routes.py`)
|
||||||
|
- `/api/scheduler/config` - Scheduler configuration
|
||||||
|
- `/api/logging/config` - Logging configuration
|
||||||
|
- `/api/config/section/advanced` - Advanced configuration
|
||||||
|
- `/api/config/backup*` - Configuration backup management
|
||||||
|
|
||||||
|
### 7. WebSocket Handlers (`websocket_handlers.py`)
|
||||||
|
- `connect` - Client connection handler
|
||||||
|
- `disconnect` - Client disconnection handler
|
||||||
|
- `get_status` - Status request handler
|
||||||
|
|
||||||
|
## Changes Made to `app.py`
|
||||||
|
|
||||||
|
1. **Removed Routes**: All route definitions have been moved to their respective blueprint files
|
||||||
|
2. **Added Imports**: Import statements for the new route blueprints
|
||||||
|
3. **Blueprint Registration**: Register all blueprints with the Flask app
|
||||||
|
4. **Global Variables**: Moved to appropriate route files where they're used
|
||||||
|
5. **Placeholder Classes**: Moved to relevant route files
|
||||||
|
6. **WebSocket Integration**: Set up socketio instance sharing with API routes
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Better Organization**: Routes are grouped by functionality
|
||||||
|
2. **Maintainability**: Easier to find and modify specific route logic
|
||||||
|
3. **Separation of Concerns**: Each file has a specific responsibility
|
||||||
|
4. **Scalability**: Easy to add new routes in appropriate files
|
||||||
|
5. **Testing**: Individual route groups can be tested separately
|
||||||
|
6. **Code Reuse**: Common functionality can be shared between route files
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The Flask app now imports and registers all blueprints:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from web.routes import (
|
||||||
|
auth_bp, auth_api_bp, api_bp, main_bp, static_bp,
|
||||||
|
diagnostic_bp, config_bp
|
||||||
|
)
|
||||||
|
|
||||||
|
app.register_blueprint(main_bp)
|
||||||
|
app.register_blueprint(auth_bp)
|
||||||
|
app.register_blueprint(auth_api_bp)
|
||||||
|
app.register_blueprint(api_bp)
|
||||||
|
app.register_blueprint(static_bp)
|
||||||
|
app.register_blueprint(diagnostic_bp)
|
||||||
|
app.register_blueprint(config_bp)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The `__init__.py` file includes graceful import handling, so if any route file has import errors, the application will continue to function with the available routes.
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- Add route-specific middleware
|
||||||
|
- Implement route-level caching
|
||||||
|
- Add route-specific rate limiting
|
||||||
|
- Create route-specific documentation
|
||||||
|
- Add route-specific testing
|
||||||
1
src/server/__init__.py
Normal file
1
src/server/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Server package
|
||||||
File diff suppressed because it is too large
Load Diff
@ -414,7 +414,7 @@ class BulkOperationsManager {
|
|||||||
|
|
||||||
const confirmed = await this.confirmOperation(
|
const confirmed = await this.confirmOperation(
|
||||||
'Bulk Delete',
|
'Bulk Delete',
|
||||||
`Permanently delete ${this.selectedItems.size} selected series?\\n\\nThis action cannot be undone`,
|
`Permanently delete ${this.selectedItems.size} selected series?\\n\\nThis action cannot be undone!`,
|
||||||
'danger'
|
'danger'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from infrastructure.providers.streaming.Provider import Provider
|
from server.infrastructure.providers.streaming.Provider import Provider
|
||||||
from infrastructure.providers.streaming.voe import VOE
|
from server.infrastructure.providers.streaming.voe import VOE
|
||||||
|
|
||||||
class Providers:
|
class Providers:
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
from core.entities.series import Serie
|
from server.core.entities.series import Serie
|
||||||
import traceback
|
import traceback
|
||||||
from infrastructure.logging.GlobalLogger import error_logger, noKeyFound_logger
|
from server.infrastructure.logging.GlobalLogger import error_logger, noKeyFound_logger
|
||||||
from core.exceptions.Exceptions import NoKeyFoundException, MatchNotFoundError
|
from server.core.exceptions.Exceptions import NoKeyFoundException, MatchNotFoundError
|
||||||
from infrastructure.providers.base_provider import Loader
|
from server.infrastructure.providers.base_provider import Loader
|
||||||
|
|
||||||
|
|
||||||
class SerieScanner:
|
class SerieScanner:
|
||||||
@ -121,7 +121,7 @@ class SerieScanner:
|
|||||||
episodes_dict = {}
|
episodes_dict = {}
|
||||||
for season, expected_count in expected_dict.items():
|
for season, expected_count in expected_dict.items():
|
||||||
existing_episodes = filedict.get(season, [])
|
existing_episodes = filedict.get(season, [])
|
||||||
missing_episodes = [ep for ep in range(1, expected_count + 1) if ep not in existing_episodes]
|
missing_episodes = [ep for ep in range(1, expected_count + 1) if ep not in existing_episodes and self.loader.IsLanguage(season, ep, key)]
|
||||||
|
|
||||||
if missing_episodes:
|
if missing_episodes:
|
||||||
episodes_dict[season] = missing_episodes
|
episodes_dict[season] = missing_episodes
|
||||||
|
|||||||
@ -12,8 +12,8 @@ from fake_useragent import UserAgent
|
|||||||
from requests.adapters import HTTPAdapter
|
from requests.adapters import HTTPAdapter
|
||||||
from urllib3.util.retry import Retry
|
from urllib3.util.retry import Retry
|
||||||
|
|
||||||
from infrastructure.providers.base_provider import Loader
|
from server.infrastructure.providers.base_provider import Loader
|
||||||
from core.interfaces.providers import Providers
|
from server.core.interfaces.providers import Providers
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|||||||
@ -23,8 +23,8 @@ from urllib3.util.retry import Retry
|
|||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from infrastructure.providers.base_provider import Loader
|
from server.infrastructure.providers.base_provider import Loader
|
||||||
from core.interfaces.providers import Providers
|
from server.core.interfaces.providers import Providers
|
||||||
from error_handler import (
|
from error_handler import (
|
||||||
with_error_recovery,
|
with_error_recovery,
|
||||||
recovery_strategies,
|
recovery_strategies,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from infrastructure.providers.aniworld_provider import AniworldLoader
|
from server.infrastructure.providers.aniworld_provider import AniworldLoader
|
||||||
from infrastructure.providers.base_provider import Loader
|
from server.infrastructure.providers.base_provider import Loader
|
||||||
|
|
||||||
class Loaders:
|
class Loaders:
|
||||||
|
|
||||||
|
|||||||
@ -180,3 +180,18 @@
|
|||||||
2025-09-28 20:18:37 - INFO - performance_optimizer - stop - Download manager stopped
|
2025-09-28 20:18:37 - INFO - performance_optimizer - stop - Download manager stopped
|
||||||
2025-09-28 20:18:38 - INFO - api_integration - stop - Webhook delivery service stopped
|
2025-09-28 20:18:38 - INFO - api_integration - stop - Webhook delivery service stopped
|
||||||
2025-09-28 20:18:38 - INFO - root - cleanup_on_exit - Application cleanup completed
|
2025-09-28 20:18:38 - INFO - root - cleanup_on_exit - Application cleanup completed
|
||||||
|
2025-09-29 14:09:22 - INFO - __main__ - <module> - Enhanced logging system initialized
|
||||||
|
2025-09-29 14:09:22 - INFO - __main__ - <module> - Starting Aniworld Flask server...
|
||||||
|
2025-09-29 14:09:22 - INFO - __main__ - <module> - Anime directory: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien
|
||||||
|
2025-09-29 14:09:22 - INFO - __main__ - <module> - Log level: INFO
|
||||||
|
2025-09-29 14:09:22 - INFO - __main__ - <module> - Scheduled operations disabled
|
||||||
|
2025-09-29 14:09:22 - INFO - __main__ - <module> - Server will be available at http://localhost:5000
|
||||||
|
2025-09-29 14:09:28 - INFO - __main__ - <module> - Enhanced logging system initialized
|
||||||
|
2025-09-29 14:09:28 - INFO - __main__ - <module> - Starting Aniworld Flask server...
|
||||||
|
2025-09-29 14:09:28 - INFO - __main__ - <module> - Anime directory: \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien
|
||||||
|
2025-09-29 14:09:28 - INFO - __main__ - <module> - Log level: INFO
|
||||||
|
2025-09-29 14:09:28 - INFO - __main__ - <module> - Scheduled operations disabled
|
||||||
|
2025-09-29 14:09:28 - INFO - __main__ - <module> - Server will be available at http://localhost:5000
|
||||||
|
2025-09-29 14:09:28 - WARNING - werkzeug - _log - * Debugger is active!
|
||||||
|
2025-09-29 14:09:47 - INFO - root - cleanup_on_exit - Application cleanup completed
|
||||||
|
2025-09-29 14:09:48 - INFO - root - cleanup_on_exit - Application cleanup completed
|
||||||
|
|||||||
@ -12,10 +12,10 @@ from datetime import datetime
|
|||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
from main import SeriesApp
|
from main import SeriesApp
|
||||||
from core.entities.series import Serie
|
from server.core.entities.series import Serie
|
||||||
from core.entities import SerieList
|
from server.core.entities import SerieList
|
||||||
from infrastructure.file_system import SerieScanner
|
from server.infrastructure.file_system import SerieScanner
|
||||||
from infrastructure.providers.provider_factory import Loaders
|
from server.infrastructure.providers.provider_factory import Loaders
|
||||||
from web.controllers.auth_controller import session_manager, require_auth, optional_auth
|
from web.controllers.auth_controller import session_manager, require_auth, optional_auth
|
||||||
from config import config
|
from config import config
|
||||||
from application.services.queue_service import download_queue_bp
|
from application.services.queue_service import download_queue_bp
|
||||||
|
|||||||
@ -19,7 +19,7 @@ def get_logging_config():
|
|||||||
"""Get current logging configuration."""
|
"""Get current logging configuration."""
|
||||||
try:
|
try:
|
||||||
# Import here to avoid circular imports
|
# Import here to avoid circular imports
|
||||||
from logging_config import logging_config as log_config
|
from server.infrastructure.logging.config import logging_config as log_config
|
||||||
|
|
||||||
config_data = {
|
config_data = {
|
||||||
'log_level': config.log_level,
|
'log_level': config.log_level,
|
||||||
@ -67,7 +67,7 @@ def update_logging_config():
|
|||||||
|
|
||||||
# Update runtime logging level
|
# Update runtime logging level
|
||||||
try:
|
try:
|
||||||
from logging_config import logging_config as log_config
|
from server.infrastructure.logging.config import logging_config as log_config
|
||||||
log_config.update_log_level(config.log_level)
|
log_config.update_log_level(config.log_level)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Fallback for basic logging
|
# Fallback for basic logging
|
||||||
@ -99,7 +99,7 @@ def update_logging_config():
|
|||||||
def list_log_files():
|
def list_log_files():
|
||||||
"""Get list of available log files."""
|
"""Get list of available log files."""
|
||||||
try:
|
try:
|
||||||
from logging_config import logging_config as log_config
|
from server.infrastructure.logging.config import logging_config as log_config
|
||||||
|
|
||||||
log_files = log_config.get_log_files()
|
log_files = log_config.get_log_files()
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ def cleanup_logs():
|
|||||||
days = int(data.get('days', 30))
|
days = int(data.get('days', 30))
|
||||||
days = max(1, min(days, 365)) # Limit between 1-365 days
|
days = max(1, min(days, 365)) # Limit between 1-365 days
|
||||||
|
|
||||||
from logging_config import logging_config as log_config
|
from server.infrastructure.logging.config import logging_config as log_config
|
||||||
cleaned_files = log_config.cleanup_old_logs(days)
|
cleaned_files = log_config.cleanup_old_logs(days)
|
||||||
|
|
||||||
logger.info(f"Cleaned up {len(cleaned_files)} old log files (older than {days} days)")
|
logger.info(f"Cleaned up {len(cleaned_files)} old log files (older than {days} days)")
|
||||||
@ -232,14 +232,14 @@ def test_logging():
|
|||||||
|
|
||||||
# Test fail2ban logging
|
# Test fail2ban logging
|
||||||
try:
|
try:
|
||||||
from logging_config import log_auth_failure
|
from server.infrastructure.logging.config import log_auth_failure
|
||||||
log_auth_failure("127.0.0.1", "test_user")
|
log_auth_failure("127.0.0.1", "test_user")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Test download progress logging
|
# Test download progress logging
|
||||||
try:
|
try:
|
||||||
from logging_config import log_download_progress
|
from server.infrastructure.logging.config import log_download_progress
|
||||||
log_download_progress("Test Series", "S01E01", 50.0, "1.2 MB/s", "5m 30s")
|
log_download_progress("Test Series", "S01E01", 50.0, "1.2 MB/s", "5m 30s")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class SessionManager:
|
|||||||
if config.enable_fail2ban_logging:
|
if config.enable_fail2ban_logging:
|
||||||
try:
|
try:
|
||||||
# Import here to avoid circular imports
|
# Import here to avoid circular imports
|
||||||
from logging_config import log_auth_failure
|
from server.infrastructure.logging.config import log_auth_failure
|
||||||
log_auth_failure(ip_address, username)
|
log_auth_failure(ip_address, username)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Fallback to simple logging if new system not available
|
# Fallback to simple logging if new system not available
|
||||||
|
|||||||
42
src/server/web/routes/__init__.py
Normal file
42
src/server/web/routes/__init__.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Routes package for Aniworld web application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Import blueprints that are available
|
||||||
|
__all__ = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .auth_routes import auth_bp, auth_api_bp
|
||||||
|
__all__.extend(['auth_bp', 'auth_api_bp'])
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .api_routes import api_bp
|
||||||
|
__all__.append('api_bp')
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .main_routes import main_bp
|
||||||
|
__all__.append('main_bp')
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .static_routes import static_bp
|
||||||
|
__all__.append('static_bp')
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .diagnostic_routes import diagnostic_bp
|
||||||
|
__all__.append('diagnostic_bp')
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .config_routes import config_bp
|
||||||
|
__all__.append('config_bp')
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
827
src/server/web/routes/api_routes.py
Normal file
827
src/server/web/routes/api_routes.py
Normal file
@ -0,0 +1,827 @@
|
|||||||
|
"""
|
||||||
|
API routes for series management, downloads, and operations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from flask_socketio import emit
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from web.controllers.auth_controller import optional_auth, require_auth
|
||||||
|
|
||||||
|
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
# Global variables to store app state
|
||||||
|
series_app = None
|
||||||
|
is_scanning = False
|
||||||
|
is_downloading = False
|
||||||
|
should_stop_downloads = False
|
||||||
|
|
||||||
|
# Placeholder process lock constants and functions
|
||||||
|
RESCAN_LOCK = "rescan"
|
||||||
|
DOWNLOAD_LOCK = "download"
|
||||||
|
CLEANUP_LOCK = "cleanup"
|
||||||
|
|
||||||
|
# Simple in-memory process lock system
|
||||||
|
_active_locks = {}
|
||||||
|
|
||||||
|
def is_process_running(lock_name):
|
||||||
|
"""Check if a process is currently running (locked)."""
|
||||||
|
return lock_name in _active_locks
|
||||||
|
|
||||||
|
def acquire_lock(lock_name, locked_by="system"):
|
||||||
|
"""Acquire a process lock."""
|
||||||
|
if lock_name in _active_locks:
|
||||||
|
raise ProcessLockError(f"Process {lock_name} is already running")
|
||||||
|
_active_locks[lock_name] = {
|
||||||
|
'locked_by': locked_by,
|
||||||
|
'timestamp': datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
def release_lock(lock_name):
|
||||||
|
"""Release a process lock."""
|
||||||
|
if lock_name in _active_locks:
|
||||||
|
del _active_locks[lock_name]
|
||||||
|
|
||||||
|
class ProcessLockError(Exception):
|
||||||
|
"""Placeholder exception for process lock errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def with_process_lock(lock_name, timeout_minutes=30):
|
||||||
|
"""Decorator for process locking."""
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
# Extract locked_by from kwargs if provided
|
||||||
|
locked_by = kwargs.pop('_locked_by', 'system')
|
||||||
|
|
||||||
|
try:
|
||||||
|
acquire_lock(lock_name, locked_by)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
release_lock(lock_name)
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
# Simple decorator to replace handle_api_errors
|
||||||
|
def handle_api_errors(f):
|
||||||
|
"""Simple error handling decorator."""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
def init_series_app():
|
||||||
|
"""Initialize the SeriesApp with configuration directory."""
|
||||||
|
global series_app
|
||||||
|
from config import config
|
||||||
|
from main import SeriesApp
|
||||||
|
directory_to_search = config.anime_directory
|
||||||
|
series_app = SeriesApp(directory_to_search)
|
||||||
|
return series_app
|
||||||
|
|
||||||
|
# Import socketio instance - this will need to be passed from app.py
|
||||||
|
socketio = None
|
||||||
|
|
||||||
|
def set_socketio(socket_instance):
|
||||||
|
"""Set the socketio instance for this blueprint."""
|
||||||
|
global socketio
|
||||||
|
socketio = socket_instance
|
||||||
|
|
||||||
|
@api_bp.route('/config/directory', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def update_directory():
|
||||||
|
"""Update anime directory configuration."""
|
||||||
|
try:
|
||||||
|
from config import config
|
||||||
|
data = request.get_json()
|
||||||
|
new_directory = data.get('directory')
|
||||||
|
|
||||||
|
if not new_directory:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Directory is required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Update configuration
|
||||||
|
config.anime_directory = new_directory
|
||||||
|
config.save_config()
|
||||||
|
|
||||||
|
# Reinitialize series app
|
||||||
|
init_series_app()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Directory updated successfully',
|
||||||
|
'directory': new_directory
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@api_bp.route('/series', methods=['GET'])
|
||||||
|
@optional_auth
|
||||||
|
def get_series():
|
||||||
|
"""Get all series data."""
|
||||||
|
try:
|
||||||
|
if series_app is None or series_app.List is None:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'series': [],
|
||||||
|
'total_series': 0,
|
||||||
|
'message': 'No series data available. Please perform a scan to load series.'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Get series data
|
||||||
|
series_data = []
|
||||||
|
for serie in series_app.List.GetList():
|
||||||
|
series_data.append({
|
||||||
|
'folder': serie.folder,
|
||||||
|
'name': serie.name or serie.folder,
|
||||||
|
'total_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()),
|
||||||
|
'missing_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()),
|
||||||
|
'status': 'ongoing',
|
||||||
|
'episodes': {
|
||||||
|
season: episodes
|
||||||
|
for season, episodes in serie.episodeDict.items()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'series': series_data,
|
||||||
|
'total_series': len(series_data)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Log the error but don't return 500 to prevent page reload loops
|
||||||
|
print(f"Error in get_series: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'series': [],
|
||||||
|
'total_series': 0,
|
||||||
|
'message': 'Error loading series data. Please try rescanning.'
|
||||||
|
})
|
||||||
|
|
||||||
|
@api_bp.route('/search', methods=['POST'])
|
||||||
|
@optional_auth
|
||||||
|
@handle_api_errors
|
||||||
|
def search_series():
|
||||||
|
"""Search for series online."""
|
||||||
|
try:
|
||||||
|
# Get the search query from the request
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or 'query' not in data:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Search query is required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
query = data['query'].strip()
|
||||||
|
if not query:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Search query cannot be empty'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Check if series_app is available
|
||||||
|
if series_app is None:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Series application not initialized'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
# Perform the search
|
||||||
|
search_results = series_app.search(query)
|
||||||
|
|
||||||
|
# Format results for the frontend
|
||||||
|
results = []
|
||||||
|
if search_results:
|
||||||
|
for result in search_results:
|
||||||
|
if isinstance(result, dict) and 'name' in result and 'link' in result:
|
||||||
|
results.append({
|
||||||
|
'name': result['name'],
|
||||||
|
'link': result['link']
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'results': results,
|
||||||
|
'total': len(results)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'Search failed: {str(e)}'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@api_bp.route('/add_series', methods=['POST'])
|
||||||
|
@optional_auth
|
||||||
|
@handle_api_errors
|
||||||
|
def add_series():
|
||||||
|
"""Add a new series to the collection."""
|
||||||
|
try:
|
||||||
|
from server.core.entities.series import Serie
|
||||||
|
|
||||||
|
# Get the request data
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Request data is required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Validate required fields
|
||||||
|
if 'link' not in data or 'name' not in data:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Both link and name are required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
link = data['link'].strip()
|
||||||
|
name = data['name'].strip()
|
||||||
|
|
||||||
|
if not link or not name:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Link and name cannot be empty'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Check if series_app is available
|
||||||
|
if series_app is None:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Series application not initialized'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
# Create and add the series
|
||||||
|
new_serie = Serie(link, name, "aniworld.to", link, {})
|
||||||
|
series_app.List.add(new_serie)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': f'Series "{name}" added successfully'
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'Failed to add series: {str(e)}'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@api_bp.route('/rescan', methods=['POST'])
|
||||||
|
@optional_auth
|
||||||
|
def rescan_series():
|
||||||
|
"""Rescan/reinit the series directory."""
|
||||||
|
global is_scanning
|
||||||
|
|
||||||
|
# Check if rescan is already running using process lock
|
||||||
|
if is_process_running(RESCAN_LOCK) or is_scanning:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Rescan is already running. Please wait for it to complete.',
|
||||||
|
'is_running': True
|
||||||
|
}), 409
|
||||||
|
|
||||||
|
def scan_thread():
|
||||||
|
global is_scanning
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use process lock to prevent duplicate rescans
|
||||||
|
@with_process_lock(RESCAN_LOCK, timeout_minutes=120)
|
||||||
|
def perform_rescan():
|
||||||
|
global is_scanning
|
||||||
|
is_scanning = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
from server.core.entities import SerieList
|
||||||
|
|
||||||
|
# Emit scanning started
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('scan_started')
|
||||||
|
|
||||||
|
# Reinit and scan
|
||||||
|
series_app.SerieScanner.Reinit()
|
||||||
|
series_app.SerieScanner.Scan(lambda folder, counter:
|
||||||
|
socketio.emit('scan_progress', {
|
||||||
|
'folder': folder,
|
||||||
|
'counter': counter
|
||||||
|
}) if socketio else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Refresh the series list
|
||||||
|
series_app.List = SerieList.SerieList(series_app.directory_to_search)
|
||||||
|
series_app.__InitList__()
|
||||||
|
|
||||||
|
# Emit scan completed
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('scan_completed')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('scan_error', {'message': str(e)})
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
is_scanning = False
|
||||||
|
|
||||||
|
perform_rescan(_locked_by='web_interface')
|
||||||
|
|
||||||
|
except ProcessLockError:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('scan_error', {'message': 'Rescan is already running'})
|
||||||
|
except Exception as e:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('scan_error', {'message': str(e)})
|
||||||
|
|
||||||
|
# Start scan in background thread
|
||||||
|
threading.Thread(target=scan_thread, daemon=True).start()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Rescan started'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Download endpoint - adds items to queue
|
||||||
|
@api_bp.route('/download', methods=['POST'])
|
||||||
|
@optional_auth
|
||||||
|
def download_series():
|
||||||
|
"""Add selected series to download queue."""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or 'folders' not in data:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Folders list is required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
folders = data['folders']
|
||||||
|
if not folders:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'No series selected'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Import the queue functions
|
||||||
|
from application.services.queue_service import add_to_download_queue
|
||||||
|
|
||||||
|
added_count = 0
|
||||||
|
for folder in folders:
|
||||||
|
try:
|
||||||
|
# Find the serie in our list
|
||||||
|
serie = None
|
||||||
|
if series_app and series_app.List:
|
||||||
|
for s in series_app.List.GetList():
|
||||||
|
if s.folder == folder:
|
||||||
|
serie = s
|
||||||
|
break
|
||||||
|
|
||||||
|
if serie:
|
||||||
|
# Check if this serie has missing episodes (non-empty episodeDict)
|
||||||
|
if serie.episodeDict:
|
||||||
|
# Create download entries for each season/episode combination
|
||||||
|
for season, episodes in serie.episodeDict.items():
|
||||||
|
for episode in episodes:
|
||||||
|
episode_info = {
|
||||||
|
'folder': folder,
|
||||||
|
'season': season,
|
||||||
|
'episode_number': episode,
|
||||||
|
'title': f'S{season:02d}E{episode:02d}',
|
||||||
|
'url': '', # Will be populated during actual download
|
||||||
|
'serie_name': serie.name or folder
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_download_queue(
|
||||||
|
serie_name=serie.name or folder,
|
||||||
|
episode_info=episode_info,
|
||||||
|
priority='normal'
|
||||||
|
)
|
||||||
|
added_count += 1
|
||||||
|
else:
|
||||||
|
# No missing episodes, add a placeholder entry indicating series is complete
|
||||||
|
episode_info = {
|
||||||
|
'folder': folder,
|
||||||
|
'season': None,
|
||||||
|
'episode_number': 'Complete',
|
||||||
|
'title': 'No missing episodes',
|
||||||
|
'url': '',
|
||||||
|
'serie_name': serie.name or folder
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_download_queue(
|
||||||
|
serie_name=serie.name or folder,
|
||||||
|
episode_info=episode_info,
|
||||||
|
priority='normal'
|
||||||
|
)
|
||||||
|
added_count += 1
|
||||||
|
else:
|
||||||
|
# Serie not found, add with folder name only
|
||||||
|
episode_info = {
|
||||||
|
'folder': folder,
|
||||||
|
'episode_number': 'Unknown',
|
||||||
|
'title': 'Serie Check Required',
|
||||||
|
'url': '',
|
||||||
|
'serie_name': folder
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_download_queue(
|
||||||
|
serie_name=folder,
|
||||||
|
episode_info=episode_info,
|
||||||
|
priority='normal'
|
||||||
|
)
|
||||||
|
added_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing folder {folder}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if added_count > 0:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': f'Added {added_count} items to download queue'
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'No items could be added to the queue'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'Failed to add to queue: {str(e)}'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@api_bp.route('/queue/start', methods=['POST'])
|
||||||
|
@optional_auth
|
||||||
|
def start_download_queue():
|
||||||
|
"""Start processing the download queue."""
|
||||||
|
global is_downloading, should_stop_downloads
|
||||||
|
|
||||||
|
# Check if download is already running using process lock
|
||||||
|
if is_process_running(DOWNLOAD_LOCK) or is_downloading:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Download is already running. Please wait for it to complete.',
|
||||||
|
'is_running': True
|
||||||
|
}), 409
|
||||||
|
|
||||||
|
def download_thread():
|
||||||
|
global is_downloading, should_stop_downloads
|
||||||
|
should_stop_downloads = False # Reset stop flag when starting
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use process lock to prevent duplicate downloads
|
||||||
|
@with_process_lock(DOWNLOAD_LOCK, timeout_minutes=720) # 12 hours max
|
||||||
|
def perform_downloads():
|
||||||
|
global is_downloading
|
||||||
|
is_downloading = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
from application.services.queue_service import start_next_download, move_download_to_completed, update_download_progress
|
||||||
|
|
||||||
|
# Emit download started
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_started')
|
||||||
|
|
||||||
|
# Process queue items
|
||||||
|
while True:
|
||||||
|
# Check for stop signal
|
||||||
|
global should_stop_downloads
|
||||||
|
if should_stop_downloads:
|
||||||
|
should_stop_downloads = False # Reset the flag
|
||||||
|
break
|
||||||
|
|
||||||
|
# Start next download
|
||||||
|
current_download = start_next_download()
|
||||||
|
if not current_download:
|
||||||
|
break # No more items in queue
|
||||||
|
|
||||||
|
try:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_progress', {
|
||||||
|
'id': current_download['id'],
|
||||||
|
'serie': current_download['serie_name'],
|
||||||
|
'episode': current_download['episode']['episode_number'],
|
||||||
|
'status': 'downloading'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Find the serie in our series list to get the key
|
||||||
|
serie = None
|
||||||
|
if series_app and series_app.List:
|
||||||
|
for s in series_app.List.GetList():
|
||||||
|
if s.folder == current_download['episode']['folder']:
|
||||||
|
serie = s
|
||||||
|
break
|
||||||
|
|
||||||
|
if not serie:
|
||||||
|
raise Exception(f"Serie not found: {current_download['episode']['folder']}")
|
||||||
|
|
||||||
|
# Check if serie has a valid key
|
||||||
|
if not hasattr(serie, 'key') or not serie.key:
|
||||||
|
raise Exception(f"Serie '{serie.name or serie.folder}' has no valid key. Please rescan or search for this series first.")
|
||||||
|
|
||||||
|
# Check if episode info indicates no missing episodes
|
||||||
|
if current_download['episode']['episode_number'] == 'Complete':
|
||||||
|
# Mark as completed immediately - no episodes to download
|
||||||
|
move_download_to_completed(current_download['id'], success=True)
|
||||||
|
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_completed', {
|
||||||
|
'id': current_download['id'],
|
||||||
|
'serie': current_download['serie_name'],
|
||||||
|
'episode': 'No missing episodes'
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create progress callback for real download
|
||||||
|
def progress_callback(d):
|
||||||
|
# Check for stop signal during download
|
||||||
|
global should_stop_downloads
|
||||||
|
if should_stop_downloads:
|
||||||
|
return
|
||||||
|
|
||||||
|
if d['status'] == 'downloading':
|
||||||
|
total = d.get('total_bytes') or d.get('total_bytes_estimate')
|
||||||
|
downloaded = d.get('downloaded_bytes', 0)
|
||||||
|
|
||||||
|
if total and downloaded:
|
||||||
|
percent = (downloaded / total) * 100
|
||||||
|
speed_bytes_per_sec = d.get('speed', 0) or 0
|
||||||
|
speed_mbps = (speed_bytes_per_sec * 8) / (1024 * 1024) if speed_bytes_per_sec else 0 # Convert to Mbps
|
||||||
|
|
||||||
|
# Calculate ETA
|
||||||
|
eta_seconds = 0
|
||||||
|
if speed_bytes_per_sec > 0:
|
||||||
|
remaining_bytes = total - downloaded
|
||||||
|
eta_seconds = remaining_bytes / speed_bytes_per_sec
|
||||||
|
|
||||||
|
update_download_progress(current_download['id'], {
|
||||||
|
'percent': percent,
|
||||||
|
'speed_mbps': speed_mbps,
|
||||||
|
'eta_seconds': eta_seconds,
|
||||||
|
'downloaded_bytes': downloaded,
|
||||||
|
'total_bytes': total
|
||||||
|
})
|
||||||
|
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_progress', {
|
||||||
|
'id': current_download['id'],
|
||||||
|
'serie': current_download['serie_name'],
|
||||||
|
'episode': current_download['episode']['episode_number'],
|
||||||
|
'progress': percent,
|
||||||
|
'speed_mbps': speed_mbps,
|
||||||
|
'eta_seconds': eta_seconds
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Progress without total size
|
||||||
|
downloaded_mb = downloaded / (1024 * 1024) if downloaded else 0
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_progress', {
|
||||||
|
'id': current_download['id'],
|
||||||
|
'serie': current_download['serie_name'],
|
||||||
|
'episode': current_download['episode']['episode_number'],
|
||||||
|
'progress': 0,
|
||||||
|
'downloaded_mb': downloaded_mb
|
||||||
|
})
|
||||||
|
|
||||||
|
elif d['status'] == 'finished':
|
||||||
|
update_download_progress(current_download['id'], {
|
||||||
|
'percent': 100,
|
||||||
|
'speed_mbps': 0,
|
||||||
|
'eta_seconds': 0
|
||||||
|
})
|
||||||
|
|
||||||
|
# Perform actual download using the loader
|
||||||
|
loader = series_app.Loaders.GetLoader(key="aniworld.to")
|
||||||
|
|
||||||
|
# Check if we should stop before starting download
|
||||||
|
if should_stop_downloads:
|
||||||
|
move_download_to_completed(current_download['id'], success=False, error='Download stopped by user')
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_stopped', {
|
||||||
|
'message': 'Download queue stopped by user'
|
||||||
|
})
|
||||||
|
should_stop_downloads = False
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check language availability first
|
||||||
|
season = current_download['episode']['season']
|
||||||
|
episode_num = current_download['episode']['episode_number']
|
||||||
|
|
||||||
|
# Ensure episode_num is an integer
|
||||||
|
try:
|
||||||
|
episode_num = int(episode_num)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise Exception(f"Invalid episode number: {episode_num}")
|
||||||
|
|
||||||
|
# Ensure season is an integer (can be None for some entries)
|
||||||
|
if season is None:
|
||||||
|
season = 1 # Default to season 1
|
||||||
|
try:
|
||||||
|
season = int(season)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise Exception(f"Invalid season number: {season}")
|
||||||
|
|
||||||
|
# Log the download attempt
|
||||||
|
print(f"Starting download: {serie.name} S{season:02d}E{episode_num:02d}")
|
||||||
|
|
||||||
|
if not loader.IsLanguage(season, episode_num, serie.key):
|
||||||
|
raise Exception(f"Episode S{season:02d}E{episode_num:02d} not available in German Dub")
|
||||||
|
|
||||||
|
# Perform the actual download with retry logic
|
||||||
|
success = False
|
||||||
|
for attempt in range(3): # 3 retry attempts
|
||||||
|
if should_stop_downloads:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
success = loader.Download(
|
||||||
|
baseDirectory=series_app.directory_to_search,
|
||||||
|
serieFolder=serie.folder,
|
||||||
|
season=season,
|
||||||
|
episode=episode_num,
|
||||||
|
key=serie.key,
|
||||||
|
language="German Dub",
|
||||||
|
progress_callback=progress_callback
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if attempt == 2: # Last attempt
|
||||||
|
raise e
|
||||||
|
import time
|
||||||
|
time.sleep(2) # Wait before retry
|
||||||
|
|
||||||
|
if should_stop_downloads:
|
||||||
|
move_download_to_completed(current_download['id'], success=False, error='Download stopped by user')
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_stopped', {
|
||||||
|
'message': 'Download queue stopped by user'
|
||||||
|
})
|
||||||
|
should_stop_downloads = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Mark as completed
|
||||||
|
move_download_to_completed(current_download['id'], success=True)
|
||||||
|
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_completed', {
|
||||||
|
'id': current_download['id'],
|
||||||
|
'serie': current_download['serie_name'],
|
||||||
|
'episode': current_download['episode']['episode_number']
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
raise Exception("Download failed after all retry attempts")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Mark as failed
|
||||||
|
move_download_to_completed(current_download['id'], success=False, error=str(e))
|
||||||
|
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_error', {
|
||||||
|
'id': current_download['id'],
|
||||||
|
'serie': current_download['serie_name'],
|
||||||
|
'episode': current_download['episode']['episode_number'],
|
||||||
|
'error': str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Emit download queue completed
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_queue_completed')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_error', {'message': str(e)})
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
is_downloading = False
|
||||||
|
|
||||||
|
perform_downloads(_locked_by='web_interface')
|
||||||
|
|
||||||
|
except ProcessLockError:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_error', {'message': 'Download is already running'})
|
||||||
|
except Exception as e:
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_error', {'message': str(e)})
|
||||||
|
|
||||||
|
# Start download in background thread
|
||||||
|
threading.Thread(target=download_thread, daemon=True).start()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Download queue processing started'
|
||||||
|
})
|
||||||
|
|
||||||
|
@api_bp.route('/queue/stop', methods=['POST'])
|
||||||
|
@optional_auth
|
||||||
|
def stop_download_queue():
|
||||||
|
"""Stop processing the download queue."""
|
||||||
|
global is_downloading, should_stop_downloads
|
||||||
|
|
||||||
|
# Check if any download is currently running
|
||||||
|
if not is_downloading and not is_process_running(DOWNLOAD_LOCK):
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'No download is currently running'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Set stop signal for graceful shutdown
|
||||||
|
should_stop_downloads = True
|
||||||
|
|
||||||
|
# Don't forcefully set is_downloading to False here, let the download thread handle it
|
||||||
|
# This prevents race conditions where the thread might still be running
|
||||||
|
|
||||||
|
# Emit stop signal to clients immediately
|
||||||
|
if socketio:
|
||||||
|
socketio.emit('download_stop_requested')
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Download stop requested. Downloads will stop gracefully.'
|
||||||
|
})
|
||||||
|
|
||||||
|
@api_bp.route('/status', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_status():
|
||||||
|
"""Get current system status."""
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
# Get anime directory from environment or config
|
||||||
|
anime_directory = os.environ.get('ANIME_DIRECTORY', 'Not configured')
|
||||||
|
|
||||||
|
# Get series count (placeholder implementation)
|
||||||
|
series_count = 0
|
||||||
|
try:
|
||||||
|
# This would normally get the actual series count from your series scanner
|
||||||
|
# For now, return a placeholder value
|
||||||
|
series_count = 0
|
||||||
|
except Exception:
|
||||||
|
series_count = 0
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'directory': anime_directory,
|
||||||
|
'series_count': series_count,
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e),
|
||||||
|
'directory': 'Error',
|
||||||
|
'series_count': 0
|
||||||
|
})
|
||||||
|
|
||||||
|
@api_bp.route('/process/locks/status', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def process_locks_status():
|
||||||
|
"""Get current process lock status."""
|
||||||
|
try:
|
||||||
|
# Use the constants and functions defined above in this file
|
||||||
|
|
||||||
|
locks = {
|
||||||
|
'rescan': {
|
||||||
|
'is_locked': is_process_running(RESCAN_LOCK),
|
||||||
|
'locked_by': 'system' if is_process_running(RESCAN_LOCK) else None,
|
||||||
|
'lock_time': None # Could be extended to track actual lock times
|
||||||
|
},
|
||||||
|
'download': {
|
||||||
|
'is_locked': is_process_running(DOWNLOAD_LOCK),
|
||||||
|
'locked_by': 'system' if is_process_running(DOWNLOAD_LOCK) else None,
|
||||||
|
'lock_time': None # Could be extended to track actual lock times
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'locks': locks,
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e),
|
||||||
|
'locks': {
|
||||||
|
'rescan': {'is_locked': False, 'locked_by': None, 'lock_time': None},
|
||||||
|
'download': {'is_locked': False, 'locked_by': None, 'lock_time': None}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Initialize the series app when the blueprint is loaded
|
||||||
|
try:
|
||||||
|
init_series_app()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to initialize series app in API blueprint: {e}")
|
||||||
|
series_app = None
|
||||||
132
src/server/web/routes/auth_routes.py
Normal file
132
src/server/web/routes/auth_routes.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
"""
|
||||||
|
Authentication routes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
|
||||||
|
from web.controllers.auth_controller import session_manager, require_auth
|
||||||
|
|
||||||
|
# Create separate blueprints for API and page routes
|
||||||
|
auth_bp = Blueprint('auth', __name__)
|
||||||
|
auth_api_bp = Blueprint('auth_api', __name__, url_prefix='/api/auth')
|
||||||
|
|
||||||
|
# Import config at module level to avoid circular imports
|
||||||
|
from config import config
|
||||||
|
|
||||||
|
def init_series_app():
|
||||||
|
"""Initialize the SeriesApp with configuration directory."""
|
||||||
|
from main import SeriesApp
|
||||||
|
directory_to_search = config.anime_directory
|
||||||
|
return SeriesApp(directory_to_search)
|
||||||
|
|
||||||
|
# API Routes
|
||||||
|
@auth_api_bp.route('/setup', methods=['POST'])
|
||||||
|
def auth_setup():
|
||||||
|
"""Complete initial setup."""
|
||||||
|
if config.has_master_password():
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Setup already completed'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
password = data.get('password')
|
||||||
|
directory = data.get('directory')
|
||||||
|
|
||||||
|
if not password or len(password) < 8:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Password must be at least 8 characters long'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
if not directory:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Directory is required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Set master password and directory
|
||||||
|
config.set_master_password(password)
|
||||||
|
config.anime_directory = directory
|
||||||
|
config.save_config()
|
||||||
|
|
||||||
|
# Reinitialize series app with new directory
|
||||||
|
init_series_app()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Setup completed successfully'
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@auth_api_bp.route('/login', methods=['POST'])
|
||||||
|
def auth_login():
|
||||||
|
"""Authenticate user."""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
if not password:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Password is required'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Verify password using session manager
|
||||||
|
result = session_manager.login(password, request.remote_addr)
|
||||||
|
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@auth_api_bp.route('/logout', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def auth_logout():
|
||||||
|
"""Logout user."""
|
||||||
|
session_manager.logout()
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'Logged out successfully'
|
||||||
|
})
|
||||||
|
|
||||||
|
@auth_api_bp.route('/status', methods=['GET'])
|
||||||
|
def auth_status():
|
||||||
|
"""Get authentication status."""
|
||||||
|
return jsonify({
|
||||||
|
'authenticated': session_manager.is_authenticated(),
|
||||||
|
'has_master_password': config.has_master_password(),
|
||||||
|
'setup_required': not config.has_master_password(),
|
||||||
|
'session_info': session_manager.get_session_info()
|
||||||
|
})
|
||||||
|
|
||||||
|
# Page Routes (Non-API)
|
||||||
|
@auth_bp.route('/login')
|
||||||
|
def login():
|
||||||
|
"""Login page."""
|
||||||
|
if not config.has_master_password():
|
||||||
|
return redirect(url_for('auth.setup'))
|
||||||
|
|
||||||
|
if session_manager.is_authenticated():
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
|
return render_template('login.html',
|
||||||
|
session_timeout=config.session_timeout_hours,
|
||||||
|
max_attempts=config.max_failed_attempts,
|
||||||
|
lockout_duration=config.lockout_duration_minutes)
|
||||||
|
|
||||||
|
@auth_bp.route('/setup')
|
||||||
|
def setup():
|
||||||
|
"""Initial setup page."""
|
||||||
|
if config.has_master_password():
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
return render_template('setup.html', current_directory=config.anime_directory)
|
||||||
191
src/server/web/routes/config_routes.py
Normal file
191
src/server/web/routes/config_routes.py
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
"""
|
||||||
|
Configuration management routes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from web.controllers.auth_controller import optional_auth, require_auth
|
||||||
|
|
||||||
|
config_bp = Blueprint('config', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
# Simple decorator to handle API errors
|
||||||
|
def handle_api_errors(f):
|
||||||
|
"""Simple error handling decorator."""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
# Scheduler configuration endpoints
|
||||||
|
@config_bp.route('/scheduler/config', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_scheduler_config():
|
||||||
|
"""Get scheduler configuration."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'config': {
|
||||||
|
'enabled': False,
|
||||||
|
'time': '03:00',
|
||||||
|
'auto_download_after_rescan': False,
|
||||||
|
'next_run': None,
|
||||||
|
'last_run': None,
|
||||||
|
'is_running': False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/scheduler/config', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def set_scheduler_config():
|
||||||
|
"""Set scheduler configuration."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Scheduler configuration saved (placeholder)'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Logging configuration endpoints
|
||||||
|
@config_bp.route('/logging/config', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_logging_config():
|
||||||
|
"""Get logging configuration."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'config': {
|
||||||
|
'log_level': 'INFO',
|
||||||
|
'enable_console_logging': True,
|
||||||
|
'enable_console_progress': True,
|
||||||
|
'enable_fail2ban_logging': False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/logging/config', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def set_logging_config():
|
||||||
|
"""Set logging configuration."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Logging configuration saved (placeholder)'
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/logging/files', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_log_files():
|
||||||
|
"""Get available log files."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'files': []
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/logging/test', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def test_logging():
|
||||||
|
"""Test logging functionality."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Test logging completed (placeholder)'
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/logging/cleanup', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def cleanup_logs():
|
||||||
|
"""Clean up old log files."""
|
||||||
|
data = request.get_json()
|
||||||
|
days = data.get('days', 30) if data else 30
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': f'Log files older than {days} days have been cleaned up (placeholder)'
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/logging/files/<filename>/tail')
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def tail_log_file(filename):
|
||||||
|
"""Get the tail of a log file."""
|
||||||
|
lines = request.args.get('lines', 100, type=int)
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'content': f'Last {lines} lines of {filename} (placeholder)',
|
||||||
|
'filename': filename
|
||||||
|
})
|
||||||
|
|
||||||
|
# Advanced configuration endpoints
|
||||||
|
@config_bp.route('/config/section/advanced', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_advanced_config():
|
||||||
|
"""Get advanced configuration."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'config': {
|
||||||
|
'max_concurrent_downloads': 3,
|
||||||
|
'provider_timeout': 30,
|
||||||
|
'enable_debug_mode': False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/config/section/advanced', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def set_advanced_config():
|
||||||
|
"""Set advanced configuration."""
|
||||||
|
data = request.get_json()
|
||||||
|
# Here you would normally save the configuration
|
||||||
|
# For now, we'll just return success
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Advanced configuration saved successfully'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Configuration backup endpoints
|
||||||
|
@config_bp.route('/config/backup', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def create_config_backup():
|
||||||
|
"""Create a configuration backup."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Configuration backup created successfully',
|
||||||
|
'filename': f'config_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/config/backups', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_config_backups():
|
||||||
|
"""Get list of configuration backups."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'backups': [] # Empty list for now - would normally list actual backup files
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/config/backup/<filename>/restore', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def restore_config_backup(filename):
|
||||||
|
"""Restore a configuration backup."""
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': f'Configuration restored from {filename}'
|
||||||
|
})
|
||||||
|
|
||||||
|
@config_bp.route('/config/backup/<filename>/download', methods=['GET'])
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def download_config_backup(filename):
|
||||||
|
"""Download a configuration backup file."""
|
||||||
|
# For now, return an empty response - would normally serve the actual file
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Backup download endpoint (placeholder)'
|
||||||
|
})
|
||||||
176
src/server/web/routes/diagnostic_routes.py
Normal file
176
src/server/web/routes/diagnostic_routes.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
"""
|
||||||
|
Diagnostic and monitoring routes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from web.controllers.auth_controller import optional_auth, require_auth
|
||||||
|
|
||||||
|
diagnostic_bp = Blueprint('diagnostic', __name__, url_prefix='/api/diagnostics')
|
||||||
|
|
||||||
|
# Simple decorator to handle API errors
|
||||||
|
def handle_api_errors(f):
|
||||||
|
"""Simple error handling decorator."""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
# Placeholder objects for missing modules
|
||||||
|
class PlaceholderNetworkChecker:
|
||||||
|
def get_network_status(self):
|
||||||
|
return {
|
||||||
|
"status": "unknown",
|
||||||
|
"connected": True,
|
||||||
|
"ping_ms": 0,
|
||||||
|
"dns_working": True
|
||||||
|
}
|
||||||
|
def check_url_reachability(self, url):
|
||||||
|
return True
|
||||||
|
|
||||||
|
class PlaceholderErrorManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.error_history = []
|
||||||
|
self.blacklisted_urls = {}
|
||||||
|
self.retry_counts = {}
|
||||||
|
|
||||||
|
class PlaceholderHealthMonitor:
|
||||||
|
def get_current_health_status(self):
|
||||||
|
return {
|
||||||
|
"status": "healthy",
|
||||||
|
"uptime": "1h 30m",
|
||||||
|
"memory_usage": "45%",
|
||||||
|
"cpu_usage": "12%"
|
||||||
|
}
|
||||||
|
|
||||||
|
class RetryableError(Exception):
|
||||||
|
"""Placeholder exception for retryable errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
network_health_checker = PlaceholderNetworkChecker()
|
||||||
|
error_recovery_manager = PlaceholderErrorManager()
|
||||||
|
health_monitor = PlaceholderHealthMonitor()
|
||||||
|
|
||||||
|
# Placeholder process lock constants and functions
|
||||||
|
RESCAN_LOCK = "rescan"
|
||||||
|
DOWNLOAD_LOCK = "download"
|
||||||
|
|
||||||
|
# Simple in-memory process lock system
|
||||||
|
_active_locks = {}
|
||||||
|
|
||||||
|
def is_process_running(lock_name):
|
||||||
|
"""Check if a process is currently running (locked)."""
|
||||||
|
return lock_name in _active_locks
|
||||||
|
|
||||||
|
@diagnostic_bp.route('/network')
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def network_diagnostics():
|
||||||
|
"""Get network diagnostics and connectivity status."""
|
||||||
|
try:
|
||||||
|
network_status = network_health_checker.get_network_status()
|
||||||
|
|
||||||
|
# Test AniWorld connectivity
|
||||||
|
aniworld_reachable = network_health_checker.check_url_reachability("https://aniworld.to")
|
||||||
|
network_status['aniworld_reachable'] = aniworld_reachable
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'data': network_status
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
raise RetryableError(f"Network diagnostics failed: {e}")
|
||||||
|
|
||||||
|
@diagnostic_bp.route('/errors')
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_error_history():
|
||||||
|
"""Get recent error history."""
|
||||||
|
try:
|
||||||
|
recent_errors = error_recovery_manager.error_history[-50:] # Last 50 errors
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'recent_errors': recent_errors,
|
||||||
|
'total_errors': len(error_recovery_manager.error_history),
|
||||||
|
'blacklisted_urls': list(error_recovery_manager.blacklisted_urls.keys())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
raise RetryableError(f"Error history retrieval failed: {e}")
|
||||||
|
|
||||||
|
@diagnostic_bp.route('/system-status')
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def system_status_summary():
|
||||||
|
"""Get comprehensive system status summary."""
|
||||||
|
try:
|
||||||
|
# Get health status
|
||||||
|
health_status = health_monitor.get_current_health_status()
|
||||||
|
|
||||||
|
# Get network status
|
||||||
|
network_status = network_health_checker.get_network_status()
|
||||||
|
|
||||||
|
# Get process status
|
||||||
|
process_status = {
|
||||||
|
'rescan_running': is_process_running(RESCAN_LOCK),
|
||||||
|
'download_running': is_process_running(DOWNLOAD_LOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get error statistics
|
||||||
|
error_stats = {
|
||||||
|
'total_errors': len(error_recovery_manager.error_history),
|
||||||
|
'recent_errors': len([e for e in error_recovery_manager.error_history
|
||||||
|
if (datetime.now() - datetime.fromisoformat(e.get('timestamp', datetime.now().isoformat()))).seconds < 3600]),
|
||||||
|
'blacklisted_urls': len(error_recovery_manager.blacklisted_urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'health': health_status,
|
||||||
|
'network': network_status,
|
||||||
|
'processes': process_status,
|
||||||
|
'errors': error_stats,
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
raise RetryableError(f"System status retrieval failed: {e}")
|
||||||
|
|
||||||
|
# Recovery routes
|
||||||
|
@diagnostic_bp.route('/recovery/clear-blacklist', methods=['POST'])
|
||||||
|
@handle_api_errors
|
||||||
|
@require_auth
|
||||||
|
def clear_blacklist():
|
||||||
|
"""Clear URL blacklist."""
|
||||||
|
try:
|
||||||
|
error_recovery_manager.blacklisted_urls.clear()
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': 'URL blacklist cleared successfully'
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
raise RetryableError(f"Blacklist clearing failed: {e}")
|
||||||
|
|
||||||
|
@diagnostic_bp.route('/recovery/retry-counts')
|
||||||
|
@handle_api_errors
|
||||||
|
@optional_auth
|
||||||
|
def get_retry_counts():
|
||||||
|
"""Get retry statistics."""
|
||||||
|
try:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'retry_counts': error_recovery_manager.retry_counts,
|
||||||
|
'total_retries': sum(error_recovery_manager.retry_counts.values())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
raise RetryableError(f"Retry statistics retrieval failed: {e}")
|
||||||
30
src/server/web/routes/main_routes.py
Normal file
30
src/server/web/routes/main_routes.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
Main application routes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, render_template, redirect, url_for
|
||||||
|
from web.controllers.auth_controller import optional_auth
|
||||||
|
|
||||||
|
main_bp = Blueprint('main', __name__)
|
||||||
|
|
||||||
|
# Placeholder process lock constants and functions
|
||||||
|
RESCAN_LOCK = "rescan"
|
||||||
|
DOWNLOAD_LOCK = "download"
|
||||||
|
|
||||||
|
# Simple in-memory process lock system
|
||||||
|
_active_locks = {}
|
||||||
|
|
||||||
|
def is_process_running(lock_name):
|
||||||
|
"""Check if a process is currently running (locked)."""
|
||||||
|
return lock_name in _active_locks
|
||||||
|
|
||||||
|
@main_bp.route('/')
|
||||||
|
@optional_auth
|
||||||
|
def index():
|
||||||
|
"""Main page route."""
|
||||||
|
# Check process status
|
||||||
|
process_status = {
|
||||||
|
'rescan_running': is_process_running(RESCAN_LOCK),
|
||||||
|
'download_running': is_process_running(DOWNLOAD_LOCK)
|
||||||
|
}
|
||||||
|
return render_template('index.html', process_status=process_status)
|
||||||
145
src/server/web/routes/static_routes.py
Normal file
145
src/server/web/routes/static_routes.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
Static file and JavaScript routes for UX features.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, Response
|
||||||
|
|
||||||
|
static_bp = Blueprint('static', __name__)
|
||||||
|
|
||||||
|
# Create placeholder managers for missing modules
|
||||||
|
class PlaceholderManager:
|
||||||
|
"""Placeholder manager for missing UX modules."""
|
||||||
|
def get_shortcuts_js(self): return ""
|
||||||
|
def get_drag_drop_js(self): return ""
|
||||||
|
def get_bulk_operations_js(self): return ""
|
||||||
|
def get_preferences_js(self): return ""
|
||||||
|
def get_search_js(self): return ""
|
||||||
|
def get_undo_redo_js(self): return ""
|
||||||
|
def get_mobile_responsive_js(self): return ""
|
||||||
|
def get_touch_gesture_js(self): return ""
|
||||||
|
def get_accessibility_js(self): return ""
|
||||||
|
def get_screen_reader_js(self): return ""
|
||||||
|
def get_contrast_js(self): return ""
|
||||||
|
def get_multiscreen_js(self): return ""
|
||||||
|
def get_css(self): return ""
|
||||||
|
def get_contrast_css(self): return ""
|
||||||
|
def get_multiscreen_css(self): return ""
|
||||||
|
|
||||||
|
# Create placeholder instances
|
||||||
|
keyboard_manager = PlaceholderManager()
|
||||||
|
drag_drop_manager = PlaceholderManager()
|
||||||
|
bulk_operations_manager = PlaceholderManager()
|
||||||
|
preferences_manager = PlaceholderManager()
|
||||||
|
advanced_search_manager = PlaceholderManager()
|
||||||
|
undo_redo_manager = PlaceholderManager()
|
||||||
|
mobile_responsive_manager = PlaceholderManager()
|
||||||
|
touch_gesture_manager = PlaceholderManager()
|
||||||
|
accessibility_manager = PlaceholderManager()
|
||||||
|
screen_reader_manager = PlaceholderManager()
|
||||||
|
color_contrast_manager = PlaceholderManager()
|
||||||
|
multi_screen_manager = PlaceholderManager()
|
||||||
|
|
||||||
|
# UX JavaScript routes
|
||||||
|
@static_bp.route('/static/js/keyboard-shortcuts.js')
|
||||||
|
def keyboard_shortcuts_js():
|
||||||
|
"""Serve keyboard shortcuts JavaScript."""
|
||||||
|
js_content = keyboard_manager.get_shortcuts_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/drag-drop.js')
|
||||||
|
def drag_drop_js():
|
||||||
|
"""Serve drag and drop JavaScript."""
|
||||||
|
js_content = drag_drop_manager.get_drag_drop_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/bulk-operations.js')
|
||||||
|
def bulk_operations_js():
|
||||||
|
"""Serve bulk operations JavaScript."""
|
||||||
|
js_content = bulk_operations_manager.get_bulk_operations_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/user-preferences.js')
|
||||||
|
def user_preferences_js():
|
||||||
|
"""Serve user preferences JavaScript."""
|
||||||
|
js_content = preferences_manager.get_preferences_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/advanced-search.js')
|
||||||
|
def advanced_search_js():
|
||||||
|
"""Serve advanced search JavaScript."""
|
||||||
|
js_content = advanced_search_manager.get_search_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/undo-redo.js')
|
||||||
|
def undo_redo_js():
|
||||||
|
"""Serve undo/redo JavaScript."""
|
||||||
|
js_content = undo_redo_manager.get_undo_redo_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
# Mobile & Accessibility JavaScript routes
|
||||||
|
@static_bp.route('/static/js/mobile-responsive.js')
|
||||||
|
def mobile_responsive_js():
|
||||||
|
"""Serve mobile responsive JavaScript."""
|
||||||
|
js_content = mobile_responsive_manager.get_mobile_responsive_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/touch-gestures.js')
|
||||||
|
def touch_gestures_js():
|
||||||
|
"""Serve touch gestures JavaScript."""
|
||||||
|
js_content = touch_gesture_manager.get_touch_gesture_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/accessibility-features.js')
|
||||||
|
def accessibility_features_js():
|
||||||
|
"""Serve accessibility features JavaScript."""
|
||||||
|
js_content = accessibility_manager.get_accessibility_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/screen-reader-support.js')
|
||||||
|
def screen_reader_support_js():
|
||||||
|
"""Serve screen reader support JavaScript."""
|
||||||
|
js_content = screen_reader_manager.get_screen_reader_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/color-contrast-compliance.js')
|
||||||
|
def color_contrast_compliance_js():
|
||||||
|
"""Serve color contrast compliance JavaScript."""
|
||||||
|
js_content = color_contrast_manager.get_contrast_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/js/multi-screen-support.js')
|
||||||
|
def multi_screen_support_js():
|
||||||
|
"""Serve multi-screen support JavaScript."""
|
||||||
|
js_content = multi_screen_manager.get_multiscreen_js()
|
||||||
|
return Response(js_content, mimetype='application/javascript')
|
||||||
|
|
||||||
|
@static_bp.route('/static/css/ux-features.css')
|
||||||
|
def ux_features_css():
|
||||||
|
"""Serve UX features CSS."""
|
||||||
|
css_content = f"""
|
||||||
|
/* Keyboard shortcuts don't require additional CSS */
|
||||||
|
|
||||||
|
{drag_drop_manager.get_css()}
|
||||||
|
|
||||||
|
{bulk_operations_manager.get_css()}
|
||||||
|
|
||||||
|
{preferences_manager.get_css()}
|
||||||
|
|
||||||
|
{advanced_search_manager.get_css()}
|
||||||
|
|
||||||
|
{undo_redo_manager.get_css()}
|
||||||
|
|
||||||
|
/* Mobile & Accessibility CSS */
|
||||||
|
{mobile_responsive_manager.get_css()}
|
||||||
|
|
||||||
|
{touch_gesture_manager.get_css()}
|
||||||
|
|
||||||
|
{accessibility_manager.get_css()}
|
||||||
|
|
||||||
|
{screen_reader_manager.get_css()}
|
||||||
|
|
||||||
|
{color_contrast_manager.get_contrast_css()}
|
||||||
|
|
||||||
|
{multi_screen_manager.get_multiscreen_css()}
|
||||||
|
"""
|
||||||
|
return Response(css_content, mimetype='text/css')
|
||||||
54
src/server/web/routes/websocket_handlers.py
Normal file
54
src/server/web/routes/websocket_handlers.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
WebSocket event handlers for real-time updates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask_socketio import emit
|
||||||
|
|
||||||
|
# Placeholder process lock constants and functions
|
||||||
|
RESCAN_LOCK = "rescan"
|
||||||
|
DOWNLOAD_LOCK = "download"
|
||||||
|
|
||||||
|
# Simple in-memory process lock system
|
||||||
|
_active_locks = {}
|
||||||
|
|
||||||
|
def is_process_running(lock_name):
|
||||||
|
"""Check if a process is currently running (locked)."""
|
||||||
|
return lock_name in _active_locks
|
||||||
|
|
||||||
|
def register_socketio_handlers(socketio):
|
||||||
|
"""Register WebSocket event handlers."""
|
||||||
|
|
||||||
|
@socketio.on('connect')
|
||||||
|
def handle_connect():
|
||||||
|
"""Handle client connection."""
|
||||||
|
emit('status', {
|
||||||
|
'message': 'Connected to server',
|
||||||
|
'processes': {
|
||||||
|
'rescan_running': is_process_running(RESCAN_LOCK),
|
||||||
|
'download_running': is_process_running(DOWNLOAD_LOCK)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@socketio.on('disconnect')
|
||||||
|
def handle_disconnect():
|
||||||
|
"""Handle client disconnection."""
|
||||||
|
print('Client disconnected')
|
||||||
|
|
||||||
|
@socketio.on('get_status')
|
||||||
|
def handle_get_status():
|
||||||
|
"""Handle status request."""
|
||||||
|
# Import series_app from the main module if available
|
||||||
|
try:
|
||||||
|
from main import SeriesApp
|
||||||
|
# This would need to be properly initialized
|
||||||
|
series_count = 0 # Placeholder
|
||||||
|
except:
|
||||||
|
series_count = 0
|
||||||
|
|
||||||
|
emit('status_update', {
|
||||||
|
'processes': {
|
||||||
|
'rescan_running': is_process_running(RESCAN_LOCK),
|
||||||
|
'download_running': is_process_running(DOWNLOAD_LOCK)
|
||||||
|
},
|
||||||
|
'series_count': series_count
|
||||||
|
})
|
||||||
@ -869,10 +869,6 @@ body {
|
|||||||
.process-status {
|
.process-status {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-text {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@ -1441,16 +1437,25 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator i {
|
.status-indicator i {
|
||||||
font-size: 12px;
|
font-size: 24px;
|
||||||
|
/* 2x bigger: 12px -> 24px */
|
||||||
|
transition: all var(--animation-duration-normal) var(--animation-easing-standard);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-text {
|
/* Rescan icon specific styling */
|
||||||
font-weight: 500;
|
#rescan-status i {
|
||||||
white-space: nowrap;
|
color: var(--color-text-disabled);
|
||||||
flex-shrink: 0;
|
/* Gray when idle */
|
||||||
margin-left: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rescan-status.running i {
|
||||||
|
color: #22c55e;
|
||||||
|
/* Green when running */
|
||||||
|
animation: iconPulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status text removed - using tooltips only */
|
||||||
|
|
||||||
.status-dot {
|
.status-dot {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@ -1485,6 +1490,20 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes iconPulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1.1) rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Process status in mobile view */
|
/* Process status in mobile view */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.process-status {
|
.process-status {
|
||||||
@ -1499,12 +1518,9 @@ body {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-text {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator i {
|
.status-indicator i {
|
||||||
font-size: 14px;
|
font-size: 20px;
|
||||||
|
/* Maintain 2x scale for mobile: was 14px -> 20px */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -206,16 +206,42 @@ class AniWorldApp {
|
|||||||
this.socket.on('download_started', (data) => {
|
this.socket.on('download_started', (data) => {
|
||||||
this.isDownloading = true;
|
this.isDownloading = true;
|
||||||
this.isPaused = false;
|
this.isPaused = false;
|
||||||
|
this.updateProcessStatus('download', true);
|
||||||
this.showDownloadQueue(data);
|
this.showDownloadQueue(data);
|
||||||
this.showStatus(`Starting download of ${data.total_series} series...`, true, true);
|
this.showStatus(`Starting download of ${data.total_series} series...`, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('download_progress', (data) => {
|
this.socket.on('download_progress', (data) => {
|
||||||
if (data.total_bytes) {
|
let status = '';
|
||||||
const percent = ((data.downloaded_bytes || 0) / data.total_bytes * 100).toFixed(1);
|
let percent = 0;
|
||||||
this.updateProgress(percent, `Downloading: ${percent}%`);
|
|
||||||
|
if (data.progress !== undefined) {
|
||||||
|
percent = data.progress;
|
||||||
|
status = `Downloading: ${percent.toFixed(1)}%`;
|
||||||
|
|
||||||
|
// Add speed information if available
|
||||||
|
if (data.speed_mbps && data.speed_mbps > 0) {
|
||||||
|
status += ` (${data.speed_mbps.toFixed(1)} Mbps)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ETA information if available
|
||||||
|
if (data.eta_seconds && data.eta_seconds > 0) {
|
||||||
|
const eta = this.formatETA(data.eta_seconds);
|
||||||
|
status += ` - ETA: ${eta}`;
|
||||||
|
}
|
||||||
|
} else if (data.total_bytes) {
|
||||||
|
percent = ((data.downloaded_bytes || 0) / data.total_bytes * 100);
|
||||||
|
status = `Downloading: ${percent.toFixed(1)}%`;
|
||||||
|
} else if (data.downloaded_mb !== undefined) {
|
||||||
|
status = `Downloaded: ${data.downloaded_mb.toFixed(1)} MB`;
|
||||||
} else {
|
} else {
|
||||||
this.updateStatus(`Downloading: ${data.percent || '0%'}`);
|
status = `Downloading: ${data.percent || '0%'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (percent > 0) {
|
||||||
|
this.updateProgress(percent, status);
|
||||||
|
} else {
|
||||||
|
this.updateStatus(status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -237,6 +263,21 @@ class AniWorldApp {
|
|||||||
this.showToast(`${this.localization.getText('download-failed')}: ${data.message}`, 'error');
|
this.showToast(`${this.localization.getText('download-failed')}: ${data.message}`, 'error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Download queue status events
|
||||||
|
this.socket.on('download_queue_completed', () => {
|
||||||
|
this.updateProcessStatus('download', false);
|
||||||
|
this.showToast('All downloads completed!', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_stop_requested', () => {
|
||||||
|
this.showToast('Stopping downloads...', 'info');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_stopped', () => {
|
||||||
|
this.updateProcessStatus('download', false);
|
||||||
|
this.showToast('Downloads stopped', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
// Download queue events
|
// Download queue events
|
||||||
this.socket.on('download_queue_update', (data) => {
|
this.socket.on('download_queue_update', (data) => {
|
||||||
this.updateDownloadQueue(data);
|
this.updateDownloadQueue(data);
|
||||||
@ -476,7 +517,13 @@ class AniWorldApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async makeAuthenticatedRequest(url, options = {}) {
|
async makeAuthenticatedRequest(url, options = {}) {
|
||||||
const response = await fetch(url, options);
|
// Ensure credentials are included for session-based authentication
|
||||||
|
const requestOptions = {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, requestOptions);
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
@ -896,30 +943,45 @@ class AniWorldApp {
|
|||||||
const statusDot = statusElement.querySelector('.status-dot');
|
const statusDot = statusElement.querySelector('.status-dot');
|
||||||
if (!statusDot) return;
|
if (!statusDot) return;
|
||||||
|
|
||||||
// Remove all status classes
|
// Remove all status classes from both dot and element
|
||||||
statusDot.classList.remove('idle', 'running', 'error');
|
statusDot.classList.remove('idle', 'running', 'error');
|
||||||
|
statusElement.classList.remove('running', 'error', 'idle');
|
||||||
|
|
||||||
|
// Capitalize process name for display
|
||||||
|
const displayName = processName.charAt(0).toUpperCase() + processName.slice(1);
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
statusDot.classList.add('error');
|
statusDot.classList.add('error');
|
||||||
statusElement.title = `${processName} error - click for details`;
|
statusElement.classList.add('error');
|
||||||
|
statusElement.title = `${displayName} error - click for details`;
|
||||||
} else if (isRunning) {
|
} else if (isRunning) {
|
||||||
statusDot.classList.add('running');
|
statusDot.classList.add('running');
|
||||||
statusElement.title = `${processName} is running...`;
|
statusElement.classList.add('running');
|
||||||
|
statusElement.title = `${displayName} is running...`;
|
||||||
} else {
|
} else {
|
||||||
statusDot.classList.add('idle');
|
statusDot.classList.add('idle');
|
||||||
statusElement.title = `${processName} is idle`;
|
statusElement.classList.add('idle');
|
||||||
|
statusElement.title = `${displayName} is idle`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkProcessLocks() {
|
async checkProcessLocks() {
|
||||||
try {
|
try {
|
||||||
const response = await this.makeAuthenticatedRequest('/api/process/locks/status');
|
const response = await this.makeAuthenticatedRequest('/api/process/locks/status');
|
||||||
if (!response) return;
|
if (!response) {
|
||||||
|
// If no response, set status as idle
|
||||||
|
this.updateProcessStatus('rescan', false);
|
||||||
|
this.updateProcessStatus('download', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if response is actually JSON and not HTML (login page)
|
// Check if response is actually JSON and not HTML (login page)
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get('content-type');
|
||||||
if (!contentType || !contentType.includes('application/json')) {
|
if (!contentType || !contentType.includes('application/json')) {
|
||||||
console.warn('Process locks API returned non-JSON response, likely authentication issue');
|
console.warn('Process locks API returned non-JSON response, likely authentication issue');
|
||||||
|
// Set status as idle if we can't get proper response
|
||||||
|
this.updateProcessStatus('rescan', false);
|
||||||
|
this.updateProcessStatus('download', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,25 +997,40 @@ class AniWorldApp {
|
|||||||
if (rescanBtn) {
|
if (rescanBtn) {
|
||||||
if (locks.rescan?.is_locked) {
|
if (locks.rescan?.is_locked) {
|
||||||
rescanBtn.disabled = true;
|
rescanBtn.disabled = true;
|
||||||
rescanBtn.querySelector('span').textContent = 'Scanning...';
|
const span = rescanBtn.querySelector('span');
|
||||||
|
if (span) span.textContent = 'Scanning...';
|
||||||
} else {
|
} else {
|
||||||
rescanBtn.disabled = false;
|
rescanBtn.disabled = false;
|
||||||
rescanBtn.querySelector('span').textContent = 'Rescan';
|
const span = rescanBtn.querySelector('span');
|
||||||
|
if (span) span.textContent = 'Rescan';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If API returns error, set status as idle
|
||||||
|
console.warn('Process locks API returned error:', data.error);
|
||||||
|
this.updateProcessStatus('rescan', false);
|
||||||
|
this.updateProcessStatus('download', false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking process locks:', error);
|
console.error('Error checking process locks:', error);
|
||||||
|
// On error, set status as idle
|
||||||
|
this.updateProcessStatus('rescan', false);
|
||||||
|
this.updateProcessStatus('download', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startProcessStatusMonitoring() {
|
startProcessStatusMonitoring() {
|
||||||
|
// Initial check on page load
|
||||||
|
this.checkProcessLocks();
|
||||||
|
|
||||||
// Check process status every 5 seconds
|
// Check process status every 5 seconds
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
this.checkProcessLocks();
|
this.checkProcessLocks();
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
console.log('Process status monitoring started');
|
||||||
}
|
}
|
||||||
|
|
||||||
async showConfigModal() {
|
async showConfigModal() {
|
||||||
@ -1928,6 +2005,25 @@ class AniWorldApp {
|
|||||||
|
|
||||||
console.log('Mobile & Accessibility features initialized');
|
console.log('Mobile & Accessibility features initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatETA(seconds) {
|
||||||
|
if (!seconds || seconds <= 0) return '---';
|
||||||
|
|
||||||
|
if (seconds < 60) {
|
||||||
|
return `${Math.round(seconds)}s`;
|
||||||
|
} else if (seconds < 3600) {
|
||||||
|
const minutes = Math.round(seconds / 60);
|
||||||
|
return `${minutes}m`;
|
||||||
|
} else if (seconds < 86400) {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.round((seconds % 3600) / 60);
|
||||||
|
return `${hours}h ${minutes}m`;
|
||||||
|
} else {
|
||||||
|
const days = Math.floor(seconds / 86400);
|
||||||
|
const hours = Math.round((seconds % 86400) / 3600);
|
||||||
|
return `${days}d ${hours}h`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the application when DOM is loaded
|
// Initialize the application when DOM is loaded
|
||||||
|
|||||||
@ -7,7 +7,7 @@ class QueueManager {
|
|||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.refreshInterval = null;
|
this.refreshInterval = null;
|
||||||
this.isReordering = false;
|
this.isReordering = false;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ class QueueManager {
|
|||||||
|
|
||||||
initSocket() {
|
initSocket() {
|
||||||
this.socket = io();
|
this.socket = io();
|
||||||
|
|
||||||
this.socket.on('connect', () => {
|
this.socket.on('connect', () => {
|
||||||
console.log('Connected to server');
|
console.log('Connected to server');
|
||||||
this.showToast('Connected to server', 'success');
|
this.showToast('Connected to server', 'success');
|
||||||
@ -40,6 +40,41 @@ class QueueManager {
|
|||||||
this.socket.on('download_progress_update', (data) => {
|
this.socket.on('download_progress_update', (data) => {
|
||||||
this.updateDownloadProgress(data);
|
this.updateDownloadProgress(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Download queue events
|
||||||
|
this.socket.on('download_started', () => {
|
||||||
|
this.showToast('Download queue started', 'success');
|
||||||
|
this.loadQueueData(); // Refresh data
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_progress', (data) => {
|
||||||
|
this.updateDownloadProgress(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_completed', (data) => {
|
||||||
|
this.showToast(`Completed: ${data.serie} - Episode ${data.episode}`, 'success');
|
||||||
|
this.loadQueueData(); // Refresh data
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_error', (data) => {
|
||||||
|
const message = data.error || data.message || 'Unknown error';
|
||||||
|
this.showToast(`Download failed: ${message}`, 'error');
|
||||||
|
this.loadQueueData(); // Refresh data
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_queue_completed', () => {
|
||||||
|
this.showToast('All downloads completed!', 'success');
|
||||||
|
this.loadQueueData(); // Refresh data
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_stop_requested', () => {
|
||||||
|
this.showToast('Stopping downloads...', 'info');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('download_stopped', () => {
|
||||||
|
this.showToast('Download queue stopped', 'success');
|
||||||
|
this.loadQueueData(); // Refresh data
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
@ -70,6 +105,14 @@ class QueueManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Download controls
|
// Download controls
|
||||||
|
document.getElementById('start-queue-btn').addEventListener('click', () => {
|
||||||
|
this.startDownloadQueue();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('stop-queue-btn').addEventListener('click', () => {
|
||||||
|
this.stopDownloadQueue();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('pause-all-btn').addEventListener('click', () => {
|
document.getElementById('pause-all-btn').addEventListener('click', () => {
|
||||||
this.pauseAllDownloads();
|
this.pauseAllDownloads();
|
||||||
});
|
});
|
||||||
@ -105,7 +148,7 @@ class QueueManager {
|
|||||||
setTheme(theme) {
|
setTheme(theme) {
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
localStorage.setItem('theme', theme);
|
localStorage.setItem('theme', theme);
|
||||||
|
|
||||||
const themeIcon = document.querySelector('#theme-toggle i');
|
const themeIcon = document.querySelector('#theme-toggle i');
|
||||||
themeIcon.className = theme === 'light' ? 'fas fa-moon' : 'fas fa-sun';
|
themeIcon.className = theme === 'light' ? 'fas fa-moon' : 'fas fa-sun';
|
||||||
}
|
}
|
||||||
@ -127,10 +170,10 @@ class QueueManager {
|
|||||||
try {
|
try {
|
||||||
const response = await this.makeAuthenticatedRequest('/api/queue/status');
|
const response = await this.makeAuthenticatedRequest('/api/queue/status');
|
||||||
if (!response) return;
|
if (!response) return;
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.updateQueueDisplay(data);
|
this.updateQueueDisplay(data);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading queue data:', error);
|
console.error('Error loading queue data:', error);
|
||||||
}
|
}
|
||||||
@ -139,19 +182,19 @@ class QueueManager {
|
|||||||
updateQueueDisplay(data) {
|
updateQueueDisplay(data) {
|
||||||
// Update statistics
|
// Update statistics
|
||||||
this.updateStatistics(data.statistics, data);
|
this.updateStatistics(data.statistics, data);
|
||||||
|
|
||||||
// Update active downloads
|
// Update active downloads
|
||||||
this.renderActiveDownloads(data.active_downloads || []);
|
this.renderActiveDownloads(data.active_downloads || []);
|
||||||
|
|
||||||
// Update pending queue
|
// Update pending queue
|
||||||
this.renderPendingQueue(data.pending_queue || []);
|
this.renderPendingQueue(data.pending_queue || []);
|
||||||
|
|
||||||
// Update completed downloads
|
// Update completed downloads
|
||||||
this.renderCompletedDownloads(data.completed_downloads || []);
|
this.renderCompletedDownloads(data.completed_downloads || []);
|
||||||
|
|
||||||
// Update failed downloads
|
// Update failed downloads
|
||||||
this.renderFailedDownloads(data.failed_downloads || []);
|
this.renderFailedDownloads(data.failed_downloads || []);
|
||||||
|
|
||||||
// Update button states
|
// Update button states
|
||||||
this.updateButtonStates(data);
|
this.updateButtonStates(data);
|
||||||
}
|
}
|
||||||
@ -161,17 +204,17 @@ class QueueManager {
|
|||||||
document.getElementById('pending-items').textContent = (data.pending_queue || []).length;
|
document.getElementById('pending-items').textContent = (data.pending_queue || []).length;
|
||||||
document.getElementById('completed-items').textContent = stats.completed_items || 0;
|
document.getElementById('completed-items').textContent = stats.completed_items || 0;
|
||||||
document.getElementById('failed-items').textContent = stats.failed_items || 0;
|
document.getElementById('failed-items').textContent = stats.failed_items || 0;
|
||||||
|
|
||||||
document.getElementById('current-speed').textContent = stats.current_speed || '0 MB/s';
|
document.getElementById('current-speed').textContent = stats.current_speed || '0 MB/s';
|
||||||
document.getElementById('average-speed').textContent = stats.average_speed || '0 MB/s';
|
document.getElementById('average-speed').textContent = stats.average_speed || '0 MB/s';
|
||||||
|
|
||||||
// Format ETA
|
// Format ETA
|
||||||
const etaElement = document.getElementById('eta-time');
|
const etaElement = document.getElementById('eta-time');
|
||||||
if (stats.eta) {
|
if (stats.eta) {
|
||||||
const eta = new Date(stats.eta);
|
const eta = new Date(stats.eta);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const diffMs = eta - now;
|
const diffMs = eta - now;
|
||||||
|
|
||||||
if (diffMs > 0) {
|
if (diffMs > 0) {
|
||||||
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||||
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
@ -186,7 +229,7 @@ class QueueManager {
|
|||||||
|
|
||||||
renderActiveDownloads(downloads) {
|
renderActiveDownloads(downloads) {
|
||||||
const container = document.getElementById('active-downloads');
|
const container = document.getElementById('active-downloads');
|
||||||
|
|
||||||
if (downloads.length === 0) {
|
if (downloads.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
@ -206,7 +249,7 @@ class QueueManager {
|
|||||||
const speed = progress.speed_mbps ? `${progress.speed_mbps.toFixed(1)} MB/s` : '0 MB/s';
|
const speed = progress.speed_mbps ? `${progress.speed_mbps.toFixed(1)} MB/s` : '0 MB/s';
|
||||||
const downloaded = progress.downloaded_mb ? `${progress.downloaded_mb.toFixed(1)} MB` : '0 MB';
|
const downloaded = progress.downloaded_mb ? `${progress.downloaded_mb.toFixed(1)} MB` : '0 MB';
|
||||||
const total = progress.total_mb ? `${progress.total_mb.toFixed(1)} MB` : 'Unknown';
|
const total = progress.total_mb ? `${progress.total_mb.toFixed(1)} MB` : 'Unknown';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="download-card active">
|
<div class="download-card active">
|
||||||
<div class="download-header">
|
<div class="download-header">
|
||||||
@ -238,7 +281,7 @@ class QueueManager {
|
|||||||
|
|
||||||
renderPendingQueue(queue) {
|
renderPendingQueue(queue) {
|
||||||
const container = document.getElementById('pending-queue');
|
const container = document.getElementById('pending-queue');
|
||||||
|
|
||||||
if (queue.length === 0) {
|
if (queue.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
@ -255,7 +298,7 @@ class QueueManager {
|
|||||||
createPendingQueueCard(download, index) {
|
createPendingQueueCard(download, index) {
|
||||||
const addedAt = new Date(download.added_at).toLocaleString();
|
const addedAt = new Date(download.added_at).toLocaleString();
|
||||||
const priorityClass = download.priority === 'high' ? 'high-priority' : '';
|
const priorityClass = download.priority === 'high' ? 'high-priority' : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="download-card pending ${priorityClass}" data-id="${download.id}">
|
<div class="download-card pending ${priorityClass}" data-id="${download.id}">
|
||||||
<div class="queue-position">${index + 1}</div>
|
<div class="queue-position">${index + 1}</div>
|
||||||
@ -278,7 +321,7 @@ class QueueManager {
|
|||||||
|
|
||||||
renderCompletedDownloads(downloads) {
|
renderCompletedDownloads(downloads) {
|
||||||
const container = document.getElementById('completed-downloads');
|
const container = document.getElementById('completed-downloads');
|
||||||
|
|
||||||
if (downloads.length === 0) {
|
if (downloads.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
@ -295,7 +338,7 @@ class QueueManager {
|
|||||||
createCompletedDownloadCard(download) {
|
createCompletedDownloadCard(download) {
|
||||||
const completedAt = new Date(download.completed_at).toLocaleString();
|
const completedAt = new Date(download.completed_at).toLocaleString();
|
||||||
const duration = this.calculateDuration(download.started_at, download.completed_at);
|
const duration = this.calculateDuration(download.started_at, download.completed_at);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="download-card completed">
|
<div class="download-card completed">
|
||||||
<div class="download-header">
|
<div class="download-header">
|
||||||
@ -314,7 +357,7 @@ class QueueManager {
|
|||||||
|
|
||||||
renderFailedDownloads(downloads) {
|
renderFailedDownloads(downloads) {
|
||||||
const container = document.getElementById('failed-downloads');
|
const container = document.getElementById('failed-downloads');
|
||||||
|
|
||||||
if (downloads.length === 0) {
|
if (downloads.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
@ -331,7 +374,7 @@ class QueueManager {
|
|||||||
createFailedDownloadCard(download) {
|
createFailedDownloadCard(download) {
|
||||||
const failedAt = new Date(download.completed_at).toLocaleString();
|
const failedAt = new Date(download.completed_at).toLocaleString();
|
||||||
const retryCount = download.retry_count || 0;
|
const retryCount = download.retry_count || 0;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="download-card failed">
|
<div class="download-card failed">
|
||||||
<div class="download-header">
|
<div class="download-header">
|
||||||
@ -358,7 +401,20 @@ class QueueManager {
|
|||||||
const hasActive = (data.active_downloads || []).length > 0;
|
const hasActive = (data.active_downloads || []).length > 0;
|
||||||
const hasPending = (data.pending_queue || []).length > 0;
|
const hasPending = (data.pending_queue || []).length > 0;
|
||||||
const hasFailed = (data.failed_downloads || []).length > 0;
|
const hasFailed = (data.failed_downloads || []).length > 0;
|
||||||
|
|
||||||
|
// Enable start button only if there are pending items and no active downloads
|
||||||
|
document.getElementById('start-queue-btn').disabled = !hasPending || hasActive;
|
||||||
|
|
||||||
|
// Show/hide start/stop buttons based on whether downloads are active
|
||||||
|
if (hasActive) {
|
||||||
|
document.getElementById('start-queue-btn').style.display = 'none';
|
||||||
|
document.getElementById('stop-queue-btn').style.display = 'inline-flex';
|
||||||
|
document.getElementById('stop-queue-btn').disabled = false;
|
||||||
|
} else {
|
||||||
|
document.getElementById('stop-queue-btn').style.display = 'none';
|
||||||
|
document.getElementById('start-queue-btn').style.display = 'inline-flex';
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('pause-all-btn').disabled = !hasActive;
|
document.getElementById('pause-all-btn').disabled = !hasActive;
|
||||||
document.getElementById('clear-queue-btn').disabled = !hasPending;
|
document.getElementById('clear-queue-btn').disabled = !hasPending;
|
||||||
document.getElementById('reorder-queue-btn').disabled = !hasPending || (data.pending_queue || []).length < 2;
|
document.getElementById('reorder-queue-btn').disabled = !hasPending || (data.pending_queue || []).length < 2;
|
||||||
@ -371,33 +427,33 @@ class QueueManager {
|
|||||||
completed: 'Clear Completed Downloads',
|
completed: 'Clear Completed Downloads',
|
||||||
failed: 'Clear Failed Downloads'
|
failed: 'Clear Failed Downloads'
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
pending: 'Are you sure you want to clear all pending downloads from the queue?',
|
pending: 'Are you sure you want to clear all pending downloads from the queue?',
|
||||||
completed: 'Are you sure you want to clear all completed downloads?',
|
completed: 'Are you sure you want to clear all completed downloads?',
|
||||||
failed: 'Are you sure you want to clear all failed downloads?'
|
failed: 'Are you sure you want to clear all failed downloads?'
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmed = await this.showConfirmModal(titles[type], messages[type]);
|
const confirmed = await this.showConfirmModal(titles[type], messages[type]);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.makeAuthenticatedRequest('/api/queue/clear', {
|
const response = await this.makeAuthenticatedRequest('/api/queue/clear', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ type })
|
body: JSON.stringify({ type })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response) return;
|
if (!response) return;
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
this.showToast(data.message, 'success');
|
this.showToast(data.message, 'success');
|
||||||
this.loadQueueData();
|
this.loadQueueData();
|
||||||
} else {
|
} else {
|
||||||
this.showToast(data.message, 'error');
|
this.showToast(data.message, 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error clearing queue:', error);
|
console.error('Error clearing queue:', error);
|
||||||
this.showToast('Failed to clear queue', 'error');
|
this.showToast('Failed to clear queue', 'error');
|
||||||
@ -411,17 +467,17 @@ class QueueManager {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ id: downloadId })
|
body: JSON.stringify({ id: downloadId })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response) return;
|
if (!response) return;
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
this.showToast('Download added back to queue', 'success');
|
this.showToast('Download added back to queue', 'success');
|
||||||
this.loadQueueData();
|
this.loadQueueData();
|
||||||
} else {
|
} else {
|
||||||
this.showToast(data.message, 'error');
|
this.showToast(data.message, 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error retrying download:', error);
|
console.error('Error retrying download:', error);
|
||||||
this.showToast('Failed to retry download', 'error');
|
this.showToast('Failed to retry download', 'error');
|
||||||
@ -431,10 +487,10 @@ class QueueManager {
|
|||||||
async retryAllFailed() {
|
async retryAllFailed() {
|
||||||
const confirmed = await this.showConfirmModal('Retry All Failed Downloads', 'Are you sure you want to retry all failed downloads?');
|
const confirmed = await this.showConfirmModal('Retry All Failed Downloads', 'Are you sure you want to retry all failed downloads?');
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
// Get all failed downloads and retry them individually
|
// Get all failed downloads and retry them individually
|
||||||
const failedCards = document.querySelectorAll('#failed-downloads .download-card.failed');
|
const failedCards = document.querySelectorAll('#failed-downloads .download-card.failed');
|
||||||
|
|
||||||
for (const card of failedCards) {
|
for (const card of failedCards) {
|
||||||
const downloadId = card.dataset.id;
|
const downloadId = card.dataset.id;
|
||||||
if (downloadId) {
|
if (downloadId) {
|
||||||
@ -450,17 +506,17 @@ class QueueManager {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ id: downloadId })
|
body: JSON.stringify({ id: downloadId })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response) return;
|
if (!response) return;
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
this.showToast('Download removed from queue', 'success');
|
this.showToast('Download removed from queue', 'success');
|
||||||
this.loadQueueData();
|
this.loadQueueData();
|
||||||
} else {
|
} else {
|
||||||
this.showToast(data.message, 'error');
|
this.showToast(data.message, 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing from queue:', error);
|
console.error('Error removing from queue:', error);
|
||||||
this.showToast('Failed to remove from queue', 'error');
|
this.showToast('Failed to remove from queue', 'error');
|
||||||
@ -471,21 +527,92 @@ class QueueManager {
|
|||||||
const start = new Date(startTime);
|
const start = new Date(startTime);
|
||||||
const end = new Date(endTime);
|
const end = new Date(endTime);
|
||||||
const diffMs = end - start;
|
const diffMs = end - start;
|
||||||
|
|
||||||
const minutes = Math.floor(diffMs / (1000 * 60));
|
const minutes = Math.floor(diffMs / (1000 * 60));
|
||||||
const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
||||||
|
|
||||||
return `${minutes}m ${seconds}s`;
|
return `${minutes}m ${seconds}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startDownloadQueue() {
|
||||||
|
try {
|
||||||
|
const response = await this.makeAuthenticatedRequest('/api/queue/start', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) return;
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showToast('Download queue started', 'success');
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
document.getElementById('start-queue-btn').style.display = 'none';
|
||||||
|
document.getElementById('stop-queue-btn').style.display = 'inline-flex';
|
||||||
|
document.getElementById('stop-queue-btn').disabled = false;
|
||||||
|
} else {
|
||||||
|
this.showToast(`Failed to start queue: ${data.message}`, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting download queue:', error);
|
||||||
|
this.showToast('Failed to start download queue', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopDownloadQueue() {
|
||||||
|
try {
|
||||||
|
const response = await this.makeAuthenticatedRequest('/api/queue/stop', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) return;
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
this.showToast('Download queue stopped', 'success');
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
document.getElementById('stop-queue-btn').style.display = 'none';
|
||||||
|
document.getElementById('start-queue-btn').style.display = 'inline-flex';
|
||||||
|
document.getElementById('start-queue-btn').disabled = false;
|
||||||
|
} else {
|
||||||
|
this.showToast(`Failed to stop queue: ${data.message}`, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping download queue:', error);
|
||||||
|
this.showToast('Failed to stop download queue', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseAllDownloads() {
|
||||||
|
// TODO: Implement pause functionality
|
||||||
|
this.showToast('Pause functionality not yet implemented', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeAllDownloads() {
|
||||||
|
// TODO: Implement resume functionality
|
||||||
|
this.showToast('Resume functionality not yet implemented', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleReorderMode() {
|
||||||
|
// TODO: Implement reorder functionality
|
||||||
|
this.showToast('Reorder functionality not yet implemented', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
async makeAuthenticatedRequest(url, options = {}) {
|
async makeAuthenticatedRequest(url, options = {}) {
|
||||||
const response = await fetch(url, options);
|
// Ensure credentials are included for session-based authentication
|
||||||
|
const requestOptions = {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, requestOptions);
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,23 +621,23 @@ class QueueManager {
|
|||||||
document.getElementById('confirm-title').textContent = title;
|
document.getElementById('confirm-title').textContent = title;
|
||||||
document.getElementById('confirm-message').textContent = message;
|
document.getElementById('confirm-message').textContent = message;
|
||||||
document.getElementById('confirm-modal').classList.remove('hidden');
|
document.getElementById('confirm-modal').classList.remove('hidden');
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
cleanup();
|
cleanup();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
cleanup();
|
cleanup();
|
||||||
resolve(false);
|
resolve(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
document.getElementById('confirm-ok').removeEventListener('click', handleConfirm);
|
document.getElementById('confirm-ok').removeEventListener('click', handleConfirm);
|
||||||
document.getElementById('confirm-cancel').removeEventListener('click', handleCancel);
|
document.getElementById('confirm-cancel').removeEventListener('click', handleCancel);
|
||||||
this.hideConfirmModal();
|
this.hideConfirmModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('confirm-ok').addEventListener('click', handleConfirm);
|
document.getElementById('confirm-ok').addEventListener('click', handleConfirm);
|
||||||
document.getElementById('confirm-cancel').addEventListener('click', handleCancel);
|
document.getElementById('confirm-cancel').addEventListener('click', handleCancel);
|
||||||
});
|
});
|
||||||
@ -523,7 +650,7 @@ class QueueManager {
|
|||||||
showToast(message, type = 'info') {
|
showToast(message, type = 'info') {
|
||||||
const container = document.getElementById('toast-container');
|
const container = document.getElementById('toast-container');
|
||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
|
|
||||||
toast.className = `toast ${type}`;
|
toast.className = `toast ${type}`;
|
||||||
toast.innerHTML = `
|
toast.innerHTML = `
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
@ -533,9 +660,9 @@ class QueueManager {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
container.appendChild(toast);
|
container.appendChild(toast);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (toast.parentElement) {
|
if (toast.parentElement) {
|
||||||
toast.remove();
|
toast.remove();
|
||||||
@ -553,7 +680,7 @@ class QueueManager {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/api/auth/logout', { method: 'POST' });
|
const response = await fetch('/api/auth/logout', { method: 'POST' });
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
this.showToast('Logged out successfully', 'success');
|
this.showToast('Logged out successfully', 'success');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -24,14 +24,12 @@
|
|||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<!-- Process Status Indicators -->
|
<!-- Process Status Indicators -->
|
||||||
<div class="process-status" id="process-status">
|
<div class="process-status" id="process-status">
|
||||||
<div class="status-indicator" id="rescan-status">
|
<div class="status-indicator" id="rescan-status" title="Scan is idle">
|
||||||
<i class="fas fa-sync-alt"></i>
|
<i class="fas fa-sync-alt"></i>
|
||||||
<span class="status-text">Scan</span>
|
|
||||||
<div class="status-dot idle"></div>
|
<div class="status-dot idle"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-indicator" id="download-status">
|
<div class="status-indicator" id="download-status" title="Download is idle">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
<span class="status-text">Download</span>
|
|
||||||
<div class="status-dot idle"></div>
|
<div class="status-dot idle"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="light">
|
<html lang="en" data-theme="light">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@ -7,6 +8,7 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@ -46,7 +48,7 @@
|
|||||||
<div class="stat-label">Total Items</div>
|
<div class="stat-label">Total Items</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-clock text-warning"></i>
|
<i class="fas fa-clock text-warning"></i>
|
||||||
@ -56,7 +58,7 @@
|
|||||||
<div class="stat-label">In Queue</div>
|
<div class="stat-label">In Queue</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-check-circle text-success"></i>
|
<i class="fas fa-check-circle text-success"></i>
|
||||||
@ -66,7 +68,7 @@
|
|||||||
<div class="stat-label">Completed</div>
|
<div class="stat-label">Completed</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-exclamation-triangle text-error"></i>
|
<i class="fas fa-exclamation-triangle text-error"></i>
|
||||||
@ -77,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Speed and ETA -->
|
<!-- Speed and ETA -->
|
||||||
<div class="speed-eta-section">
|
<div class="speed-eta-section">
|
||||||
<div class="speed-info">
|
<div class="speed-info">
|
||||||
@ -115,7 +117,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="active-downloads-list" id="active-downloads">
|
<div class="active-downloads-list" id="active-downloads">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<i class="fas fa-pause-circle"></i>
|
<i class="fas fa-pause-circle"></i>
|
||||||
@ -132,6 +134,14 @@
|
|||||||
Download Queue
|
Download Queue
|
||||||
</h2>
|
</h2>
|
||||||
<div class="section-actions">
|
<div class="section-actions">
|
||||||
|
<button id="start-queue-btn" class="btn btn-primary" disabled>
|
||||||
|
<i class="fas fa-play"></i>
|
||||||
|
Start Downloads
|
||||||
|
</button>
|
||||||
|
<button id="stop-queue-btn" class="btn btn-secondary" disabled style="display: none;">
|
||||||
|
<i class="fas fa-stop"></i>
|
||||||
|
Stop Downloads
|
||||||
|
</button>
|
||||||
<button id="clear-queue-btn" class="btn btn-secondary" disabled>
|
<button id="clear-queue-btn" class="btn btn-secondary" disabled>
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
Clear Queue
|
Clear Queue
|
||||||
@ -142,7 +152,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pending-queue-list" id="pending-queue">
|
<div class="pending-queue-list" id="pending-queue">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<i class="fas fa-list"></i>
|
<i class="fas fa-list"></i>
|
||||||
@ -165,7 +175,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="completed-downloads-list" id="completed-downloads">
|
<div class="completed-downloads-list" id="completed-downloads">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<i class="fas fa-check-circle"></i>
|
<i class="fas fa-check-circle"></i>
|
||||||
@ -192,7 +202,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="failed-downloads-list" id="failed-downloads">
|
<div class="failed-downloads-list" id="failed-downloads">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<i class="fas fa-check-circle text-success"></i>
|
<i class="fas fa-check-circle text-success"></i>
|
||||||
@ -238,4 +248,5 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
||||||
<script src="{{ url_for('static', filename='js/queue.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/queue.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
66
test_stop_functionality.py
Normal file
66
test_stop_functionality.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple test script to verify the stop download functionality works properly.
|
||||||
|
This simulates the browser behavior without authentication requirements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
def test_stop_download_functionality():
|
||||||
|
"""Test the stop download functionality."""
|
||||||
|
base_url = "http://127.0.0.1:5000"
|
||||||
|
|
||||||
|
print("Testing Stop Download Functionality")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Test 1: Try to stop when no downloads are running
|
||||||
|
print("\n1. Testing stop when no downloads are running...")
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{base_url}/api/queue/stop", timeout=5)
|
||||||
|
print(f"Status Code: {response.status_code}")
|
||||||
|
print(f"Response: {response.json()}")
|
||||||
|
|
||||||
|
if response.status_code == 400:
|
||||||
|
print("✓ Correctly returns error when no downloads are running")
|
||||||
|
elif response.status_code == 401:
|
||||||
|
print("⚠ Authentication required - this is expected")
|
||||||
|
else:
|
||||||
|
print(f"✗ Unexpected response: {response.status_code}")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"✗ Connection error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test 2: Check queue status endpoint
|
||||||
|
print("\n2. Testing queue status endpoint...")
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{base_url}/api/queue/status", timeout=5)
|
||||||
|
print(f"Status Code: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"Response: {response.json()}")
|
||||||
|
print("✓ Queue status endpoint working")
|
||||||
|
elif response.status_code == 401:
|
||||||
|
print("⚠ Authentication required for queue status")
|
||||||
|
else:
|
||||||
|
print(f"✗ Unexpected status code: {response.status_code}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"✗ Connection error: {e}")
|
||||||
|
|
||||||
|
# Test 3: Check if the server is responding
|
||||||
|
print("\n3. Testing server health...")
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{base_url}/", timeout=5)
|
||||||
|
print(f"Status Code: {response.status_code}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✓ Server is responding")
|
||||||
|
else:
|
||||||
|
print(f"⚠ Server returned: {response.status_code}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"✗ Server connection error: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_stop_download_functionality()
|
||||||
Loading…
x
Reference in New Issue
Block a user