Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: frontend and backend improvements #197

Merged
merged 74 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
4b46c38
rework in progress.
Jan 22, 2024
d24dd0b
fix: correct limits for orionoid
Jan 22, 2024
8ce2967
fix: switch to comprehensions
Jan 22, 2024
1aa153a
fix: disable plex logging for id mismatches
Jan 22, 2024
b3be2d5
time for sleep. rework still wip.
Jan 24, 2024
0b8147d
feat: parser works. needs more work. language needs a rewrite. disabl…
Jan 25, 2024
c893a14
Merge remote-tracking branch 'origin/main' into fix/parser/add_attribute
Jan 25, 2024
9826b55
fix: overseerr bug on using external ids
Jan 25, 2024
55b10d2
fix: remove plex debug line for users
Jan 25, 2024
570c445
disable tvdb checks from listrr, overseerr, plex. needs reworked.
Jan 25, 2024
700c100
set torrentio to disabled by default. removed parse logs. raised torr…
Jan 25, 2024
8dc29e1
Set all default settings to disabled by default for onboarding
Jan 25, 2024
ed2181f
add extra logging attr. for debugging large groups of data.
Jan 25, 2024
50b5464
feat: started status page rewrite
AyushSehrawat Jan 25, 2024
c607d10
add dev branch builds with :dev tag (#165)
KingPin Jan 25, 2024
7d56273
fix: listrr validation
Jan 25, 2024
78f3e8e
rework in progress.
Jan 22, 2024
78f69c6
fix: correct limits for orionoid
Jan 22, 2024
70f8d1b
fix: switch to comprehensions
Jan 22, 2024
110618f
fix: disable plex logging for id mismatches
Jan 22, 2024
fef38d5
time for sleep. rework still wip.
Jan 24, 2024
3997cad
feat: parser works. needs more work. language needs a rewrite. disabl…
Jan 25, 2024
54dc70a
feat: frontend improvements (#158)
AyushSehrawat Jan 24, 2024
3d834c3
feat: frontend improvements (#159)
AyushSehrawat Jan 24, 2024
c900616
docs: minor improvements (#160)
AyushSehrawat Jan 24, 2024
21f1601
docs: minor improvements (#161)
AyushSehrawat Jan 24, 2024
3cd5160
docs: minor improvements (#162)
AyushSehrawat Jan 24, 2024
ac368bd
fix: correct parsing of external id's (#163)
dreulavelle Jan 24, 2024
7ba5bc9
fix: overseerr bug on using external ids
Jan 25, 2024
c09a70a
fix: remove plex debug line for users
Jan 25, 2024
8edf185
disable tvdb checks from listrr, overseerr, plex. needs reworked.
Jan 25, 2024
724acd6
set torrentio to disabled by default. removed parse logs. raised torr…
Jan 25, 2024
63af903
Set all default settings to disabled by default for onboarding
Jan 25, 2024
b960520
add extra logging attr. for debugging large groups of data.
Jan 25, 2024
54cd31d
fix: listrr validation
Jan 25, 2024
918cc2a
Merge branch 'main' into dev
dreulavelle Jan 25, 2024
c33e46f
feat: rewrite of status page almost done
AyushSehrawat Jan 25, 2024
8a6aa75
Merge remote-tracking branch 'origin/dev' into fix/parser/add_attribute
Jan 25, 2024
c41285c
add verbose logging in plex to debug looping
Jan 26, 2024
5836f3d
revert debug logging for plex. figured out looping issue.
Jan 26, 2024
c6e2065
add back plex log. add boilerplate for trakt content service. wip
Jan 26, 2024
5409a53
add back plex log. add boilerplate for trakt content service. wip
Jan 26, 2024
eb5f201
Merge remote-tracking branch 'origin/feat/better-status-page' into dev
Jan 26, 2024
ff3cb20
feat: status page improvements (#169)
AyushSehrawat Jan 26, 2024
543e4fe
Feat/better status page (#170)
AyushSehrawat Jan 26, 2024
a85b9fd
feat: new settings (#176)
AyushSehrawat Jan 28, 2024
6053486
added more validation and logging
Jan 28, 2024
f83e9d4
changed name of test_items module
Jan 28, 2024
6e257b1
refactor: edited minor things in settings (#177)
AyushSehrawat Jan 28, 2024
a0cd09a
Merge remote-tracking branch 'origin/fix/parser/add_attribute' into dev
Jan 28, 2024
4285dc0
remove tzdata
Jan 28, 2024
11265ef
refactor: edited minor things in settings (#177)
AyushSehrawat Jan 28, 2024
4efbf54
remove tzdata
Jan 28, 2024
a55d424
fix typo on parser
Jan 28, 2024
95859c4
Merge branch 'fix/parser/add_attribute' into dev
Jan 28, 2024
b667931
increase ratelimits on second_limiters
Jan 28, 2024
7858108
Fix/parser/add attribute (#179)
dreulavelle Jan 29, 2024
c70d214
add extra attrs to extended api endpoint
Jan 29, 2024
0f4b493
feat: status page improvements (#182)
AyushSehrawat Jan 29, 2024
33bd106
chore: fix pnpm deps conflict
AyushSehrawat Jan 30, 2024
a88163c
[DEV] feat: frontend improvements (#193)
AyushSehrawat Feb 1, 2024
7d9d42d
Merge branch 'main' into dev
AyushSehrawat Feb 1, 2024
9c0cc34
fix: shows not being downloaded
Feb 2, 2024
d4b4647
fix: add check if data attr exists for orionoid
Feb 2, 2024
7af264d
fix: validate on empty symlink path correctly
Feb 2, 2024
b2b49ab
fix: added more validation to symlink paths
Feb 2, 2024
cee7aa3
feat: added some checks to save settings (#196)
AyushSehrawat Feb 2, 2024
fd974e0
feat: improve symlink validation. fixed torrentio rate limits once an…
Feb 2, 2024
12fcf8d
fix: remove extra debug line from orionoid. correct settings path in …
Feb 3, 2024
8197e82
fix: tweaked symlink validation.
Feb 3, 2024
5ab17a2
fix: raise jackett ratelimit
Feb 3, 2024
ae5b6bb
fix: raise orionoid ratelimit
Feb 3, 2024
a39e277
feat: add back second limiter with 1/5s per on Torrentio. This will h…
Feb 3, 2024
7155bd7
Merge branch 'main' into dev
dreulavelle Feb 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions backend/program/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,9 @@ def validate(self):
return all(service.initialized for service in self.core_manager.services)

def stop(self):
try:
for service in self.core_manager.services:
if getattr(service, "running", False):
service.stop()
self.pickly.stop()
settings.save()
self.running = False
except Exception as e:
logger.error("Iceberg stopping with exception: %s", e)
pass
for service in self.core_manager.services:
if getattr(service, "running", False):
service.stop()
self.pickly.stop()
settings.save()
self.running = False
6 changes: 3 additions & 3 deletions backend/program/realdebrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ def chunks(lst, n):
"active_stream",
{"hash": stream_hash, "files": wanted_files, "id": None},
)
all_filenames = [file_info["filename"] for file_info in wanted_files.values()]
for file in all_filenames:
logger.debug(f"Found cached file {file} for {item.log_string}")
# all_filenames = [file_info["filename"] for file_info in wanted_files.values()]
# for file in all_filenames:
# logger.debug(f"Found cached file {file} for {item.log_string}")
return True
item.streams[stream_hash] = None
return False
Expand Down
15 changes: 7 additions & 8 deletions backend/program/scrapers/jackett.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self, _):
self.initialized = self.validate_settings()
if not self.initialized and not self.api_key:
return
self.minute_limiter = RateLimiter(max_calls=60, period=60, raise_on_limit=True)
self.minute_limiter = RateLimiter(max_calls=1000, period=3600, raise_on_limit=True)
self.second_limiter = RateLimiter(max_calls=1, period=5)
self.parse_logging = False
logger.info("Jackett initialized!")
Expand All @@ -45,7 +45,7 @@ def validate_settings(self) -> bool:
except ReadTimeout:
return True
except Exception as e:
logger.exception("Jackett failed to initialize with API Key: %s", e)
logger.error("Jackett failed to initialize with API Key: %s", e)
return False
if self.settings.url:
try:
Expand All @@ -57,9 +57,10 @@ def validate_settings(self) -> bool:
if not response.is_ok:
return False
except ReadTimeout:
logger.warn("Jackett connection timeout.")
return True
except Exception as e:
logger.exception("Jackett failed to initialize: %s", e)
logger.error("Jackett failed to initialize: %s", e)
return False
logger.info("Jackett is not configured and will not be used.")
return False
Expand All @@ -75,13 +76,11 @@ def run(self, item):
logger.warn("Jackett rate limit hit for item: %s", item.log_string)
return
except RequestException as e:
self.minute_limiter.limit_hit()
logger.exception("Jackett request exception: %s", e, exc_info=True)
logger.debug("Jackett request exception: %s", e, exc_info=True)
return
except Exception as e:
self.minute_limiter.limit_hit()
# logger.debug("Jackett exception for item: %s - Exception: %s", item.log_string, e.args[0], exc_info=True)
# logger.debug("Exception details: %s", traceback.format_exc())
logger.debug("Jackett exception for item: %s - Exception: %s", item.log_string, e.args[0], exc_info=True)
logger.debug("Exception details: %s", traceback.format_exc())
return

def _scrape_item(self, item):
Expand Down
27 changes: 14 additions & 13 deletions backend/program/scrapers/orionoid.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ def __init__(self, _):
self.orionoid_limit = 0
self.orionoid_remaining = 0
self.parse_logging = False
self.max_calls = 100 if not self.is_premium else 60
self.period = 86400 if not self.is_premium else 60
self.max_calls = 100 if not self.is_premium else 1000
self.period = 86400 if not self.is_premium else 3600
self.minute_limiter = RateLimiter(max_calls=self.max_calls, period=self.period, raise_on_limit=True)
self.second_limiter = RateLimiter(max_calls=1, period=5)
logger.info("Orionoid initialized!")
Expand All @@ -43,19 +43,20 @@ def validate_settings(self) -> bool:
if not self.settings.enabled:
logger.debug("Orionoid is set to disabled.")
return False
if self.settings.api_key:
return True
if len(self.settings.api_key) != 32 or self.settings.api_key == "":
logger.error("Orionoid API Key is not valid or not set. Please check your settings.")
return False
try:
url = f"https://api.orionoid.com?keyapp={KEY_APP}&keyuser={self.settings.api_key}&mode=user&action=retrieve"
response = get(url, retry_if_failed=False)
if response.is_ok:
return True
if not response.data.result.status == "success":
logger.error(f"Orionoid API Key is invalid. Status: {response.data.result.status}")
return False
if not response.is_ok:
logger.error(f"Orionoid Status Code: {response.status_code}, Reason: {response.reason}")
return False
if response.is_ok and hasattr(response.data, "result"):
if not response.data.result.status == "success":
logger.error(f"Orionoid API Key is invalid. Status: {response.data.result.status}")
return False
if not response.is_ok:
logger.error(f"Orionoid Status Code: {response.status_code}, Reason: {response.reason}")
return False
return True
except Exception as e:
logger.exception("Orionoid failed to initialize: %s", e)
return False
Expand All @@ -67,7 +68,7 @@ def check_premium(self) -> bool:
"""
url = f"https://api.orionoid.com?keyapp={KEY_APP}&keyuser={self.settings.api_key}&mode=user&action=retrieve"
response = get(url, retry_if_failed=False)
if response.is_ok:
if response.is_ok and hasattr(response.data, "data"):
active = True if response.data.data.status == "active" else False
premium = response.data.data.subscription.package.premium
debrid = response.data.data.service.realdebrid
Expand Down
15 changes: 3 additions & 12 deletions backend/program/scrapers/torrentio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
""" Torrentio scraper module """
import os
from typing import Optional
from pydantic import BaseModel
from requests import ConnectTimeout, ReadTimeout
Expand All @@ -22,7 +21,7 @@ class Torrentio:
def __init__(self, _):
self.key = "torrentio"
self.settings = TorrentioConfig(**settings_manager.get(f"scraping.{self.key}"))
self.minute_limiter = RateLimiter(max_calls=60, period=60, raise_on_limit=True)
self.minute_limiter = RateLimiter(max_calls=300, period=3600, raise_on_limit=True)
self.second_limiter = RateLimiter(max_calls=1, period=5)
self.initialized = self.validate_settings()
if not self.initialized:
Expand Down Expand Up @@ -57,26 +56,18 @@ def run(self, item):
self._scrape_item(item)
except RateLimitExceeded:
self.minute_limiter.limit_hit()
logger.warn("Torrentio rate limit hit for item: %s", item.log_string)
return
except ConnectTimeout:
self.minute_limiter.limit_hit()
logger.warn("Torrentio connection timeout for item: %s", item.log_string)
return
except ReadTimeout:
self.minute_limiter.limit_hit()
logger.warn("Torrentio read timeout for item: %s", item.log_string)
return
except RequestException as e:
self.minute_limiter.limit_hit()
logger.warn("Torrentio request exception: %s", e)
return
except AttributeError:
# TODO: will fix later
self.minute_limiter.limit_hit()
return
except Exception as e:
self.minute_limiter.limit_hit()
logger.warn("Torrentio failed to scrape item: %s", e)
logger.warn("Torrentio exception thrown: %s", e)
return

def _scrape_item(self, item):
Expand Down
108 changes: 63 additions & 45 deletions backend/program/symlink.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Symlinking module"""
import os
from pathlib import Path
from typing import NamedTuple
from pydantic import BaseModel
from utils.settings import settings_manager as settings
from utils.logger import logger
Expand All @@ -10,6 +11,9 @@ class SymlinkConfig(BaseModel):
host_path: Path
container_path: Path

class Setting(NamedTuple):
key: str
value: str

class Symlinker():
"""
Expand All @@ -26,60 +30,74 @@ class Symlinker():
def __init__(self, _):
self.key = "symlink"
self.settings = SymlinkConfig(**settings.get(self.key))
self.initialized = False

if (self.settings.host_path / "__all__").exists():
logger.debug("Detected Zurg host path. Using __all__ folder for host path.")
settings.set(self.key, self.settings.host_path)
self.settings.host_path = Path(self.settings.host_path) / "__all__"
elif (self.settings.host_path / "torrents").exists():
logger.debug("Detected standard rclone host path. Using torrents folder for host path.")
settings.set(self.key, self.settings.host_path)
self.settings.host_path = Path(self.settings.host_path) / "torrents"

self.library_path = self.settings.host_path.parent / "library"

if not self.validate():
logger.error("Symlink configuration is invalid. Please check the host and container paths.")
return

self.initialize_library_paths()

if not self.create_initial_folders():
logger.error("Failed to create initial library folders.")
self.initialized = self.validate()
if not self.initialized:
logger.error("Symlink initialization failed due to invalid configuration.")
return

logger.info("Found rclone mount path: %s", self.settings.host_path)
logger.info("Symlinks will be placed in library path: %s", self.library_path)
logger.info("Plex will see the symlinks in: %s", self.settings.container_path.parent / "library")
logger.info("Rclone path symlinks are pointed to: %s", self.settings.host_path)
logger.info("Symlinks will be placed in: %s", self.library_path)
logger.info("Symlink initialized!")
self.initialized = True

def validate(self):
if not self.settings.host_path or not self.settings.container_path:
"""Validate paths and create the initial folders."""
host_path = Path(self.settings.host_path) if self.settings.host_path else None
container_path = Path(self.settings.container_path) if self.settings.container_path else None
if not host_path or not container_path or host_path == Path('.') or container_path == Path('.'):
logger.error("Host or container path not provided, is empty, or is set to the current directory.")
return False
host_path = Path(self.settings.host_path)
if not host_path.exists() or not host_path.is_dir():
logger.error(f"Invalid host path: {self.settings.host_path}")
if not host_path.is_absolute():
logger.error(f"Host path is not an absolute path: {host_path}")
return False
return True

def initialize_library_paths(self):
self.library_path_movies = self.library_path / "movies"
self.library_path_shows = self.library_path / "shows"
self.library_path_anime_movies = self.library_path / "anime_movies"
self.library_path_anime_shows = self.library_path / "anime_shows"
if not container_path.is_absolute():
logger.error(f"Container path is not an absolute path: {container_path}")
return False
try:
if not host_path.is_dir():
logger.error(f"Host path is not a directory or does not exist: {host_path}")
return False
if not container_path.is_dir():
logger.error(f"Container path is not a directory or does not exist: {container_path}")
return False
if Path(self.settings.host_path / "__all__").exists() and Path(self.settings.host_path / "__all__").is_dir():
logger.debug("Detected Zurg host path. Using __all__ folder for host path.")
self.settings.host_path = self.settings.host_path / "__all__"
elif Path(self.settings.host_path / "torrents").exists() and Path(self.settings.host_path / "torrents").is_dir():
logger.debug("Detected standard rclone host path. Using torrents folder for host path.")
self.settings.host_path = self.settings.host_path / "torrents"
if not self.create_initial_folders():
logger.error("Failed to create initial library folders.")
return False
return True
except FileNotFoundError as e:
logger.error(f"Path not found: {e}")
except PermissionError as e:
logger.error(f"Permission denied when accessing path: {e}")
except OSError as e:
logger.error(f"OS error when validating paths: {e}")
return False

def create_initial_folders(self):
for library in [self.library_path_movies,
self.library_path_shows,
self.library_path_anime_movies,
self.library_path_anime_shows]:
try:
library.mkdir(parents=True, exist_ok=True)
except Exception as e:
logger.error("Failed to create directory %s: %s", library, e)
return False
"""Create the initial library folders."""
try:
self.library_path = self.settings.container_path.parent / "library"
self.library_path_movies = self.library_path / "movies"
self.library_path_shows = self.library_path / "shows"
self.library_path_anime_movies = self.library_path / "anime_movies"
self.library_path_anime_shows = self.library_path / "anime_shows"
folders = [self.library_path_movies,
self.library_path_shows,
self.library_path_anime_movies,
self.library_path_anime_shows]
for folder in folders:
if not folder.exists():
folder.mkdir(parents=True, exist_ok=True)
except PermissionError as e:
logger.error(f"Permission denied when creating directory: {e}")
return False
except OSError as e:
logger.error(f"OS error when creating directory: {e}")
return False
return True

def run(self, item):
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/lib/forms/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z } from 'zod';

// General Settings -----------------------------------------------------------------------------------
export const generalSettingsToGet: string[] = ['debug', 'log', 'symlink', 'real_debrid'];
export const generalSettingsServices: string[] = ['symlink', 'real_debrid'];

export const generalSettingsSchema = z.object({
debug: z.boolean().default(true),
Expand Down Expand Up @@ -51,6 +52,7 @@ export function generalSettingsToSet(form: SuperValidated<GeneralSettingsSchema>

// Content Settings -----------------------------------------------------------------------------------
export const contentSettingsToGet: string[] = ['content'];
export const contentSettingsServices: string[] = ['content'];

export const contentSettingsSchema = z.object({
overseerr_enabled: z.boolean().default(false),
Expand Down Expand Up @@ -126,6 +128,7 @@ export function contentSettingsToSet(form: SuperValidated<ContentSettingsSchema>

// Media Server Settings -----------------------------------------------------------------------------------
export const mediaServerSettingsToGet: string[] = ['plex'];
export const mediaServerSettingsServices: string[] = ['plex'];

export const mediaServerSettingsSchema = z.object({
plex_token: z.string().optional().default(''),
Expand Down Expand Up @@ -154,6 +157,7 @@ export function mediaServerSettingsToSet(form: SuperValidated<MediaServerSetting

// Scrapers Settings -----------------------------------------------------------------------------------
export const scrapersSettingsToGet: string[] = ['scraping'];
export const scrapersSettingsServices: string[] = ['scraping'];

export const scrapersSettingsSchema = z.object({
after_2: z.number().nonnegative().default(0.5),
Expand Down
Loading