Skip to content
Draft
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
7 changes: 7 additions & 0 deletions src/program/db/db_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,13 @@ def run_thread_with_db_item(
item = runner_result.media_items[0]
run_at = runner_result.run_at

if item.is_excluded:
logger.trace(
f"Item {item.log_string} is excluded, deleting from DB."
)

session.delete(item)

if not cancellation_event.is_set():
# Update parent item based on type
if isinstance(input_item, Episode):
Expand Down
7 changes: 7 additions & 0 deletions src/program/managers/event_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,13 @@ def submit_job(
item (Event, optional): The event item to process. Defaults to None.
"""

if event and event.content_item and event.content_item.is_excluded:
logger.debug(
f"Event {event.log_message if event else 'N/A'} is excluded from {service.__class__.__name__}, skipping submission."
)

return

log_message = f"Submitting service {service.__class__.__name__} to be executed"

# Content services dont provide an event.
Expand Down
26 changes: 22 additions & 4 deletions src/program/media/item.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""MediaItem class"""

from datetime import datetime
from functools import cached_property
from typing import Any, Literal, TYPE_CHECKING, Self, TypeVar

from kink import di
Expand Down Expand Up @@ -363,6 +364,23 @@
logger.error(f"Failed to schedule task for {self.log_string}: {e}")
return False

return False

Check warning on line 367 in src/program/media/item.py

View workflow job for this annotation

GitHub Actions / lint

Type analysis indicates code is unreachable (reportUnreachable)

@cached_property
def is_excluded(self) -> bool:
"""Check if the item is excluded based on its IDs."""

from program.utils.exclusions import exclusions

is_excluded = exclusions.is_excluded(self)

if is_excluded:
logger.trace(
f"Item {self.log_string} is being excluded as the ID was found in exclusions."
)

return is_excluded

@property
def is_released(self) -> bool:
"""Check if an item has been released."""
Expand Down Expand Up @@ -763,7 +781,7 @@
super().__init__(item)

def __repr__(self):
return f"Movie:{self.log_string}:{self.state.name}"
return f"Movie [tmdb: {self.tmdb_id} | imdb: {self.imdb_id}]: {self.log_string} - {self.state.name}"

def __hash__(self):
return super().__hash__()
Expand Down Expand Up @@ -892,7 +910,7 @@
return super().store_state(given_state)

def __repr__(self):
return f"Show:{self.log_string}:{self.state.name}"
return f"Show [tvdb: {self.tvdb_id}]: #{self.log_string} - {self.state.name}"

def __hash__(self):
return super().__hash__()
Expand Down Expand Up @@ -1079,7 +1097,7 @@
return value

def __repr__(self):
return f"Season:{self.number}:{self.state.name}"
return f"Season [tvdb: {self.tvdb_id}]: {self.log_string} #{self.number} - {self.state.name}"

def __hash__(self):
return super().__hash__()
Expand Down Expand Up @@ -1178,7 +1196,7 @@
super().__init__(item)

def __repr__(self):
return f"Episode:{self.number}:{self.state.name}"
return f"Episode [tvdb: {self.tvdb_id}]: {self.log_string} #{self.number} - {self.state.name}"

def __hash__(self):
return super().__hash__()
Expand Down
7 changes: 7 additions & 0 deletions src/program/services/filesystem/filesystem_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def get_key(cls) -> str:

def _initialize_rivenvfs(self, downloader: Downloader):
"""Initialize or synchronize RivenVFS"""

try:
from .vfs import RivenVFS

Expand Down Expand Up @@ -74,6 +75,12 @@ def run(self, item: "MediaItem") -> MediaItemGenerator:

# Process each episode/movie
for episode_or_movie in items_to_process:
if item.is_excluded:
logger.debug(
f"Item {episode_or_movie.log_string} is excluded from filesystem processing, skipping."
)
continue # Item is excluded, skip processing

success = self.riven_vfs.add(episode_or_movie)

if not success:
Expand Down
28 changes: 27 additions & 1 deletion src/program/services/filesystem/vfs/rivenvfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,20 @@
True if successfully added, False otherwise
"""

if item.is_excluded:
logger.info(
f"Excluding {item.log_string} from VFS add based on exclusion rules"
)

return False

from program.media.media_entry import MediaEntry

Check failure on line 541 in src/program/services/filesystem/vfs/rivenvfs.py

View workflow job for this annotation

GitHub Actions / lint

Import "MediaEntry" is not accessed (reportUnusedImport)

# Only process if this item has a filesystem entry
if not item.filesystem_entry:
logger.debug(f"Item {item.id} has no filesystem_entry, skipping VFS add")
return False

# Only process if this item has a media entry
if not (entry := item.media_entry):
logger.debug(f"Item {item.id} has no media entry, skipping VFS add")
Expand Down Expand Up @@ -749,6 +763,7 @@
# Step 1: Re-match all entries against current library profiles and collect item IDs
item_ids = list[int]()
rematched_count = 0
excluded_item_ids = set()

Check failure on line 766 in src/program/services/filesystem/vfs/rivenvfs.py

View workflow job for this annotation

GitHub Actions / lint

Type of "excluded_item_ids" is partially unknown   Type of "excluded_item_ids" is "set[Unknown]" (reportUnknownVariableType)

with db_session() as session:
entries = (
Expand All @@ -765,6 +780,10 @@
)
continue

if item.is_excluded:
excluded_item_ids.add(item.id)
continue

# Re-match library profiles based on current settings
new_profiles = matcher.get_matching_profiles(item)
old_profiles = entry.library_profiles or []
Expand All @@ -780,7 +799,9 @@

session.commit()

logger.debug(f"Re-matched {rematched_count} entries with updated profiles")
logger.debug(
f"Re-matched {rematched_count} entries with updated profiles; excluded {len(excluded_item_ids)} items due to excluded_items settings."

Check failure on line 803 in src/program/services/filesystem/vfs/rivenvfs.py

View workflow job for this annotation

GitHub Actions / lint

Argument type is partially unknown   Argument corresponds to parameter "obj" in function "len"   Argument type is "set[Unknown]" (reportUnknownArgumentType)
)

# Step 2: Clear VFS tree and rebuild from scratch
logger.debug("Clearing VFS tree for rebuild")
Expand Down Expand Up @@ -868,6 +889,11 @@
item: MediaItem to re-sync
"""

if item.is_excluded:
logger.debug(f"Item {item.id} is excluded, skipping individual sync")

return

from sqlalchemy.orm import object_session
from program.db.db import db_session

Expand Down
3 changes: 3 additions & 0 deletions src/program/services/indexers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def run(
) -> MediaItemGenerator:
"""Run the appropriate indexer based on item type."""

if item.is_excluded:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could still index but then just set the state to Paused or something.. so if later they decide they want it, they can just unpause it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch this, they could be excluding because its not indexing properly I suppose.

return

if isinstance(item, Movie) or (item.tmdb_id and not item.tvdb_id):
yield from self.tmdb_indexer.run(
item=item,
Expand Down
7 changes: 7 additions & 0 deletions src/program/services/scrapers/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ def parse_results(
if infohash in processed_infohashes:
continue

if infohash in settings_manager.settings.filesystem.excluded_items.infohashes:
logger.trace(
f"Skipping torrent for {item.log_string} due to excluded infohash: {infohash}"
)

continue

try:
torrent = rtn.rank(
raw_title=raw_title,
Expand Down
8 changes: 7 additions & 1 deletion src/program/settings/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,18 @@ def validate_library_path(cls, v: str):
return v


class ExcludedItems(BaseModel):
shows: set[str] = Field(default_factory=set)
movies: set[str] = Field(default_factory=set)
infohashes: set[str] = Field(default_factory=set)


class FilesystemModel(Observable):
mount_path: Path = Field(
default=Path("/path/to/riven/mount"),
description="Path where Riven will mount the virtual filesystem",
)

excluded_items: ExcludedItems = Field(default_factory=ExcludedItems)
library_profiles: dict[str, LibraryProfile] = Field(
default_factory=lambda: {
"anime": LibraryProfile(
Expand Down
42 changes: 42 additions & 0 deletions src/program/utils/exclusions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from typing import TYPE_CHECKING

from program.media.item import Movie, Show
from program.settings.manager import settings_manager

Check failure on line 4 in src/program/utils/exclusions.py

View workflow job for this annotation

GitHub Actions / lint

Type of "settings_manager" is unknown (reportUnknownVariableType)

Check failure on line 4 in src/program/utils/exclusions.py

View workflow job for this annotation

GitHub Actions / lint

Import "program.settings.manager" could not be resolved (reportMissingImports)

if TYPE_CHECKING:
from program.media.item import MediaItem


class Exclusions:
excluded_shows: set[str]
excluded_movies: set[str]

def __init__(self):
excluded_items = settings_manager.settings.filesystem.excluded_items

Check failure on line 15 in src/program/utils/exclusions.py

View workflow job for this annotation

GitHub Actions / lint

Type of "excluded_items" is unknown (reportUnknownVariableType)

self.excluded_movies = excluded_items.movies
self.excluded_shows = excluded_items.shows

def is_excluded(self, item: "MediaItem") -> bool:
is_excluded_movie = self._is_excluded_movie(item)

Check failure on line 21 in src/program/utils/exclusions.py

View workflow job for this annotation

GitHub Actions / lint

Argument of type "MediaItem" cannot be assigned to parameter "item" of type "Movie" in function "_is_excluded_movie"   "MediaItem" is not assignable to "Movie" (reportArgumentType)
is_excluded_show = self._is_excluded_show(item._get_top_parent())

Check failure on line 22 in src/program/utils/exclusions.py

View workflow job for this annotation

GitHub Actions / lint

Cannot access attribute "_get_top_parent" for class "MediaItem"   Attribute "_get_top_parent" is unknown (reportAttributeAccessIssue)

Check failure on line 22 in src/program/utils/exclusions.py

View workflow job for this annotation

GitHub Actions / lint

Argument type is unknown   Argument corresponds to parameter "item" in function "_is_excluded_show" (reportUnknownArgumentType)

return is_excluded_movie or is_excluded_show

def _is_excluded_show(self, item: Show) -> bool:
if item.tvdb_id is None:
return False

return str(item.tvdb_id) in self.excluded_shows

def _is_excluded_movie(self, item: Movie) -> bool:
if item.tmdb_id is None and item.imdb_id is None:
return False

return (
str(item.tmdb_id) in self.excluded_movies
or str(item.imdb_id) in self.excluded_movies
)


exclusions = Exclusions()
Loading