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

Concurrency and show fixes #52

Merged
merged 3 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
408 changes: 204 additions & 204 deletions backend/.gitignore

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions backend/controllers/items.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from fastapi import APIRouter, HTTPException, Request
from program.media import MediaItemState
from utils.logger import logger


router = APIRouter(
Expand All @@ -26,14 +25,14 @@ async def get_items(request: Request):
}


@router.get("/{state}")
async def get_item(request: Request, state: str):
state = MediaItemState[state]
items = request.app.program.media_items.get_items_with_state(state).items

@router.get("/extended/{item_id}")
async def get_extended_item_info(request: Request, item_id: str):
item = request.app.program.media_items.get_item_by_id(item_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return {
"success": True,
"items": [item.to_dict() for item in items],
"item": item.to_extended_dict(), # Assuming this method exists
}


Expand Down
19 changes: 6 additions & 13 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,24 @@
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from program.program import Program
from utils.thread import ThreadRunner
from program import Program
from controllers.settings import router as settings_router
from controllers.items import router as items_router
from controllers.default import router as default_router


sys.path.append(os.getcwd())
program = Program()
runner = ThreadRunner(program.run, 5)


def lifespan(app: FastAPI):
runner.start()
app.program.start()
yield
runner.stop()
app.program.stop()


app = FastAPI(lifespan=lifespan)
app.program = Program()

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
Expand All @@ -30,15 +29,9 @@ def lifespan(app: FastAPI):
allow_headers=["*"],
)

app.program = program

app.include_router(default_router)
app.include_router(settings_router)
app.include_router(items_router)

if __name__ == "__main__":
try:
uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=False)
except KeyboardInterrupt:
print("Exiting...")
sys.exit(0)
uvicorn.run(app, host="0.0.0.0", port=8080, reload=False)
79 changes: 79 additions & 0 deletions backend/program/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Program main module"""
import os
from typing import Optional
from pydantic import BaseModel, HttpUrl, Field
from program.symlink import Symlinker
from utils.logger import logger, get_data_path
from utils.settings import settings_manager
from program.media import MediaItemContainer
from program.libraries.plex import Library as Plex
from program.debrid.realdebrid import Debrid as RealDebrid
from program.content import Content
from program.scrapers import Scraping


# Pydantic models for configuration
class PlexConfig(BaseModel):
user: str
token: str
address: HttpUrl
watchlist: Optional[HttpUrl] = None


class MdblistConfig(BaseModel):
lists: list[str] = Field(default_factory=list)
api_key: str
update_interval: int = 80


class OverseerrConfig(BaseModel):
url: HttpUrl
api_key: str


class RealDebridConfig(BaseModel):
api_key: str


class TorrentioConfig(BaseModel):
filter: str


class Settings(BaseModel):
version: str
debug: bool
service_mode: bool
log: bool
menu_on_startup: bool
plex: PlexConfig
mdblist: MdblistConfig
overseerr: OverseerrConfig
scraper_torrentio: TorrentioConfig
realdebrid: RealDebridConfig


class Program:
"""Program class"""

def __init__(self):
self.settings = settings_manager.get_all()
self.media_items = MediaItemContainer(items=[])
self.data_path = get_data_path()
self.media_items.load(os.path.join(self.data_path, "media.pkl"))
self.threads = [
Content(self.media_items), # Content must be first
Plex(self.media_items),
RealDebrid(self.media_items),
Symlinker(self.media_items),
Scraping(self.media_items),
]
logger.info("Iceberg initialized")

def start(self):
for thread in self.threads:
thread.start()

def stop(self):
for thread in self.threads:
thread.stop()
self.media_items.save(os.path.join(self.data_path, "media.pkl"))
45 changes: 45 additions & 0 deletions backend/program/content/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import threading
import time

from utils.logger import logger
from .mdblist import Mdblist
from .overseerr import Overseerr
from .plex_watchlist import PlexWatchlist


class Content(threading.Thread):
def __init__(self, media_items):
super().__init__(name="Content")
self.services = [
Mdblist(media_items),
Overseerr(media_items),
PlexWatchlist(media_items),
]
self.valid = False
self.running = False
while not self.validate():
logger.error(
"You have no content services enabled, please enable at least one!"
)
time.sleep(5)

for service in self.services:
if service.initialized:
service.run()

def validate(self):
return any(service.initialized for service in self.services)

def run(self) -> None:
while self.running:
for service in self.services:
if service.initialized:
service.run()

def start(self) -> None:
self.running = True
super().start()

def stop(self) -> None:
self.running = False
super().join()
16 changes: 6 additions & 10 deletions backend/program/content/mdblist.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
from program.updaters.trakt import Updater as Trakt


class Content:
class Mdblist:
"""Content class for mdblist"""

def __init__(
self,
):
def __init__(self, media_items: MediaItemContainer):
self.initialized = False
self.media_items = media_items
self.settings = settings_manager.get("mdblist")
if not self._validate_settings():
logger.info("mdblist is not configured and will not be used.")
Expand All @@ -29,26 +28,23 @@ def _validate_settings(self):
)
return not "Invalid API key!" in response.text

def update_items(self, media_items: MediaItemContainer):
def run(self):
"""Fetch media from mdblist and add them to media_items attribute
if they are not already there"""
try:
with self.rate_limiter:
logger.debug("Getting items...")

items = []
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 media_items]
new_items = [item for item in items if item not in self.media_items]
container = self.updater.create_items(new_items)
added_items = media_items.extend(container)
added_items = self.media_items.extend(container)
if len(added_items) > 0:
logger.info("Added %s items", len(added_items))
logger.debug("Done!")
except RateLimitExceeded:
pass

Expand Down
15 changes: 6 additions & 9 deletions backend/program/content/overseerr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
from program.updaters.trakt import Updater as Trakt


class Content:
class Overseerr:
"""Content class for overseerr"""

def __init__(
self,
):
def __init__(self, media_items: MediaItemContainer):
self.initialized = False
self.media_items = media_items
self.settings = settings_manager.get("overseerr")
if self.settings.get("api_key") == "" or not self._validate_settings():
logger.info("Overseerr is not configured and will not be used.")
Expand All @@ -33,17 +32,15 @@ def _validate_settings(self):
except ConnectTimeout:
return False

def update_items(self, media_items: MediaItemContainer):
def run(self):
"""Fetch media from overseerr and add them to media_items attribute
if they are not already there"""
logger.debug("Getting items...")
items = self._get_items_from_overseerr(10000)
new_items = [item for item in items if item not in media_items]
new_items = [item for item in items if item not in self.media_items]
container = self.updater.create_items(new_items)
added_items = media_items.extend(container)
added_items = self.media_items.extend(container)
if len(added_items) > 0:
logger.info("Added %s items", len(added_items))
logger.debug("Done!")

def _get_items_from_overseerr(self, amount: int):
"""Fetch media from overseerr"""
Expand Down
13 changes: 6 additions & 7 deletions backend/program/content/plex_watchlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import json


class Content:
class PlexWatchlist:
"""Class for managing Plex watchlist"""

def __init__(self):
def __init__(self, media_items: MediaItemContainer):
self.initialized = False
self.media_items = media_items
self.watchlist_url = settings.get("plex")["watchlist"]
if not self.watchlist_url or not self._validate_settings():
logger.info(
Expand All @@ -32,17 +33,15 @@ def _validate_settings(self):
except ConnectTimeout:
return False

def update_items(self, media_items: MediaItemContainer):
def run(self):
"""Fetch media from Plex watchlist and add them to media_items attribute
if they are not already there"""
logger.debug("Getting items...")
items = self._get_items_from_plex_watchlist()
new_items = [item for item in items if item not in media_items]
new_items = [item for item in items if item not in self.media_items]
container = self.updater.create_items(new_items)
added_items = media_items.extend(container)
added_items = self.media_items.extend(container)
if len(added_items) > 0:
logger.info("Added %s items", len(added_items))
logger.debug("Done!")

def _get_items_from_plex_watchlist(self) -> list:
"""Fetch media from Plex watchlist"""
Expand Down
29 changes: 24 additions & 5 deletions backend/program/debrid/realdebrid.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Realdebrid module"""
import os
import re
import threading
import time

import requests
from requests import ConnectTimeout
from utils.logger import logger
Expand All @@ -24,14 +24,19 @@ def get_user():
return response.json()


class Debrid: # TODO CHECK TORRENTS LIST BEFORE DOWNLOAD, IF DOWNLOADED AND NOT IN LIBRARY CHOOSE ANOTHER TORRENT
class Debrid(
threading.Thread
): # TODO CHECK TORRENTS LIST BEFORE DOWNLOAD, IF DOWNLOADED AND NOT IN LIBRARY CHOOSE ANOTHER TORRENT
"""Real-Debrid API Wrapper"""

def __init__(self):
def __init__(self, media_items: MediaItemContainer):
super().__init__(name="Debrid")
# Realdebrid class library is a necessity
while True:
self.settings = settings_manager.get("realdebrid")
self.media_items = media_items
self.auth_headers = {"Authorization": f'Bearer {self.settings["api_key"]}'}
self.running = False
if self._validate_settings():
self._torrents = {}
break
Expand All @@ -48,23 +53,37 @@ def _validate_settings(self):
except ConnectTimeout:
return False

def download(self, media_items: MediaItemContainer):
def run(self):
while self.running:
self.download()

def start(self) -> None:
self.running = True
super().start()

def stop(self) -> None:
self.running = False
super().join()

def download(self):
"""Download given media items from real-debrid.com"""
added_files = 0

items = []
for item in media_items:
for item in self.media_items:
if item.state is not MediaItemState.LIBRARY:
if item.type == "movie" and item.state is MediaItemState.SCRAPE:
items.append(item)
if item.type == "show":
item._lock.acquire()
for season in item.seasons:
if season.state is MediaItemState.SCRAPE:
items.append(season)
else:
for episode in season.episodes:
if episode.state is MediaItemState.SCRAPE:
items.append(episode)
item._lock.release()

for item in items:
added_files += self._download(item)
Expand Down
Loading
Loading