Skip to content

Commit 602f732

Browse files
refactor: downloaders reworked (#903)
* feat: enable TorBoxDownloader and refactor stream processing in Downloader * refactor: downloaders refactor. wip * fix: fixed torbox id handling * fix: mediafusion incorrectly parsing titles. housekeeping downloader * fix: add global timeout of 15s * fix: fixed rd instantavail endpoint * fix: alldebrid * fix: remove chunking on downloaders * fix: check download state before downloading. added bucket limit feature * fix: episode duplicate downloads * fix: alldebrid after instant endpoint removal * chore: sorted imports and updated deps --------- Co-authored-by: Spoked <[email protected]> Co-authored-by: Gaisberg <None> Co-authored-by: Spoked <[email protected]>
1 parent 4c68cfb commit 602f732

20 files changed

+1046
-1061
lines changed

poetry.lock

+256-285
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/program/managers/event_manager.py

+91-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
2+
import sys
23
import threading
4+
import time
35
import traceback
46
from concurrent.futures import Future, ThreadPoolExecutor
57
from datetime import datetime
@@ -170,7 +172,6 @@ def submit_job(self, service, program, event=None):
170172
item (Event, optional): The event item to process. Defaults to None.
171173
"""
172174
log_message = f"Submitting service {service.__name__} to be executed"
173-
item_id = None
174175
# Content services dont provide an event.
175176
if event:
176177
log_message += f" with {event.log_message}"
@@ -186,6 +187,95 @@ def submit_job(self, service, program, event=None):
186187
sse_manager.publish_event("event_update", self.get_event_updates())
187188
future.add_done_callback(lambda f:self._process_future(f, service))
188189

190+
# For debugging purposes we can monitor the execution time of the service. (comment out above and uncomment below)
191+
# def submit_job(self, service, program, event=None):
192+
# """
193+
# Submits a job to be executed by the service.
194+
195+
# Args:
196+
# service (type): The service class to execute.
197+
# program (Program): The program containing the service.
198+
# item (Event, optional): The event item to process. Defaults to None.
199+
# """
200+
# log_message = f"Submitting service {service.__name__} to be executed"
201+
# if event:
202+
# log_message += f" with {event.log_message}"
203+
# logger.debug(log_message)
204+
205+
# cancellation_event = threading.Event()
206+
# executor = self._find_or_create_executor(service)
207+
208+
# # Add start time to track execution duration
209+
# start_time = datetime.now()
210+
211+
# def _monitor_execution(future):
212+
# """Monitor execution time and log if taking too long"""
213+
# while not future.done():
214+
# execution_time = (datetime.now() - start_time).total_seconds()
215+
# if execution_time > 180: # 3 minutes
216+
# current_thread = None
217+
# for thread in threading.enumerate():
218+
# if thread.name.startswith(service.__name__) and not thread.name.endswith('_monitor'):
219+
# current_thread = thread
220+
# break
221+
222+
# if current_thread:
223+
# # Get stack frames for the worker thread
224+
# frames = sys._current_frames()
225+
# thread_frame = None
226+
# for thread_id, frame in frames.items():
227+
# if thread_id == current_thread.ident:
228+
# thread_frame = frame
229+
# break
230+
231+
# if thread_frame:
232+
# stack_trace = ''.join(traceback.format_stack(thread_frame))
233+
# else:
234+
# stack_trace = "Could not get stack trace for worker thread"
235+
# else:
236+
# stack_trace = "Could not find worker thread"
237+
238+
# logger.warning(
239+
# f"Service {service.__name__} execution taking longer than 3 minutes!\n"
240+
# f"Event: {event.log_message if event else 'No event'}\n"
241+
# f"Execution time: {execution_time:.1f} seconds\n"
242+
# f"Thread name: {current_thread.name if current_thread else 'Unknown'}\n"
243+
# f"Thread alive: {current_thread.is_alive() if current_thread else 'Unknown'}\n"
244+
# f"Stack trace:\n{stack_trace}"
245+
# )
246+
247+
# # Cancel the future and kill the thread
248+
# future.cancellation_event.set()
249+
# future.cancel()
250+
# if current_thread:
251+
# logger.warning(f"Killing thread {current_thread.name} due to timeout")
252+
# self._futures.remove(future)
253+
# if event:
254+
# self.remove_event_from_running(event)
255+
# return # Exit the monitoring thread
256+
257+
# time.sleep(60) # Check every minute
258+
259+
# future = executor.submit(db_functions.run_thread_with_db_item,
260+
# program.all_services[service].run,
261+
# service, program, event, cancellation_event)
262+
263+
# # Start monitoring thread
264+
# monitor_thread = threading.Thread(
265+
# target=_monitor_execution,
266+
# args=(future,),
267+
# name=f"{service.__name__}_monitor",
268+
# daemon=True
269+
# )
270+
# monitor_thread.start()
271+
272+
# future.cancellation_event = cancellation_event
273+
# if event:
274+
# future.event = event
275+
# self._futures.append(future)
276+
# sse_manager.publish_event("event_update", self.get_event_updates())
277+
# future.add_done_callback(lambda f: self._process_future(f, service))
278+
189279
def cancel_job(self, item_id: str, suppress_logs=False):
190280
"""
191281
Cancels a job associated with the given item.

src/program/media/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from .item import Episode, MediaItem, Movie, Season, Show, ShowMediaType, MovieMediaType, MediaType # noqa
1+
from .item import Episode, MediaItem, Movie, Season, Show # noqa
22
from .state import States # noqa

src/program/media/item.py

+4-20
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,6 @@
1818
from ..db.db_functions import blacklist_stream, reset_streams
1919
from .stream import Stream
2020

21-
class ShowMediaType(Enum):
22-
"""Show media types"""
23-
Show = "show"
24-
Season = "season"
25-
Episode = "episode"
26-
27-
class MovieMediaType(Enum):
28-
"""Media types"""
29-
Movie = "movie"
30-
31-
class MediaType(Enum):
32-
"""Combined media types"""
33-
Show = ShowMediaType.Show.value
34-
Season = ShowMediaType.Season.value
35-
Episode = ShowMediaType.Episode.value
36-
Movie = MovieMediaType.Movie.value
3721

3822
class MediaItem(db.Model):
3923
"""MediaItem class"""
@@ -426,7 +410,7 @@ def copy(self, other):
426410
return self
427411

428412
def __init__(self, item):
429-
self.type = MovieMediaType.Movie.value
413+
self.type = "movie"
430414
self.file = item.get("file", None)
431415
super().__init__(item)
432416

@@ -448,7 +432,7 @@ class Show(MediaItem):
448432
}
449433

450434
def __init__(self, item):
451-
self.type = ShowMediaType.Show.value
435+
self.type = "show"
452436
self.locations = item.get("locations", [])
453437
self.seasons: list[Season] = item.get("seasons", [])
454438
self.propagate_attributes_to_childs()
@@ -563,7 +547,7 @@ def store_state(self, given_state: States = None) -> None:
563547
super().store_state(given_state)
564548

565549
def __init__(self, item):
566-
self.type = ShowMediaType.Season.value
550+
self.type = "season"
567551
self.number = item.get("number", None)
568552
self.episodes: list[Episode] = item.get("episodes", [])
569553
super().__init__(item)
@@ -662,7 +646,7 @@ class Episode(MediaItem):
662646
}
663647

664648
def __init__(self, item):
665-
self.type = ShowMediaType.Episode.value
649+
self.type = "episode"
666650
self.number = item.get("number", None)
667651
self.file = item.get("file", None)
668652
super().__init__(item)

src/program/program.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,11 @@ def _schedule_functions(self) -> None:
254254
}
255255

256256
if settings_manager.settings.symlink.repair_symlinks:
257-
scheduled_functions[fix_broken_symlinks] = {
258-
"interval": 60 * 60 * settings_manager.settings.symlink.repair_interval,
259-
"args": [settings_manager.settings.symlink.library_path, settings_manager.settings.symlink.rclone_path]
260-
}
257+
# scheduled_functions[fix_broken_symlinks] = {
258+
# "interval": 60 * 60 * settings_manager.settings.symlink.repair_interval,
259+
# "args": [settings_manager.settings.symlink.library_path, settings_manager.settings.symlink.rclone_path]
260+
# }
261+
logger.warning("Symlink repair is disabled, this will be re-enabled in the future.")
261262

262263
for func, config in scheduled_functions.items():
263264
self.scheduler.add_job(

0 commit comments

Comments
 (0)