Skip to content

Improvements to release date sorting#77

Merged
jyourstone merged 2 commits into
mainfrom
sorting
Aug 19, 2025
Merged

Improvements to release date sorting#77
jyourstone merged 2 commits into
mainfrom
sorting

Conversation

@jyourstone
Copy link
Copy Markdown
Owner

@jyourstone jyourstone commented Aug 19, 2025

If there are episodes in the playlist that have the same release date, it now sorts them by season and episode number.

Summary by Sourcery

Improve release date ordering by adding helper methods for episode metadata and refining sorting to group episodes first and sort them by season and episode numbers when dates are identical.

Enhancements:

  • Introduce GetSeasonNumber, GetEpisodeNumber, and IsEpisode helpers for extracting episode metadata
  • Enhance release date sorting to group items by date, prioritize episodes over other items, and order episodes by season and episode numbers in both ascending and descending modes

…aylist that have the same release date, it now sorts them by season and episode number.
@jyourstone jyourstone added the enhancement New feature or request label Aug 19, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Aug 19, 2025

Reviewer's Guide

Enhances release date sorting by introducing utilities to detect episode metadata and updating both ascending and descending sort orders to group items by release date and tie-break using episode status, season number, and episode number.

File-Level Changes

Change Details Files
Add utilities for extracting episode metadata
  • Define GetSeasonNumber to retrieve season index or return 0
  • Define GetEpisodeNumber to retrieve episode index or return 0
  • Define IsEpisode to check if an item is a TV episode
Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs
Revise ascending release date sorting to handle ties
  • Guard against null item lists
  • Sort items by release date at date precision
  • Within same date, prioritize episodes over non-episodes
  • Then sort episodes by season and episode numbers
Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs
Revise descending release date sorting to handle ties
  • Guard against null item lists
  • Sort items by release date descending at date precision
  • Within same date, prioritize episodes over non-episodes
  • Then sort episodes by season and episode numbers in descending order
Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@jyourstone has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b9561e8 and 479b048.

📒 Files selected for processing (1)
  • Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs (3 hunks)

Walkthrough

The change adds three public static methods to OrderUtilities: GetSeasonNumber(BaseItem), GetEpisodeNumber(BaseItem), and IsEpisode(BaseItem). ReleaseDateOrder.OrderBy now groups items by release date and sorts groups ascending; within each group, items are ordered with episodes first, then by season number, then episode number. ReleaseDateOrderDesc.OrderBy similarly groups by release date but sorts groups descending; within groups, episodes come first, then season and episode numbers in descending order. Reflection retrieves index values; null collections are handled.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sorting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs:1580` </location>
<code_context>
+        /// </summary>
+        /// <param name="item">The BaseItem to check</param>
+        /// <returns>True if the item is an episode, false otherwise</returns>
+        public static bool IsEpisode(BaseItem item)
+        {
+            return item?.GetType().Name == "Episode";
</code_context>

<issue_to_address>
Type name comparison may be fragile if class names change.

Using string comparison for type checking can break if the class name changes or if inheritance is involved. Prefer 'is Episode' or direct type comparison for reliability.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        public static bool IsEpisode(BaseItem item)
        {
            return item?.GetType().Name == "Episode";
        }
=======
        public static bool IsEpisode(BaseItem item)
        {
            return item is Episode;
        }
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1580 to +1583
public static bool IsEpisode(BaseItem item)
{
return item?.GetType().Name == "Episode";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Type name comparison may be fragile if class names change.

Using string comparison for type checking can break if the class name changes or if inheritance is involved. Prefer 'is Episode' or direct type comparison for reliability.

Suggested change
public static bool IsEpisode(BaseItem item)
{
return item?.GetType().Name == "Episode";
}
public static bool IsEpisode(BaseItem item)
{
return item is Episode;
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs (5)

1515-1543: Avoid reflection in hot path: use typed Episode properties for season number

Reflection here is unnecessary and will be called repeatedly during sorting. Strongly-typed access is faster and simpler.

Apply this diff to replace reflection with a type check and direct property access:

-        public static int GetSeasonNumber(BaseItem item)
-        {
-            try
-            {
-                var parentIndexProperty = item.GetType().GetProperty("ParentIndexNumber");
-                if (parentIndexProperty != null)
-                {
-                    var value = parentIndexProperty.GetValue(item);
-                    if (value is int intValue)
-                        return intValue;
-                    if (value != null)
-                    {
-                        var nullableValue = value as int?;
-                        if (nullableValue.HasValue)
-                            return nullableValue.Value;
-                    }
-                }
-            }
-            catch
-            {
-                // Ignore errors and fall back to 0
-            }
-            return 0;
-        }
+        public static int GetSeasonNumber(BaseItem item)
+        {
+            return item is MediaBrowser.Controller.Entities.TV.Episode ep
+                ? (ep.ParentIndexNumber ?? 0)
+                : 0;
+        }

Additionally, consider making these helpers internal if you don't intend to expose new public API surface.


1545-1573: Same here: drop reflection and read IndexNumber directly

Direct access is faster and clearer; this runs in sort key selectors.

Apply this diff:

-        public static int GetEpisodeNumber(BaseItem item)
-        {
-            try
-            {
-                var indexProperty = item.GetType().GetProperty("IndexNumber");
-                if (indexProperty != null)
-                {
-                    var value = indexProperty.GetValue(item);
-                    if (value is int intValue)
-                        return intValue;
-                    if (value != null)
-                    {
-                        var nullableValue = value as int?;
-                        if (nullableValue.HasValue)
-                            return nullableValue.Value;
-                    }
-                }
-            }
-            catch
-            {
-                // Ignore errors and fall back to 0
-            }
-            return 0;
-        }
+        public static int GetEpisodeNumber(BaseItem item)
+        {
+            return item is MediaBrowser.Controller.Entities.TV.Episode ep
+                ? (ep.IndexNumber ?? 0)
+                : 0;
+        }

1575-1583: Prefer type checks over string name comparisons

String-based type checks are brittle and slower. Use the concrete type check.

Apply this diff:

-        public static bool IsEpisode(BaseItem item)
-        {
-            return item?.GetType().Name == "Episode";
-        }
+        public static bool IsEpisode(BaseItem item)
+        {
+            return item is MediaBrowser.Controller.Entities.TV.Episode;
+        }

1692-1699: Simplify to a single composite OrderBy and use date-only to group “same release date”

Grouping then sorting is more work than necessary and compares full DateTime (time-of-day), which can split items released on the same calendar day. A composite OrderBy chain is simpler and faster, and using .Date matches the PR intent.

Apply this diff:

-            // Group by release date, then sort each group properly
-            return items.GroupBy(OrderUtilities.GetReleaseDate)
-                        .OrderBy(g => g.Key) // Sort date groups ascending
-                        .SelectMany(dateGroup => 
-                            dateGroup.OrderBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
-                                     .ThenBy(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
-                                     .ThenBy(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0)
-                        );
+            // Sort by release date (day precision), then within the same day: episodes first, then by SxxExx
+            return items
+                .OrderBy(item => OrderUtilities.GetReleaseDate(item).Date)
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);

Note: If you prefer not to prioritize episodes over non-episodes within the same date, drop the ThenBy that enforces episodes-first and only keep the season/episode ThenBys; non-episodes will retain stable relative order.


1709-1718: Mirror the same composite approach for descending; confirm intended tie-breaks

The descending variant should also switch to a composite OrderBy chain. Also, confirm whether “descending” within the same date should reverse season/episode, or stay ascending (since the date is identical). Current code chooses descending for both.

Apply this diff:

-            // Group by release date, then sort each group properly
-            return items.GroupBy(OrderUtilities.GetReleaseDate)
-                        .OrderByDescending(g => g.Key) // Sort date groups descending
-                        .SelectMany(dateGroup => 
-                            dateGroup.OrderBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
-                                     .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0) // Season descending
-                                     .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0) // Episode descending
-                        );
+            // Sort by release date (day precision) descending; within same day, episodes first then SxxExx
+            return items
+                .OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
+                .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
+                .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);

Questions to confirm:

  • Do we really want “episodes first” within a date group, or only to tie-break episodes relative to each other? The PR summary only mentions ordering episodes among themselves.
  • For the Desc variant, should season/episode be descending within the same date? Many users expect S/E to be ascending even when the top-level date is descending. Happy to adjust and send a patch either way.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 975f309 and b9561e8.

📒 Files selected for processing (1)
  • Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.cs

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.cs: For direct property access in C#, use simple property assignments for cheap operations (e.g., Genres, Studios, Tags, Name, ProductionYear).
For expensive operations requiring Library Manager queries, use query objects and reflection to invoke methods (e.g., GetPeople).
For reflection-based discovery in C#, use reflection to enumerate properties and methods for debugging or dynamic access.
When debugging unknown data structures in C#, log the type, properties, and, if enumerable, the first few items and their properties.
BaseItem properties such as Name, ProductionYear, OfficialRating, Genres, Studios, Tags, CommunityRating, CriticRating, DateCreated, DateModified, DateLastRefreshed, and IsPlayed(user) should be accessed directly.
People/Cast/Crew fields require Library Manager queries using InternalPeopleQuery with ItemId.
Audio/Subtitle streams should be accessed via MediaSources property or GetMediaStreams() method.
Related items should be accessed using appropriate query objects.
User data fields (PlayCount, IsFavorite, LastPlayedDate) require UserDataManager.
Artists (track-level) and AlbumArtists (album-level) are separate from People field (movies/TV only) and are cheap operations (simple property access via reflection), not included in expensive field optimization logic.

Files:

  • Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs
**/SmartPlaylist*.cs

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

For performance optimization, use two-phase filtering for expensive fields (AudioLanguages, People): phase 1 for cheap properties, phase 2 for expensive data only for items passing phase 1.

Files:

  • Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs
🧬 Code Graph Analysis (1)
Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs (1)
Jellyfin.Plugin.SmartPlaylist/QueryEngine/Factory.cs (1)
  • BaseItem (689-713)

…ic and improve readability. Update IsEpisode method to use type checking instead of string comparison. Enhance sorting logic in playlist methods to directly order items by release date and episode attributes without grouping.
@jyourstone
Copy link
Copy Markdown
Owner Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs:1654` </location>
<code_context>
         public override IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items)
         {
-            return items == null ? [] : items.OrderBy(OrderUtilities.GetReleaseDate);
+            if (items == null) return [];
+
+            // Sort by release date (day precision), then within the same day: episodes first, then by season/episode
</code_context>

<issue_to_address>
Returning an empty array for null input is fine, but consider using Array.Empty<BaseItem>() for clarity.

Array.Empty<BaseItem>() is preferable to [] as it avoids extra allocations and enhances code clarity.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
            if (items == null) return [];
=======
            if (items == null) return Array.Empty<BaseItem>();
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs:1658` </location>
<code_context>
+
+            // Sort by release date (day precision), then within the same day: episodes first, then by season/episode
+            return items
+                .OrderBy(item => OrderUtilities.GetReleaseDate(item).Date)
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);
</code_context>

<issue_to_address>
The ordering logic prioritizes episodes within the same release date, but may not handle items with missing or default season/episode numbers optimally.

If season or episode numbers default to 0, specials or items with missing data may be sorted before regular episodes. Consider refining the logic to better handle these cases.

Suggested implementation:

```csharp
            // Sort by release date (day precision), then within the same day: episodes first, then by season/episode
            return items
                .OrderBy(item => OrderUtilities.GetReleaseDate(item).Date)
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? GetSortableSeasonNumber(item) : int.MaxValue)
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? GetSortableEpisodeNumber(item) : int.MaxValue);

```

```csharp
        /// <summary>
        /// Returns a sortable season number, putting specials or missing data last.
        /// </summary>
        private static int GetSortableSeasonNumber(BaseItem item)
        {
            var season = OrderUtilities.GetSeasonNumber(item);
            // Treat 0 or negative as missing/special, sort them last
            return (season > 0) ? season : int.MaxValue;
        }

        /// <summary>
        /// Returns a sortable episode number, putting specials or missing data last.
        /// </summary>
        private static int GetSortableEpisodeNumber(BaseItem item)
        {
            var episode = OrderUtilities.GetEpisodeNumber(item);
            // Treat 0 or negative as missing/special, sort them last
            return (episode > 0) ? episode : int.MaxValue;
        }

```
</issue_to_address>

### Comment 3
<location> `Jellyfin.Plugin.SmartPlaylist/SmartPlaylist.cs:1675` </location>
<code_context>
+
+            // Sort by release date (day precision) descending; within same day, episodes first then season/episode descending
+            return items
+                .OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
+                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
+                .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
+                .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);
</code_context>

<issue_to_address>
Descending order logic may result in specials or items with missing season/episode numbers appearing before regular episodes.

Consider refining the sorting logic to ensure items with missing or default season/episode numbers are placed appropriately relative to regular episodes.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
            // Sort by release date (day precision) descending; within same day, episodes first then season/episode descending
            return items
                .OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
                .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
                .ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);
=======
            // Sort by release date (day precision) descending; within same day, episodes first, regular episodes before specials/missing, then season/episode descending
            return items
                .OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
                .ThenBy(item =>
                    OrderUtilities.IsEpisode(item)
                        ? (OrderUtilities.GetSeasonNumber(item) > 0 && OrderUtilities.GetEpisodeNumber(item) > 0 ? 0 : 1)
                        : 2
                ) // Regular episodes before specials/missing
                .ThenByDescending(item => OrderUtilities.IsEpisode(item) && OrderUtilities.GetSeasonNumber(item) > 0 ? OrderUtilities.GetSeasonNumber(item) : int.MinValue)
                .ThenByDescending(item => OrderUtilities.IsEpisode(item) && OrderUtilities.GetEpisodeNumber(item) > 0 ? OrderUtilities.GetEpisodeNumber(item) : int.MinValue);
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

public override IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items)
{
return items == null ? [] : items.OrderBy(OrderUtilities.GetReleaseDate);
if (items == null) return [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Returning an empty array for null input is fine, but consider using Array.Empty() for clarity.

Array.Empty() is preferable to [] as it avoids extra allocations and enhances code clarity.

Suggested change
if (items == null) return [];
if (items == null) return Array.Empty<BaseItem>();

Comment on lines +1658 to +1659
.OrderBy(item => OrderUtilities.GetReleaseDate(item).Date)
.ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The ordering logic prioritizes episodes within the same release date, but may not handle items with missing or default season/episode numbers optimally.

If season or episode numbers default to 0, specials or items with missing data may be sorted before regular episodes. Consider refining the logic to better handle these cases.

Suggested implementation:

            // Sort by release date (day precision), then within the same day: episodes first, then by season/episode
            return items
                .OrderBy(item => OrderUtilities.GetReleaseDate(item).Date)
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? GetSortableSeasonNumber(item) : int.MaxValue)
                .ThenBy(item => OrderUtilities.IsEpisode(item) ? GetSortableEpisodeNumber(item) : int.MaxValue);
        /// <summary>
        /// Returns a sortable season number, putting specials or missing data last.
        /// </summary>
        private static int GetSortableSeasonNumber(BaseItem item)
        {
            var season = OrderUtilities.GetSeasonNumber(item);
            // Treat 0 or negative as missing/special, sort them last
            return (season > 0) ? season : int.MaxValue;
        }

        /// <summary>
        /// Returns a sortable episode number, putting specials or missing data last.
        /// </summary>
        private static int GetSortableEpisodeNumber(BaseItem item)
        {
            var episode = OrderUtilities.GetEpisodeNumber(item);
            // Treat 0 or negative as missing/special, sort them last
            return (episode > 0) ? episode : int.MaxValue;
        }

Comment on lines +1673 to +1678
// Sort by release date (day precision) descending; within same day, episodes first then season/episode descending
return items
.OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
.ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
.ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
.ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Descending order logic may result in specials or items with missing season/episode numbers appearing before regular episodes.

Consider refining the sorting logic to ensure items with missing or default season/episode numbers are placed appropriately relative to regular episodes.

Suggested change
// Sort by release date (day precision) descending; within same day, episodes first then season/episode descending
return items
.OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
.ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
.ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetSeasonNumber(item) : 0)
.ThenByDescending(item => OrderUtilities.IsEpisode(item) ? OrderUtilities.GetEpisodeNumber(item) : 0);
// Sort by release date (day precision) descending; within same day, episodes first, regular episodes before specials/missing, then season/episode descending
return items
.OrderByDescending(item => OrderUtilities.GetReleaseDate(item).Date)
.ThenBy(item => OrderUtilities.IsEpisode(item) ? 0 : 1) // Episodes first within same date
.ThenBy(item =>
OrderUtilities.IsEpisode(item)
? (OrderUtilities.GetSeasonNumber(item) > 0 && OrderUtilities.GetEpisodeNumber(item) > 0 ? 0 : 1)
: 2
) // Regular episodes before specials/missing
.ThenByDescending(item => OrderUtilities.IsEpisode(item) && OrderUtilities.GetSeasonNumber(item) > 0 ? OrderUtilities.GetSeasonNumber(item) : int.MinValue)
.ThenByDescending(item => OrderUtilities.IsEpisode(item) && OrderUtilities.GetEpisodeNumber(item) > 0 ? OrderUtilities.GetEpisodeNumber(item) : int.MinValue);

@jyourstone jyourstone merged commit 79de19d into main Aug 19, 2025
1 check passed
@jyourstone jyourstone deleted the sorting branch August 19, 2025 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant