Compare commits

..

5 Commits

Author SHA1 Message Date
423b77033c moved routing 2025-09-29 14:13:15 +02:00
b73210a3c9 fix some import issues 2025-09-29 12:14:42 +02:00
b2d77a099b backup 2025-09-29 11:51:58 +02:00
f9102d7bcd fix loading icon 2025-09-29 11:08:49 +02:00
7cc0d7c7a5 fixed search and add 2025-09-29 10:34:09 +02:00
34 changed files with 2704 additions and 1056 deletions

5
.vscode/launch.json vendored
View File

@ -9,12 +9,13 @@
"env": {
"FLASK_APP": "app.py",
"FLASK_ENV": "development",
"PYTHONPATH": "${workspaceFolder}"
"PYTHONPATH": "${workspaceFolder}/src;${workspaceFolder}"
},
"args": [],
"jinja": true,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src/server"
"cwd": "${workspaceFolder}/src",
"python": "C:/Users/lukas/anaconda3/envs/AniWorld/python.exe"
},
{
"name": "Python: CLI Tool",

0
errors.log Normal file
View File

0
noGerFound.log Normal file
View File

49
src/config.json Normal file
View 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
View 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 Im the Strongest Im 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 Im the Strongest Im Not Even an Adventurer Yet! (2024)\data for I Parry Everything What Do You Mean Im the Strongest Im 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\Ill 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\Ill Become a Villainess Who Goes Down in History (2024)\data for Ill 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 Gods 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 Gods Go-To Girl (2025)\data for The Gorilla Gods 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 Saints 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 Saints Magic Power is Omnipotent (2021)\data for The Saints 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\Worlds 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\Worlds End Harem (2022)\data for Worlds 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)

View File

0
src/logs/downloads.log Normal file
View File

View 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
View File

@ -0,0 +1 @@
# Server package

File diff suppressed because it is too large Load Diff

View File

@ -414,7 +414,7 @@ class BulkOperationsManager {
const confirmed = await this.confirmOperation(
'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'
);

View File

@ -1,7 +1,7 @@
from infrastructure.providers.streaming.Provider import Provider
from infrastructure.providers.streaming.voe import VOE
from server.infrastructure.providers.streaming.Provider import Provider
from server.infrastructure.providers.streaming.voe import VOE
class Providers:

View File

@ -1,11 +1,11 @@
import os
import re
import logging
from core.entities.series import Serie
from server.core.entities.series import Serie
import traceback
from infrastructure.logging.GlobalLogger import error_logger, noKeyFound_logger
from core.exceptions.Exceptions import NoKeyFoundException, MatchNotFoundError
from infrastructure.providers.base_provider import Loader
from server.infrastructure.logging.GlobalLogger import error_logger, noKeyFound_logger
from server.core.exceptions.Exceptions import NoKeyFoundException, MatchNotFoundError
from server.infrastructure.providers.base_provider import Loader
class SerieScanner:
@ -121,7 +121,7 @@ class SerieScanner:
episodes_dict = {}
for season, expected_count in expected_dict.items():
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:
episodes_dict[season] = missing_episodes

View File

@ -12,8 +12,8 @@ from fake_useragent import UserAgent
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from infrastructure.providers.base_provider import Loader
from core.interfaces.providers import Providers
from server.infrastructure.providers.base_provider import Loader
from server.core.interfaces.providers import Providers
from yt_dlp import YoutubeDL
import shutil

View File

@ -23,8 +23,8 @@ from urllib3.util.retry import Retry
from yt_dlp import YoutubeDL
import shutil
from infrastructure.providers.base_provider import Loader
from core.interfaces.providers import Providers
from server.infrastructure.providers.base_provider import Loader
from server.core.interfaces.providers import Providers
from error_handler import (
with_error_recovery,
recovery_strategies,

View File

@ -1,5 +1,5 @@
from infrastructure.providers.aniworld_provider import AniworldLoader
from infrastructure.providers.base_provider import Loader
from server.infrastructure.providers.aniworld_provider import AniworldLoader
from server.infrastructure.providers.base_provider import Loader
class Loaders:

View File

@ -180,3 +180,18 @@
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 - 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

View File

@ -12,10 +12,10 @@ from datetime import datetime
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from main import SeriesApp
from core.entities.series import Serie
from core.entities import SerieList
from infrastructure.file_system import SerieScanner
from infrastructure.providers.provider_factory import Loaders
from server.core.entities.series import Serie
from server.core.entities import SerieList
from server.infrastructure.file_system import SerieScanner
from server.infrastructure.providers.provider_factory import Loaders
from web.controllers.auth_controller import session_manager, require_auth, optional_auth
from config import config
from application.services.queue_service import download_queue_bp

View File

@ -19,7 +19,7 @@ def get_logging_config():
"""Get current logging configuration."""
try:
# 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 = {
'log_level': config.log_level,
@ -67,7 +67,7 @@ def update_logging_config():
# Update runtime logging level
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)
except ImportError:
# Fallback for basic logging
@ -99,7 +99,7 @@ def update_logging_config():
def list_log_files():
"""Get list of available log files."""
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()
@ -200,7 +200,7 @@ def cleanup_logs():
days = int(data.get('days', 30))
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)
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
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")
except ImportError:
pass
# Test download progress logging
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")
except ImportError:
pass

View File

@ -64,7 +64,7 @@ class SessionManager:
if config.enable_fail2ban_logging:
try:
# 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)
except ImportError:
# Fallback to simple logging if new system not available

View 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

View 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

View 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)

View 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)'
})

View 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}")

View 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)

View 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')

View 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
})

View File

@ -869,10 +869,6 @@ body {
.process-status {
gap: 4px;
}
.status-text {
font-size: 0.8rem;
}
}
@media (max-width: 768px) {
@ -1441,16 +1437,25 @@ body {
}
.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 {
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
margin-left: 2px;
/* Rescan icon specific styling */
#rescan-status i {
color: var(--color-text-disabled);
/* Gray when idle */
}
#rescan-status.running i {
color: #22c55e;
/* Green when running */
animation: iconPulse 2s infinite;
}
/* Status text removed - using tooltips only */
.status-dot {
width: 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 */
@media (max-width: 768px) {
.process-status {
@ -1499,12 +1518,9 @@ body {
gap: 4px;
}
.status-text {
display: none;
}
.status-indicator i {
font-size: 14px;
font-size: 20px;
/* Maintain 2x scale for mobile: was 14px -> 20px */
}
}

View File

@ -206,16 +206,42 @@ class AniWorldApp {
this.socket.on('download_started', (data) => {
this.isDownloading = true;
this.isPaused = false;
this.updateProcessStatus('download', true);
this.showDownloadQueue(data);
this.showStatus(`Starting download of ${data.total_series} series...`, true, true);
});
this.socket.on('download_progress', (data) => {
if (data.total_bytes) {
const percent = ((data.downloaded_bytes || 0) / data.total_bytes * 100).toFixed(1);
this.updateProgress(percent, `Downloading: ${percent}%`);
let status = '';
let percent = 0;
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 {
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');
});
// 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
this.socket.on('download_queue_update', (data) => {
this.updateDownloadQueue(data);
@ -476,7 +517,13 @@ class AniWorldApp {
}
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) {
window.location.href = '/login';
@ -896,30 +943,45 @@ class AniWorldApp {
const statusDot = statusElement.querySelector('.status-dot');
if (!statusDot) return;
// Remove all status classes
// Remove all status classes from both dot and element
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) {
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) {
statusDot.classList.add('running');
statusElement.title = `${processName} is running...`;
statusElement.classList.add('running');
statusElement.title = `${displayName} is running...`;
} else {
statusDot.classList.add('idle');
statusElement.title = `${processName} is idle`;
statusElement.classList.add('idle');
statusElement.title = `${displayName} is idle`;
}
}
async checkProcessLocks() {
try {
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)
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
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;
}
@ -935,25 +997,40 @@ class AniWorldApp {
if (rescanBtn) {
if (locks.rescan?.is_locked) {
rescanBtn.disabled = true;
rescanBtn.querySelector('span').textContent = 'Scanning...';
const span = rescanBtn.querySelector('span');
if (span) span.textContent = 'Scanning...';
} else {
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) {
console.error('Error checking process locks:', error);
// On error, set status as idle
this.updateProcessStatus('rescan', false);
this.updateProcessStatus('download', false);
}
}
startProcessStatusMonitoring() {
// Initial check on page load
this.checkProcessLocks();
// Check process status every 5 seconds
setInterval(() => {
if (this.isConnected) {
this.checkProcessLocks();
}
}, 5000);
console.log('Process status monitoring started');
}
async showConfigModal() {
@ -1928,6 +2005,25 @@ class AniWorldApp {
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

View File

@ -7,7 +7,7 @@ class QueueManager {
this.socket = null;
this.refreshInterval = null;
this.isReordering = false;
this.init();
}
@ -21,7 +21,7 @@ class QueueManager {
initSocket() {
this.socket = io();
this.socket.on('connect', () => {
console.log('Connected to server');
this.showToast('Connected to server', 'success');
@ -40,6 +40,41 @@ class QueueManager {
this.socket.on('download_progress_update', (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() {
@ -70,6 +105,14 @@ class QueueManager {
});
// 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', () => {
this.pauseAllDownloads();
});
@ -105,7 +148,7 @@ class QueueManager {
setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
const themeIcon = document.querySelector('#theme-toggle i');
themeIcon.className = theme === 'light' ? 'fas fa-moon' : 'fas fa-sun';
}
@ -127,10 +170,10 @@ class QueueManager {
try {
const response = await this.makeAuthenticatedRequest('/api/queue/status');
if (!response) return;
const data = await response.json();
this.updateQueueDisplay(data);
} catch (error) {
console.error('Error loading queue data:', error);
}
@ -139,19 +182,19 @@ class QueueManager {
updateQueueDisplay(data) {
// Update statistics
this.updateStatistics(data.statistics, data);
// Update active downloads
this.renderActiveDownloads(data.active_downloads || []);
// Update pending queue
this.renderPendingQueue(data.pending_queue || []);
// Update completed downloads
this.renderCompletedDownloads(data.completed_downloads || []);
// Update failed downloads
this.renderFailedDownloads(data.failed_downloads || []);
// Update button states
this.updateButtonStates(data);
}
@ -161,17 +204,17 @@ class QueueManager {
document.getElementById('pending-items').textContent = (data.pending_queue || []).length;
document.getElementById('completed-items').textContent = stats.completed_items || 0;
document.getElementById('failed-items').textContent = stats.failed_items || 0;
document.getElementById('current-speed').textContent = stats.current_speed || '0 MB/s';
document.getElementById('average-speed').textContent = stats.average_speed || '0 MB/s';
// Format ETA
const etaElement = document.getElementById('eta-time');
if (stats.eta) {
const eta = new Date(stats.eta);
const now = new Date();
const diffMs = eta - now;
if (diffMs > 0) {
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
@ -186,7 +229,7 @@ class QueueManager {
renderActiveDownloads(downloads) {
const container = document.getElementById('active-downloads');
if (downloads.length === 0) {
container.innerHTML = `
<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 downloaded = progress.downloaded_mb ? `${progress.downloaded_mb.toFixed(1)} MB` : '0 MB';
const total = progress.total_mb ? `${progress.total_mb.toFixed(1)} MB` : 'Unknown';
return `
<div class="download-card active">
<div class="download-header">
@ -238,7 +281,7 @@ class QueueManager {
renderPendingQueue(queue) {
const container = document.getElementById('pending-queue');
if (queue.length === 0) {
container.innerHTML = `
<div class="empty-state">
@ -255,7 +298,7 @@ class QueueManager {
createPendingQueueCard(download, index) {
const addedAt = new Date(download.added_at).toLocaleString();
const priorityClass = download.priority === 'high' ? 'high-priority' : '';
return `
<div class="download-card pending ${priorityClass}" data-id="${download.id}">
<div class="queue-position">${index + 1}</div>
@ -278,7 +321,7 @@ class QueueManager {
renderCompletedDownloads(downloads) {
const container = document.getElementById('completed-downloads');
if (downloads.length === 0) {
container.innerHTML = `
<div class="empty-state">
@ -295,7 +338,7 @@ class QueueManager {
createCompletedDownloadCard(download) {
const completedAt = new Date(download.completed_at).toLocaleString();
const duration = this.calculateDuration(download.started_at, download.completed_at);
return `
<div class="download-card completed">
<div class="download-header">
@ -314,7 +357,7 @@ class QueueManager {
renderFailedDownloads(downloads) {
const container = document.getElementById('failed-downloads');
if (downloads.length === 0) {
container.innerHTML = `
<div class="empty-state">
@ -331,7 +374,7 @@ class QueueManager {
createFailedDownloadCard(download) {
const failedAt = new Date(download.completed_at).toLocaleString();
const retryCount = download.retry_count || 0;
return `
<div class="download-card failed">
<div class="download-header">
@ -358,7 +401,20 @@ class QueueManager {
const hasActive = (data.active_downloads || []).length > 0;
const hasPending = (data.pending_queue || []).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('clear-queue-btn').disabled = !hasPending;
document.getElementById('reorder-queue-btn').disabled = !hasPending || (data.pending_queue || []).length < 2;
@ -371,33 +427,33 @@ class QueueManager {
completed: 'Clear Completed Downloads',
failed: 'Clear Failed Downloads'
};
const messages = {
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?',
failed: 'Are you sure you want to clear all failed downloads?'
};
const confirmed = await this.showConfirmModal(titles[type], messages[type]);
if (!confirmed) return;
try {
const response = await this.makeAuthenticatedRequest('/api/queue/clear', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type })
});
if (!response) return;
const data = await response.json();
if (data.status === 'success') {
this.showToast(data.message, 'success');
this.loadQueueData();
} else {
this.showToast(data.message, 'error');
}
} catch (error) {
console.error('Error clearing queue:', error);
this.showToast('Failed to clear queue', 'error');
@ -411,17 +467,17 @@ class QueueManager {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: downloadId })
});
if (!response) return;
const data = await response.json();
if (data.status === 'success') {
this.showToast('Download added back to queue', 'success');
this.loadQueueData();
} else {
this.showToast(data.message, 'error');
}
} catch (error) {
console.error('Error retrying download:', error);
this.showToast('Failed to retry download', 'error');
@ -431,10 +487,10 @@ class QueueManager {
async retryAllFailed() {
const confirmed = await this.showConfirmModal('Retry All Failed Downloads', 'Are you sure you want to retry all failed downloads?');
if (!confirmed) return;
// Get all failed downloads and retry them individually
const failedCards = document.querySelectorAll('#failed-downloads .download-card.failed');
for (const card of failedCards) {
const downloadId = card.dataset.id;
if (downloadId) {
@ -450,17 +506,17 @@ class QueueManager {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: downloadId })
});
if (!response) return;
const data = await response.json();
if (data.status === 'success') {
this.showToast('Download removed from queue', 'success');
this.loadQueueData();
} else {
this.showToast(data.message, 'error');
}
} catch (error) {
console.error('Error removing from queue:', error);
this.showToast('Failed to remove from queue', 'error');
@ -471,21 +527,92 @@ class QueueManager {
const start = new Date(startTime);
const end = new Date(endTime);
const diffMs = end - start;
const minutes = Math.floor(diffMs / (1000 * 60));
const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);
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 = {}) {
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) {
window.location.href = '/login';
return null;
}
return response;
}
@ -494,23 +621,23 @@ class QueueManager {
document.getElementById('confirm-title').textContent = title;
document.getElementById('confirm-message').textContent = message;
document.getElementById('confirm-modal').classList.remove('hidden');
const handleConfirm = () => {
cleanup();
resolve(true);
};
const handleCancel = () => {
cleanup();
resolve(false);
};
const cleanup = () => {
document.getElementById('confirm-ok').removeEventListener('click', handleConfirm);
document.getElementById('confirm-cancel').removeEventListener('click', handleCancel);
this.hideConfirmModal();
};
document.getElementById('confirm-ok').addEventListener('click', handleConfirm);
document.getElementById('confirm-cancel').addEventListener('click', handleCancel);
});
@ -523,7 +650,7 @@ class QueueManager {
showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
@ -533,9 +660,9 @@ class QueueManager {
</button>
</div>
`;
container.appendChild(toast);
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
@ -553,7 +680,7 @@ class QueueManager {
try {
const response = await fetch('/api/auth/logout', { method: 'POST' });
const data = await response.json();
if (data.status === 'success') {
this.showToast('Logged out successfully', 'success');
setTimeout(() => {

View File

@ -24,14 +24,12 @@
<div class="header-actions">
<!-- Process Status Indicators -->
<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>
<span class="status-text">Scan</span>
<div class="status-dot idle"></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>
<span class="status-text">Download</span>
<div class="status-dot idle"></div>
</div>
</div>

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<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 href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<div class="app-container">
<!-- Header -->
@ -46,7 +48,7 @@
<div class="stat-label">Total Items</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-clock text-warning"></i>
@ -56,7 +58,7 @@
<div class="stat-label">In Queue</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-check-circle text-success"></i>
@ -66,7 +68,7 @@
<div class="stat-label">Completed</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-exclamation-triangle text-error"></i>
@ -77,7 +79,7 @@
</div>
</div>
</div>
<!-- Speed and ETA -->
<div class="speed-eta-section">
<div class="speed-info">
@ -115,7 +117,7 @@
</button>
</div>
</div>
<div class="active-downloads-list" id="active-downloads">
<div class="empty-state">
<i class="fas fa-pause-circle"></i>
@ -132,6 +134,14 @@
Download Queue
</h2>
<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>
<i class="fas fa-trash"></i>
Clear Queue
@ -142,7 +152,7 @@
</button>
</div>
</div>
<div class="pending-queue-list" id="pending-queue">
<div class="empty-state">
<i class="fas fa-list"></i>
@ -165,7 +175,7 @@
</button>
</div>
</div>
<div class="completed-downloads-list" id="completed-downloads">
<div class="empty-state">
<i class="fas fa-check-circle"></i>
@ -192,7 +202,7 @@
</button>
</div>
</div>
<div class="failed-downloads-list" id="failed-downloads">
<div class="empty-state">
<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="{{ url_for('static', filename='js/queue.js') }}"></script>
</body>
</html>

View 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()