diff --git a/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs b/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs index 8f83802..4b80794 100644 --- a/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs +++ b/Jellyfin.Plugin.Reports/Api/Common/ReportBuilderBase.cs @@ -238,9 +238,8 @@ protected List> GetReportOptions(IReportsHeader request, Fun protected string GetSeriesProductionYear(BaseItem item) { - string productionYear = item.ProductionYear.ToString(); - var series = item as Series; - if (series == null) + string productionYear = item.ProductionYear?.ToString(CultureInfo.InvariantCulture); + if (item is not Series series) { if (item.ProductionYear == null || item.ProductionYear == 0) return string.Empty; @@ -248,10 +247,10 @@ protected string GetSeriesProductionYear(BaseItem item) } if (series.Status == SeriesStatus.Continuing) - return productionYear += "-Present"; + return productionYear + "-Present"; if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear) - return productionYear += "-" + series.EndDate.Value.Year; + return productionYear + "-" + series.EndDate.Value.Year; return productionYear; } @@ -298,8 +297,8 @@ protected string GetVideoResolution(BaseItem item) MediaStreamType.Video); if (stream != null && stream.Width != null) return string.Format(CultureInfo.InvariantCulture, "{0} * {1}", - stream.Width, - stream.Height != null ? stream.Height.ToString() : "-"); + stream.Width, + stream.Height?.ToString(CultureInfo.InvariantCulture) ?? "-"); return string.Empty; } diff --git a/Jellyfin.Plugin.Reports/Api/Data/ReportBuilder.cs b/Jellyfin.Plugin.Reports/Api/Data/ReportBuilder.cs index 7063e08..2454cd6 100644 --- a/Jellyfin.Plugin.Reports/Api/Data/ReportBuilder.cs +++ b/Jellyfin.Plugin.Reports/Api/Data/ReportBuilder.cs @@ -1,6 +1,7 @@ #nullable disable using System.Collections.Generic; +using System.Globalization; using System.Linq; using Jellyfin.Plugin.Reports.Api.Common; using Jellyfin.Plugin.Reports.Api.Model; @@ -389,7 +390,7 @@ private ReportOptions GetOption(HeaderMetadata header, string sortFiel break; case HeaderMetadata.SeasonNumber: - option.Column = (i, r) => this.GetObject(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString()); + option.Column = (i, r) => this.GetObject(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber?.ToString(CultureInfo.InvariantCulture)); option.Header.SortField = "IndexNumber"; option.Header.HeaderFieldType = ReportFieldType.Int; break; @@ -429,7 +430,7 @@ private ReportOptions GetOption(HeaderMetadata header, string sortFiel break; case HeaderMetadata.EpisodeNumber: - option.Column = (i, r) => this.GetObject(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString()); + option.Column = (i, r) => this.GetObject(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber?.ToString(CultureInfo.InvariantCulture)); //option.Header.SortField = "IndexNumber"; //option.Header.HeaderFieldType = ReportFieldType.Int; break; diff --git a/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs b/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs index 6ccf130..9649739 100644 --- a/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs +++ b/Jellyfin.Plugin.Reports/Api/Data/ReportExport.cs @@ -21,7 +21,7 @@ public static MemoryStream ExportToCsv(ReportResult reportResult) static string EscapeText(string text) { string escapedText = text.Replace("\"", "\"\"", System.StringComparison.Ordinal); - return text.IndexOfAny(new char[4] { '"', ',', '\n', '\r' }) == -1 ? escapedText : $"\"{escapedText}\""; + return text.IndexOfAny(['"', ',', '\n', '\r']) == -1 ? escapedText : $"\"{escapedText}\""; } static void AppendRows(StreamWriter writer, List rows) { @@ -153,7 +153,7 @@ static void AddReportRows(IXLWorksheet worksheet, List reportRows, re nextRow += rows.Count(); } - using IXLWorkbook workbook = new XLWorkbook(XLEventTracking.Disabled); + using var workbook = new XLWorkbook(XLEventTracking.Disabled); IXLWorksheet worksheet = workbook.Worksheets.Add("ReportExport"); // Add report rows diff --git a/Jellyfin.Plugin.Reports/Api/ReportRequests.cs b/Jellyfin.Plugin.Reports/Api/ReportRequests.cs index 1df1243..9dc3018 100644 --- a/Jellyfin.Plugin.Reports/Api/ReportRequests.cs +++ b/Jellyfin.Plugin.Reports/Api/ReportRequests.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; using System.Linq; +using DocumentFormat.OpenXml.Spreadsheet; using Jellyfin.Data.Enums; using Jellyfin.Plugin.Reports.Api.Common; using MediaBrowser.Model.Entities; @@ -94,7 +95,7 @@ protected BaseReportRequest() public bool? HasTrailer { get; set; } // [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AdjacentTo { get; set; } + public Guid? AdjacentTo { get; set; } // [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MinIndexNumber { get; set; } @@ -403,46 +404,50 @@ protected BaseReportRequest() public string[] GetGenres() { - return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + return (Genres ?? string.Empty).Split( '|', StringSplitOptions.RemoveEmptyEntries); } public string[] GetTags() { - return (Tags ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + return (Tags ?? string.Empty).Split('|', StringSplitOptions.RemoveEmptyEntries); } public string[] GetOfficialRatings() { - return (OfficialRatings ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + return (OfficialRatings ?? string.Empty).Split('|', StringSplitOptions.RemoveEmptyEntries); } - public string[] GetMediaTypes() + public MediaType[] GetMediaTypes() { - return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + return (MediaTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(r => Enum.TryParse(r, out MediaType mt) ? mt : (MediaType?)null) + .Where(r => r is not null) + .Select(r => r.Value) + .ToArray(); } public BaseItemKind[] GetIncludeItemTypes() => GetBaseItemKinds(IncludeItemTypes); public string[] GetExcludeItemIds() { - return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + return (ExcludeItemIds ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries); } public BaseItemKind[] GetExcludeItemTypes() => GetBaseItemKinds(ExcludeItemTypes); public int[] GetYears() { - return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); + return (Years ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); } public Guid[] GetGuids(string value) { - return (value ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(i => new Guid(i)).ToArray(); + return (value ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries).Select(i => new Guid(i)).ToArray(); } public string[] GetStudios() { - return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + return (Studios ?? string.Empty).Split('|', StringSplitOptions.RemoveEmptyEntries); } public Guid[] GetArtistIds() @@ -462,7 +467,7 @@ public Guid[] GetGenreIds() public string[] GetPersonTypes() { - return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + return (PersonTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries); } public Guid[] GetPersonIds() @@ -484,7 +489,7 @@ public VideoType[] GetVideoTypes() return Array.Empty(); } - return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); + return val.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); } /// @@ -500,7 +505,7 @@ public ItemFilter[] GetFilters() return Array.Empty(); } - return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray(); + return val.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray(); } /// @@ -523,18 +528,18 @@ public ImageType[] GetImageTypes() /// Gets the order by. /// /// IEnumerable{ItemSortBy}. - public ValueTuple[] GetOrderBy() + public ValueTuple[] GetOrderBy() { return GetOrderBy(SortBy, SortOrder); } - public static (string, SortOrder)[] GetOrderBy(string sortBy, string requestedSortOrder) + public static (ItemSortBy, SortOrder)[] GetOrderBy(string sortBy, string requestedSortOrder) { var val = sortBy; if (string.IsNullOrEmpty(val)) { - return Array.Empty<(string, SortOrder)>(); + return Array.Empty<(ItemSortBy, SortOrder)>(); } var vals = val.Split(','); @@ -545,16 +550,21 @@ public static (string, SortOrder)[] GetOrderBy(string sortBy, string requestedSo var sortOrders = requestedSortOrder.Split(','); - var result = new (string, SortOrder)[vals.Length]; + var result = new (ItemSortBy, SortOrder)[vals.Length]; for (var i = 0; i < vals.Length; i++) { + if (!Enum.TryParse(vals[i], out ItemSortBy currentSortBy)) + { + continue; + } + var sortOrderIndex = sortOrders.Length > i ? i : 0; var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? Jellyfin.Data.Enums.SortOrder.Descending : Jellyfin.Data.Enums.SortOrder.Ascending; - result[i] = (vals[i], sortOrder); + result[i] = (currentSortBy, sortOrder); } return result; diff --git a/Jellyfin.Plugin.Reports/Api/ReportsController.cs b/Jellyfin.Plugin.Reports/Api/ReportsController.cs index 6faee18..062e8d2 100644 --- a/Jellyfin.Plugin.Reports/Api/ReportsController.cs +++ b/Jellyfin.Plugin.Reports/Api/ReportsController.cs @@ -1,4 +1,5 @@ -using System.Net.Mime; +using System; +using System.Net.Mime; using System.Threading.Tasks; using Jellyfin.Plugin.Reports.Api.Common; using Jellyfin.Plugin.Reports.Api.Model; @@ -6,13 +7,14 @@ using MediaBrowser.Model.Activity; using MediaBrowser.Model.Globalization; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Plugin.Reports.Api { [ApiController] [Route("[controller]")] - [Authorize(Policy = "DefaultAuthorization")] + [Authorize] [Produces(MediaTypeNames.Application.Json)] public class ReportsController : ControllerBase { @@ -37,7 +39,7 @@ public ActionResult GetItemReport( [FromQuery] bool? hasSubtitles, [FromQuery] bool? hasSpecialFeature, [FromQuery] bool? hasTrailer, - [FromQuery] string? adjacentTo, + [FromQuery] Guid? adjacentTo, [FromQuery] int? minIndexNumber, [FromQuery] int? parentIndexNumber, [FromQuery] bool? hasParentalRating, @@ -260,7 +262,7 @@ public async Task> GetReportDownload( [FromQuery] bool? hasSubtitles, [FromQuery] bool? hasSpecialFeature, [FromQuery] bool? hasTrailer, - [FromQuery] string? adjacentTo, + [FromQuery] Guid? adjacentTo, [FromQuery] int? minIndexNumber, [FromQuery] int? parentIndexNumber, [FromQuery] bool? hasParentalRating, @@ -427,7 +429,7 @@ public async Task> GetReportDownload( foreach (var (key, value) in headers) { - Response.Headers.Add(key, value); + Response.Headers.Append(key, value); } return File(content, contentType); diff --git a/Jellyfin.Plugin.Reports/Jellyfin.Plugin.Reports.csproj b/Jellyfin.Plugin.Reports/Jellyfin.Plugin.Reports.csproj index 00f6757..b88bfdb 100644 --- a/Jellyfin.Plugin.Reports/Jellyfin.Plugin.Reports.csproj +++ b/Jellyfin.Plugin.Reports/Jellyfin.Plugin.Reports.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 Jellyfin.Plugin.Reports true ../jellyfin.ruleset diff --git a/build.yaml b/build.yaml index af460a0..3f10f57 100644 --- a/build.yaml +++ b/build.yaml @@ -3,8 +3,8 @@ name: "Reports" guid: "d4312cd9-5c90-4f38-82e8-51da566790e8" imageUrl: "https://repo.jellyfin.org/releases/plugin/images/jellyfin-plugin-reports.png" version: 15 -targetAbi: "10.8.1.0" -framework: "net6.0" +targetAbi: "10.9.0.0" +framework: "net8.0" owner: "jellyfin" overview: "Generate reports of your media library" description: "Generate reports of your media library" @@ -16,14 +16,3 @@ artifacts: - "ExcelNumberFormat.dll" - "System.IO.Packaging.dll" changelog: |2- - ### Bug Fixes ### - - Fix CSV Export Format (#73) @mwildgoose - - ### Code or Repo Maintenance ### - - Use meta ci (#66) @crobibero - - Performance improvements (#59) @Bond-009 - - Style fixes, enable analysis and nullable (#57) @Bond-009 - - ### CI & build changes ### - - fix: meta ci workflows (#68) @h1dden-da3m0n - - Use meta ci (#66) @crobibero