Skip to content

Commit

Permalink
Realdebrid and Symlink Changes (#351)
Browse files Browse the repository at this point in the history
* fix: extra validation on set_active_files

* feat: several fixes. wip on symlinking

* feat: symlinking working as it should now!

* fix: minor tweaks to release dates and container

* fix: symlink get_infohash issue

* feat: updated logging for api. add symlink validation

* fix: tidy api logging

---------

Co-authored-by: Spoked <Spoked@localhost>
  • Loading branch information
dreulavelle and Spoked authored Jun 6, 2024
1 parent 666a1ee commit 2c70838
Show file tree
Hide file tree
Showing 23 changed files with 1,018 additions and 368 deletions.
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.git/
.gitignore
.dockerignore
docker-compose.yml
docker-compose*
Dockerfile
makefile
htmlcov/
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ logs/
settings.json
.vscode
.git
docker-compose.yml
makefile
.ruff_cache/
*.dat
Expand Down
61 changes: 37 additions & 24 deletions backend/controllers/items.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import APIRouter, HTTPException, Request
from program.content.overseerr import Overseerr
from program.media.state import States
from utils.logger import logger

Expand Down Expand Up @@ -27,7 +28,7 @@ async def get_items(request: Request):

@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)
item = request.app.program.media_items.get_item(item_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return {
Expand All @@ -37,53 +38,65 @@ async def get_extended_item_info(request: Request, item_id: str):

@router.delete("/remove/id/{item_id}")
async def remove_item(request: Request, item_id: str):
item = request.app.program.media_items.get_item_by_id(item_id)
if item is None:
item = request.app.program.media_items.get_item(item_id)
if not item:
logger.error(f"Item with ID {item_id} not found")
raise HTTPException(status_code=404, detail="Item not found")

request.app.program.media_items.remove(item)
if item.symlinked:
request.app.program.media_items.remove_symlink(item)
logger.success(f"Removed symlink for item with ID {item_id}")

overseerr_result = request.app.program.content.overseerr.delete_request(item_id)
if overseerr_result:
logger.success(f"Deleted Overseerr request for item with ID {item_id}")
else:
logger.error(f"Failed to delete Overseerr request for item with ID {item_id}")

logger.log("API", f"Removed symlink for item with ID {item_id}")

overseerr_service = request.app.program.services.get(Overseerr)
if overseerr_service and overseerr_service.initialized:
try:
overseerr_result = overseerr_service.delete_request(item_id)
if overseerr_result:
logger.log("API", f"Deleted Overseerr request for item with ID {item_id}")
else:
logger.log("API", f"Failed to delete Overseerr request for item with ID {item_id}")
except Exception as e:
logger.error(f"Exception occurred while deleting Overseerr request for item with ID {item_id}: {e}")

return {
"success": True,
"message": f"Removed {item_id}",
}

@router.delete("/remove/imdb/{imdb_id}")
async def remove_item_by_imdb(request: Request, imdb_id: str):
item = request.app.program.media_items.get_item_by_imdb_id(imdb_id)
if item is None:
item = request.app.program.media_items.get_item(imdb_id)
if not item:
logger.error(f"Item with IMDb ID {imdb_id} not found")
raise HTTPException(status_code=404, detail="Item not found")

request.app.program.media_items.remove(item)
if item.symlinked:
if item.symlinked or (item.file and item.folder): # TODO: this needs to be checked later..
request.app.program.media_items.remove_symlink(item)
logger.success(f"Removed symlink for item with IMDb ID {imdb_id}")

overseerr_result = request.app.program.content.overseerr.delete_request(imdb_id)
if overseerr_result:
logger.success(f"Deleted Overseerr request for item with IMDb ID {imdb_id}")
logger.log("API", f"Removed symlink for item with IMDb ID {imdb_id}")

overseerr_service = request.app.program.services.get(Overseerr)
if overseerr_service and overseerr_service.initialized:
try:
overseerr_result = overseerr_service.delete_request(item.overseerr_id)
if overseerr_result:
logger.log("API", f"Deleted Overseerr request for item with IMDb ID {imdb_id}")
else:
logger.error(f"Failed to delete Overseerr request for item with IMDb ID {imdb_id}")
except Exception as e:
logger.error(f"Exception occurred while deleting Overseerr request for item with IMDb ID {imdb_id}: {e}")
else:
logger.error(f"Failed to delete Overseerr request for item with IMDb ID {imdb_id}")
logger.error("Overseerr service not found in program services")

return {
"success": True,
"message": f"Removed item with IMDb ID {imdb_id}",
}

@router.get("/imdb/{imdb_id}")
async def get_imdb_info(request: Request, imdb_id: str):
item = request.app.program.media_items.get_item_by_imdb_id(imdb_id)
item = request.app.program.media_items.get_item(imdb_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return {"success": True, "item": item.to_extended_dict()}
30 changes: 24 additions & 6 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,26 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from program import Program
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from utils.logger import logger


class LoguruMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start_time = time.time()
try:
response = await call_next(request)
except Exception as e:
logger.exception(f"Exception during request processing: {e}")
raise
finally:
process_time = time.time() - start_time
logger.log(
"API", f"{request.method} {request.url.path} - {response.status_code if 'response' in locals() else '500'} - {process_time:.2f}s"
)
return response

parser = argparse.ArgumentParser()
parser.add_argument(
"--ignore_cache",
Expand All @@ -34,6 +52,9 @@
allow_headers=["*"],
)

# Add the custom Loguru middleware
app.add_middleware(LoguruMiddleware)

app.include_router(default_router)
app.include_router(settings_router)
app.include_router(items_router)
Expand Down Expand Up @@ -61,20 +82,17 @@ def run_in_thread(self):
config = uvicorn.Config(app, host="0.0.0.0", port=8080, log_config=None)
server = Server(config=config)


with server.run_in_thread():
try:
app.program.start()
app.program.run()
app.program.stop()
except AttributeError as e:
logger.error(f"Program failed to initialize: {e}")
except KeyboardInterrupt:
app.program.stop()
sys.exit(0)
pass
except Exception as e:
logger.exception(f"Error in main thread: {e}")
finally:
app.program.stop()
logger.critical("Server has been stopped")
# need to terminate to give back control of terminal to user
sys.exit(0)
sys.exit(0)
8 changes: 4 additions & 4 deletions backend/program/content/overseerr.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Overseerr content module"""

from typing import Union

from program.indexers.trakt import get_imdbid_from_tmdb
from program.media.item import MediaItem
from program.settings.manager import settings_manager
from requests.exceptions import ConnectionError, RetryError
from urllib3.exceptions import MaxRetryError, NewConnectionError
from utils.logger import logger
from utils.request import delete, get, ping, post
from requests.exceptions import RetryError, ConnectionError
from urllib3.exceptions import MaxRetryError
from urllib3.exceptions import NewConnectionError


class Overseerr:
Expand Down Expand Up @@ -141,7 +141,7 @@ def delete_request(mediaId: int) -> bool:
settings.url + f"/api/v1/request/{mediaId}",
additional_headers=headers,
)
logger.info(f"Deleted request {mediaId} from overseerr")
logger.success(f"Deleted request {mediaId} from overseerr")
return response.is_ok
except Exception as e:
logger.error(f"Failed to delete request from overseerr: {str(e)}")
Expand Down
8 changes: 6 additions & 2 deletions backend/program/content/plex_watchlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,13 @@ def _get_items_from_watchlist(self) -> Generator[MediaItem, None, None]:
url = f"https://metadata.provider.plex.tv/library/sections/watchlist/all?X-Plex-Token={self.token}&{filter_params}"
response = get(url)
if not response.is_ok or not hasattr(response.data, "MediaContainer"):
yield
logger.error("Invalid response or missing MediaContainer in response data.")
return
for item in response.data.MediaContainer.Metadata:
media_container = getattr(response.data, "MediaContainer", None)
if not media_container or not hasattr(media_container, "Metadata"):
logger.error("MediaContainer is missing Metadata attribute.")
return
for item in media_container.Metadata:
if hasattr(item, "ratingKey") and item.ratingKey:
imdb_id = self._ratingkey_to_imdbid(item.ratingKey)
if imdb_id and imdb_id not in self.recurring_items:
Expand Down
2 changes: 1 addition & 1 deletion backend/program/content/trakt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from urllib.parse import urlencode, urlparse

import regex
from requests import RequestException
from program.media.item import MediaItem, Movie, Show
from program.settings.manager import settings_manager
from requests import RequestException
from utils.logger import logger
from utils.request import RateLimiter, get, post

Expand Down
Loading

0 comments on commit 2c70838

Please sign in to comment.