Skip to content

Commit

Permalink
release(minor): Version 4.2.0
Browse files Browse the repository at this point in the history
Last release of the plugin before starting on adding the TMDB support (in code). Next _major_ release after this minor release will be waiting for Shoko Server 5.0 and Jellyfin 10.10 to be released.

Not really much else to say, except maybe read on if you care what changed in this release, otherwise have a nice day. 🍃

 # Highlights

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

 ## New Additions

- The settings page have been visually overhauled. The whole page has been rewritten to support a tab based view. Some settings have been marked as _advanced_ and hidden until the new _advanced mode_ is enabled. And the ground work for future utilities has been laid down. Refer to the newly updated [documentation](https://docs.shokoanime.com/jellyfin/configuring-shokofin#configuring-the-plugin-settings) for more information about the new settings and the _advanced mode_. (8a06ac5, 006e955, 172e788, cad46bb, 46621a5, d550585, 50f10a3, 96066d1, 541e5bf, bbe1c6d, dd56da0, 2c07b65, 5f20870, 0d765de, 7f48195, ca2583a, 53436a2, befd5a0, 75a0e8a, 2ba25c1, c25f4e9, fd6520f, 4f49841, 1f739a6, 4652327, ecd1398, 6044d90, 365034f, df1a57b, 0d636e6, 2d3d7ca, 0fc5054, e42173f, 946b341, c09c6ea, dd9a2ce, e163f99)

- Added support for logos for daily (and next stable) Shoko Server users. (1d4705e, 8cf2b11)

- Added support for image language codes. (c5b0fd4, 18849ae, 274294f, 123016f)

- Added new _advanced_ options to allow changing where the VFS live. You can place them in the Jellyfin data directory (the default), the Jellyfin cache directory, or any other custom directory. (6d0c4b3, 4bbe366, edb3823, 2c2ec46, 221c54c, 6829d74, 1414bb0)

- Add an _advanced_ option to physically attach the VFS to libraries. (715f740, 109b967, 9b3fce1, 00465e5, 2da817f, fa6b9e1, 5e10d54, 2290804, ca68c22, 8b9c5cb, 669503f, b575f8f, f57c547, 2c2ec46, 221c54c, 6929250, 6829d74, 7344b33, 1414bb0, 7c53798, 8239677, 8ec4bcc)

- Added a new _advanced_ option to resolve any symbolic links to their real target before generating the symbolic links to place to the VFS, effectively skipping the additionally needed lookup steps later when Jellyfin accesses the symbolic link placed in the VFS. (96518c5)

- Implemented episode/movie merging upon refresh. (aadf950, 6848190, aeb94b5)

- Added Shoko preferred descriptions for daily (and next stable) Shoko Server users. (9c772da)

- ~~Added a VFS preview endpoint for debugging purposes. A debug utility may or may not appear in the plugin soon-_ish_. (4baee92)~~

 ## Bug Fixes

- Append base path to image URLs. (4890504,  359bfef, f7c361f, fddf8d0, b720a39)

- Fix models for latest daily server and current stable server. (35bb6ca, 1d4705e, 3163fac, cf9c31d, 70bfe17, 56e30c4, 0d9503e)

- Remove all unused tvdb fields. (3806a6f)

- Expect the unexpected. (fb5fe4c)

- Add missing query parameter in series file endpoint. (e481000)

- Sanitize collection overviews. (e481000)

- Add back and fix image is available check. (87ef8f5, ea69f41, e1f3757)

- Fix specials placement by air date. (4b92e26, b7ca959)

- Check if plugin is enabled for item before looking up id during watch data syncing. (6e4fe63, f11bef4)

- Sanitize release groups in VFS file names. (6700b77)

- Attach VFS children to first available media folder. (bd8fe03, ec0d30d)

- Enable schedule tasks. (c9ebc96)

- Only add extras for movies if the movie will exist. (18509fe)

- Stop skipping cache for import folder. (922c5ab)

- Properly generate VFS for movie libraries. (0a9de51, 6220224)

- Attach import folder relative path during events. (47d0a41)

- Better error handling during VFS generation. (7687480)

- Fixed paths used in event dispatcher. (2e842b5)

- Fix-up faulty config on start-up and save. (e700a3a)

- Correct episode ordering before creating specials anchors. (76dc219)

- Don't try to remove VFS root for media folders. (9153c73)

- Start uglifying the year regex for season merging. (3369217)

 ## Miscellaneous Changes

- Add 'manners movie' as extras. (55c7ce3)

- Merge paths for special and other type episodes. (ea04250)

- Always split & merge entries. (dd54dfe)

- Mark chronological season ordering as experimental (again) and _advanced_. (1b37ccc, 68886b8)

- Promote auto merge version from the experimental section. (cad46bb)

- Modernize scheduled tasks and hide some behind the new _advanced mode_. (dd56da0)

- Tweaked log level of some tasks. (ee373fc)

- Fixed error message. (453dc77)

- Add path and/or other details when throwing. (5ea5cb1)

- Overhaul the merge versions manager. (aadf950)

- Generate all configurations first. (fa6b9e1, 5e10d54, 2290804, ca68c22, 8b9c5cb, 669503f, b575f8f, f57c547, 2c2ec46, 8239677)

- Enable file events by default. (b500f26)

- Log library id in import folder iteration step during VFS generation. (c9a6f75)

- Pass country code to content rating utility and rename misleading method names. (7162969)

- Remove `*Override` settings. (ecd1398, 6044d90)

- Get movie images using episode id. (fac3ce6, fa261c5, 720e48c)

- Allow VFS to auto-select thread count based on cpu if setting is set to 0. (4e5e6a5)

 ## Repository Changes

- QoL DX changes. (fa2d93f, e140e19, 8746698, 4744a56, 1ac96b2, 74d5a9f, f88cc78, 55fc933)

- Update repository read-me. (cfb7dc8)

- Update `git-log-json.mjs` to fix messages with skip ci set on them. (4d30335)

- Add files per commit for `git-log-json.mjs`. (20acd6b)

- Log which key is throwing from the guarded memory cache. (32ea3fe)

For the full list of changes, please check out the [complete changelog](4.1.1...4.2.0) here on GitHub.
  • Loading branch information
revam committed Oct 10, 2024
2 parents 53a34cb + e163f99 commit f76a729
Show file tree
Hide file tree
Showing 88 changed files with 5,115 additions and 2,932 deletions.
60 changes: 54 additions & 6 deletions .github/workflows/git-log-json.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ const RangeOrHash = process.argv[2] || "";
// Form the git log command
const GitLogCommandBase = `git log ${RangeOrHash}`;

const EndingMarkers = new Set([
".",
",",
"!",
"?",
]);

const Placeholders = {
"H": "commit",
"P": "parents",
Expand Down Expand Up @@ -62,6 +69,41 @@ for (const [placeholder, name] of Object.entries(Placeholders)) {
}
}

// Add file-level changes to each commit
for (const commitId of commitOrder) {
const fileStatusOutput = execSync(`git diff --name-status ${commitId}^ ${commitId}`).toString();
const lineChangesOutput = execSync(`git diff --numstat ${commitId}^ ${commitId}`).toString();

const files = [];
const fileStatusLines = fileStatusOutput.split(/\r\n|\r|\n/g).filter(a => a);
const lineChangesLines = lineChangesOutput.split(/\r\n|\r|\n/g).filter(a => a);

for (const [index, line] of fileStatusLines.entries()) {
const [rawStatus, path] = line.split(/\t/);
const status = rawStatus === "M" ?
"modified"
: rawStatus === "A" ?
"added"
: rawStatus === "D" ?
"deleted"
: rawStatus === "R" ?
"renamed"
: "untracked";
const lineChangeParts = lineChangesLines[index].split(/\t/);
const addedLines = parseInt(lineChangeParts[0] || "0", 10);
const removedLines = parseInt(lineChangeParts[1] || "0", 10);

files.push({
path,
status,
addedLines,
removedLines,
});
}

commits[commitId].files = files;
}

// Trim trailing newlines from all values in the commits object
for (const commit of Object.values(commits)) {
for (const key in commit) {
Expand All @@ -72,9 +114,9 @@ for (const commit of Object.values(commits)) {
}

// Convert commits object to a list of values
const commitsList = commitOrder.slice().reverse()
const commitsList = commitOrder.reverse()
.map((commitId) => commits[commitId])
.map(({ commit, parents, tree, subject, body, author_name, author_email, author_date, committer_name, committer_email, committer_date }) => ({
.map(({ commit, parents, tree, subject, body, author_name, author_email, author_date, committer_name, committer_email, committer_date, files }) => ({
commit,
parents,
tree,
Expand All @@ -101,17 +143,23 @@ const commitsList = commitOrder.slice().reverse()
date: new Date(committer_date).toISOString(),
timeZone: committer_date.substring(19) === "Z" ? "+00:00" : committer_date.substring(19),
},
files,
}))
.map((commit) => ({
...commit,
subject: /[a-z]/.test(commit.subject[0]) ? commit.subject[0].toUpperCase() + commit.subject.slice(1) : commit.subject,
subject: commit.subject.replace(/\[(?:skip|no) *ci\]/ig, "").trim().replace(/[\.:]+^/, ""),
body: commit.body ? commit.body.replace(/\[(?:skip|no) *ci\]/ig, "").trimEnd() : commit.body,
isSkipCI: /\[(?:skip|no) *ci\]/i.test(commit.subject) || Boolean(commit.body && /\[(?:skip|no) *ci\]/i.test(commit.body)),
type: commit.type == "feature" ? "feat" : commit.type === "refacor" ? "refactor" : commit.type == "mics" ? "misc" : commit.type,
}))
.map((commit) => ({
...commit,
subject: commit.subject.replace(/\[(?:skip|no) *ci\]/ig, "").trim().replace(/[\.:]+^/, ""),
body: commit.body ? commit.body.replace(/\[(?:skip|no) *ci\]/ig, "").trimEnd() : commit.body,
isSkipCI: /\[(?:skip|no) *ci\]/i.test(commit.subject) || Boolean(commit.body && /\[(?:skip|no) *ci\]/i.test(commit.body)),
subject: ((subject) => {
subject = (/[a-z]/.test(subject[0]) ? subject[0].toUpperCase() + subject.slice(1) : subject).trim();
if (subject.length > 0 && EndingMarkers.has(subject[subject.length - 1]))
subject = subject.slice(0, subject.length - 1);
return subject;
})(commit.subject),
}))
.filter((commit) => !(commit.type === "misc" && (commit.subject === "update unstable manifest" || commit.subject === "Update repo manifest" || commit.subject === "Update unstable repo manifest")));

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ compatible with what.
| `2.x.x` | `10.8` | `4.1.2` |
| `3.x.x` | `10.8` | `4.2.0` |
| `4.x.x` | `10.9` | `4.2.2` |
| `dev` | `10.9` | `4.2.2` |
| `dev` | `10.9` | `dev` |

### Official Repository

Expand Down
4 changes: 2 additions & 2 deletions Shokofin/API/Info/FileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class FileInfo

public FileInfo(File file, List<List<(EpisodeInfo Episode, CrossReference.EpisodeCrossReferenceIDs CrossReference, string Id)>> groupedEpisodeLists, string seriesId)
{
var episodeList = groupedEpisodeLists.FirstOrDefault() ?? new();
var alternateEpisodeLists = groupedEpisodeLists.Count > 1 ? groupedEpisodeLists.GetRange(1, groupedEpisodeLists.Count - 1) : new();
var episodeList = groupedEpisodeLists.FirstOrDefault() ?? [];
var alternateEpisodeLists = groupedEpisodeLists.Count > 1 ? groupedEpisodeLists.GetRange(1, groupedEpisodeLists.Count - 1) : [];
Id = file.Id.ToString();
SeriesId = seriesId;
ExtraType = episodeList.FirstOrDefault(tuple => tuple.Episode.ExtraType != null).Episode?.ExtraType;
Expand Down
61 changes: 44 additions & 17 deletions Shokofin/API/Info/SeasonInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public class SeasonInfo
/// </summary>
public readonly List<EpisodeInfo> ExtrasList;

/// <summary>
/// A list of special episodes that come before normal episodes.
/// </summary>
public readonly IReadOnlySet<string> SpecialsBeforeEpisodes;

/// <summary>
/// A dictionary holding mappings for the previous normal episode for every special episode in a series.
/// </summary>
Expand Down Expand Up @@ -118,15 +123,26 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
.Where(r => r.RelatedIDs.Shoko.HasValue)
.DistinctBy(r => r.RelatedIDs.Shoko!.Value)
.ToDictionary(r => r.RelatedIDs.Shoko!.Value.ToString(), r => r.Type);
var specialsBeforeEpisodes = new HashSet<string>();
var specialsAnchorDictionary = new Dictionary<EpisodeInfo, EpisodeInfo>();
var specialsList = new List<EpisodeInfo>();
var episodesList = new List<EpisodeInfo>();
var extrasList = new List<EpisodeInfo>();
var altEpisodesList = new List<EpisodeInfo>();
var seriesIdOrder = new string[] { seriesId }.Concat(extraIds).ToList();

// Order the episodes by date.
episodes = episodes
.OrderBy(episode => !episode.AniDB.AirDate.HasValue)
.ThenBy(episode => episode.AniDB.AirDate)
.ThenBy(e => seriesIdOrder.IndexOf(e.Shoko.IDs.ParentSeries.ToString()))
.ThenBy(episode => episode.AniDB.Type)
.ThenBy(episode => episode.AniDB.EpisodeNumber)
.ToList();

// Iterate over the episodes once and store some values for later use.
int index = 0;
int lastNormalEpisode = 0;
int lastNormalEpisode = -1;
foreach (var episode in episodes) {
if (episode.Shoko.IsHidden)
continue;
Expand All @@ -147,11 +163,16 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
}
else if (episode.AniDB.Type == EpisodeType.Special) {
specialsList.Add(episode);
var previousEpisode = episodes
.GetRange(lastNormalEpisode, index - lastNormalEpisode)
.FirstOrDefault(e => e.AniDB.Type == EpisodeType.Normal);
if (previousEpisode != null)
specialsAnchorDictionary[episode] = previousEpisode;
if (lastNormalEpisode == -1) {
specialsBeforeEpisodes.Add(episode.Id);
}
else {
var previousEpisode = episodes
.GetRange(lastNormalEpisode, index - lastNormalEpisode)
.FirstOrDefault(e => e.AniDB.Type == EpisodeType.Normal);
if (previousEpisode != null)
specialsAnchorDictionary[episode] = previousEpisode;
}
}
break;
}
Expand All @@ -161,7 +182,6 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
// We order the lists after sorting them into buckets because the bucket
// sort we're doing above have the episodes ordered by air date to get
// the previous episode anchors right.
var seriesIdOrder = new string[] { seriesId }.Concat(extraIds).ToList();
episodesList = episodesList
.OrderBy(e => seriesIdOrder.IndexOf(e.Shoko.IDs.ParentSeries.ToString()))
.ThenBy(e => e.AniDB.Type)
Expand All @@ -187,22 +207,28 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
type = SeriesType.Web;

episodesList = altEpisodesList;
altEpisodesList = new();
altEpisodesList = [];

// Re-create the special anchors because the episode list changed.
index = 0;
lastNormalEpisode = 0;
lastNormalEpisode = -1;
specialsBeforeEpisodes.Clear();
specialsAnchorDictionary.Clear();
foreach (var episode in episodes) {
if (episodesList.Contains(episode)) {
lastNormalEpisode = index;
}
else if (specialsList.Contains(episode)) {
var previousEpisode = episodes
.GetRange(lastNormalEpisode, index - lastNormalEpisode)
.FirstOrDefault(e => e.AniDB.Type == EpisodeType.Normal);
if (previousEpisode != null)
specialsAnchorDictionary[episode] = previousEpisode;
if (lastNormalEpisode == -1) {
specialsBeforeEpisodes.Add(episode.Id);
}
else {
var previousEpisode = episodes
.GetRange(lastNormalEpisode, index - lastNormalEpisode)
.FirstOrDefault(e => specialsList.Contains(e));
if (previousEpisode != null)
specialsAnchorDictionary[episode] = previousEpisode;
}
}
index++;
}
Expand All @@ -216,11 +242,11 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
if (specialsList.Count > 0) {
extrasList.AddRange(specialsList);
specialsAnchorDictionary.Clear();
specialsList = new();
specialsList = [];
}
if (altEpisodesList.Count > 0) {
extrasList.AddRange(altEpisodesList);
altEpisodesList = new();
altEpisodesList = [];
}
}

Expand All @@ -242,6 +268,7 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable<string> ext
EpisodeList = episodesList;
AlternateEpisodesList = altEpisodesList;
ExtrasList = extrasList;
SpecialsBeforeEpisodes = specialsBeforeEpisodes;
SpecialsAnchors = specialsAnchorDictionary;
SpecialsList = specialsList;
Relations = relations;
Expand All @@ -266,7 +293,7 @@ public bool IsEmpty(int offset = 0)
}

private static string? GetImagePath(Image image)
=> image != null && image.IsAvailable ? image.ToURLString() : null;
=> image != null && image.IsAvailable ? image.ToURLString(internalUrl: true) : null;

private static PersonInfo? RoleToPersonInfo(Role role)
=> role.Type switch
Expand Down
2 changes: 1 addition & 1 deletion Shokofin/API/Info/ShowInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public ShowInfo(SeasonInfo seasonInfo, string? collectionId = null)
ProductionLocations = seasonInfo.ProductionLocations;
Studios = seasonInfo.Studios;
Staff = seasonInfo.Staff;
SeasonList = new List<SeasonInfo>() { seasonInfo };
SeasonList = [seasonInfo];
SeasonNumberBaseDictionary = seasonNumberBaseDictionary;
SeasonOrderDictionary = seasonOrderDictionary;
SpecialsDict = seasonInfo.SpecialsList.ToDictionary(episodeInfo => episodeInfo.Id, episodeInfo => episodeInfo.Shoko.Size > 0);
Expand Down
10 changes: 5 additions & 5 deletions Shokofin/API/Models/ApiException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class ApiException : Exception

private record ValidationResponse
{
public Dictionary<string, string[]> errors = new();
public Dictionary<string, string[]> errors = [];

public string title = string.Empty;

Expand All @@ -33,22 +33,22 @@ public ApiException(HttpStatusCode statusCode, string source, string? message) :
{
StatusCode = statusCode;
Type = ApiExceptionType.Simple;
ValidationErrors = new();
ValidationErrors = [];
}

protected ApiException(HttpStatusCode statusCode, RemoteApiException inner) : base(inner.Message, inner)
{
StatusCode = statusCode;
Type = ApiExceptionType.RemoteException;
Inner = inner;
ValidationErrors = new();
ValidationErrors = [];
}

protected ApiException(HttpStatusCode statusCode, string source, string? message, Dictionary<string, string[]>? validationErrors = null): base(string.IsNullOrEmpty(message) ? source : $"{source}: {message}")
{
StatusCode = statusCode;
Type = ApiExceptionType.ValidationErrors;
ValidationErrors = validationErrors ?? new();
ValidationErrors = validationErrors ?? [];
}

public static ApiException FromResponse(HttpResponseMessage response)
Expand All @@ -63,7 +63,7 @@ public static ApiException FromResponse(HttpResponseMessage response)
var index = text.IndexOf("HEADERS");
if (index != -1) {
var (firstLine, lines) = text[..index].TrimEnd().Split('\n');
var (name, splitMessage) = firstLine?.Split(':') ?? Array.Empty<string>();
var (name, splitMessage) = firstLine?.Split(':') ?? [];
var message = string.Join(':', splitMessage).Trim();
var stackTrace = string.Join('\n', lines);
return new ApiException(response.StatusCode, new RemoteApiException(name ?? "InternalServerException", message, stackTrace));
Expand Down
4 changes: 3 additions & 1 deletion Shokofin/API/Models/ComponentVersion.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Text.Json.Serialization;

Expand All @@ -17,7 +18,8 @@ public class ComponentVersion
/// <summary>
/// Version number.
/// </summary>
public string Version { get; set; } = string.Empty;
[DefaultValue("1.0.0.0")]
public Version Version { get; set; } = new("1.0.0.0");

/// <summary>
/// Commit SHA.
Expand Down
2 changes: 1 addition & 1 deletion Shokofin/API/Models/CrossReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class CrossReference
/// The Episode IDs
/// </summary>
[JsonPropertyName("EpisodeIDs")]
public List<EpisodeCrossReferenceIDs> Episodes { get; set; } = new();
public List<EpisodeCrossReferenceIDs> Episodes { get; set; } = [];

/// <summary>
/// File episode cross-reference for a series.
Expand Down
Loading

0 comments on commit f76a729

Please sign in to comment.