Skip to content

Commit

Permalink
refactor: content classes. wip.
Browse files Browse the repository at this point in the history
  • Loading branch information
Spoked authored and Spoked committed Feb 7, 2024
1 parent 0f40b70 commit b73e95c
Show file tree
Hide file tree
Showing 16 changed files with 109 additions and 80 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
data/
logs/
settings.json
.vscode
.vscode/*
!.vscode/launch.json
__pycache__
.git
docker-compose.yml
Expand Down
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Iceberg",
"type": "python",
"request": "launch",
"program": "backend/main.py",
}
]
}
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ COPY --from=frontend --chown=node:node /app/node_modules /iceberg/frontend/node_
COPY --from=frontend --chown=node:node /app/package.json /iceberg/frontend/package.json

# Backend
COPY VERSION /iceberg/VERSION
COPY backend/ /iceberg/backend
RUN python3 -m venv /venv
COPY requirements.txt /iceberg/requirements.txt
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.4.6
1 change: 1 addition & 0 deletions backend/controllers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ async def root():
return {
"success": True,
"message": "Iceburg is running!",
"version": settings_manager.settings.version
}


Expand Down
7 changes: 3 additions & 4 deletions backend/program/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ def start(self):
logger.info("Iceberg v%s starting!", settings_manager.settings.version)
self.initialized = False
self.media_items = MediaItemContainer(items=[])
self.data_path = data_dir_path
os.makedirs(self.data_path, exist_ok=True)
os.makedirs(data_dir_path, exist_ok=True)
if not self.startup_args.dev:
self.pickly = Pickly(self.media_items, self.data_path)
self.pickly = Pickly(self.media_items, data_dir_path)
self.pickly.start()
self.core_manager = ServiceManager(self.media_items, True, Content, Plex, Scraping, Debrid, Symlinker)
self.core_manager = ServiceManager(self.media_items, True, Plex, Content, Scraping, Debrid, Symlinker)
if self.validate():
logger.info("Iceberg started!")
else:
Expand Down
37 changes: 37 additions & 0 deletions backend/program/content/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from program.media.container import MediaItemContainer
from program.updaters.trakt import Updater as Trakt


class ContentServiceBase:
"""Base class for content providers"""

def __init__(self, media_items: MediaItemContainer):
self.media_items = media_items
self.updater = Trakt()
self.not_found_ids = []
self.next_run_time = 0

def validate(self):
"""Validate the content provider settings."""
raise NotImplementedError("The 'validate' method must be implemented by subclasses.")

def run(self):
"""Fetch new media from the content provider."""
raise NotImplementedError("The 'run' method must be implemented by subclasses.")

def process_items(self, items, requested_by):
"""Process fetched media items and log the results."""
new_items = [item for item in items if self.is_valid_item(item)]
if not new_items:
return
container = self.updater.create_items(new_items)
added_items = self.media_items.extend(container)
for item in added_items:
if hasattr(item, "set"):
item.set("requested_by", requested_by)
return added_items

def is_valid_item(self, item) -> bool:
"""Check if the item is valid for processing and not already in media_items"""
is_unique = item not in self.media_items
return item is not None and is_unique
30 changes: 12 additions & 18 deletions backend/program/content/listrr.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""Listrr content module"""
from time import time
from requests.exceptions import HTTPError
from program.settings.manager import settings_manager
from utils.logger import logger
from utils.request import get, ping
from requests.exceptions import HTTPError
from program.settings.manager import settings_manager
from program.media.container import MediaItemContainer
from program.updaters.trakt import Updater as Trakt, get_imdbid_from_tmdb
from program.updaters.trakt import get_imdbid_from_tmdb
from program.content.base import ContentServiceBase


class Listrr:
class Listrr(ContentServiceBase):
"""Content class for Listrr"""

def __init__(self, media_items: MediaItemContainer):
Expand All @@ -19,10 +20,7 @@ def __init__(self, media_items: MediaItemContainer):
self.initialized = self.validate()
if not self.initialized:
return
self.media_items = media_items
self.updater = Trakt()
self.not_found_ids = []
self.next_run_time = 0
super().__init__(media_items)
logger.info("Listrr initialized!")

def validate(self) -> bool:
Expand Down Expand Up @@ -62,32 +60,28 @@ def run(self):
self.next_run_time = time() + self.settings.update_interval
movie_items = self._get_items_from_Listrr("Movies", self.settings.movie_lists)
show_items = self._get_items_from_Listrr("Shows", self.settings.show_lists)
items = set(movie_items + show_items)
new_items = [item for item in items if item not in self.media_items and item is not None]
if not new_items:
items = movie_items.extend(show_items)
added_items = self.process_items(items, "Listrr")
if not added_items:
return
container = self.updater.create_items(new_items)
for item in container:
item.set("requested_by", "Listrr")
added_items = self.media_items.extend(container)
length = len(added_items)
if length >= 1 and length <= 5:
for item in added_items:
logger.info("Added %s", item.log_string)
elif length > 5:
logger.info("Added %s items", length)
if self.not_found_ids:
logger.warn("Failed to process %s items, skipping.", len(self.not_found_ids))
logger.debug("Failed to process %s items, skipping.", len(self.not_found_ids))

def _get_items_from_Listrr(self, content_type, content_lists):
def _get_items_from_Listrr(self, content_type, content_lists) -> MediaItemContainer:
"""Fetch unique IMDb IDs from Listrr for a given type and list of content."""
unique_ids = set()
if not content_lists:
return list(unique_ids)

for list_id in content_lists:
if not list_id or len(list_id) != 24:
continue # Skip invalid list IDs
continue

page, total_pages = 1, 1
while page <= total_pages:
Expand Down
26 changes: 10 additions & 16 deletions backend/program/content/mdblist.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Mdblist content module"""
from time import time
from program.settings.manager import settings_manager
from utils.logger import logger
from utils.request import RateLimitExceeded, RateLimiter, get, ping
from program.settings.manager import settings_manager
from program.media.container import MediaItemContainer
from program.updaters.trakt import Updater as Trakt
from utils.request import RateLimitExceeded, RateLimiter, get, ping
from program.content.base import ContentServiceBase


class Mdblist:
class Mdblist(ContentServiceBase):
"""Content class for mdblist"""

def __init__(self, media_items: MediaItemContainer):
Expand All @@ -16,11 +16,9 @@ def __init__(self, media_items: MediaItemContainer):
self.initialized = self.validate()
if not self.initialized:
return
self.media_items = media_items
self.updater = Trakt()
self.next_run_time = 0
self.requests_per_2_minutes = self._calculate_request_time()
self.rate_limiter = RateLimiter(self.requests_per_2_minutes, 120, True)
super().__init__(media_items)
logger.info("mdblist initialized")

def validate(self):
Expand All @@ -47,17 +45,13 @@ def run(self):
self.next_run_time = time() + self.settings.update_interval
try:
with self.rate_limiter:
items = []
items = MediaItemContainer()
for list_id in self.settings.lists:
if list_id:
items += self._get_items_from_list(
list_id, self.settings.api_key
)
new_items = [item for item in items if item not in self.media_items and item is not None]
container = self.updater.create_items(new_items)
for item in container:
item.set("requested_by", "Mdblist")
added_items = self.media_items.extend(container)
items.extend(self._get_items_from_list(list_id, self.settings.api_key))
added_items = self.process_items(items, "Mdblist")
if not added_items:
return
length = len(added_items)
if length >= 1 and length <= 5:
for item in added_items:
Expand Down
24 changes: 9 additions & 15 deletions backend/program/content/overseerr.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Mdblist content module"""
from time import time
from program.settings.manager import settings_manager
from utils.logger import logger
from utils.request import get, ping
from program.settings.manager import settings_manager
from program.media.container import MediaItemContainer
from program.updaters.trakt import Updater as Trakt, get_imdbid_from_tmdb
from program.updaters.trakt import get_imdbid_from_tmdb
from program.content.base import ContentServiceBase


class Overseerr:
class Overseerr(ContentServiceBase):
"""Content class for overseerr"""

def __init__(self, media_items: MediaItemContainer):
Expand All @@ -17,10 +18,7 @@ def __init__(self, media_items: MediaItemContainer):
self.initialized = self.validate()
if not self.initialized:
return
self.media_items = media_items
self.updater = Trakt()
self.not_found_ids = []
self.next_run_time = 0
super().__init__(media_items)
logger.info("Overseerr initialized!")

def validate(self) -> bool:
Expand Down Expand Up @@ -53,23 +51,19 @@ def run(self):
self.not_found_ids.clear()
self.next_run_time = time() + self.settings.update_interval
items = self._get_items_from_overseerr(10000)
new_items = [item for item in items if item not in self.media_items and item is not None]
if not new_items:
added_items = self.process_items(items, "Overseerr")
if not added_items:
return
container = self.updater.create_items(new_items)
for item in container:
item.set("requested_by", "Overseerr")
added_items = self.media_items.extend(container)
length = len(added_items)
if length >= 1 and length <= 5:
for item in added_items:
logger.info("Added %s", item.log_string)
elif length > 5:
logger.info("Added %s items", length)
if self.not_found_ids:
logger.warn("Failed to process %s items, skipping.", len(self.not_found_ids))
logger.debug("Failed to process %s items, skipping.", len(self.not_found_ids))

def _get_items_from_overseerr(self, amount: int) -> list[str]:
def _get_items_from_overseerr(self, amount: int) -> MediaItemContainer:
"""Fetch media from overseerr"""
response = get(
self.settings.url + f"/api/v1/request?take={amount}",
Expand Down
23 changes: 8 additions & 15 deletions backend/program/content/plex_watchlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from utils.logger import logger
from program.settings.manager import settings_manager
from program.media.container import MediaItemContainer
from program.updaters.trakt import Updater as Trakt
from program.content.base import ContentServiceBase


class PlexWatchlist:
class PlexWatchlist(ContentServiceBase):
"""Class for managing Plex Watchlists"""

def __init__(self, media_items: MediaItemContainer):
Expand All @@ -19,10 +19,7 @@ def __init__(self, media_items: MediaItemContainer):
if not self.initialized:
return
self.token = settings_manager.settings.plex.token
self.media_items = media_items
self.updater = Trakt()
self.not_found_ids = []
self.next_run_time = 0
super().__init__(media_items)
logger.info("Plex Watchlist initialized!")

def validate(self):
Expand Down Expand Up @@ -53,23 +50,19 @@ def run(self):
self.not_found_ids.clear()
self.next_run_time = time() + self.settings.update_interval
items = self._create_unique_list()
new_items = [item for item in items if item not in self.media_items and item is not None]
if not new_items:
added_items = self.process_items(items, "Plex Watchlist")
if not added_items:
return
container = self.updater.create_items(new_items)
for item in container:
item.set("requested_by", "Plex Watchlist")
added_items = self.media_items.extend(container)
length = len(added_items)
if length >= 1 and length <= 5:
for item in added_items:
logger.info("Added %s", item.log_string)
elif length > 5:
logger.info("Added %s items", length)
if self.not_found_ids:
logger.warn("Failed to process %s items, skipping.", len(self.not_found_ids))

def _create_unique_list(self):
logger.debug("Failed to process %s items, skipping.", len(self.not_found_ids))
def _create_unique_list(self) -> MediaItemContainer:
"""Create a unique list of items from Plex RSS and Watchlist."""
if not self.rss_enabled:
return self._get_items_from_watchlist()
Expand Down
8 changes: 2 additions & 6 deletions backend/program/settings/manager.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import json
import os
import shutil
from pathlib import Path

from pydantic import ValidationError

from utils import data_dir_path
from program.settings.models import AppModel, NotifyingBaseModel
from utils import data_dir_path
from utils.logger import logger
from utils.observable import Observable

Expand All @@ -21,7 +17,7 @@ def __init__(self):

NotifyingBaseModel.set_notify_observers(self.notify_observers)

if not os.path.exists(self.settings_file):
if not self.settings_file.exists():
self.settings = AppModel()
self.notify_observers()
else:
Expand Down
7 changes: 6 additions & 1 deletion backend/program/settings/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Iceberg settings models"""
from pathlib import Path
from pydantic import BaseModel
from utils import version_file_path


class NotifyingBaseModel(BaseModel):
Expand Down Expand Up @@ -96,8 +97,12 @@ class ParserModel(NotifyingBaseModel):

# Application Settings

def get_version() -> str:
with open(version_file_path.resolve()) as file:
return file.read()

class AppModel(NotifyingBaseModel):
version: str = "0.4.6"
version: str = get_version()
debug: bool = True
log: bool = True
plex: PlexModel = PlexModel()
Expand Down
2 changes: 1 addition & 1 deletion backend/program/updaters/trakt.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def create_item_from_imdb_id(imdb_id: str):
except UnboundLocalError:
logger.error("Unknown item %s with response %s", imdb_id, response)
return None
logger.error("Unable to create item from IMDb ID %s", imdb_id)
logger.error("Unable to create item from IMDb ID %s, skipping..", imdb_id)
return None

def get_imdbid_from_tvdb(tvdb_id: str) -> str:
Expand Down
7 changes: 5 additions & 2 deletions backend/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from pathlib import Path
import os

data_dir_path = Path(os.path.abspath(__file__)).parent.parent.parent / "data"

root_dir = Path(__file__).resolve().parents[2]

data_dir_path = root_dir / "data"
version_file_path = root_dir / "VERSION"
Loading

0 comments on commit b73e95c

Please sign in to comment.