Skip to content

Commit

Permalink
release(major): Version 5.0.0
Browse files Browse the repository at this point in the history
So apparently Jellyfin 10.10 was released. Didn't notice because there hasn't been an announcement in their discord server yet, but here we are.

 # Highlights

Here are some of the key features and improvements since the last stable release (4.2.2):

 ## Breaking Changes

- **Removed TvDB usage throughout the plugin, except for the TvDB IDs.** (1083a0c)

- **Dropped support for Shoko Server below version 5.0.0** (13fc638)

- **Enabled 'Attach VFS to Libraries' (_an advanced setting_) by default for new installs.** (dfdb115)

- **Removed the episode namespace migration scheduled task since the method we used to get all user data for a user is removed in 10.10**. Let this be your warning to migrate your user data **before** updating to 10.10 if needed. (a51d760)

 ## Bug Fixes

- Fixed the usage of user data for Jellyfin 10.10. (0361e83)

- Fixed the usage of `SetProviderId` for Jellyfin 10.10. (23c7575)

- Simplify multi resolver code now that Jellyfin 10.10 is out. (80d2758)

 ## Repository Changes

- Update to Jellyfin 10.10. (3dff389)

For the full list of changes, please check out the [complete changelog](v4.2.2...v5.0.0) here on GitHub.
revam committed Oct 27, 2024
2 parents 4d9f055 + 3dff389 commit e7ac1ef
Showing 20 changed files with 76 additions and 362 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -38,8 +38,9 @@ compatible with what.
| `2.x.x` | `10.8` | `4.1.2` |
| `3.x.x` | `10.8` | `4.2.0` |
| `4.0.0``4.1.1` | `10.9` | `4.2.2` |
| `4.2.0``4.x.x` | `10.9` | `4.2.2``5.0.0` |
| `dev` | `10.9` | `dev` |
| `4.2.0``4.2.2` | `10.9` | `4.2.2``5.0.0` |
| `5.x.x` | `10.10` | `5.0.0` |
| `dev` | `10.10` | `dev` |

### Official Repository

3 changes: 0 additions & 3 deletions Shokofin/API/Info/EpisodeInfo.cs
Original file line number Diff line number Diff line change
@@ -14,14 +14,11 @@ public class EpisodeInfo

public Episode.AniDB AniDB;

public Episode.TvDB? TvDB;

public EpisodeInfo(Episode episode)
{
Id = episode.IDs.Shoko.ToString();
ExtraType = Ordering.GetExtraType(episode.AniDBEntity);
Shoko = episode;
AniDB = episode.AniDBEntity;
TvDB = episode.TvDBEntityList?.FirstOrDefault();
}
}
3 changes: 0 additions & 3 deletions Shokofin/API/Info/SeasonInfo.cs
Original file line number Diff line number Diff line change
@@ -18,8 +18,6 @@ public class SeasonInfo

public readonly Series.AniDBWithDate AniDB;

public readonly Series.TvDB? TvDB;

public readonly SeriesType Type;

/// <summary>
@@ -254,7 +252,6 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
ExtraIds = extraIds.ToArray();
Shoko = series;
AniDB = series.AniDBEntity;
TvDB = series.TvDBEntityList.FirstOrDefault();
Type = type;
EarliestImportedAt = earliestImportedAt;
LastImportedAt = lastImportedAt;
30 changes: 1 addition & 29 deletions Shokofin/API/Models/Episode.cs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ public class Episode
{
/// <summary>
/// All identifiers related to the episode entry, e.g. the Shoko, AniDB,
/// TvDB, etc.
/// TMDB, etc.
/// </summary>
public EpisodeIDs IDs { get; set; } = new();

@@ -47,13 +47,6 @@ public class Episode
[JsonPropertyName("AniDB")]
public AniDB AniDBEntity { get; set; } = new();

/// <summary>
/// The <see cref="Episode.TvDB"/> entries, if <see cref="DataSource.TvDB"/>
/// is included in the data to add.
/// </summary>
[JsonPropertyName("TvDB")]
public List<TvDB> TvDBEntityList { get; set; } = [];

/// <summary>
/// File cross-references for the episode.
/// </summary>
@@ -83,32 +76,11 @@ public class AniDB
public Rating Rating { get; set; } = new();
}

public class TvDB
{
[JsonPropertyName("Season")]
public int SeasonNumber { get; set; }

[JsonPropertyName("Number")]
public int EpisodeNumber { get; set; }

public string Description { get; set; } = string.Empty;

public int? AirsAfterSeason { get; set; }

public int? AirsBeforeSeason { get; set; }

public int? AirsBeforeEpisode { get; set; }

public Image Thumbnail { get; set; } = new();
}

public class EpisodeIDs : IDs
{
public int ParentSeries { get; set; }

public int AniDB { get; set; }

public List<int> TvDB { get; set; } = [];
}
}

5 changes: 3 additions & 2 deletions Shokofin/API/Models/Image.cs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ namespace Shokofin.API.Models;
public class Image
{
/// <summary>
/// AniDB, TvDB, TMDB, etc.
/// AniDB, TMDB, etc.
/// </summary>
public ImageSource Source { get; set; } = ImageSource.AniDB;

@@ -90,7 +90,8 @@ public enum ImageSource
AniDB = 1,

/// <summary>
///
/// Deprecated, but kept until the next major release for backwards compatibility.
/// TODO: REMOVE THIS IN 6.0
/// </summary>
TvDB = 2,

13 changes: 1 addition & 12 deletions Shokofin/API/Models/Series.cs
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ public class Series

/// <summary>
/// All identifiers related to the series entry, e.g. the Shoko, AniDB,
/// TvDB, etc.
/// TMDB, etc.
/// </summary>
public SeriesIDs IDs { get; set; } = new();

@@ -45,12 +45,6 @@ public class Series
[JsonPropertyName("AniDB")]
public AniDBWithDate AniDBEntity { get; set; } = new();

/// <summary>
/// The TvDB entries, if any.
/// </summary>
[JsonPropertyName("TvDB")]
public List<TvDB> TvDBEntityList { get; set; }= [];

public SeriesSizes Sizes { get; set; } = new();

/// <summary>
@@ -191,11 +185,6 @@ public DateTime? EndDate
}
}

public class TvDB
{
public string Description { get; set; } = string.Empty;
}

public class SeriesIDs : IDs
{
public int ParentGroup { get; set; } = 0;
2 changes: 1 addition & 1 deletion Shokofin/API/Models/Title.cs
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ public class Title
public bool IsDefault { get; set; }

/// <summary>
/// AniDB, TvDB, AniList, etc.
/// AniDB, TMDB, AniList, etc.
/// </summary>
public string Source { get; set; } = "Unknown";
}
81 changes: 16 additions & 65 deletions Shokofin/API/ShokoAPIClient.cs
Original file line number Diff line number Diff line change
@@ -22,27 +22,6 @@ public class ShokoAPIClient : IDisposable

private readonly ILogger<ShokoAPIClient> Logger;

private static ComponentVersion? ServerVersion =>
Plugin.Instance.Configuration.ServerVersion;

private static readonly DateTime EpisodeSeriesParentAddedDate = DateTime.Parse("2023-04-17T00:00:00.000Z");

private static bool UseEpisodeGetSeriesEndpoint =>
ServerVersion != null && ((ServerVersion.ReleaseChannel == ReleaseChannel.Stable && ServerVersion.Version == new Version("4.2.2.0")) || (ServerVersion.ReleaseDate.HasValue && ServerVersion.ReleaseDate.Value < EpisodeSeriesParentAddedDate));

private static readonly DateTime StableCutOffDate = DateTime.Parse("2023-12-16T00:00:00.000Z");

private static bool UseOlderSeriesAndFileEndpoints =>
ServerVersion != null && ((ServerVersion.ReleaseChannel == ReleaseChannel.Stable && ServerVersion.Version == new Version("4.2.2.0")) || (ServerVersion.ReleaseDate.HasValue && ServerVersion.ReleaseDate.Value < StableCutOffDate));

private static readonly DateTime ImportFolderCutOffDate = DateTime.Parse("2024-03-28T00:00:00.000Z");

private static bool UseOlderImportFolderFileEndpoints =>
ServerVersion != null && ((ServerVersion.ReleaseChannel == ReleaseChannel.Stable && ServerVersion.Version == new Version("4.2.2.0")) || (ServerVersion.ReleaseDate.HasValue && ServerVersion.ReleaseDate.Value < ImportFolderCutOffDate));

public static bool AllowEpisodeImages =>
ServerVersion is { } serverVersion && serverVersion.Version > new Version("4.2.2.0");

private readonly GuardedMemoryCache _cache;

public ShokoAPIClient(ILogger<ShokoAPIClient> logger)
@@ -270,9 +249,6 @@ public Task<HttpResponseMessage> GetImageAsync(ImageSource imageSource, ImageTyp

public Task<File> GetFile(string id)
{
if (UseOlderSeriesAndFileEndpoints)
return Get<File>($"/api/v3/File/{id}?includeXRefs=true&includeDataFrom=AniDB");

return Get<File>($"/api/v3/File/{id}?include=XRefs&includeDataFrom=AniDB");
}

@@ -288,19 +264,12 @@ public Task<List<File>> GetFileByPath(string path)

public async Task<IReadOnlyList<File>> GetFilesForSeries(string seriesId)
{
if (UseOlderSeriesAndFileEndpoints)
return await Get<List<File>>($"/api/v3/Series/{seriesId}/File?pageSize=0&includeXRefs=true&includeDataFrom=AniDB").ConfigureAwait(false);

var listResult = await Get<ListResult<File>>($"/api/v3/Series/{seriesId}/File?pageSize=0&include=XRefs&includeDataFrom=AniDB").ConfigureAwait(false);
return listResult.List;
}

public async Task<ListResult<File>> GetFilesForImportFolder(int importFolderId, string subPath, int page = 1)
{
if (UseOlderImportFolderFileEndpoints) {
return await Get<ListResult<File>>($"/api/v3/ImportFolder/{importFolderId}/File?page={page}&pageSize=100&includeXRefs=true").ConfigureAwait(false);
}

return await Get<ListResult<File>>($"/api/v3/ImportFolder/{importFolderId}/File?page={page}&folderPath={Uri.EscapeDataString(subPath)}&pageSize=1000&include=XRefs").ConfigureAwait(false);
}

@@ -347,42 +316,24 @@ public async Task<bool> ScrobbleFile(string fileId, string episodeId, string eve

public Task<Episode> GetEpisode(string id)
{
return Get<Episode>($"/api/v3/Episode/{id}?includeDataFrom=AniDB,TvDB&includeXRefs=true");
return Get<Episode>($"/api/v3/Episode/{id}?includeDataFrom=AniDB,TMDB&includeXRefs=true");
}

public async Task<EpisodeImages?> GetEpisodeImages(string id)
{
try {
if (AllowEpisodeImages) {
var episodeImages = await Get<EpisodeImages>($"/api/v3/Episode/{id}/Images");
// If the episode has no 'movie' images, get the series images to compensate.
if (episodeImages.Posters.Count is 0) {
var episode1 = await GetEpisode(id);
var seriesImages1 = await GetSeriesImages(episode1.IDs.ParentSeries.ToString()) ?? new();

episodeImages.Posters = seriesImages1.Posters;
episodeImages.Logos = seriesImages1.Logos;
episodeImages.Banners = seriesImages1.Banners;
episodeImages.Backdrops = seriesImages1.Backdrops;
}
return episodeImages;
}

var episode0 = await GetEpisode(id);
var seriesId0 = episode0.IDs.ParentSeries.ToString();
if (UseEpisodeGetSeriesEndpoint) {
var series = await GetSeriesFromEpisode(id);
if (series != null)
seriesId0 = series.IDs.Shoko.ToString();
var episodeImages = await Get<EpisodeImages>($"/api/v3/Episode/{id}/Images");
// If the episode has no 'movie' images, get the series images to compensate.
if (episodeImages.Posters.Count is 0) {
var episode1 = await GetEpisode(id);
var seriesImages1 = await GetSeriesImages(episode1.IDs.ParentSeries.ToString()) ?? new();

episodeImages.Posters = seriesImages1.Posters;
episodeImages.Logos = seriesImages1.Logos;
episodeImages.Banners = seriesImages1.Banners;
episodeImages.Backdrops = seriesImages1.Backdrops;
}
var seriesImages0 = seriesId0 is not "0" ? await GetSeriesImages(seriesId0) ?? new() : new();
return new() {
Banners = seriesImages0.Banners,
Backdrops = seriesImages0.Backdrops,
Posters = seriesImages0.Posters,
Logos = seriesImages0.Logos,
Thumbnails = episode0.TvDBEntityList.FirstOrDefault()?.Thumbnail is { } thumbnail ? [thumbnail] : [],
};
return episodeImages;
}
catch (ApiException e) when (e.StatusCode == HttpStatusCode.NotFound) {
return null;
@@ -391,22 +342,22 @@ public Task<Episode> GetEpisode(string id)

public Task<ListResult<Episode>> GetEpisodesFromSeries(string seriesId)
{
return Get<ListResult<Episode>>($"/api/v3/Series/{seriesId}/Episode?pageSize=0&includeHidden=true&includeMissing=true&includeDataFrom=AniDB,TvDB&includeXRefs=true");
return Get<ListResult<Episode>>($"/api/v3/Series/{seriesId}/Episode?pageSize=0&includeHidden=true&includeMissing=true&includeDataFrom=AniDB,TMDB&includeXRefs=true");
}

public Task<Series> GetSeries(string id)
{
return Get<Series>($"/api/v3/Series/{id}?includeDataFrom=AniDB,TvDB");
return Get<Series>($"/api/v3/Series/{id}?includeDataFrom=AniDB,TMDB");
}

public Task<Series> GetSeriesFromEpisode(string id)
{
return Get<Series>($"/api/v3/Episode/{id}/Series?includeDataFrom=AniDB,TvDB");
return Get<Series>($"/api/v3/Episode/{id}/Series?includeDataFrom=AniDB,TMDB");
}

public Task<List<Series>> GetSeriesInGroup(string groupID, int filterID = 0, bool recursive = false)
{
return Get<List<Series>>($"/api/v3/Filter/{filterID}/Group/{groupID}/Series?recursive={recursive}&includeMissing=true&includeIgnored=false&includeDataFrom=AniDB,TvDB");
return Get<List<Series>>($"/api/v3/Filter/{filterID}/Group/{groupID}/Series?recursive={recursive}&includeMissing=true&includeIgnored=false&includeDataFrom=AniDB,TMDB");
}

public Task<List<Role>> GetSeriesCast(string id)
3 changes: 1 addition & 2 deletions Shokofin/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
@@ -569,7 +569,6 @@ public PluginConfiguration()
DescriptionProvider.Shoko,
DescriptionProvider.AniDB,
DescriptionProvider.TMDB,
DescriptionProvider.TvDB,
];
HideUnverifiedTags = true;
TagSources = TagSource.ContentIndicators | TagSource.Dynamic | TagSource.DynamicCast | TagSource.DynamicEnding | TagSource.Elements |
@@ -603,7 +602,7 @@ public PluginConfiguration()
VFS_Threads = 4;
VFS_AddReleaseGroup = false;
VFS_AddResolution = false;
VFS_AttachRoot = false;
VFS_AttachRoot = true;
VFS_Location = VirtualRootLocation.Default;
VFS_CustomLocation = null;
VFS_ResolveLinks = false;
16 changes: 2 additions & 14 deletions Shokofin/Pages/Settings.html
Original file line number Diff line number Diff line change
@@ -320,18 +320,6 @@ <h3 class="listItemBodyText">TMDB | Follow metadata language in library</h3>
<span class="material-icons keyboard_arrow_up" aria-hidden="true"></span>
</button>
</div>
<div class="listItem sortableOption" data-option="TvDB">
<label class="listItemCheckboxContainer">
<input is="emby-checkbox" type="checkbox" data-option="TvDB">
<span></span>
</label>
<div class="listItemBody">
<h3 class="listItemBodyText">TvDB | Follow metadata language in library</h3>
</div>
<button type="button" is="paper-icon-button-light" title="Up" class="btnSortableMoveUp btnSortable">
<span class="material-icons keyboard_arrow_up" aria-hidden="true"></span>
</button>
</div>
</div>
<div class="fieldDescription">The metadata providers to use as the source of descriptions for entities, in priority order.</div>
</div>
@@ -1168,8 +1156,8 @@ <h3>Basic Settings</h3>
<select is="emby-select" id="SpecialsPlacement" name="SpecialsPlacement" class="emby-select-withcolor emby-select">
<option value="AfterSeason">Always place specials after the normal episodes (Default)</option>
<option value="InBetweenSeasonByAirDate">Use release dates to place specials</option>
<option value="InBetweenSeasonByOtherData">Loosely use the TvDB/TMDB data available in Shoko to place specials</option>
<option value="InBetweenSeasonMixed">Either loosely use the TvDB/TMDB data available in Shoko or fallback to using release dates to place specials</option>
<option value="InBetweenSeasonByOtherData">Loosely use the TMDB data available in Shoko to place specials</option>
<option value="InBetweenSeasonMixed">Either loosely use the TMDB data available in Shoko or fallback to using release dates to place specials</option>
<option value="Excluded">Exclude specials from the seasons</option>
</select>
<div class="fieldDescription selectFieldDescription">Determines how specials are placed within seasons. <strong>Warning:</strong> Modifying this setting requires a recreation (read as; delete existing then create a new) of any libraries using this plugin — otherwise you <strong>will</strong> have mixed metadata.</div>
14 changes: 14 additions & 0 deletions Shokofin/Plugin.cs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
@@ -211,6 +212,19 @@ public Plugin(UsageTracker usageTracker, IServerConfigurationManager configurati
// Disable VFS if we can't create symbolic links on Windows and no configuration exists.
if (!configExists && !CanCreateSymbolicLinks) {
Configuration.VFS_Enabled = false;
// Remove TvDB from the list of description providers.
var index = Configuration.DescriptionSourceList.IndexOf(Text.DescriptionProvider.TvDB);
if (index != -1) {
var list = Configuration.DescriptionSourceList.ToList();
list.RemoveAt(index);
Configuration.DescriptionSourceList = [.. list];
}
index = Configuration.DescriptionSourceOrder.IndexOf(Text.DescriptionProvider.TvDB);
if (index != -1) {
var list = Configuration.DescriptionSourceOrder.ToList();
list.RemoveAt(index);
Configuration.DescriptionSourceOrder = [.. list];
}
SaveConfiguration();
}
}
2 changes: 1 addition & 1 deletion Shokofin/Providers/CustomBoxSetProvider.cs
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ private async Task<bool> EnsureGroupCollectionIsCorrect(Folder collectionRoot, B
private bool EnsureNoTmdbIdIsSet(BoxSet collection)
{
var willRemove = collection.HasProviderId(MetadataProvider.TmdbCollection);
collection.SetProviderId(MetadataProvider.TmdbCollection.ToString(), null);
collection.ProviderIds.Remove(MetadataProvider.TmdbCollection.ToString());
return willRemove;
}

11 changes: 1 addition & 10 deletions Shokofin/Resolvers/ShokoResolver.cs
Original file line number Diff line number Diff line change
@@ -223,16 +223,7 @@ NamingOptions namingOptions
File.Delete(keepFile);
}

// TODO: uncomment the code snippet once we reach JF 10.10.
// return new() { Items = items, ExtraFiles = new() };

// TODO: Remove these two hacks once we have proper support for adding multiple series at once.
if (!items.Any(i => i is Movie) && items.Count > 0) {
fileInfoList.Clear();
fileInfoList.AddRange(items.OrderBy(s => int.Parse(s.Path.GetAttributeValue(ShokoSeriesId.Name)!)).Select(s => FileSystem.GetFileSystemInfo(s.Path)));
}

return new() { Items = items.Where(i => i is Movie).ToList(), ExtraFiles = items.OfType<TvSeries>().Select(s => FileSystem.GetFileSystemInfo(s.Path)).ToList() };
return new() { Items = items, ExtraFiles = [] };
}

return null;
2 changes: 1 addition & 1 deletion Shokofin/Shokofin.csproj
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@

<ItemGroup>
<PackageReference Include="AsyncKeyedLock" Version="6.4.2" />
<PackageReference Include="Jellyfin.Controller" Version="10.9.7" />
<PackageReference Include="Jellyfin.Controller" Version="10.10.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(SignalRVersion)" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
</ItemGroup>
3 changes: 1 addition & 2 deletions Shokofin/Sync/SyncExtensions.cs
Original file line number Diff line number Diff line change
@@ -93,11 +93,10 @@ public static UserItemData MergeWithFileUserStats(this UserItemData userData, Fi
return userData;
}

public static UserItemData ToUserData(this File.UserStats userStats, Video video, Guid userId)
public static UserItemData ToUserData(this File.UserStats userStats, Video video)
{
return new UserItemData
{
UserId = userId,
Key = video.GetUserDataKeys()[0],
LastPlayedDate = null,
}.MergeWithFileUserStats(userStats);
33 changes: 23 additions & 10 deletions Shokofin/Sync/UserDataSyncManager.cs
Original file line number Diff line number Diff line change
@@ -467,11 +467,14 @@ public void OnItemAddedOrUpdated(object? sender, ItemChangeEventArgs e)

private Task SyncSeries(Series series, UserConfiguration userConfig, UserItemData? userData, SyncDirection direction, string seriesId)
{
var user = UserManager.GetUserById(userConfig.UserId);
if (user == null) {
return Task.CompletedTask;
}
// Try to load the user-data if it was not provided
userData ??= UserDataManager.GetUserData(userConfig.UserId, series);
userData ??= UserDataManager.GetUserData(user, series);
// Create some new user-data if none exists.
userData ??= new UserItemData {
UserId = userConfig.UserId,
Key = series.GetUserDataKeys()[0],
};

@@ -482,11 +485,14 @@ private Task SyncSeries(Series series, UserConfiguration userConfig, UserItemDat

private Task SyncSeason(Season season, UserConfiguration userConfig, UserItemData? userData, SyncDirection direction, string seriesId)
{
var user = UserManager.GetUserById(userConfig.UserId);
if (user == null) {
return Task.CompletedTask;
}
// Try to load the user-data if it was not provided
userData ??= UserDataManager.GetUserData(userConfig.UserId, season);
userData ??= UserDataManager.GetUserData(user, season);
// Create some new user-data if none exists.
userData ??= new UserItemData {
UserId = userConfig.UserId,
Key = season.GetUserDataKeys()[0],
};

@@ -497,15 +503,18 @@ private Task SyncSeason(Season season, UserConfiguration userConfig, UserItemDat

private Task SyncVideo(Video video, UserConfiguration userConfig, UserItemData? userData, SyncDirection direction, string episodeId)
{
var user = UserManager.GetUserById(userConfig.UserId);
if (user == null) {
return Task.CompletedTask;
}
if (!userConfig.SyncRestrictedVideos && video.CustomRating == "XXX") {
Logger.LogTrace("Skipped {SyncDirection} user data for video {VideoName}. (Episode={EpisodeId})", direction.ToString(), video.Name, episodeId);
return Task.CompletedTask;
}
// Try to load the user-data if it was not provided
userData ??= UserDataManager.GetUserData(userConfig.UserId, video);
userData ??= UserDataManager.GetUserData(user, video);
// Create some new user-data if none exists.
userData ??= new UserItemData {
UserId = userConfig.UserId,
Key = video.GetUserDataKeys()[0],
LastPlayedDate = null,
};
@@ -522,11 +531,15 @@ private Task SyncVideo(Video video, UserConfiguration userConfig, UserItemData?
private async Task SyncVideo(Video video, UserConfiguration userConfig, SyncDirection direction, string fileId, string episodeId)
{
try {
var user = UserManager.GetUserById(userConfig.UserId);
if (user == null) {
return;
}
if (!userConfig.SyncRestrictedVideos && video.CustomRating == "XXX") {
Logger.LogTrace("Skipped {SyncDirection} user data for video {VideoName}. (File={FileId},Episode={EpisodeId})", direction.ToString(), video.Name, fileId, episodeId);
return;
}
var localUserStats = UserDataManager.GetUserData(userConfig.UserId, video);
var localUserStats = UserDataManager.GetUserData(user, video);
var remoteUserStats = await APIClient.GetFileUserStats(fileId, userConfig.Token);
bool isInSync = UserDataEqualsFileUserStats(localUserStats, remoteUserStats);
Logger.LogInformation("{SyncDirection} user data for video {VideoName}. (User={UserId},File={FileId},Episode={EpisodeId},Local={HaveLocal},Remote={HaveRemote},InSync={IsInSync})", direction.ToString(), video.Name, userConfig.UserId, fileId, episodeId, localUserStats != null, remoteUserStats != null, isInSync);
@@ -560,12 +573,12 @@ private async Task SyncVideo(Video video, UserConfiguration userConfig, SyncDire
break;
// Create a new local stats entry if there is no local entry.
if (localUserStats == null) {
UserDataManager.SaveUserData(userConfig.UserId, video, localUserStats = remoteUserStats.ToUserData(video, userConfig.UserId), UserDataSaveReason.Import, CancellationToken.None);
UserDataManager.SaveUserData(user, video, localUserStats = remoteUserStats.ToUserData(video), UserDataSaveReason.Import, CancellationToken.None);
Logger.LogDebug("{SyncDirection} user data for video {VideoName} successful. (User={UserId},File={FileId},Episode={EpisodeId})", SyncDirection.Import.ToString(), video.Name, userConfig.UserId, fileId, episodeId);
}
// Else merge the remote stats into the local stats entry.
else if (!localUserStats.LastPlayedDate.HasValue || remoteUserStats.LastUpdatedAt > localUserStats.LastPlayedDate.Value) {
UserDataManager.SaveUserData(userConfig.UserId, video, localUserStats.MergeWithFileUserStats(remoteUserStats), UserDataSaveReason.Import, CancellationToken.None);
UserDataManager.SaveUserData(user, video, localUserStats.MergeWithFileUserStats(remoteUserStats), UserDataSaveReason.Import, CancellationToken.None);
Logger.LogDebug("{SyncDirection} user data for video {VideoName} successful. (User={UserId},File={FileId},Episode={EpisodeId})", SyncDirection.Import.ToString(), video.Name, userConfig.UserId, fileId, episodeId);
}
break;
@@ -599,7 +612,7 @@ private async Task SyncVideo(Video video, UserConfiguration userConfig, SyncDire
}
// Else import if the remote state is fresher then the local state.
else if (localUserStats.LastPlayedDate.Value < remoteUserStats.LastUpdatedAt) {
UserDataManager.SaveUserData(userConfig.UserId, video, localUserStats.MergeWithFileUserStats(remoteUserStats), UserDataSaveReason.Import, CancellationToken.None);
UserDataManager.SaveUserData(user, video, localUserStats.MergeWithFileUserStats(remoteUserStats), UserDataSaveReason.Import, CancellationToken.None);
Logger.LogDebug("{SyncDirection} user data for video {VideoName} successful. (User={UserId},File={FileId},Episode={EpisodeId})", SyncDirection.Import.ToString(), video.Name, userConfig.UserId, fileId, episodeId);
}
break;
166 changes: 0 additions & 166 deletions Shokofin/Tasks/MigrateEpisodeUserDataTask.cs

This file was deleted.

36 changes: 4 additions & 32 deletions Shokofin/Utils/Ordering.cs
Original file line number Diff line number Diff line change
@@ -105,7 +105,7 @@ public enum SpecialOrderType {
InBetweenSeasonByAirDate = 4,

/// <summary>
/// Place the specials in-between normal episodes based upon the data from TvDB or TMDB.
/// Place the specials in-between normal episodes based upon the data from TMDB.
/// </summary>
InBetweenSeasonByOtherData = 5,
}
@@ -162,11 +162,10 @@ public static (int?, int?, int?, bool) GetSpecialPlacement(ShowInfo showInfo, Se
return (null, null, null, showInfo.IsSpecial(episodeInfo));
}

// Abort if episode is not a TvDB special or AniDB special
// Abort if episode is not a TMDB special or AniDB special
if (!showInfo.IsSpecial(episodeInfo))
return (null, null, null, false);

int? episodeNumber = null;
int seasonNumber = GetSeasonNumber(showInfo, seasonInfo, episodeInfo);
int? airsBeforeEpisodeNumber = null;
int? airsBeforeSeasonNumber = null;
@@ -175,10 +174,10 @@ public static (int?, int?, int?, bool) GetSpecialPlacement(ShowInfo showInfo, Se
default:
airsAfterSeasonNumber = seasonNumber;
break;
case SpecialOrderType.InBetweenSeasonMixed:
case SpecialOrderType.InBetweenSeasonByAirDate:
byAirDate:
// Reset the order if we come from `SpecialOrderType.InBetweenSeasonMixed`.
episodeNumber = null;
int? episodeNumber = null;
if (seasonInfo.SpecialsBeforeEpisodes.Contains(episodeInfo.Id)) {
airsBeforeSeasonNumber = seasonNumber;
break;
@@ -195,34 +194,7 @@ public static (int?, int?, int?, bool) GetSpecialPlacement(ShowInfo showInfo, Se
airsAfterSeasonNumber = seasonNumber;
}
break;
case SpecialOrderType.InBetweenSeasonMixed:
case SpecialOrderType.InBetweenSeasonByOtherData:
// We need to have TvDB/TMDB data in the first place to do this method.
if (episodeInfo.TvDB == null) {
if (order == SpecialOrderType.InBetweenSeasonMixed) goto byAirDate;
break;
}

episodeNumber = episodeInfo.TvDB.AirsBeforeEpisode;
if (!episodeNumber.HasValue) {
if (episodeInfo.TvDB.AirsBeforeSeason.HasValue) {
airsBeforeSeasonNumber = seasonNumber;
break;
}

if (order == SpecialOrderType.InBetweenSeasonMixed) goto byAirDate;
airsAfterSeasonNumber = seasonNumber;
break;
}

var nextEpisode = seasonInfo.EpisodeList.FirstOrDefault(e => e.TvDB != null && e.TvDB.SeasonNumber == seasonNumber && e.TvDB.EpisodeNumber == episodeNumber);
if (nextEpisode != null) {
airsBeforeEpisodeNumber = GetEpisodeNumber(showInfo, seasonInfo, nextEpisode);
airsBeforeSeasonNumber = seasonNumber;
break;
}

if (order == SpecialOrderType.InBetweenSeasonMixed) goto byAirDate;
break;
}

8 changes: 2 additions & 6 deletions Shokofin/Utils/Text.cs
Original file line number Diff line number Diff line change
@@ -86,7 +86,8 @@ public enum DescriptionProvider {
AniDB = 2,

/// <summary>
/// Provide the description from TvDB.
/// Deprecated, but kept until the next major release for backwards compatibility.
/// TODO: REMOVE THIS IN 6.0
/// </summary>
TvDB = 3,

@@ -157,21 +158,18 @@ public static string GetDescription(ShowInfo show, string? metadataLanguage)
=> GetDescriptionByDict(new() {
{DescriptionProvider.Shoko, show.Shoko?.Description ?? show.DefaultSeason.Shoko.Description},
{DescriptionProvider.AniDB, metadataLanguage is "en" ? show.DefaultSeason.AniDB.Description : null},
{DescriptionProvider.TvDB, show.DefaultSeason.TvDB?.Description},
});

public static string GetDescription(SeasonInfo season, string? metadataLanguage)
=> GetDescriptionByDict(new() {
{DescriptionProvider.Shoko, season.Shoko.Description},
{DescriptionProvider.AniDB, metadataLanguage is "en" ? season.AniDB.Description : null},
{DescriptionProvider.TvDB, season.TvDB?.Description},
});

public static string GetDescription(EpisodeInfo episode, string? metadataLanguage)
=> GetDescriptionByDict(new() {
{DescriptionProvider.Shoko, episode.Shoko.Description},
{DescriptionProvider.AniDB, metadataLanguage is "en" ? episode.AniDB.Description : null},
{DescriptionProvider.TvDB, episode.TvDB?.Description},
});

public static string GetDescription(IEnumerable<EpisodeInfo> episodeList, string? metadataLanguage)
@@ -201,8 +199,6 @@ private static string GetDescriptionByDict(Dictionary<DescriptionProvider, strin
descriptions.TryGetValue(DescriptionProvider.Shoko, out var desc) ? SanitizeAnidbDescription(desc ?? string.Empty) : null,
DescriptionProvider.AniDB =>
descriptions.TryGetValue(DescriptionProvider.AniDB, out var desc) ? SanitizeAnidbDescription(desc ?? string.Empty) : null,
DescriptionProvider.TvDB =>
descriptions.TryGetValue(DescriptionProvider.TvDB, out var desc) ? desc : null,
_ => null
};
if (!string.IsNullOrEmpty(overview))
2 changes: 1 addition & 1 deletion build.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: "Shoko"
guid: "5216ccbf-d24a-4eb3-8a7e-7da4230b7052"
imageUrl: https://raw.githubusercontent.com/ShokoAnime/Shokofin/metadata/banner.png
targetAbi: "10.9.7.0"
targetAbi: "10.10.0.0"
owner: "ShokoAnime"
overview: "Manage your anime from Jellyfin using metadata from Shoko"
description: >

0 comments on commit e7ac1ef

Please sign in to comment.