diff --git a/.gitea/scripts/build-plugins.sh b/.gitea/scripts/build-plugins.sh new file mode 100755 index 0000000..c28c883 --- /dev/null +++ b/.gitea/scripts/build-plugins.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Build one plugin zip per Jellyfin generation into ./artifacts. +# +# Jellyfin binds a plugin to the exact API it was compiled against, and 10.11 changed +# signatures that still compile but fail at runtime. So each generation gets its own DLL +# (see the TargetFrameworks comment in Jellyfin.Plugin.SRFPlay.csproj) and its own +# manifest entry. Jellyfin installs the highest version whose targetAbi is <= the server +# version, so the flavour code is folded into the version to keep them apart: +# +# net8.0 -> targetAbi 10.9.0.0 (Jellyfin 10.9 / 10.10) code 9 +# net9.0 -> targetAbi 10.11.0.0 (Jellyfin 10.11) code 11 +# net10.0 -> targetAbi 12.0.0.0 (Jellyfin 12) code 12 +# +# Usage: build-plugins.sh +# is a printf format that receives the flavour code, e.g. +# "1.2.0.%d" -> 1.2.0.9, 1.2.0.11, 1.2.0.12 (release) +# "1.0.20260924.204%02d" -> 1.0.20260924.20409, ...20411, ...20412 (nightly) +# +# Writes artifacts/builds.tsv with one line per zip: version, targetAbi, zip path, md5. +set -euo pipefail + +FORMAT="$1" + +FLAVOURS=( + "net8.0 10.9.0.0 9" + "net9.0 10.11.0.0 11" + "net10.0 12.0.0.0 12" +) + +mkdir -p artifacts +: > artifacts/builds.tsv + +# jprm has no targetAbi flag and reads it from build.yaml, so rewrite it per flavour. +cp build.yaml build.yaml.orig +trap 'mv -f build.yaml.orig build.yaml' EXIT + +for flavour in "${FLAVOURS[@]}"; do + read -r framework abi code <<< "$flavour" + # shellcheck disable=SC2059 # the format is the argument by design + version=$(printf "$FORMAT" "$code") + + cp build.yaml.orig build.yaml + sed -i \ + -e "s/^version:.*/version: \"${version}\"/" \ + -e "s/^targetAbi:.*/targetAbi: \"${abi}\"/" \ + -e "s/^framework:.*/framework: \"${framework}\"/" \ + -e "s/^dotnet_framework:.*/dotnet_framework: \"${framework}\"/" \ + build.yaml + + echo ">> ${version}: ${framework}, targetAbi ${abi}" + jprm --verbosity=debug plugin build . --dotnet-framework "${framework}" --version "${version}" + + zip="artifacts/srfplay_${version}.zip" + if [ ! -f "${zip}" ]; then + echo "jprm did not produce ${zip}" >&2 + exit 1 + fi + + printf '%s\t%s\t%s\t%s\n' "${version}" "${abi}" "${zip}" "$(md5sum "${zip}" | awk '{print $1}')" >> artifacts/builds.tsv +done + +cat artifacts/builds.tsv diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 5f1183e..739469c 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -51,14 +51,10 @@ jobs: LABEL="master" fi echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "format=${VERSION}%02d" >> $GITHUB_OUTPUT echo "label=${LABEL}" >> $GITHUB_OUTPUT echo "Build version: ${VERSION} (${LABEL})" - - name: Set build version - working-directory: build-${{ github.run_id }} - run: | - sed -i "s/^version:.*/version: \"${{ steps.version.outputs.version }}\"/" build.yaml - - name: Build solution working-directory: build-${{ github.run_id }} run: dotnet build Jellyfin.Plugin.SRFPlay.sln --configuration Release --no-restore --no-self-contained /m:1 @@ -67,23 +63,16 @@ jobs: working-directory: build-${{ github.run_id }} run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal - - name: Build Jellyfin Plugin - id: jprm + - name: Build Jellyfin Plugins working-directory: build-${{ github.run_id }} - run: | - mkdir -p artifacts - jprm --verbosity=debug plugin build . - ARTIFACT=$(find . -name "*.zip" -type f -print -quit | sed 's|^\./||') - LATEST="artifacts/srfplay_latest.zip" - cp "${ARTIFACT}" "${LATEST}" - echo "artifact=${LATEST}" >> $GITHUB_OUTPUT - echo "Found artifact: ${ARTIFACT} -> ${LATEST}" + # One zip per Jellyfin generation (see .gitea/scripts/build-plugins.sh). + run: .gitea/scripts/build-plugins.sh "${{ steps.version.outputs.format }}" - - name: Upload build artifact + - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: srfplay-${{ steps.version.outputs.label }}-${{ steps.version.outputs.version }} - path: build-${{ github.run_id }}/${{ steps.jprm.outputs.artifact }} + path: build-${{ github.run_id }}/artifacts/*.zip retention-days: 30 if-no-files-found: error diff --git a/.gitea/workflows/latest.yaml b/.gitea/workflows/latest.yaml index 430dc43..c51e8bb 100644 --- a/.gitea/workflows/latest.yaml +++ b/.gitea/workflows/latest.yaml @@ -27,18 +27,15 @@ jobs: # Date-based nightly version: 1.0.. # The run_number suffix keeps multiple builds on the same day # monotonically increasing so Jellyfin always offers the newest. + # Each Jellyfin generation gets its own build, so the last segment + # is <2-digit flavour code>, e.g. run 204 -> 20409/20411/20412 + # (see .gitea/scripts/build-plugins.sh). DATE=$(date -u +"%Y%m%d") VERSION="1.0.${DATE}.${{ github.run_number }}" echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "format=${VERSION}%02d" >> $GITHUB_OUTPUT echo "Nightly version: ${VERSION}" - - name: Set build version - working-directory: build-${{ github.run_id }} - run: | - VERSION="${{ steps.version.outputs.version }}" - sed -i "s/^version:.*/version: \"${VERSION}\"/" build.yaml - cat build.yaml - - name: Restore dependencies working-directory: build-${{ github.run_id }} run: dotnet restore Jellyfin.Plugin.SRFPlay.sln @@ -51,25 +48,9 @@ jobs: working-directory: build-${{ github.run_id }} run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal - - name: Build Jellyfin Plugin - id: jprm + - name: Build Jellyfin Plugins working-directory: build-${{ github.run_id }} - run: | - mkdir -p artifacts - jprm --verbosity=debug plugin build . - ARTIFACT=$(find . -name "*.zip" -type f -print -quit | sed 's|^\./||') - ARTIFACT_NAME=$(basename "${ARTIFACT}") - echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT - echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT - echo "Found artifact: ${ARTIFACT}" - - - name: Calculate checksum - id: checksum - working-directory: build-${{ github.run_id }} - run: | - CHECKSUM=$(md5sum "${{ steps.jprm.outputs.artifact }}" | awk '{print $1}') - echo "checksum=${CHECKSUM}" >> $GITHUB_OUTPUT - echo "Checksum: ${CHECKSUM}" + run: .gitea/scripts/build-plugins.sh "${{ steps.version.outputs.format }}" - name: Delete existing nightly release working-directory: build-${{ github.run_id }} @@ -129,13 +110,15 @@ jobs: exit 1 fi - # Upload plugin artifact - echo "Uploading plugin artifact..." - curl -f -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/zip" \ - --data-binary "@${{ steps.jprm.outputs.artifact }}" \ - "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=${{ steps.jprm.outputs.artifact_name }}" + # Upload one plugin zip per Jellyfin generation + while IFS=$'\t' read -r _ _ ZIP _; do + echo "Uploading ${ZIP}..." + curl -f -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/zip" \ + --data-binary "@${ZIP}" \ + "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=$(basename "${ZIP}")" + done < artifacts/builds.tsv # Upload build.yaml echo "Uploading build.yaml..." @@ -155,12 +138,10 @@ jobs: REPO_OWNER="${{ github.repository_owner }}" REPO_NAME="${{ github.event.repository.name }}" GITEA_URL="${{ github.server_url }}" - VERSION="${{ steps.version.outputs.version }}" - CHECKSUM="${{ steps.checksum.outputs.checksum }}" - ARTIFACT_NAME="${{ steps.jprm.outputs.artifact_name }}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - DOWNLOAD_URL="${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/latest/${ARTIFACT_NAME}" SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + # builds.tsv is read before the checkout below replaces the tree. + BUILDS=$(cat artifacts/builds.tsv) git config user.name "Gitea Actions" git config user.email "actions@gitea.tourolle.paris" @@ -178,25 +159,24 @@ jobs: # edits must be master, not whatever this build started from. git checkout -B master origin/master - NEW_VERSION=$(cat < 0) | split("\t") + | {version: .[0], changelog: "Nightly build \(.[0]) (\($sha))", targetAbi: .[1], + sourceUrl: "\($url)/\(.[2] | split("/") | last)", checksum: .[3], timestamp: $ts}) + | reverse') - # Prepend the new build and keep only the most recent 5 nightlies. - jq --argjson newver "${NEW_VERSION}" \ - '.[0].versions = ([$newver] + .[0].versions)[0:5]' \ + # Prepend the new builds and keep only the most recent 5 nightlies + # (3 entries each, one per Jellyfin generation). + jq --argjson newvers "${NEW_VERSIONS}" \ + '.[0].versions = ($newvers + .[0].versions)[0:15]' \ manifest-nightly.json > manifest.tmp && mv manifest.tmp manifest-nightly.json git add manifest-nightly.json - git commit -m "Nightly: ${VERSION} (${SHORT_SHA})" || echo "No changes to commit" + git commit -m "Nightly: ${{ steps.version.outputs.version }} (${SHORT_SHA})" || echo "No changes to commit" git push origin master - name: Cleanup diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 7306b15..75f4e42 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -35,13 +35,6 @@ jobs: echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT echo "Building version: ${VERSION}" - - name: Update build.yaml with version - working-directory: release-${{ github.run_id }} - run: | - VERSION="${{ steps.get_version.outputs.version_number }}" - sed -i "s/^version:.*/version: \"${VERSION}\"/" build.yaml - cat build.yaml - - name: Cache NuGet packages uses: actions/cache@v3 with: @@ -61,25 +54,11 @@ jobs: working-directory: release-${{ github.run_id }} run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal - - name: Build Jellyfin Plugin - id: jprm + - name: Build Jellyfin Plugins working-directory: release-${{ github.run_id }} - run: | - mkdir -p artifacts - jprm --verbosity=debug plugin build ./ - ARTIFACT=$(find . -name "*.zip" -type f -print -quit | sed 's|^\./||') - ARTIFACT_NAME=$(basename "${ARTIFACT}") - echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT - echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT - echo "Found artifact: ${ARTIFACT}" - - - name: Calculate checksum - id: checksum - working-directory: release-${{ github.run_id }} - run: | - CHECKSUM=$(md5sum "${{ steps.jprm.outputs.artifact }}" | awk '{print $1}') - echo "checksum=${CHECKSUM}" >> $GITHUB_OUTPUT - echo "Checksum: ${CHECKSUM}" + # v1.2.0 -> 1.2.0.9 / 1.2.0.11 / 1.2.0.12, one per Jellyfin generation + # (see .gitea/scripts/build-plugins.sh). + run: .gitea/scripts/build-plugins.sh "${{ steps.get_version.outputs.version_number }}.%d" - name: Create Release working-directory: release-${{ github.run_id }} @@ -111,13 +90,15 @@ jobs: exit 1 fi - # Upload plugin artifact - echo "Uploading plugin artifact..." - curl -f -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/zip" \ - --data-binary "@${{ steps.jprm.outputs.artifact }}" \ - "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=${{ steps.jprm.outputs.artifact_name }}" + # Upload one plugin zip per Jellyfin generation + while IFS=$'\t' read -r _ _ ZIP _; do + echo "Uploading ${ZIP}..." + curl -f -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/zip" \ + --data-binary "@${ZIP}" \ + "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=$(basename "${ZIP}")" + done < artifacts/builds.tsv # Upload build.yaml echo "Uploading build.yaml..." @@ -139,29 +120,28 @@ jobs: REPO_NAME="${{ github.event.repository.name }}" GITEA_URL="${{ github.server_url }}" VERSION="${{ steps.get_version.outputs.version_number }}" - CHECKSUM="${{ steps.checksum.outputs.checksum }}" - ARTIFACT_NAME="${{ steps.jprm.outputs.artifact_name }}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - DOWNLOAD_URL="${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/${{ steps.get_version.outputs.version }}/${ARTIFACT_NAME}" + # builds.tsv is read before the checkout below replaces the tree. + BUILDS=$(cat artifacts/builds.tsv) git config user.name "Gitea Actions" git config user.email "actions@gitea.tourolle.paris" git fetch origin master - git checkout master + # -B against origin/master for the same reason as the nightly: a bare + # `checkout master` edits the manifest on a stale base if master moved. + git checkout -B master origin/master - NEW_VERSION=$(cat < 0) | split("\t") + | {version: .[0], changelog: "Release \($release)", targetAbi: .[1], + sourceUrl: "\($url)/\(.[2] | split("/") | last)", checksum: .[3], timestamp: $ts}) + | reverse') - jq --argjson newver "${NEW_VERSION}" '.[0].versions = [$newver] + .[0].versions' manifest.json > manifest.tmp && mv manifest.tmp manifest.json + jq --argjson newvers "${NEW_VERSIONS}" '.[0].versions = $newvers + .[0].versions' manifest.json > manifest.tmp && mv manifest.tmp manifest.json git add manifest.json git commit -m "Update manifest.json for version ${VERSION}" git push origin master diff --git a/Jellyfin.Plugin.SRFPlay.Tests/Jellyfin.Plugin.SRFPlay.Tests.csproj b/Jellyfin.Plugin.SRFPlay.Tests/Jellyfin.Plugin.SRFPlay.Tests.csproj index 22b4348..c5cea0d 100644 --- a/Jellyfin.Plugin.SRFPlay.Tests/Jellyfin.Plugin.SRFPlay.Tests.csproj +++ b/Jellyfin.Plugin.SRFPlay.Tests/Jellyfin.Plugin.SRFPlay.Tests.csproj @@ -5,13 +5,13 @@ - Exe - net8.0 + + net8.0;net9.0;net10.0 enable enable diff --git a/Jellyfin.Plugin.SRFPlay/Configuration/configPage.html b/Jellyfin.Plugin.SRFPlay/Configuration/configPage.html index 46f2268..1d56931 100644 --- a/Jellyfin.Plugin.SRFPlay/Configuration/configPage.html +++ b/Jellyfin.Plugin.SRFPlay/Configuration/configPage.html @@ -257,7 +257,7 @@ fetch(ApiClient.serverAddress() + '/Plugins/SRFPlay/Maintenance/ResumePoints/Cleanup?clearAll=' + (clearAll ? 'true' : 'false'), { method: 'POST', - headers: { 'X-Emby-Token': ApiClient.accessToken() } + headers: { 'Authorization': 'MediaBrowser Token="' + ApiClient.accessToken() + '"' } }) .then(function(response) { if (!response.ok) { @@ -285,7 +285,7 @@ getHeaders: function() { return { - 'X-Emby-Token': ApiClient.accessToken() + 'Authorization': 'MediaBrowser Token="' + ApiClient.accessToken() + '"' }; }, diff --git a/Jellyfin.Plugin.SRFPlay/Configuration/recordingPage.html b/Jellyfin.Plugin.SRFPlay/Configuration/recordingPage.html index efd387c..671394a 100644 --- a/Jellyfin.Plugin.SRFPlay/Configuration/recordingPage.html +++ b/Jellyfin.Plugin.SRFPlay/Configuration/recordingPage.html @@ -92,7 +92,7 @@ verifySession: function(session) { var self = this; fetch(this.serverUrl + '/System/Info', { - headers: { 'X-Emby-Token': this.token } + headers: { 'Authorization': 'MediaBrowser Token="' + this.token + '"' } }).then(function(r) { if (r.ok) { document.getElementById('srfrecUserInfo').textContent = '(Server: ' + self.serverUrl + ')'; @@ -119,7 +119,7 @@ method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-Emby-Authorization': 'MediaBrowser Client="SRF Recordings", Device="Web", DeviceId="srfrec-' + Date.now() + '", Version="1.0.0"' + 'Authorization': 'MediaBrowser Client="SRF Recordings", Device="Web", DeviceId="srfrec-' + Date.now() + '", Version="1.0.0"' }, body: JSON.stringify({ Username: user, Pw: pass }) }) @@ -151,7 +151,7 @@ }, getHeaders: function() { - return { 'X-Emby-Token': this.token }; + return { 'Authorization': 'MediaBrowser Token="' + this.token + '"' }; }, formatDate: function(dateStr) { diff --git a/Jellyfin.Plugin.SRFPlay/Jellyfin.Plugin.SRFPlay.csproj b/Jellyfin.Plugin.SRFPlay/Jellyfin.Plugin.SRFPlay.csproj index c824c13..ffcd980 100644 --- a/Jellyfin.Plugin.SRFPlay/Jellyfin.Plugin.SRFPlay.csproj +++ b/Jellyfin.Plugin.SRFPlay/Jellyfin.Plugin.SRFPlay.csproj @@ -1,7 +1,17 @@ - net8.0 + + net8.0;net9.0;net10.0 Jellyfin.Plugin.SRFPlay true true @@ -10,9 +20,30 @@ ../jellyfin.ruleset - + + $(DefineConstants);JELLYFIN_10_11_OR_GREATER + + + + $(DefineConstants);JELLYFIN_12_OR_GREATER + + + + + + + + + + + + + + + + diff --git a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ContentRefreshTask.cs b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ContentRefreshTask.cs index b066df9..2f5bfc2 100644 --- a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ContentRefreshTask.cs +++ b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ContentRefreshTask.cs @@ -104,11 +104,7 @@ public class ContentRefreshTask : IScheduledTask // Run every X hours as configured return new[] { - new TaskTriggerInfo - { - Type = TaskTriggerInfo.TriggerInterval, - IntervalTicks = TimeSpan.FromHours(intervalHours).Ticks - } + TaskTriggers.Interval(TimeSpan.FromHours(intervalHours)) }; } } diff --git a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ExpirationCheckTask.cs b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ExpirationCheckTask.cs index 2996afb..6053d1e 100644 --- a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ExpirationCheckTask.cs +++ b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ExpirationCheckTask.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.SRFPlay.Services.Interfaces; +using Jellyfin.Plugin.SRFPlay.Utilities; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -87,16 +88,8 @@ public class ExpirationCheckTask : IScheduledTask // Run daily at 3 AM or every X hours as configured return new[] { - new TaskTriggerInfo - { - Type = TaskTriggerInfo.TriggerDaily, - TimeOfDayTicks = TimeSpan.FromHours(3).Ticks - }, - new TaskTriggerInfo - { - Type = TaskTriggerInfo.TriggerInterval, - IntervalTicks = TimeSpan.FromHours(intervalHours).Ticks - } + TaskTriggers.Daily(TimeSpan.FromHours(3)), + TaskTriggers.Interval(TimeSpan.FromHours(intervalHours)) }; } } diff --git a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/RecordingSchedulerTask.cs b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/RecordingSchedulerTask.cs index c5089ce..8a7d9b3 100644 --- a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/RecordingSchedulerTask.cs +++ b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/RecordingSchedulerTask.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.SRFPlay.Services.Interfaces; +using Jellyfin.Plugin.SRFPlay.Utilities; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -66,11 +67,7 @@ public class RecordingSchedulerTask : IScheduledTask { return new[] { - new TaskTriggerInfo - { - Type = TaskTriggerInfo.TriggerInterval, - IntervalTicks = TimeSpan.FromSeconds(30).Ticks - } + TaskTriggers.Interval(TimeSpan.FromSeconds(30)) }; } } diff --git a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ResumeCleanupTask.cs b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ResumeCleanupTask.cs index 7046970..317bd65 100644 --- a/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ResumeCleanupTask.cs +++ b/Jellyfin.Plugin.SRFPlay/ScheduledTasks/ResumeCleanupTask.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.SRFPlay.Services.Interfaces; +using Jellyfin.Plugin.SRFPlay.Utilities; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -77,11 +78,7 @@ public class ResumeCleanupTask : IScheduledTask // Runs after the 3 AM expiration check so items deleted there are already gone. return new[] { - new TaskTriggerInfo - { - Type = TaskTriggerInfo.TriggerDaily, - TimeOfDayTicks = TimeSpan.FromHours(4).Ticks - } + TaskTriggers.Daily(TimeSpan.FromHours(4)) }; } } diff --git a/Jellyfin.Plugin.SRFPlay/Services/Interfaces/IResumeCleanupService.cs b/Jellyfin.Plugin.SRFPlay/Services/Interfaces/IResumeCleanupService.cs index 4560e9e..ff67020 100644 --- a/Jellyfin.Plugin.SRFPlay/Services/Interfaces/IResumeCleanupService.cs +++ b/Jellyfin.Plugin.SRFPlay/Services/Interfaces/IResumeCleanupService.cs @@ -1,6 +1,10 @@ using System.Collections.Generic; using System.Threading; +#if JELLYFIN_10_11_OR_GREATER +using Jellyfin.Database.Implementations.Entities; +#else using Jellyfin.Data.Entities; +#endif using Jellyfin.Plugin.SRFPlay.Api.Models; using MediaBrowser.Controller.Entities; diff --git a/Jellyfin.Plugin.SRFPlay/Services/ResumeCleanupService.cs b/Jellyfin.Plugin.SRFPlay/Services/ResumeCleanupService.cs index 104fbbb..5178e28 100644 --- a/Jellyfin.Plugin.SRFPlay/Services/ResumeCleanupService.cs +++ b/Jellyfin.Plugin.SRFPlay/Services/ResumeCleanupService.cs @@ -3,7 +3,11 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +#if JELLYFIN_10_11_OR_GREATER +using Jellyfin.Database.Implementations.Entities; +#else using Jellyfin.Data.Entities; +#endif using Jellyfin.Data.Enums; using Jellyfin.Plugin.SRFPlay.Api.Models; using Jellyfin.Plugin.SRFPlay.Configuration; @@ -71,7 +75,11 @@ public class ResumeCleanupService : IResumeCleanupService var channelIds = GetPluginChannelIds(); +#if JELLYFIN_12_OR_GREATER + foreach (var user in _userManager.GetUsers()) +#else foreach (var user in _userManager.Users) +#endif { cancellationToken.ThrowIfCancellationRequested(); result.UsersChecked++; diff --git a/Jellyfin.Plugin.SRFPlay/Utilities/TaskTriggers.cs b/Jellyfin.Plugin.SRFPlay/Utilities/TaskTriggers.cs new file mode 100644 index 0000000..419a66f --- /dev/null +++ b/Jellyfin.Plugin.SRFPlay/Utilities/TaskTriggers.cs @@ -0,0 +1,49 @@ +using System; +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.Plugin.SRFPlay.Utilities; + +/// +/// Builds scheduled task triggers across Jellyfin versions. +/// Jellyfin 10.11 replaced the string constants on with the +/// TaskTriggerInfoType enum. A task whose GetDefaultTriggers throws is silently given a +/// 24h fallback trigger, so this must be built against the matching Jellyfin version. +/// +public static class TaskTriggers +{ + /// + /// Creates a trigger that fires repeatedly at a fixed interval. + /// + /// The interval between runs. + /// The trigger. + public static TaskTriggerInfo Interval(TimeSpan interval) + { + return new TaskTriggerInfo + { +#if JELLYFIN_10_11_OR_GREATER + Type = TaskTriggerInfoType.IntervalTrigger, +#else + Type = TaskTriggerInfo.TriggerInterval, +#endif + IntervalTicks = interval.Ticks + }; + } + + /// + /// Creates a trigger that fires once a day at the given time. + /// + /// The time of day to run at. + /// The trigger. + public static TaskTriggerInfo Daily(TimeSpan timeOfDay) + { + return new TaskTriggerInfo + { +#if JELLYFIN_10_11_OR_GREATER + Type = TaskTriggerInfoType.DailyTrigger, +#else + Type = TaskTriggerInfo.TriggerDaily, +#endif + TimeOfDayTicks = timeOfDay.Ticks + }; + } +} diff --git a/README.md b/README.md index 6eb07e2..a7d3ad2 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,20 @@ Examples: ### Prerequisites -- .NET 8.0 SDK +- .NET 10.0 SDK (builds all three targets below) - Jellyfin 10.9.11 or later +The plugin is built once per Jellyfin generation, because 10.11 changed APIs in ways that +compile but fail at runtime. Installing from the repository picks the right one automatically. + +| Jellyfin | Target framework | targetAbi | +|---|---|---| +| 10.9, 10.10 | net8.0 | 10.9.0.0 | +| 10.11 | net9.0 | 10.11.0.0 | +| 12 | net10.0 | 12.0.0.0 | + +`.gitea/scripts/build-plugins.sh` builds all three zips the way CI does. + ### Build Steps ```bash @@ -111,7 +122,7 @@ dotnet build ### Output -The compiled plugin will be in `bin/Debug/net8.0/` +The compiled plugins will be in `bin/Debug/net8.0/`, `bin/Debug/net9.0/` and `bin/Debug/net10.0/` ## Manual Installation diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 8af791c..2885c48 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -100,6 +100,8 @@ + +