github MotWakorb/enhancedchannelmanager v0.17.4

5 hours ago

Added

  • Clear Emby Logos — flush Emby's cached channel logos so it re-fetches fresh ones (GH #475, bd-v9tp7, build 0009). Emby caches channel logos and keeps serving a stale image even after the logo changes upstream in Dispatcharr; the operator had no way to force a refresh. Replicates Channel Identifiarr's "clear logos" feature, reusing ECM's existing Emby connection — the saved emby_base_url + emby_api_key with X-Emby-Token auth — so no new credentials and no username/password login (a static Emby API key authenticates GET /LiveTv/Channels enumeration and the per-channel DELETE /Items/{id}/Images/{type} calls just as well; deleting the cached image makes Emby re-download the logo from its source on next channel access). Backend: new EmbyClient.get_livetv_channels() (the auth gate) + delete_item_image(id, type) with a Primary/LogoLight/LogoLightColor image-type whitelist; new admin-gated POST /api/emby/clear-logos returns 202 + {job_id} and runs a supervised background task (the full-lineup sweep — hundreds of channels × up to 3 types — would otherwise exceed the 30s request-timeout middleware, same 202+poll pattern as bulk-commit, bd-ggxks), with a GET /api/emby/clear-logos/{job_id} poll endpoint. The worker clears all Live TV channels (accepts an optional channel_ids filter for a future per-group UI), is per-channel resilient (one channel lacking a logo type never aborts the run), and validates Emby is configured (400 otherwise). Progress via the notification system, like stream probe: the job emits a task_clear_emby_logos progress notification (create → rate-limited updates → finalize success/warning) that the NotificationCenter renders live — a progress bar, deleted/skipped/error counts, and the current channel name — driven by a new clearing active status. Frontend: a "Clear Cached Logos" block in Settings → Integrations → Emby with per-type checkboxes (default all three) and a fire-and-forget button gated on a saved Emby key. MCP: new clear_emby_logos tool (202+poll, same whitelist). (GH #475, bd-v9tp7, build 0009)
  • Global date-format preference under Settings → Appearance (bd-8j47e, build 0007). ECM now has a single instance-wide "Date Format" control (Automatic / Month-Day-Year / Day-Month-Year / Year-Month-Day ISO) that drives every date and time shown in the UI. Previously date rendering was inconsistent: frontend/src/utils/formatting.ts and ~14 components each formatted dates independently — some hardcoded en-US (always m/d/y), others passed no locale and silently deferred to each viewer's browser locale. That split is exactly why one operator saw 06/04 while another saw 04/06 on the same screen. Fix: a single setDateFormatLocale/getDateLocale resolver in formatting.ts maps the preference to a locale (auto→browser, mdy→en-US, dmy→en-GB, iso→en-CA), and every date call site — the shared formatters plus all direct toLocaleString/toLocaleDateString/Intl.DateTimeFormat callers — routes through it. The setting is applied on app init and updates live (no reload) when changed. Backend persists date_format in DispatcharrSettings alongside theme. Scope: settings are instance-wide, so this is one shared format for all users; the default is auto, which preserves the prior per-browser behavior, so no display changes until an operator explicitly pins a format. Time-of-day 12h/24h rendering is unchanged. (bd-8j47e, build 0007)

Changed

  • {normalized_name} now applies the rule's normalization groups in every auto-creation action, not just Create Channel (GH #466, bd-6gvt8, build 0006). The template variable is documented as "Name after normalization rules", but it only behaved that way in Create Channel — which re-normalizes its expanded name in a separate step. Everywhere else (Assign TVG-ID, Set Variable, Assign Logo, …) {normalized_name} resolved to the raw stream name, because StreamContext.normalized_name is never populated during a run and the variable fell back to the stream name. So with a "Strip country prefix" group, {normalized_name} produced CHANNEL in Create Channel but US | CHANNEL in Assign TVG-ID. Fix: {normalized_name} is now resolved once at the per-action template-context chokepoint (_build_template_context) using the firing rule's normalization_group_ids, so it means the same thing in every action. Scope of change: only rules that actually have normalization groups are affected — a rule with no groups still yields the raw stream name (unchanged), and Create Channel output is unchanged (its post-expansion normalization is idempotent on the already-normalized value). (GH #466, bd-6gvt8, build 0006)

Fixed

  • Print Channel Guide rendered nothing but greyed-out phantom rows for groups numbered with float/decimal channel numbers (e.g. OTA 2.1, 5.1) (bd-szsb2, build 0008). With "Show empty slots" on (the default), generatePrintHtml walked the group's range by integer steps and looked each slot up in a map keyed by the channel's exact number. For a float-numbered group no integer ever matched a float key, so every real channel was skipped (no data) and every integer in the range printed as a greyed placeholder — the operator saw "2–38, all empty, plus channels that don't exist." A secondary bug parsed the per-group From/To bounds with parseInt, truncating e.g. 38.5 → 38 and dropping the highest float channel from the range. Fix: a group containing any non-integer channel number now skips integer gap-fill entirely and renders only its real channels (the integer gaps between 2.1 and 5.1 are unused major numbers, not "missing channels" — PO decision); pure-integer groups keep the existing empty-slot fill unchanged. The four From/To parse sites switch parseIntparseFloat so decimal bounds and the top float channel are respected. Adds 3 float-specific tests (render-not-skipped, no-placeholders-for-float-group, no-parseInt-truncation regression). (bd-szsb2, build 0008)
  • Scheduled tasks sent a "starting" external alert (Telegram/Discord/email) on every run even when only warning/error alerts were enabled (GH #462, bd-on4sr, build 0005). A task's start/progress notification (task_scheduler._create_progress_notification) is an "info"-level " starting..." message, but task_engine._execute_task wired its external-alert dispatch straight from the master send_alerts toggle, never consulting the per-task alert_on_info flag. So a task configured with only warning + error alerts (alert_on_info=False, the column default) still pushed a "starting" alert to every configured channel on every run — the reporter saw it via Telegram for the auto-creation schedule. The completion alert was already gated per-level (check_and_run_tasks honours alert_on_success/_warning/_error/_info); only the start notification leaked. Fix: _execute_task now passes send_alerts AND alert_on_info as the progress-notification alert flag, so the "starting" message dispatches externally only when info alerts are explicitly enabled. The in-app NotificationCenter toast is unchanged — it stays gated by show_notifications, independent of the external-alert level. (GH #462, bd-on4sr, build 0005)
  • Deleting a normalization rule group left a dangling reference in auto-creation rules that then refused to save (GH #465, bd-miut3, build 0004). An auto-creation rule stores the normalization rule groups it applies as normalization_group_ids (the "Normalization Groups" selector in the rule editor — the groups that strip quality tags). DELETE /api/normalization/groups/{id} removed the group and its rules but never stripped that id from any rule's normalization_group_ids, so the rule kept a reference to a group that no longer existed. On the next edit the rule editor reloaded the full id list but couldn't render a checkbox for the missing group, and the write-time validator (_validate_normalization_group_ids, bd-i75ax) then rejected every save with 422 — normalization_group_ids do not exist; the only workaround was "Clear all + re-select". Impact was save-block only — at run time normalize(group_ids=…) filters by membership, so a missing id was silently ignored and auto-creation still ran correctly. Fix: deleting a normalization group now cascade-strips its id from every auto-creation rule's normalization_group_ids in the same transaction (_strip_normalization_group_ref), and a startup heal (_heal_orphaned_normalization_group_refs, in _run_migrations) repairs rules orphaned by deletions that happened before this fix shipped — so already-broken rules become saveable again without operator action. (GH #465, bd-miut3, build 0004)
  • Scheduled tasks showed "Next Run: Never" in Settings → Scheduled Tasks despite having an active schedule (GH #468, bd-a80u2, build 0003). The "Next Run" the UI renders came from the registry's in-memory instance._next_run, which is loaded from the DB only once at startup (sync_from_database). Every path that maintains schedules — the schedule create/update/delete endpoints (_update_task_next_run) and task_engine.check_and_run_tasks after a run — updates the database (scheduled_tasks.next_run_at + task_schedules.next_run_at) but never refreshes the in-memory instance. Tasks whose default schedule is MANUAL (Stream Probe, Re-probe Failed Streams, EPG/M3U refresh, etc.) start with instance._next_run = None, so adding a schedule through the multi-schedule UI left the in-memory value at None and the UI showed "Never" until the next container restart. Compounding this, _save_task_to_db wrote the stale in-memory None back over the correctly-computed scheduled_tasks.next_run_at, so any later sync_to_database (e.g. editing alert settings) clobbered the DB value and made "Never" persist across restarts. Impact was display-only — the task engine fires off the child task_schedules.next_run_at rows, which were always correct, so the tasks did run on schedule. Fix: GET /api/tasks and GET /api/tasks/{id} now source "Next Run" from the earliest enabled task_schedules.next_run_at (the real firing source) instead of the stale in-memory value, falling back to the in-memory value only for tasks with no schedule rows; and _save_task_to_db no longer overwrites next_run_at for tasks that have task_schedules rows (those are DB-managed). (GH #468, bd-a80u2, build 0003)
  • Bulk channel creation re-paginated the entire Dispatcharr logo catalog once per channel, making large batches O(channels × catalog-pages) and exhausting the request budget (bd-raehx, build 0002). Every createChannel op carrying a logoUrl but no logoId called find_logo_by_url(), which paginates the whole logo catalog (500/page) on every call with no caching. Two real debug bundles confirm the cost: a 441-op SiriusXM batch issued 2,391 logo GETs (~5-page catalog, finished server-side in ~6 min after a 504), and a 113-op batch against a larger ~25-page catalog issued 859 logo GETs and reached only ~channel 22 before the 30s 504. This is why batches "created channels but didn't assign streams past ~15", why an op reported failed while the write still happened (the handler keeps running after the client's 504), and why adding streams manually afterward worked. Fix: _run_bulk_commit now builds a per-run {url → logo} index once, lazily (only the first time an op actually needs a logo lookup, so validateOnly and logo-free batches pay nothing), and every subsequent op reuses it; logos created mid-batch are inserted into the index so a later op sharing the same logoUrl reuses them instead of creating a duplicate (also fixes a latent duplicate-logo bug). Catalog fetches drop from ~859/2,391 to ~25/5 — one pagination per batch. The per-channel find_logo_by_url first-match semantics and the "logo failure still creates the channel without a logo" fallthrough are preserved. (bd-raehx, build 0002)
  • Bulk-commit partial outcomes are now flagged explicitly so the client can distinguish "some applied, some failed" from a total failure (bd-5xciq, build 0002). The BulkCommitResponse envelope gains a partial boolean (operationsApplied > 0 AND operationsFailed > 0), present on every return path. This makes a partially-applied batch reconcilable via tempIdMap instead of reading as a flat failure that prompts a duplicate-creating retry. The frontend BulkCommitResponse type carries the field; existing useEditMode rendering already derives partial state from the applied/failed counts, so behavior is unchanged and the flag is exposed for future use. (bd-5xciq, build 0002)
  • POST /api/channels/bulk-commit returned 504 mid-flight on large batches while the handler kept running, producing duplicates on retry (bd-ggxks, build 0001). The endpoint was synchronous and pinned behind the 30s ECM_REQUEST_TIMEOUT_SECONDS middleware. A 441-op SiriusXM batch (~6 min of sequential Dispatcharr POSTs) caused the middleware to fire 504 Gateway Timeout to the operator while the handler kept running in the background; the operator retried, each retry committed another ~30 partial channels, and Dispatcharr accumulated duplicates. Fix: bulk-commit now follows the bd-cns7j / bd-enfsy 202+poll pattern. Non-validateOnly POST /api/channels/bulk-commit returns 202 + {job_id, status: "running"} immediately and dispatches a supervised background task; clients poll GET /api/channels/bulk-commit/{job_id} until the status is terminal (completed with the full BulkCommitResponse under result, or failed with an error message). validateOnly stays synchronous (200 with the response body inline) because pre-commit validation is fast and the frontend uses it for instant feedback. Frontend bulkCommit() and the MCP bulk_commit_channels / build_channel_lineup tools drive the new POST→poll loop transparently — their public shapes are unchanged so all three useEditMode call sites and existing MCP consumers keep working. Job state is in-memory with a 30-min TTL prune on every new POST; completed jobs are evicted on first read so RAM stays bounded; failed jobs persist until the TTL so operators can re-poll the error message. (bd-ggxks, build 0001)

Don't miss a new enhancedchannelmanager release

NewReleases is sending notifications on new releases.