From 892fde89b53615f80eaa70bfa0be206c5ba05f13 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 13 Sep 2025 08:52:53 -0500 Subject: [PATCH 1/3] Fixed broken migration for plugins --- apps/plugins/migrations/0001_initial.py | 30 ++++++++++++++----------- apps/plugins/migrations/__init__.py | 1 + 2 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 apps/plugins/migrations/__init__.py diff --git a/apps/plugins/migrations/0001_initial.py b/apps/plugins/migrations/0001_initial.py index dd1bdc76..6de1490a 100644 --- a/apps/plugins/migrations/0001_initial.py +++ b/apps/plugins/migrations/0001_initial.py @@ -1,25 +1,29 @@ +# Generated by Django 5.2.4 on 2025-09-13 13:51 + from django.db import migrations, models class Migration(migrations.Migration): + initial = True - dependencies = [] + dependencies = [ + ] operations = [ migrations.CreateModel( - name="PluginConfig", + name='PluginConfig', fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("key", models.CharField(max_length=128, unique=True)), - ("name", models.CharField(max_length=255)), - ("version", models.CharField(blank=True, default="", max_length=64)), - ("description", models.TextField(blank=True, default="")), - ("enabled", models.BooleanField(default=False)), # merged change - ("ever_enabled", models.BooleanField(default=False)), # merged addition - ("settings", models.JSONField(blank=True, default=dict)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=128, unique=True)), + ('name', models.CharField(max_length=255)), + ('version', models.CharField(blank=True, default='', max_length=64)), + ('description', models.TextField(blank=True, default='')), + ('enabled', models.BooleanField(default=False)), + ('ever_enabled', models.BooleanField(default=False)), + ('settings', models.JSONField(blank=True, default=dict)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), ], - ) + ), ] diff --git a/apps/plugins/migrations/__init__.py b/apps/plugins/migrations/__init__.py new file mode 100644 index 00000000..ade076bd --- /dev/null +++ b/apps/plugins/migrations/__init__.py @@ -0,0 +1 @@ +# This file marks the migrations package for the plugins app. From 75816b5d8e764f31f230a474f12d01cdf09c47df Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 13 Sep 2025 09:07:08 -0500 Subject: [PATCH 2/3] Change folder creation for Recordings in entrypoint from 'recordings' to 'Recordings' --- docker/init/03-init-dispatcharr.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/init/03-init-dispatcharr.sh b/docker/init/03-init-dispatcharr.sh index b9c3c63b..7c62cf74 100644 --- a/docker/init/03-init-dispatcharr.sh +++ b/docker/init/03-init-dispatcharr.sh @@ -1,7 +1,7 @@ #!/bin/bash mkdir -p /data/logos -mkdir -p /data/recordings +mkdir -p /data/Recordings mkdir -p /data/uploads/m3us mkdir -p /data/uploads/epgs mkdir -p /data/m3us From 41d7066d6ea53568fd53ae5c93ee59194c91c005 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 13 Sep 2025 11:49:04 -0500 Subject: [PATCH 3/3] Fix incorrect paths for DVR and Plugins. --- apps/channels/api_views.py | 2 +- apps/channels/tasks.py | 6 +- apps/plugins/loader.py | 4 +- .../0016_update_dvr_template_paths.py | 61 +++ core/models.py | 4 +- docker/init/03-init-dispatcharr.sh | 3 +- frontend/src/pages/Plugins.jsx | 504 ++++++++++++++---- frontend/src/pages/Settings.jsx | 8 +- 8 files changed, 480 insertions(+), 112 deletions(-) create mode 100644 core/migrations/0016_update_dvr_template_paths.py diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index 08331381..0cdd716a 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -1857,7 +1857,7 @@ class RecordingViewSet(viewsets.ModelViewSet): except Exception: pass - library_dir = '/app/data' + library_dir = '/data' allowed_roots = ['/data/', library_dir.rstrip('/') + '/'] def _safe_remove(path: str): diff --git a/apps/channels/tasks.py b/apps/channels/tasks.py index d4faa7bb..5135ddaf 100755 --- a/apps/channels/tasks.py +++ b/apps/channels/tasks.py @@ -578,8 +578,8 @@ def _build_output_paths(channel, program, start_time, end_time): Build (final_path, temp_ts_path, final_filename) using DVR templates. """ from core.models import CoreSettings - # Root for DVR recordings: fixed to /app/data inside the container - library_root = '/app/data' + # Root for DVR recordings: fixed to /data/recordings inside the container + library_root = '/data/recordings' is_movie, season, episode, year, sub_title = _parse_epg_tv_movie_info(program) show = _safe_name(program.get('title') if isinstance(program, dict) else channel.name) @@ -632,7 +632,7 @@ def _build_output_paths(channel, program, start_time, end_time): if not is_movie and not rel_path: rel_path = f"TV_Shows/{show}/S{season:02d}E{episode:02d}.mkv" # Keep any leading folder like 'Recordings/' from the template so users can - # structure their library under /app/data as desired. + # structure their library under /data as desired. if not rel_path.lower().endswith('.mkv'): rel_path = f"{rel_path}.mkv" diff --git a/apps/plugins/loader.py b/apps/plugins/loader.py index 2999e869..5422ae7e 100644 --- a/apps/plugins/loader.py +++ b/apps/plugins/loader.py @@ -26,7 +26,7 @@ class LoadedPlugin: class PluginManager: - """Singleton manager that discovers and runs plugins from /app/data/plugins.""" + """Singleton manager that discovers and runs plugins from /data/plugins.""" _instance: Optional["PluginManager"] = None @@ -37,7 +37,7 @@ class PluginManager: return cls._instance def __init__(self) -> None: - self.plugins_dir = os.environ.get("DISPATCHARR_PLUGINS_DIR", "/app/data/plugins") + self.plugins_dir = os.environ.get("DISPATCHARR_PLUGINS_DIR", "/data/plugins") self._registry: Dict[str, LoadedPlugin] = {} # Ensure plugins directory exists diff --git a/core/migrations/0016_update_dvr_template_paths.py b/core/migrations/0016_update_dvr_template_paths.py new file mode 100644 index 00000000..5e729c47 --- /dev/null +++ b/core/migrations/0016_update_dvr_template_paths.py @@ -0,0 +1,61 @@ +# Generated manually to update DVR template paths + +from django.db import migrations +from django.utils.text import slugify + + +def update_dvr_template_paths(apps, schema_editor): + """Remove 'Recordings/' prefix from DVR template paths""" + CoreSettings = apps.get_model("core", "CoreSettings") + + # Define the updates needed + updates = [ + (slugify("DVR TV Template"), "TV_Shows/{show}/S{season:02d}E{episode:02d}.mkv"), + (slugify("DVR Movie Template"), "Movies/{title} ({year}).mkv"), + (slugify("DVR TV Fallback Template"), "TV_Shows/{show}/{start}.mkv"), + (slugify("DVR Movie Fallback Template"), "Movies/{start}.mkv"), + ] + + # Update each setting + for key, new_value in updates: + try: + setting = CoreSettings.objects.get(key=key) + setting.value = new_value + setting.save() + print(f"Updated {setting.name}: {new_value}") + except CoreSettings.DoesNotExist: + print(f"Setting with key '{key}' not found - skipping") + + +def reverse_dvr_template_paths(apps, schema_editor): + """Add back 'Recordings/' prefix to DVR template paths""" + CoreSettings = apps.get_model("core", "CoreSettings") + + # Define the reverse updates (add back Recordings/ prefix) + updates = [ + (slugify("DVR TV Template"), "Recordings/TV_Shows/{show}/S{season:02d}E{episode:02d}.mkv"), + (slugify("DVR Movie Template"), "Recordings/Movies/{title} ({year}).mkv"), + (slugify("DVR TV Fallback Template"), "Recordings/TV_Shows/{show}/{start}.mkv"), + (slugify("DVR Movie Fallback Template"), "Recordings/Movies/{start}.mkv"), + ] + + # Update each setting back to original + for key, original_value in updates: + try: + setting = CoreSettings.objects.get(key=key) + setting.value = original_value + setting.save() + print(f"Reverted {setting.name}: {original_value}") + except CoreSettings.DoesNotExist: + print(f"Setting with key '{key}' not found - skipping") + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0015_dvr_templates"), + ] + + operations = [ + migrations.RunPython(update_dvr_template_paths, reverse_dvr_template_paths), + ] \ No newline at end of file diff --git a/core/models.py b/core/models.py index f9d49f7b..ba040666 100644 --- a/core/models.py +++ b/core/models.py @@ -255,7 +255,7 @@ class CoreSettings(models.Model): return cls.objects.get(key=DVR_TV_FALLBACK_TEMPLATE_KEY).value except cls.DoesNotExist: # default requested by user - return "Recordings/TV_Shows/{show}/{start}.mkv" + return "TV_Shows/{show}/{start}.mkv" @classmethod def get_dvr_movie_fallback_template(cls): @@ -263,7 +263,7 @@ class CoreSettings(models.Model): try: return cls.objects.get(key=DVR_MOVIE_FALLBACK_TEMPLATE_KEY).value except cls.DoesNotExist: - return "Recordings/Movies/{start}.mkv" + return "Movies/{start}.mkv" @classmethod def get_dvr_comskip_enabled(cls): diff --git a/docker/init/03-init-dispatcharr.sh b/docker/init/03-init-dispatcharr.sh index 7c62cf74..629c5a51 100644 --- a/docker/init/03-init-dispatcharr.sh +++ b/docker/init/03-init-dispatcharr.sh @@ -1,11 +1,12 @@ #!/bin/bash mkdir -p /data/logos -mkdir -p /data/Recordings +mkdir -p /data/recordings mkdir -p /data/uploads/m3us mkdir -p /data/uploads/epgs mkdir -p /data/m3us mkdir -p /data/epgs +mkdir -p /data/plugins mkdir -p /app/logo_cache mkdir -p /app/media diff --git a/frontend/src/pages/Plugins.jsx b/frontend/src/pages/Plugins.jsx index 9494e301..f2902523 100644 --- a/frontend/src/pages/Plugins.jsx +++ b/frontend/src/pages/Plugins.jsx @@ -49,7 +49,10 @@ const Field = ({ field, value, onChange }) => { return (