diff --git a/.copr/Makefile b/.copr/Makefile deleted file mode 100644 index ba330ada95..0000000000 --- a/.copr/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \ - deployment/fedora-package-x64/pkg-src/jellyfin.spec) - -deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz: - curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \ - https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \ - || curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \ - https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \ - -srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz - cd deployment/fedora-package-x64; \ - SOURCE_DIR=../.. \ - WORKDIR="$${PWD}"; \ - package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \ - pkg_src_dir="$${WORKDIR}/pkg-src"; \ - GNU_TAR=1; \ - tar \ - --transform "s,^\.,jellyfin-$(VERSION)," \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude='deployment' \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - -czf "pkg-src/jellyfin-$(VERSION).tar.gz" \ - -C $${SOURCE_DIR} ./ || GNU_TAR=0; \ - if [ $${GNU_TAR} -eq 0 ]; then \ - package_temporary_dir="$$(mktemp -d)"; \ - mkdir -p "$${package_temporary_dir}/jellyfin"; \ - tar \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude='deployment' \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \ - -C $${SOURCE_DIR} ./; \ - mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \ - tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \ - -C "$${package_temporary_dir}/jellyfin-$(VERSION); \ - rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \ - tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \ - -C "$${package_temporary_dir}" "jellyfin-$(VERSION); \ - rm -rf $${package_temporary_dir}; \ - fi; \ - rpmbuild -bs pkg-src/jellyfin.spec \ - --define "_sourcedir $$PWD/pkg-src/" \ - --define "_srcrpmdir $(outdir)" diff --git a/.copr/Makefile b/.copr/Makefile new file mode 120000 index 0000000000..ec3c90dfd9 --- /dev/null +++ b/.copr/Makefile @@ -0,0 +1 @@ +../fedora/Makefile \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index dc9aaa3ed5..b84e563efa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf -max_line_length = null +max_line_length = off # YAML indentation [*.{yml,yaml}] @@ -22,6 +22,7 @@ indent_size = 2 # XML indentation [*.{csproj,xml}] indent_size = 2 + ############################### # .NET Coding Conventions # ############################### @@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent -dotnet_prefer_inferred_tuple_names = true:suggestion -dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = true:silent dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent + ############################### # Naming Conventions # ############################### @@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field -dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected dotnet_naming_symbols.non_private_static_fields.required_modifiers = static dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case @@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion csharp_prefer_simple_default_expression = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion + ############################### # C# Formatting Rules # ############################### @@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false # Wrapping preferences csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true -############################### -# VB Coding Conventions # -############################### -[*.vb] -# Modifier preferences -visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..e902dc7124 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Joshua must review all changes to deployment and build.sh +deployment/* @joshuaboniface +build.sh @joshuaboniface diff --git a/.gitignore b/.gitignore index 523c45a7e6..46f036ad91 100644 --- a/.gitignore +++ b/.gitignore @@ -245,14 +245,14 @@ pip-log.txt ######################### # Artifacts for debian-x64 -deployment/debian-package-x64/pkg-src/.debhelper/ -deployment/debian-package-x64/pkg-src/*.debhelper -deployment/debian-package-x64/pkg-src/debhelper-build-stamp -deployment/debian-package-x64/pkg-src/files -deployment/debian-package-x64/pkg-src/jellyfin.substvars -deployment/debian-package-x64/pkg-src/jellyfin/ +debian/.debhelper/ +debian/*.debhelper +debian/debhelper-build-stamp +debian/files +debian/jellyfin.substvars +debian/jellyfin/ # Don't ignore the debian/bin folder -!deployment/debian-package-x64/pkg-src/bin/ +!debian/bin/ deployment/**/dist/ deployment/**/pkg-dist/ @@ -272,3 +272,8 @@ dist # BenchmarkDotNet artifacts BenchmarkDotNet.Artifacts + +# Ignore web artifacts from native builds +web/ +web-src.* +MediaBrowser.WebDashboard/jellyfin-web/ diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs index 473005b556..b3aad85cec 100644 --- a/DvdLib/BigEndianBinaryReader.cs +++ b/DvdLib/BigEndianBinaryReader.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Buffers.Binary; using System.IO; diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj index fd0cb5e255..64d041cb05 100644 --- a/DvdLib/DvdLib.csproj +++ b/DvdLib/DvdLib.csproj @@ -13,6 +13,7 @@ netstandard2.1 false true + true diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs index 268ab897ee..2eab400f7d 100644 --- a/DvdLib/Ifo/Cell.cs +++ b/DvdLib/Ifo/Cell.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.IO; namespace DvdLib.Ifo diff --git a/DvdLib/Ifo/CellPlaybackInfo.cs b/DvdLib/Ifo/CellPlaybackInfo.cs index e588e51ac0..6e33a0ec5a 100644 --- a/DvdLib/Ifo/CellPlaybackInfo.cs +++ b/DvdLib/Ifo/CellPlaybackInfo.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.IO; namespace DvdLib.Ifo diff --git a/DvdLib/Ifo/CellPositionInfo.cs b/DvdLib/Ifo/CellPositionInfo.cs index 2b973e0830..216aa0f77a 100644 --- a/DvdLib/Ifo/CellPositionInfo.cs +++ b/DvdLib/Ifo/CellPositionInfo.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.IO; namespace DvdLib.Ifo diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs index bd3bd97040..1e69429f82 100644 --- a/DvdLib/Ifo/Chapter.cs +++ b/DvdLib/Ifo/Chapter.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + namespace DvdLib.Ifo { public class Chapter diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index 5af58a2dc8..ca20baa73f 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs index 3688089ec7..978af90c2e 100644 --- a/DvdLib/Ifo/DvdTime.cs +++ b/DvdLib/Ifo/DvdTime.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace DvdLib.Ifo diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs index af08afa356..9f62512706 100644 --- a/DvdLib/Ifo/Program.cs +++ b/DvdLib/Ifo/Program.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; namespace DvdLib.Ifo diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs index 7b003005b9..4860360afd 100644 --- a/DvdLib/Ifo/ProgramChain.cs +++ b/DvdLib/Ifo/ProgramChain.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs index 335e929928..abf806d2c0 100644 --- a/DvdLib/Ifo/Title.cs +++ b/DvdLib/Ifo/Title.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.IO; diff --git a/DvdLib/Ifo/UserOperation.cs b/DvdLib/Ifo/UserOperation.cs index 757a5a05db..5d111ebc06 100644 --- a/DvdLib/Ifo/UserOperation.cs +++ b/DvdLib/Ifo/UserOperation.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace DvdLib.Ifo diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs index 33f4468d9f..b63be3a647 100644 --- a/Emby.Naming/Audio/AlbumParser.cs +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -1,9 +1,9 @@ +#nullable enable #pragma warning disable CS1591 using System; using System.Globalization; using System.IO; -using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Common; @@ -21,8 +21,7 @@ namespace Emby.Naming.Audio public bool IsMultiPart(string path) { var filename = Path.GetFileName(path); - - if (string.IsNullOrEmpty(filename)) + if (filename.Length == 0) { return false; } @@ -39,18 +38,22 @@ namespace Emby.Naming.Audio filename = filename.Replace(')', ' '); filename = Regex.Replace(filename, @"\s+", " "); - filename = filename.TrimStart(); + ReadOnlySpan trimmedFilename = filename.TrimStart(); foreach (var prefix in _options.AlbumStackingPrefixes) { - if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0) + if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { continue; } - var tmp = filename.Substring(prefix.Length); + var tmp = trimmedFilename.Slice(prefix.Length).Trim(); - tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty; + int index = tmp.IndexOf(' '); + if (index != -1) + { + tmp = tmp.Slice(0, index); + } if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) { diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index 25d5f8735e..6b2f4be93e 100644 --- a/Emby.Naming/Audio/AudioFileParser.cs +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -1,3 +1,4 @@ +#nullable enable #pragma warning disable CS1591 using System; @@ -11,7 +12,7 @@ namespace Emby.Naming.Audio { public static bool IsAudioFile(string path, NamingOptions options) { - var extension = Path.GetExtension(path) ?? string.Empty; + var extension = Path.GetExtension(path); return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); } } diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs index 07de728514..ed6ba8881c 100644 --- a/Emby.Naming/Common/EpisodeExpression.cs +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -23,11 +23,6 @@ namespace Emby.Naming.Common { } - public EpisodeExpression() - : this(null) - { - } - public string Expression { get => _expression; @@ -48,6 +43,6 @@ namespace Emby.Naming.Common public string[] DateTimeFormats { get; set; } - public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled); } } diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index 88ec3e2d60..24e59f90a3 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -1,3 +1,4 @@ +#nullable enable #pragma warning disable CS1591 using System; @@ -16,11 +17,11 @@ namespace Emby.Naming.Subtitles _options = options; } - public SubtitleInfo ParseFile(string path) + public SubtitleInfo? ParseFile(string path) { - if (string.IsNullOrEmpty(path)) + if (path.Length == 0) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException("File path can't be empty.", nameof(path)); } var extension = Path.GetExtension(path); @@ -52,11 +53,6 @@ namespace Emby.Naming.Subtitles private string[] GetFlags(string path) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _. var file = Path.GetFileName(path); diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 0b75a8cce9..b4aee614b0 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -89,14 +89,14 @@ namespace Emby.Naming.Video if (parseName) { var cleanDateTimeResult = CleanDateTime(name); + name = cleanDateTimeResult.Name; + year = cleanDateTimeResult.Year; if (extraResult.ExtraType == null - && TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan newName)) + && TryCleanString(name, out ReadOnlySpan newName)) { name = newName.ToString(); } - - year = cleanDateTimeResult.Year; } return new VideoFileInfo diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index befecc570b..869b7407e2 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -143,7 +143,7 @@ namespace Emby.Notifications var notification = new NotificationRequest { - Description = "Please see jellyfin.media for details.", + Description = "Please see jellyfin.org for details.", NotificationType = type, Name = _localization.GetLocalizedString("NewVersionIsAvailable") }; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 2201c3cfc2..10a7e879e9 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -105,6 +105,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; +using Prometheus.DotNetRuntime; namespace Emby.Server.Implementations { @@ -258,6 +259,12 @@ namespace Emby.Server.Implementations _startupOptions = options; + // Initialize runtime stat collection + if (ServerConfigurationManager.Configuration.EnableMetrics) + { + DotNetRuntimeStatsBuilder.Default().StartCollecting(); + } + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); _networkManager.NetworkChanged += OnNetworkChanged; diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index db7c35a7c8..dea9b6682a 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Updates; -using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 22955850ab..6ee6230fc6 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -375,5 +375,15 @@ namespace Emby.Server.Implementations.Data return userData; } + + /// + /// + /// There is nothing to dispose here since and + /// are managed by . + /// See . + /// + protected override void Dispose(bool dispose) + { + } } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index bf4a0d939f..44fc932e39 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -39,6 +39,7 @@ + diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 464ca3a0b5..2e9ecc4ae6 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -426,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer if (!noCache) { - if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal, out var ifModifiedSinceHeader)) + if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var ifModifiedSinceHeader)) { _logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]); return null; diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 5e0466629d..4089aa578e 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -82,6 +82,10 @@ namespace Emby.Server.Implementations.HttpServer { return null; } + else if (inString.Length == 0) + { + return inString; + } var newString = new StringBuilder(inString.Length); diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 6b9f4d052c..e27145a1d2 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -35,7 +35,8 @@ namespace Emby.Server.Implementations.Library return null; } - public static int? GetDefaultSubtitleStreamIndex(List streams, + public static int? GetDefaultSubtitleStreamIndex( + List streams, string[] preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) @@ -115,7 +116,8 @@ namespace Emby.Server.Implementations.Library .ThenBy(i => i.Index); } - public static void SetSubtitleStreamScores(List streams, + public static void SetSubtitleStreamScores( + List streams, string[] preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 1d61ed57eb..06ff3e611b 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Text.RegularExpressions; @@ -12,24 +14,24 @@ namespace Emby.Server.Implementations.Library /// Gets the attribute value. /// /// The STR. - /// The attrib. + /// The attrib. /// System.String. - /// attrib - public static string GetAttributeValue(this string str, string attrib) + /// or is empty. + public static string? GetAttributeValue(this string str, string attribute) { - if (string.IsNullOrEmpty(str)) + if (str.Length == 0) { - throw new ArgumentNullException(nameof(str)); + throw new ArgumentException("String can't be empty.", nameof(str)); } - if (string.IsNullOrEmpty(attrib)) + if (attribute.Length == 0) { - throw new ArgumentNullException(nameof(attrib)); + throw new ArgumentException("String can't be empty.", nameof(attribute)); } - string srch = "[" + attrib + "="; + string srch = "[" + attribute + "="; int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - if (start > -1) + if (start != -1) { start += srch.Length; int end = str.IndexOf(']', start); @@ -37,7 +39,7 @@ namespace Emby.Server.Implementations.Library } // for imdbid we also accept pattern matching - if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) { var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase); return m.Success ? m.Value : null; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 34dcbbe285..7ca15b4e55 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -118,10 +118,12 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(fileSystem)); } + if (item == null) { throw new ArgumentNullException(nameof(item)); } + if (args == null) { throw new ArgumentNullException(nameof(args)); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index d63bc6bda8..b8feb5535f 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -608,6 +608,31 @@ namespace Emby.Server.Implementations.Library return dto; } + public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + IAuthenticationProvider authenticationProvider = GetAuthenticationProvider(user); + bool hasConfiguredPassword = authenticationProvider.HasPassword(user); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(authenticationProvider.GetEasyPasswordHash(user)); + + bool hasPassword = user.Configuration.EnableLocalPassword && + !string.IsNullOrEmpty(remoteEndPoint) && + _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; + + PublicUserDto dto = new PublicUserDto + { + Name = user.Name, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword, + }; + + return dto; + } + public UserDto GetOfflineUserDto(User user) { var dto = GetUserDto(user); diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index 1363eaf854..20447347b3 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -4,7 +4,7 @@ "Folders": "Fouers", "Favorites": "Gunstelinge", "HeaderFavoriteShows": "Gunsteling Vertonings", - "ValueSpecialEpisodeName": "Spesiaal - {0}", + "ValueSpecialEpisodeName": "Spesiale - {0}", "HeaderAlbumArtists": "Album Kunstenaars", "Books": "Boeke", "HeaderNextUp": "Volgende", diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 53e2f58de8..0753ea39d4 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -1,5 +1,5 @@ { - "Albums": "Άλμπουμ", + "Albums": "Άλμπουμς", "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}", "Application": "Εφαρμογή", "Artists": "Καλλιτέχνες", @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} τελείωσε να παίζει {1} σε {2}", "ValueHasBeenAddedToLibrary": "{0} προστέθηκαν στη βιβλιοθήκη πολυμέσων σας", "ValueSpecialEpisodeName": "Σπέσιαλ - {0}", - "VersionNumber": "Έκδοση {0}" + "VersionNumber": "Έκδοση {0}", + "TaskRefreshPeople": "Ανανέωση Ατόμων", + "TaskCleanLogsDescription": "Διαγράφει τα αρχεία καταγραφής που είναι άνω των {0} ημερών.", + "TaskCleanLogs": "Καθαρισμός Καταλόγου Καταγραφής", + "TaskRefreshLibraryDescription": "Σαρώνει την βιβλιοθήκη πολυμέσων σας για νέα αρχεία και αναζωογονεί τα μεταδεδομένα.", + "TaskRefreshLibrary": "Βιβλιοθήκη Σάρωσης Πολυμέσων", + "TaskRefreshChapterImagesDescription": "Δημιουργεί μικρογραφίες για βίντεο με κεφάλαια.", + "TaskRefreshChapterImages": "Εξαγωγή Εικόνων Κεφαλαίου", + "TaskCleanCacheDescription": "Τα διαγραμμένα αρχεία προσωρινής μνήμης που δεν χρειάζονται πλέον από το σύστημα.", + "TaskCleanCache": "Καθαρισμός Καταλόγου Προσωρινής Μνήμης", + "TasksChannelsCategory": "Κανάλια Διαδικτύου", + "TasksApplicationCategory": "Εφαρμογή", + "TasksLibraryCategory": "Βιβλιοθήκη", + "TasksMaintenanceCategory": "Συντήρηση", + "TaskDownloadMissingSubtitlesDescription": "Αναζητήσεις στο διαδίκτυο όπου λείπουν υπότιτλους με βάση τη διαμόρφωση μεταδεδομένων.", + "TaskDownloadMissingSubtitles": "Λήψη υπότιτλων που λείπουν", + "TaskRefreshChannelsDescription": "Ανανεώνει τις πληροφορίες καναλιού στο διαδικτύου.", + "TaskRefreshChannels": "Ανανέωση Καναλιών", + "TaskCleanTranscodeDescription": "Διαγράφει αρχείου διακωδικοποιητή περισσότερο από μία ημέρα.", + "TaskCleanTranscode": "Καθαρισμός Kαταλόγου Διακωδικοποιητή", + "TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τις προσθήκες που έχουν ρυθμιστεί για αυτόματη ενημέρωση.", + "TaskUpdatePlugins": "Ενημέρωση Προσθηκών", + "TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας." } diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index e0bbe90b36..d93920f433 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -11,7 +11,7 @@ "Collections": "Colecciones", "DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOnlineWithName": "{0} está conectado", - "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}", + "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index de1baada84..e7bd3959bf 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -71,7 +71,7 @@ "ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskStartedWithName": "{0} iniciada", "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", - "Shows": "Series", + "Shows": "Mostrar", "Songs": "Canciones", "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}", diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index b39adefe70..f8d6e0e09b 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "Suorat lähetykset", + "HeaderLiveTV": "Live-TV", "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.", "NameSeasonUnknown": "Tuntematon Kausi", "NameSeasonNumber": "Kausi {0}", @@ -12,7 +12,7 @@ "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty", "MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}", "MessageApplicationUpdated": "Jellyfin palvelin on päivitetty", - "Latest": "Viimeisin", + "Latest": "Uusimmat", "LabelRunningTimeValue": "Toiston kesto: {0}", "LabelIpAddressValue": "IP-osoite: {0}", "ItemRemovedWithName": "{0} poistettiin kirjastosta", @@ -41,7 +41,7 @@ "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}", "Books": "Kirjat", "AuthenticationSucceededWithUserName": "{0} todennus onnistui", - "Artists": "Esiintyjät", + "Artists": "Artistit", "Application": "Sovellus", "AppDeviceValues": "Sovellus: {0}, Laite: {1}", "Albums": "Albumit", @@ -67,21 +67,21 @@ "UserDownloadingItemWithValues": "{0} lataa {1}", "UserDeletedWithName": "Käyttäjä {0} poistettu", "UserCreatedWithName": "Käyttäjä {0} luotu", - "TvShows": "TV-Ohjelmat", + "TvShows": "TV-sarjat", "Sync": "Synkronoi", - "SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}", + "SubtitleDownloadFailureFromForItem": "Tekstitysten lataus ({0} -> {1}) epäonnistui //this string would have to be generated for each provider and movie because of finnish cases, sorry", "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.", "Songs": "Kappaleet", - "Shows": "Ohjelmat", - "ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen", + "Shows": "Sarjat", + "ServerNameNeedsToBeRestarted": "{0} täytyy käynnistää uudelleen", "ProviderValue": "Tarjoaja: {0}", "Plugin": "Liitännäinen", "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty", - "NotificationOptionVideoPlayback": "Videon toisto aloitettu", - "NotificationOptionUserLockedOut": "Käyttäjä lukittu", + "NotificationOptionVideoPlayback": "Videota toistetaan", + "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos", "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui", - "NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan", - "NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu", + "NotificationOptionServerRestartRequired": "Palvelin pitää käynnistää uudelleen", + "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty", "NotificationOptionPluginUninstalled": "Liitännäinen poistettu", "NotificationOptionPluginInstalled": "Liitännäinen asennettu", "NotificationOptionPluginError": "Ongelma liitännäisessä", @@ -90,8 +90,8 @@ "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu", "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu", "NotificationOptionAudioPlayback": "Toistetaan ääntä", - "NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu", - "NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla", + "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu", + "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla", "TasksMaintenanceCategory": "Ylläpito", "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.", "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset", diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 2c9dae6a17..c2349ba5bb 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -94,5 +94,23 @@ "ValueSpecialEpisodeName": "Spécial - {0}", "VersionNumber": "Version {0}", "TasksLibraryCategory": "Bibliothèque", - "TasksMaintenanceCategory": "Entretien" + "TasksMaintenanceCategory": "Entretien", + "TaskDownloadMissingSubtitlesDescription": "Recherche l'internet pour des sous-titres manquants à base de métadonnées configurées.", + "TaskDownloadMissingSubtitles": "Télécharger des sous-titres manquants", + "TaskRefreshChannelsDescription": "Rafraîchit des informations des chaines d'internet.", + "TaskRefreshChannels": "Rafraîchir des chaines", + "TaskCleanTranscodeDescription": "Retirer des fichiers de transcodage de plus qu'un jour.", + "TaskCleanTranscode": "Nettoyer le directoire de transcodage", + "TaskUpdatePluginsDescription": "Télécharger et installer des mises à jours des plugins qui sont configurés m.à.j. automisés.", + "TaskUpdatePlugins": "Mise à jour des plugins", + "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.", + "TaskRefreshPeople": "Rafraîchir les acteurs", + "TaskCleanLogsDescription": "Retire les données qui ont plus que {0} jours.", + "TaskCleanLogs": "Nettoyer les données de directoire", + "TaskRefreshLibraryDescription": "Analyse votre bibliothèque média pour des nouveaux fichiers et rafraîchit les métadonnées.", + "TaskRefreshChapterImages": "Extraire des images du chapitre", + "TaskRefreshChapterImagesDescription": "Créer des vignettes pour des vidéos qui ont des chapitres", + "TaskRefreshLibrary": "Analyser la bibliothèque de média", + "TaskCleanCache": "Nettoyer le cache de directoire", + "TasksApplicationCategory": "Application" } diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 9611e33f57..c8291a2020 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -1,41 +1,41 @@ { - "Albums": "Albom", - "AppDeviceValues": "App: {0}, Grät: {1}", - "Application": "Aawändig", - "Artists": "Könstler", - "AuthenticationSucceededWithUserName": "{0} het sech aagmäudet", - "Books": "Büecher", - "CameraImageUploadedFrom": "Es nöis Foti esch ufeglade worde vo {0}", - "Channels": "Kanäu", - "ChapterNameValue": "Kapitu {0}", - "Collections": "Sammlige", - "DeviceOfflineWithName": "{0} esch offline gange", - "DeviceOnlineWithName": "{0} esch online cho", - "FailedLoginAttemptWithUserName": "Fäugschlagne Aamäudeversuech vo {0}", - "Favorites": "Favorite", + "Albums": "Alben", + "AppDeviceValues": "App: {0}, Gerät: {1}", + "Application": "Anwendung", + "Artists": "Künstler", + "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet", + "Books": "Bücher", + "CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen", + "Channels": "Kanäle", + "ChapterNameValue": "Kapitel {0}", + "Collections": "Sammlungen", + "DeviceOfflineWithName": "{0} wurde getrennt", + "DeviceOnlineWithName": "{0} ist verbunden", + "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", + "Favorites": "Favoriten", "Folders": "Ordner", "Genres": "Genres", - "HeaderAlbumArtists": "Albom-Könstler", + "HeaderAlbumArtists": "Album-Künstler", "HeaderCameraUploads": "Kamera-Uploads", - "HeaderContinueWatching": "Wiiterluege", - "HeaderFavoriteAlbums": "Lieblingsalbe", - "HeaderFavoriteArtists": "Lieblings-Interprete", - "HeaderFavoriteEpisodes": "Lieblingsepisode", - "HeaderFavoriteShows": "Lieblingsserie", + "HeaderContinueWatching": "weiter schauen", + "HeaderFavoriteAlbums": "Lieblingsalben", + "HeaderFavoriteArtists": "Lieblings-Künstler", + "HeaderFavoriteEpisodes": "Lieblingsepisoden", + "HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteSongs": "Lieblingslieder", - "HeaderLiveTV": "Live-Färnseh", - "HeaderNextUp": "Als nächts", - "HeaderRecordingGroups": "Ufnahmegruppe", - "HomeVideos": "Heimfilmli", - "Inherit": "Hinzuefüege", - "ItemAddedWithName": "{0} esch de Bibliothek dezuegfüegt worde", - "ItemRemovedWithName": "{0} esch vo de Bibliothek entfärnt worde", - "LabelIpAddressValue": "IP-Adrässe: {0}", - "LabelRunningTimeValue": "Loufziit: {0}", - "Latest": "Nöischti", - "MessageApplicationUpdated": "Jellyfin Server esch aktualisiert worde", - "MessageApplicationUpdatedTo": "Jellyfin Server esch of Version {0} aktualisiert worde", - "MessageNamedServerConfigurationUpdatedWithValue": "De Serveriistöuigsberiich {0} esch aktualisiert worde", + "HeaderLiveTV": "Live-Fernseh", + "HeaderNextUp": "Als Nächstes", + "HeaderRecordingGroups": "Aufnahme-Gruppen", + "HomeVideos": "Heimvideos", + "Inherit": "Vererben", + "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", + "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", + "LabelIpAddressValue": "IP-Adresse: {0}", + "LabelRunningTimeValue": "Laufzeit: {0}", + "Latest": "Neueste", + "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", + "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", + "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert", "MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde", "MixedContent": "Gmeschti Inhäut", "Movies": "Film", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Audiowedergab gstartet", "NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt", "NotificationOptionCameraImageUploaded": "Foti ueglade", - "NotificationOptionInstallationFailed": "Installationsfäuer", + "NotificationOptionInstallationFailed": "Installationsfehler", "NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt", "NotificationOptionPluginError": "Plugin-Fäuer", "NotificationOptionPluginInstalled": "Plugin installiert", @@ -92,5 +92,16 @@ "UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt", "ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde", "ValueSpecialEpisodeName": "Extra - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskCleanLogs": "Lösche Log Pfad", + "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", + "TaskRefreshLibrary": "Scanne alle Bibliotheken", + "TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.", + "TaskRefreshChapterImages": "Extrahiere Kapitel-Bilder", + "TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.", + "TaskCleanCache": "Leere Cache Pfad", + "TasksChannelsCategory": "Internet Kanäle", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliothek", + "TasksMaintenanceCategory": "Verwaltung" } diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 2662913621..8abe31d2a0 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -62,7 +62,7 @@ "NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlaybackStopped": "Video playback stopped", "Photos": "תמונות", - "Playlists": "רשימות ניגון", + "Playlists": "רשימות הפעלה", "Plugin": "Plugin", "PluginInstalledWithName": "{0} was installed", "PluginUninstalledWithName": "{0} was uninstalled", diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 6947178d7a..c169a35e79 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -30,7 +30,7 @@ "Inherit": "Naslijedi", "ItemAddedWithName": "{0} je dodano u biblioteku", "ItemRemovedWithName": "{0} je uklonjen iz biblioteke", - "LabelIpAddressValue": "Ip adresa: {0}", + "LabelIpAddressValue": "IP adresa: {0}", "LabelRunningTimeValue": "Vrijeme rada: {0}", "Latest": "Najnovije", "MessageApplicationUpdated": "Jellyfin Server je ažuriran", @@ -92,5 +92,13 @@ "UserStoppedPlayingItemWithValues": "{0} je zaustavio {1}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Specijal - {0}", - "VersionNumber": "Verzija {0}" + "VersionNumber": "Verzija {0}", + "TaskRefreshLibraryDescription": "Skenira vašu medijsku knjižnicu sa novim datotekama i osvježuje metapodatke.", + "TaskRefreshLibrary": "Skeniraj medijsku knjižnicu", + "TaskRefreshChapterImagesDescription": "Stvara sličice za videozapise koji imaju poglavlja.", + "TaskRefreshChapterImages": "Raspakiraj slike poglavlja", + "TaskCleanCacheDescription": "Briše priručne datoteke nepotrebne za sistem.", + "TaskCleanCache": "Očisti priručnu memoriju", + "TasksApplicationCategory": "Aplikacija", + "TasksMaintenanceCategory": "Održavanje" } diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index 8df137302f..bbdf99abab 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -91,5 +91,12 @@ "Songs": "Песни", "Shows": "Серии", "ServerNameNeedsToBeRestarted": "{0} треба да се рестартира", - "ScheduledTaskStartedWithName": "{0} започна" + "ScheduledTaskStartedWithName": "{0} започна", + "TaskRefreshChapterImages": "Извези Слики од Поглавје", + "TaskCleanCacheDescription": "Ги брише кешираните фајлови што не се повеќе потребни од системот.", + "TaskCleanCache": "Исчисти Го Кешот", + "TasksChannelsCategory": "Интернет Канали", + "TasksApplicationCategory": "Апликација", + "TasksLibraryCategory": "Библиотека", + "TasksMaintenanceCategory": "Одржување" } diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index baa12e98ec..41c74d54de 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -5,7 +5,7 @@ "Artists": "Artiesten", "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "Books": "Boeken", - "CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}", + "CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd via {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", @@ -26,7 +26,7 @@ "HeaderLiveTV": "Live TV", "HeaderNextUp": "Volgende", "HeaderRecordingGroups": "Opnamegroepen", - "HomeVideos": "Start video's", + "HomeVideos": "Home video's", "Inherit": "Overerven", "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek", "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Muziek gestart", "NotificationOptionAudioPlaybackStopped": "Muziek gestopt", "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload", - "NotificationOptionInstallationFailed": "Installatie mislukking", + "NotificationOptionInstallationFailed": "Installatie mislukt", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionPluginError": "Plug-in fout", "NotificationOptionPluginInstalled": "Plug-in geïnstalleerd", diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index e9d9bbf2e1..bdc0d0169b 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} zakończył odtwarzanie {1} na {2}", "ValueHasBeenAddedToLibrary": "{0} został dodany do biblioteki mediów", "ValueSpecialEpisodeName": "Specjalne - {0}", - "VersionNumber": "Wersja {0}" + "VersionNumber": "Wersja {0}", + "TaskDownloadMissingSubtitlesDescription": "Przeszukuje internet w poszukiwaniu brakujących napisów w oparciu o konfigurację metadanych.", + "TaskDownloadMissingSubtitles": "Pobierz brakujące napisy", + "TaskRefreshChannelsDescription": "Odświeża informacje o kanałach internetowych.", + "TaskRefreshChannels": "Odśwież kanały", + "TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.", + "TaskCleanTranscode": "Wyczyść folder transkodowania", + "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.", + "TaskUpdatePlugins": "Aktualizuj pluginy", + "TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.", + "TaskRefreshPeople": "Odśwież obsadę", + "TaskCleanLogsDescription": "Kasuje pliki logów starsze niż {0} dni.", + "TaskCleanLogs": "Wyczyść folder logów", + "TaskRefreshLibraryDescription": "Skanuje Twoją bibliotekę mediów dla nowych plików i odświeżenia metadanych.", + "TaskRefreshLibrary": "Skanuj bibliotekę mediów", + "TaskRefreshChapterImagesDescription": "Tworzy miniatury dla filmów posiadających rozdziały.", + "TaskRefreshChapterImages": "Wydobądź grafiki rozdziałów", + "TaskCleanCacheDescription": "Usuwa niepotrzebne i przestarzałe pliki cache.", + "TaskCleanCache": "Wyczyść folder Cache", + "TasksChannelsCategory": "Kanały internetowe", + "TasksApplicationCategory": "Aplikacja", + "TasksLibraryCategory": "Biblioteka", + "TasksMaintenanceCategory": "Konserwacja" } diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index b60dd33bd5..60c58d472d 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -92,5 +92,26 @@ "UserStoppedPlayingItemWithValues": "{0} je nehal predvajati {1} na {2}", "ValueHasBeenAddedToLibrary": "{0} je bil dodan vaši knjižnici", "ValueSpecialEpisodeName": "Poseben - {0}", - "VersionNumber": "Različica {0}" + "VersionNumber": "Različica {0}", + "TaskDownloadMissingSubtitles": "Prenesi manjkajoče podnapise", + "TaskRefreshChannelsDescription": "Osveži podatke spletnih kanalov.", + "TaskRefreshChannels": "Osveži kanale", + "TaskCleanTranscodeDescription": "Izbriše več kot dan stare datoteke prekodiranja.", + "TaskCleanTranscode": "Počisti mapo prekodiranja", + "TaskUpdatePluginsDescription": "Prenese in namesti posodobitve za dodatke, ki imajo omogočene samodejne posodobitve.", + "TaskUpdatePlugins": "Posodobi dodatke", + "TaskRefreshPeopleDescription": "Osveži metapodatke za igralce in režiserje v vaši knjižnici.", + "TaskRefreshPeople": "Osveži osebe", + "TaskCleanLogsDescription": "Izbriše dnevniške datoteke starejše od {0} dni.", + "TaskCleanLogs": "Počisti mapo dnevnika", + "TaskRefreshLibraryDescription": "Preišče vašo knjižnico za nove datoteke in osveži metapodatke.", + "TaskRefreshLibrary": "Preišči knjižnico predstavnosti", + "TaskRefreshChapterImagesDescription": "Ustvari sličice za poglavja videoposnetkov.", + "TaskRefreshChapterImages": "Izvleči slike poglavij", + "TaskCleanCacheDescription": "Izbriše predpomnjene datoteke, ki niso več potrebne.", + "TaskCleanCache": "Počisti mapo predpomnilnika", + "TasksChannelsCategory": "Spletni kanali", + "TasksApplicationCategory": "Aplikacija", + "TasksLibraryCategory": "Knjižnica", + "TasksMaintenanceCategory": "Vzdrževanje" } diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index b7c50394ae..c8662b2cab 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -9,7 +9,7 @@ "Channels": "Kanaler", "ChapterNameValue": "Kapitel {0}", "Collections": "Samlingar", - "DeviceOfflineWithName": "{0} har tappat anslutningen", + "DeviceOfflineWithName": "{0} har kopplat från", "DeviceOnlineWithName": "{0} är ansluten", "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}", "Favorites": "Favoriter", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats", "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades", "NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp", - "NotificationOptionInstallationFailed": "Fel vid installation", + "NotificationOptionInstallationFailed": "Installationen misslyckades", "NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till", "NotificationOptionPluginError": "Fel uppstod med tillägget", "NotificationOptionPluginInstalled": "Tillägg har installerats", @@ -113,5 +113,6 @@ "TasksChannelsCategory": "Internetkanaler", "TasksApplicationCategory": "Applikation", "TasksLibraryCategory": "Bibliotek", - "TasksMaintenanceCategory": "Underhåll" + "TasksMaintenanceCategory": "Underhåll", + "TaskRefreshPeople": "Uppdatera Personer" } diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json new file mode 100644 index 0000000000..b2e0b66fe1 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -0,0 +1,36 @@ +{ + "MusicVideos": "Музичні відео", + "Music": "Музика", + "Movies": "Фільми", + "MessageApplicationUpdatedTo": "Jellyfin Server був оновлений до версії {0}", + "MessageApplicationUpdated": "Jellyfin Server був оновлений", + "Latest": "Останні", + "LabelIpAddressValue": "IP-адреси: {0}", + "ItemRemovedWithName": "{0} видалено з бібліотеки", + "ItemAddedWithName": "{0} додано до бібліотеки", + "HeaderNextUp": "Наступний", + "HeaderLiveTV": "Ефірне ТБ", + "HeaderFavoriteSongs": "Улюблені пісні", + "HeaderFavoriteShows": "Улюблені шоу", + "HeaderFavoriteEpisodes": "Улюблені серії", + "HeaderFavoriteArtists": "Улюблені виконавці", + "HeaderFavoriteAlbums": "Улюблені альбоми", + "HeaderContinueWatching": "Продовжити перегляд", + "HeaderCameraUploads": "Завантажено з камери", + "HeaderAlbumArtists": "Виконавці альбомів", + "Genres": "Жанри", + "Folders": "Директорії", + "Favorites": "Улюблені", + "DeviceOnlineWithName": "{0} під'єднано", + "DeviceOfflineWithName": "{0} від'єднано", + "Collections": "Колекції", + "ChapterNameValue": "Глава {0}", + "Channels": "Канали", + "CameraImageUploadedFrom": "Нова фотографія завантажена з {0}", + "Books": "Книги", + "AuthenticationSucceededWithUserName": "{0} успішно авторизовані", + "Artists": "Виконавці", + "Application": "Додаток", + "AppDeviceValues": "Додаток: {0}, Пристрій: {1}", + "Albums": "Альбоми" +} diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 224748e611..a67a67582f 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -1,6 +1,6 @@ { "Albums": "專輯", - "AppDeviceValues": "軟體: {0}, 設備: {1}", + "AppDeviceValues": "軟件: {0}, 設備: {1}", "Application": "應用程式", "Artists": "藝人", "AuthenticationSucceededWithUserName": "{0} 授權成功", @@ -92,5 +92,8 @@ "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", "ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫", "ValueSpecialEpisodeName": "特典 - {0}", - "VersionNumber": "版本{0}" + "VersionNumber": "版本{0}", + "TaskDownloadMissingSubtitles": "下載遺失的字幕", + "TaskUpdatePlugins": "更新插件", + "TasksApplicationCategory": "應用程式" } diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index 23e22afd58..56e23d5492 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using MediaBrowser.Common.Extensions; namespace Emby.Server.Implementations.Services { @@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Services if (propertySerializerEntry.PropertyType == typeof(bool)) { //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value - propertyTextValue = LeftPart(propertyTextValue, ','); + propertyTextValue = StringExtensions.LeftPart(propertyTextValue, ',').ToString(); } var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue); @@ -95,19 +96,6 @@ namespace Emby.Server.Implementations.Services return instance; } - - public static string LeftPart(string strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle); - return pos == -1 - ? strVal - : strVal.Substring(0, pos); - } } internal static class TypeAccessor diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index 5d4407f3b8..483c63ade7 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -1,4 +1,5 @@ using System; +using MediaBrowser.Common.Extensions; namespace Emby.Server.Implementations.Services { @@ -13,25 +14,12 @@ namespace Emby.Server.Implementations.Services public static string GetMethodName(this Type type) { var typeName = type.FullName != null // can be null, e.g. generic types - ? LeftPart(type.FullName, "[[") // Generic Fullname - .Replace(type.Namespace + ".", string.Empty) // Trim Namespaces - .Replace("+", ".") // Convert nested into normal type + ? StringExtensions.LeftPart(type.FullName, "[[", StringComparison.Ordinal).ToString() // Generic Fullname + .Replace(type.Namespace + ".", string.Empty, StringComparison.Ordinal) // Trim Namespaces + .Replace("+", ".", StringComparison.Ordinal) // Convert nested into normal type : type.Name; return type.IsGenericParameter ? "'" + typeName : typeName; } - - private static string LeftPart(string strVal, string needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase); - return pos == -1 - ? strVal - : strVal.Substring(0, pos); - } } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 1781df8b51..ee5131c1ff 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Mime; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -62,6 +63,9 @@ namespace Emby.Server.Implementations.SocketSharp if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip)) { ip = Request.HttpContext.Connection.RemoteIpAddress; + + // Default to the loopback address if no RemoteIpAddress is specified (i.e. during integration tests) + ip ??= IPAddress.Loopback; } } @@ -89,7 +93,10 @@ namespace Emby.Server.Implementations.SocketSharp public IQueryCollection QueryString => Request.Query; - public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress); + public bool IsLocal => + (Request.HttpContext.Connection.LocalIpAddress == null + && Request.HttpContext.Connection.RemoteIpAddress == null) + || Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress); public string HttpMethod => Request.Method; @@ -216,14 +223,14 @@ namespace Emby.Server.Implementations.SocketSharp pi = pi.Slice(1); } - format = LeftPart(pi, '/'); + format = pi.LeftPart('/'); if (format.Length > FormatMaxLength) { return null; } } - format = LeftPart(format, '.'); + format = format.LeftPart('.'); if (format.Contains("json", StringComparison.OrdinalIgnoreCase)) { return "application/json"; @@ -235,16 +242,5 @@ namespace Emby.Server.Implementations.SocketSharp return null; } - - public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle); - return pos == -1 ? strVal : strVal.Slice(0, pos); - } } } diff --git a/Jellyfin.Data/DbContexts/Jellyfin.cs b/Jellyfin.Data/DbContexts/Jellyfin.cs new file mode 100644 index 0000000000..fd488ce7d7 --- /dev/null +++ b/Jellyfin.Data/DbContexts/Jellyfin.cs @@ -0,0 +1,1140 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Data.DbContexts +{ + /// + public partial class Jellyfin : DbContext + { + #region DbSets + public virtual Microsoft.EntityFrameworkCore.DbSet Artwork { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Books { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet BookMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Chapters { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Collections { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet CollectionItems { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Companies { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet CompanyMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet CustomItems { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet CustomItemMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Episodes { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet EpisodeMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Genres { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Groups { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Libraries { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet LibraryItems { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet LibraryRoot { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MediaFiles { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MediaFileStream { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Metadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviders { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviderIds { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Movies { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MovieMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbums { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbumMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Permissions { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet People { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet PersonRoles { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Photo { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet PhotoMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Preferences { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet ProviderMappings { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Ratings { get; set; } + + /// + /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to + /// store review ratings, not age ratings + /// + public virtual Microsoft.EntityFrameworkCore.DbSet RatingSources { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Releases { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Seasons { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet SeasonMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Series { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet SeriesMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Tracks { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet TrackMetadata { get; set; } + public virtual Microsoft.EntityFrameworkCore.DbSet Users { get; set; } + #endregion DbSets + + /// + /// Default connection string + /// + public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + + /// + public Jellyfin(DbContextOptions options) : base(options) + { + } + + partial void CustomInit(DbContextOptionsBuilder optionsBuilder); + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + CustomInit(optionsBuilder); + } + + partial void OnModelCreatingImpl(ModelBuilder modelBuilder); + partial void OnModelCreatedImpl(ModelBuilder modelBuilder); + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + OnModelCreatingImpl(modelBuilder); + + modelBuilder.HasDefaultSchema("jellyfin"); + + modelBuilder.Entity() + .ToTable("Artwork") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Path) + .HasMaxLength(65535) + .IsRequired() + .HasField("_Path") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Kind) + .IsRequired() + .HasField("_Kind") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity().HasIndex(t => t.Kind); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + + modelBuilder.Entity() + .HasMany(x => x.BookMetadata) + .WithOne() + .HasForeignKey("BookMetadata_BookMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Releases) + .WithOne() + .HasForeignKey("Release_Releases_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.ISBN) + .HasField("_ISBN") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.Publishers) + .WithOne() + .HasForeignKey("Company_Publishers_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("Chapter") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Language) + .HasMaxLength(3) + .IsRequired() + .HasField("_Language") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.TimeStart) + .IsRequired() + .HasField("_TimeStart") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.TimeEnd) + .HasField("_TimeEnd") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + + modelBuilder.Entity() + .ToTable("Collection") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasMany(x => x.CollectionItem) + .WithOne() + .HasForeignKey("CollectionItem_CollectionItem_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("CollectionItem") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.LibraryItem) + .WithOne() + .HasForeignKey("LibraryItem_Id") + .IsRequired(); + modelBuilder.Entity() + .HasOne(x => x.Next) + .WithOne() + .HasForeignKey("CollectionItem_Next_Id") + .IsRequired(); + modelBuilder.Entity() + .HasOne(x => x.Previous) + .WithOne() + .HasForeignKey("CollectionItem_Previous_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("Company") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasMany(x => x.CompanyMetadata) + .WithOne() + .HasForeignKey("CompanyMetadata_CompanyMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasOne(x => x.Parent) + .WithOne() + .HasForeignKey("Company_Parent_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.Description) + .HasMaxLength(65535) + .HasField("_Description") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Headquarters) + .HasMaxLength(255) + .HasField("_Headquarters") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Country) + .HasMaxLength(2) + .HasField("_Country") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Homepage) + .HasMaxLength(1024) + .HasField("_Homepage") + .UsePropertyAccessMode(PropertyAccessMode.Property); + + modelBuilder.Entity() + .HasMany(x => x.CustomItemMetadata) + .WithOne() + .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Releases) + .WithOne() + .HasForeignKey("Release_Releases_Id") + .IsRequired(); + + + modelBuilder.Entity() + .Property(t => t.EpisodeNumber) + .HasField("_EpisodeNumber") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.Releases) + .WithOne() + .HasForeignKey("Release_Releases_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.EpisodeMetadata) + .WithOne() + .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.Outline) + .HasMaxLength(1024) + .HasField("_Outline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Plot) + .HasMaxLength(65535) + .HasField("_Plot") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Tagline) + .HasMaxLength(1024) + .HasField("_Tagline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + + modelBuilder.Entity() + .ToTable("Genre") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(255) + .IsRequired() + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity().HasIndex(t => t.Name) + .IsUnique(); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + + modelBuilder.Entity() + .ToTable("Groups") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(255) + .IsRequired(); + modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); + modelBuilder.Entity() + .HasMany(x => x.GroupPermissions) + .WithOne() + .HasForeignKey("Permission_GroupPermissions_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.ProviderMappings) + .WithOne() + .HasForeignKey("ProviderMapping_ProviderMappings_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Preferences) + .WithOne() + .HasForeignKey("Preference_Preferences_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("Library") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .IsRequired() + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + + modelBuilder.Entity() + .ToTable("LibraryItem") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.UrlId) + .IsRequired() + .HasField("_UrlId") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity().HasIndex(t => t.UrlId) + .IsUnique(); + modelBuilder.Entity() + .Property(t => t.DateAdded) + .IsRequired() + .HasField("_DateAdded") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.LibraryRoot) + .WithOne() + .HasForeignKey("LibraryRoot_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("LibraryRoot") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Path) + .HasMaxLength(65535) + .IsRequired() + .HasField("_Path") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.NetworkPath) + .HasMaxLength(65535) + .HasField("_NetworkPath") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.Library) + .WithOne() + .HasForeignKey("Library_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("MediaFile") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Path) + .HasMaxLength(65535) + .IsRequired() + .HasField("_Path") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Kind) + .IsRequired() + .HasField("_Kind") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasMany(x => x.MediaFileStreams) + .WithOne() + .HasForeignKey("MediaFileStream_MediaFileStreams_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("MediaFileStream") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.StreamNumber) + .IsRequired() + .HasField("_StreamNumber") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + + modelBuilder.Entity() + .ToTable("Metadata") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Title) + .HasMaxLength(1024) + .IsRequired() + .HasField("_Title") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.OriginalTitle) + .HasMaxLength(1024) + .HasField("_OriginalTitle") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.SortTitle) + .HasMaxLength(1024) + .HasField("_SortTitle") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Language) + .HasMaxLength(3) + .IsRequired() + .HasField("_Language") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.ReleaseDate) + .HasField("_ReleaseDate") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.DateAdded) + .IsRequired() + .HasField("_DateAdded") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.DateModified) + .IsRequired() + .HasField("_DateModified") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasMany(x => x.PersonRoles) + .WithOne() + .HasForeignKey("PersonRole_PersonRoles_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Genres) + .WithOne() + .HasForeignKey("Genre_Genres_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Artwork) + .WithOne() + .HasForeignKey("Artwork_Artwork_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Ratings) + .WithOne() + .HasForeignKey("Rating_Ratings_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Sources) + .WithOne() + .HasForeignKey("MetadataProviderId_Sources_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("MetadataProvider") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .IsRequired() + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + + modelBuilder.Entity() + .ToTable("MetadataProviderId") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.ProviderId) + .HasMaxLength(255) + .IsRequired() + .HasField("_ProviderId") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.MetadataProvider) + .WithOne() + .HasForeignKey("MetadataProvider_Id") + .IsRequired(); + + modelBuilder.Entity() + .HasMany(x => x.Releases) + .WithOne() + .HasForeignKey("Release_Releases_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.MovieMetadata) + .WithOne() + .HasForeignKey("MovieMetadata_MovieMetadata_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.Outline) + .HasMaxLength(1024) + .HasField("_Outline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Plot) + .HasMaxLength(65535) + .HasField("_Plot") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Tagline) + .HasMaxLength(1024) + .HasField("_Tagline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Country) + .HasMaxLength(2) + .HasField("_Country") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.Studios) + .WithOne() + .HasForeignKey("Company_Studios_Id") + .IsRequired(); + + modelBuilder.Entity() + .HasMany(x => x.MusicAlbumMetadata) + .WithOne() + .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Tracks) + .WithOne() + .HasForeignKey("Track_Tracks_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.Barcode) + .HasMaxLength(255) + .HasField("_Barcode") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.LabelNumber) + .HasMaxLength(255) + .HasField("_LabelNumber") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Country) + .HasMaxLength(2) + .HasField("_Country") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.Labels) + .WithOne() + .HasForeignKey("Company_Labels_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("Permissions") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Kind) + .IsRequired() + .HasField("_Kind") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Value) + .IsRequired(); + modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); + + modelBuilder.Entity() + .ToTable("Person") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.UrlId) + .IsRequired() + .HasField("_UrlId") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .IsRequired() + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.SourceId) + .HasMaxLength(255) + .HasField("_SourceId") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.DateAdded) + .IsRequired() + .HasField("_DateAdded") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.DateModified) + .IsRequired() + .HasField("_DateModified") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasMany(x => x.Sources) + .WithOne() + .HasForeignKey("MetadataProviderId_Sources_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("PersonRole") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Role) + .HasMaxLength(1024) + .HasField("_Role") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Type) + .IsRequired() + .HasField("_Type") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.Person) + .WithOne() + .HasForeignKey("Person_Id") + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasOne(x => x.Artwork) + .WithOne() + .HasForeignKey("Artwork_Artwork_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Sources) + .WithOne() + .HasForeignKey("MetadataProviderId_Sources_Id") + .IsRequired(); + + modelBuilder.Entity() + .HasMany(x => x.PhotoMetadata) + .WithOne() + .HasForeignKey("PhotoMetadata_PhotoMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Releases) + .WithOne() + .HasForeignKey("Release_Releases_Id") + .IsRequired(); + + + modelBuilder.Entity() + .ToTable("Preferences") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Kind) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.Value) + .HasMaxLength(65535) + .IsRequired(); + modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); + + modelBuilder.Entity() + .ToTable("ProviderMappings") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.ProviderName) + .HasMaxLength(255) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.ProviderSecrets) + .HasMaxLength(65535) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.ProviderData) + .HasMaxLength(65535) + .IsRequired(); + modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); + + modelBuilder.Entity() + .ToTable("Rating") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Value) + .IsRequired() + .HasField("_Value") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Votes) + .HasField("_Votes") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.RatingType) + .WithOne() + .HasForeignKey("RatingSource_RatingType_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("RatingType") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.MaximumValue) + .IsRequired() + .HasField("_MaximumValue") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.MinimumValue) + .IsRequired() + .HasField("_MinimumValue") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasOne(x => x.Source) + .WithOne() + .HasForeignKey("MetadataProviderId_Source_Id") + .IsRequired(); + + modelBuilder.Entity() + .ToTable("Release") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .HasField("_Id") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.Name) + .HasMaxLength(1024) + .IsRequired() + .HasField("_Name") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Timestamp) + .IsRequired() + .HasField("_Timestamp") + .UsePropertyAccessMode(PropertyAccessMode.Property) + .IsRowVersion(); + modelBuilder.Entity() + .HasMany(x => x.MediaFiles) + .WithOne() + .HasForeignKey("MediaFile_MediaFiles_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Chapters) + .WithOne() + .HasForeignKey("Chapter_Chapters_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.SeasonNumber) + .HasField("_SeasonNumber") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.SeasonMetadata) + .WithOne() + .HasForeignKey("SeasonMetadata_SeasonMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Episodes) + .WithOne() + .HasForeignKey("Episode_Episodes_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.Outline) + .HasMaxLength(1024) + .HasField("_Outline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + + modelBuilder.Entity() + .Property(t => t.AirsDayOfWeek) + .HasField("_AirsDayOfWeek") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.AirsTime) + .HasField("_AirsTime") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.FirstAired) + .HasField("_FirstAired") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.SeriesMetadata) + .WithOne() + .HasForeignKey("SeriesMetadata_SeriesMetadata_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Seasons) + .WithOne() + .HasForeignKey("Season_Seasons_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.Outline) + .HasMaxLength(1024) + .HasField("_Outline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Plot) + .HasMaxLength(65535) + .HasField("_Plot") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Tagline) + .HasMaxLength(1024) + .HasField("_Tagline") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Property(t => t.Country) + .HasMaxLength(2) + .HasField("_Country") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.Networks) + .WithOne() + .HasForeignKey("Company_Networks_Id") + .IsRequired(); + + modelBuilder.Entity() + .Property(t => t.TrackNumber) + .HasField("_TrackNumber") + .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .HasMany(x => x.Releases) + .WithOne() + .HasForeignKey("Release_Releases_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.TrackMetadata) + .WithOne() + .HasForeignKey("TrackMetadata_TrackMetadata_Id") + .IsRequired(); + + + modelBuilder.Entity() + .ToTable("Users") + .HasKey(t => t.Id); + modelBuilder.Entity() + .Property(t => t.Id) + .IsRequired() + .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(t => t.LastLoginTimestamp) + .IsRequired() + .IsRowVersion(); + modelBuilder.Entity() + .Property(t => t.Username) + .HasMaxLength(255) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.Password) + .HasMaxLength(65535); + modelBuilder.Entity() + .Property(t => t.MustUpdatePassword) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.AudioLanguagePreference) + .HasMaxLength(255) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.AuthenticationProviderId) + .HasMaxLength(255) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.GroupedFolders) + .HasMaxLength(65535); + modelBuilder.Entity() + .Property(t => t.InvalidLoginAttemptCount) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.LatestItemExcludes) + .HasMaxLength(65535); + modelBuilder.Entity() + .Property(t => t.MyMediaExcludes) + .HasMaxLength(65535); + modelBuilder.Entity() + .Property(t => t.OrderedViews) + .HasMaxLength(65535); + modelBuilder.Entity() + .Property(t => t.SubtitleMode) + .HasMaxLength(255) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.PlayDefaultAudioTrack) + .IsRequired(); + modelBuilder.Entity() + .Property(t => t.SubtitleLanguagePrefernce) + .HasMaxLength(255); + modelBuilder.Entity() + .HasMany(x => x.Groups) + .WithOne() + .HasForeignKey("Group_Groups_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Permissions) + .WithOne() + .HasForeignKey("Permission_Permissions_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.ProviderMappings) + .WithOne() + .HasForeignKey("ProviderMapping_ProviderMappings_Id") + .IsRequired(); + modelBuilder.Entity() + .HasMany(x => x.Preferences) + .WithOne() + .HasForeignKey("Preference_Preferences_Id") + .IsRequired(); + + OnModelCreatedImpl(modelBuilder); + } + } +} diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs new file mode 100644 index 0000000000..be13686dc2 --- /dev/null +++ b/Jellyfin.Data/Entities/Artwork.cs @@ -0,0 +1,208 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Artwork + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Artwork() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Artwork CreateArtworkUnsafe() + { + return new Artwork(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + public Artwork(string path, global::Jellyfin.Data.Enums.ArtKind kind, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.PersonRole _personrole1) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + this.Path = path; + + this.Kind = kind; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Artwork.Add(this); + + if (_personrole1 == null) throw new ArgumentNullException(nameof(_personrole1)); + _personrole1.Artwork = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + public static Artwork Create(string path, global::Jellyfin.Data.Enums.ArtKind kind, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.PersonRole _personrole1) + { + return new Artwork(path, kind, _metadata0, _personrole1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Path + /// + protected string _Path; + /// + /// When provided in a partial class, allows value of Path to be changed before setting. + /// + partial void SetPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Path to be changed before returning. + /// + partial void GetPath(ref string result); + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Path + { + get + { + string value = _Path; + GetPath(ref value); + return (_Path = value); + } + set + { + string oldValue = _Path; + SetPath(oldValue, ref value); + if (oldValue != value) + { + _Path = value; + } + } + } + + /// + /// Backing field for Kind + /// + internal global::Jellyfin.Data.Enums.ArtKind _Kind; + /// + /// When provided in a partial class, allows value of Kind to be changed before setting. + /// + partial void SetKind(global::Jellyfin.Data.Enums.ArtKind oldValue, ref global::Jellyfin.Data.Enums.ArtKind newValue); + /// + /// When provided in a partial class, allows value of Kind to be changed before returning. + /// + partial void GetKind(ref global::Jellyfin.Data.Enums.ArtKind result); + + /// + /// Indexed, Required + /// + [Required] + public global::Jellyfin.Data.Enums.ArtKind Kind + { + get + { + global::Jellyfin.Data.Enums.ArtKind value = _Kind; + GetKind(ref value); + return (_Kind = value); + } + set + { + global::Jellyfin.Data.Enums.ArtKind oldValue = _Kind; + SetKind(oldValue, ref value); + if (oldValue != value) + { + _Kind = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs new file mode 100644 index 0000000000..30c89ae5c5 --- /dev/null +++ b/Jellyfin.Data/Entities/Book.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Book: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Book(): base() + { + BookMetadata = new System.Collections.Generic.HashSet(); + Releases = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Book CreateBookUnsafe() + { + return new Book(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public Book(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + this.BookMetadata = new System.Collections.Generic.HashSet(); + this.Releases = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Book Create(Guid urlid, DateTime dateadded) + { + return new Book(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection BookMetadata { get; protected set; } + + public virtual ICollection Releases { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs new file mode 100644 index 0000000000..3a28244d69 --- /dev/null +++ b/Jellyfin.Data/Entities/BookMetadata.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class BookMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected BookMetadata(): base() + { + Publishers = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static BookMetadata CreateBookMetadataUnsafe() + { + return new BookMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public BookMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Book _book0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_book0 == null) throw new ArgumentNullException(nameof(_book0)); + _book0.BookMetadata.Add(this); + + this.Publishers = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static BookMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Book _book0) + { + return new BookMetadata(title, language, dateadded, datemodified, _book0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for ISBN + /// + protected long? _ISBN; + /// + /// When provided in a partial class, allows value of ISBN to be changed before setting. + /// + partial void SetISBN(long? oldValue, ref long? newValue); + /// + /// When provided in a partial class, allows value of ISBN to be changed before returning. + /// + partial void GetISBN(ref long? result); + + public long? ISBN + { + get + { + long? value = _ISBN; + GetISBN(ref value); + return (_ISBN = value); + } + set + { + long? oldValue = _ISBN; + SetISBN(oldValue, ref value); + if (oldValue != value) + { + _ISBN = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Publishers { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs new file mode 100644 index 0000000000..21a5dd73ee --- /dev/null +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -0,0 +1,274 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Chapter + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Chapter() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Chapter CreateChapterUnsafe() + { + return new Chapter(); + } + + /// + /// Public constructor with required data + /// + /// ISO-639-3 3-character language codes + /// + /// + public Chapter(string language, long timestart, global::Jellyfin.Data.Entities.Release _release0) + { + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + this.TimeStart = timestart; + + if (_release0 == null) throw new ArgumentNullException(nameof(_release0)); + _release0.Chapters.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// ISO-639-3 3-character language codes + /// + /// + public static Chapter Create(string language, long timestart, global::Jellyfin.Data.Entities.Release _release0) + { + return new Chapter(language, timestart, _release0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Backing field for Language + /// + protected string _Language; + /// + /// When provided in a partial class, allows value of Language to be changed before setting. + /// + partial void SetLanguage(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Language to be changed before returning. + /// + partial void GetLanguage(ref string result); + + /// + /// Required, Min length = 3, Max length = 3 + /// ISO-639-3 3-character language codes + /// + [Required] + [MinLength(3)] + [MaxLength(3)] + [StringLength(3)] + public string Language + { + get + { + string value = _Language; + GetLanguage(ref value); + return (_Language = value); + } + set + { + string oldValue = _Language; + SetLanguage(oldValue, ref value); + if (oldValue != value) + { + _Language = value; + } + } + } + + /// + /// Backing field for TimeStart + /// + protected long _TimeStart; + /// + /// When provided in a partial class, allows value of TimeStart to be changed before setting. + /// + partial void SetTimeStart(long oldValue, ref long newValue); + /// + /// When provided in a partial class, allows value of TimeStart to be changed before returning. + /// + partial void GetTimeStart(ref long result); + + /// + /// Required + /// + [Required] + public long TimeStart + { + get + { + long value = _TimeStart; + GetTimeStart(ref value); + return (_TimeStart = value); + } + set + { + long oldValue = _TimeStart; + SetTimeStart(oldValue, ref value); + if (oldValue != value) + { + _TimeStart = value; + } + } + } + + /// + /// Backing field for TimeEnd + /// + protected long? _TimeEnd; + /// + /// When provided in a partial class, allows value of TimeEnd to be changed before setting. + /// + partial void SetTimeEnd(long? oldValue, ref long? newValue); + /// + /// When provided in a partial class, allows value of TimeEnd to be changed before returning. + /// + partial void GetTimeEnd(ref long? result); + + public long? TimeEnd + { + get + { + long? value = _TimeEnd; + GetTimeEnd(ref value); + return (_TimeEnd = value); + } + set + { + long? oldValue = _TimeEnd; + SetTimeEnd(oldValue, ref value); + if (oldValue != value) + { + _TimeEnd = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs new file mode 100644 index 0000000000..68979eb2fe --- /dev/null +++ b/Jellyfin.Data/Entities/Collection.cs @@ -0,0 +1,131 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Collection + { + partial void Init(); + + /// + /// Default constructor + /// + public Collection() + { + CollectionItem = new System.Collections.Generic.LinkedList(); + + Init(); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection CollectionItem { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs new file mode 100644 index 0000000000..8e575e0a28 --- /dev/null +++ b/Jellyfin.Data/Entities/CollectionItem.cs @@ -0,0 +1,151 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class CollectionItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CollectionItem() + { + // NOTE: This class has one-to-one associations with CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CollectionItem CreateCollectionItemUnsafe() + { + return new CollectionItem(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + public CollectionItem(global::Jellyfin.Data.Entities.Collection _collection0, global::Jellyfin.Data.Entities.CollectionItem _collectionitem1, global::Jellyfin.Data.Entities.CollectionItem _collectionitem2) + { + // NOTE: This class has one-to-one associations with CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + if (_collection0 == null) throw new ArgumentNullException(nameof(_collection0)); + _collection0.CollectionItem.Add(this); + + if (_collectionitem1 == null) throw new ArgumentNullException(nameof(_collectionitem1)); + _collectionitem1.Next = this; + + if (_collectionitem2 == null) throw new ArgumentNullException(nameof(_collectionitem2)); + _collectionitem2.Previous = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + public static CollectionItem Create(global::Jellyfin.Data.Entities.Collection _collection0, global::Jellyfin.Data.Entities.CollectionItem _collectionitem1, global::Jellyfin.Data.Entities.CollectionItem _collectionitem2) + { + return new CollectionItem(_collection0, _collectionitem1, _collectionitem2); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required + /// + public virtual global::Jellyfin.Data.Entities.LibraryItem LibraryItem { get; set; } + + /// + /// TODO check if this properly updated dependant and has the proper principal relationship + /// + public virtual global::Jellyfin.Data.Entities.CollectionItem Next { get; set; } + + /// + /// TODO check if this properly updated dependant and has the proper principal relationship + /// + public virtual global::Jellyfin.Data.Entities.CollectionItem Previous { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs new file mode 100644 index 0000000000..444ae9c564 --- /dev/null +++ b/Jellyfin.Data/Entities/Company.cs @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Company + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Company() + { + CompanyMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Company CreateCompanyUnsafe() + { + return new Company(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public Company(global::Jellyfin.Data.Entities.MovieMetadata _moviemetadata0, global::Jellyfin.Data.Entities.SeriesMetadata _seriesmetadata1, global::Jellyfin.Data.Entities.MusicAlbumMetadata _musicalbummetadata2, global::Jellyfin.Data.Entities.BookMetadata _bookmetadata3, global::Jellyfin.Data.Entities.Company _company4) + { + if (_moviemetadata0 == null) throw new ArgumentNullException(nameof(_moviemetadata0)); + _moviemetadata0.Studios.Add(this); + + if (_seriesmetadata1 == null) throw new ArgumentNullException(nameof(_seriesmetadata1)); + _seriesmetadata1.Networks.Add(this); + + if (_musicalbummetadata2 == null) throw new ArgumentNullException(nameof(_musicalbummetadata2)); + _musicalbummetadata2.Labels.Add(this); + + if (_bookmetadata3 == null) throw new ArgumentNullException(nameof(_bookmetadata3)); + _bookmetadata3.Publishers.Add(this); + + if (_company4 == null) throw new ArgumentNullException(nameof(_company4)); + _company4.Parent = this; + + this.CompanyMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static Company Create(global::Jellyfin.Data.Entities.MovieMetadata _moviemetadata0, global::Jellyfin.Data.Entities.SeriesMetadata _seriesmetadata1, global::Jellyfin.Data.Entities.MusicAlbumMetadata _musicalbummetadata2, global::Jellyfin.Data.Entities.BookMetadata _bookmetadata3, global::Jellyfin.Data.Entities.Company _company4) + { + return new Company(_moviemetadata0, _seriesmetadata1, _musicalbummetadata2, _bookmetadata3, _company4); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection CompanyMetadata { get; protected set; } + + public virtual global::Jellyfin.Data.Entities.Company Parent { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs new file mode 100644 index 0000000000..6d636e8846 --- /dev/null +++ b/Jellyfin.Data/Entities/CompanyMetadata.cs @@ -0,0 +1,234 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class CompanyMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CompanyMetadata(): base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CompanyMetadata CreateCompanyMetadataUnsafe() + { + return new CompanyMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public CompanyMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Company _company0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_company0 == null) throw new ArgumentNullException(nameof(_company0)); + _company0.CompanyMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static CompanyMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Company _company0) + { + return new CompanyMetadata(title, language, dateadded, datemodified, _company0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Description + /// + protected string _Description; + /// + /// When provided in a partial class, allows value of Description to be changed before setting. + /// + partial void SetDescription(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Description to be changed before returning. + /// + partial void GetDescription(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Description + { + get + { + string value = _Description; + GetDescription(ref value); + return (_Description = value); + } + set + { + string oldValue = _Description; + SetDescription(oldValue, ref value); + if (oldValue != value) + { + _Description = value; + } + } + } + + /// + /// Backing field for Headquarters + /// + protected string _Headquarters; + /// + /// When provided in a partial class, allows value of Headquarters to be changed before setting. + /// + partial void SetHeadquarters(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Headquarters to be changed before returning. + /// + partial void GetHeadquarters(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string Headquarters + { + get + { + string value = _Headquarters; + GetHeadquarters(ref value); + return (_Headquarters = value); + } + set + { + string oldValue = _Headquarters; + SetHeadquarters(oldValue, ref value); + if (oldValue != value) + { + _Headquarters = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get + { + string value = _Country; + GetCountry(ref value); + return (_Country = value); + } + set + { + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } + } + } + + /// + /// Backing field for Homepage + /// + protected string _Homepage; + /// + /// When provided in a partial class, allows value of Homepage to be changed before setting. + /// + partial void SetHomepage(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Homepage to be changed before returning. + /// + partial void GetHomepage(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Homepage + { + get + { + string value = _Homepage; + GetHomepage(ref value); + return (_Homepage = value); + } + set + { + string oldValue = _Homepage; + SetHomepage(oldValue, ref value); + if (oldValue != value) + { + _Homepage = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs new file mode 100644 index 0000000000..eb6d2752d3 --- /dev/null +++ b/Jellyfin.Data/Entities/CustomItem.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class CustomItem: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CustomItem(): base() + { + CustomItemMetadata = new System.Collections.Generic.HashSet(); + Releases = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CustomItem CreateCustomItemUnsafe() + { + return new CustomItem(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public CustomItem(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + this.CustomItemMetadata = new System.Collections.Generic.HashSet(); + this.Releases = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static CustomItem Create(Guid urlid, DateTime dateadded) + { + return new CustomItem(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection CustomItemMetadata { get; protected set; } + + public virtual ICollection Releases { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs new file mode 100644 index 0000000000..f2c15d3fe3 --- /dev/null +++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class CustomItemMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CustomItemMetadata(): base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CustomItemMetadata CreateCustomItemMetadataUnsafe() + { + return new CustomItemMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public CustomItemMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.CustomItem _customitem0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_customitem0 == null) throw new ArgumentNullException(nameof(_customitem0)); + _customitem0.CustomItemMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static CustomItemMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.CustomItem _customitem0) + { + return new CustomItemMetadata(title, language, dateadded, datemodified, _customitem0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs new file mode 100644 index 0000000000..3a23f0976f --- /dev/null +++ b/Jellyfin.Data/Entities/Episode.cs @@ -0,0 +1,127 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Episode: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Episode(): base() + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Releases = new System.Collections.Generic.HashSet(); + EpisodeMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Episode CreateEpisodeUnsafe() + { + return new Episode(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public Episode(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Season _season0) + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.UrlId = urlid; + + if (_season0 == null) throw new ArgumentNullException(nameof(_season0)); + _season0.Episodes.Add(this); + + this.Releases = new System.Collections.Generic.HashSet(); + this.EpisodeMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public static Episode Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Season _season0) + { + return new Episode(urlid, dateadded, _season0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for EpisodeNumber + /// + protected int? _EpisodeNumber; + /// + /// When provided in a partial class, allows value of EpisodeNumber to be changed before setting. + /// + partial void SetEpisodeNumber(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of EpisodeNumber to be changed before returning. + /// + partial void GetEpisodeNumber(ref int? result); + + public int? EpisodeNumber + { + get + { + int? value = _EpisodeNumber; + GetEpisodeNumber(ref value); + return (_EpisodeNumber = value); + } + set + { + int? oldValue = _EpisodeNumber; + SetEpisodeNumber(oldValue, ref value); + if (oldValue != value) + { + _EpisodeNumber = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Releases { get; protected set; } + + public virtual ICollection EpisodeMetadata { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs new file mode 100644 index 0000000000..963219140d --- /dev/null +++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class EpisodeMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected EpisodeMetadata(): base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static EpisodeMetadata CreateEpisodeMetadataUnsafe() + { + return new EpisodeMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public EpisodeMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Episode _episode0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_episode0 == null) throw new ArgumentNullException(nameof(_episode0)); + _episode0.EpisodeMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static EpisodeMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Episode _episode0) + { + return new EpisodeMetadata(title, language, dateadded, datemodified, _episode0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get + { + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); + } + set + { + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } + } + } + + /// + /// Backing field for Plot + /// + protected string _Plot; + /// + /// When provided in a partial class, allows value of Plot to be changed before setting. + /// + partial void SetPlot(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Plot to be changed before returning. + /// + partial void GetPlot(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Plot + { + get + { + string value = _Plot; + GetPlot(ref value); + return (_Plot = value); + } + set + { + string oldValue = _Plot; + SetPlot(oldValue, ref value); + if (oldValue != value) + { + _Plot = value; + } + } + } + + /// + /// Backing field for Tagline + /// + protected string _Tagline; + /// + /// When provided in a partial class, allows value of Tagline to be changed before setting. + /// + partial void SetTagline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Tagline to be changed before returning. + /// + partial void GetTagline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Tagline + { + get + { + string value = _Tagline; + GetTagline(ref value); + return (_Tagline = value); + } + set + { + string oldValue = _Tagline; + SetTagline(oldValue, ref value); + if (oldValue != value) + { + _Tagline = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs new file mode 100644 index 0000000000..982600553e --- /dev/null +++ b/Jellyfin.Data/Entities/Genre.cs @@ -0,0 +1,163 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Genre + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Genre() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Genre CreateGenreUnsafe() + { + return new Genre(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public Genre(string name, global::Jellyfin.Data.Entities.Metadata _metadata0) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Genres.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Genre Create(string name, global::Jellyfin.Data.Entities.Metadata _metadata0) + { + return new Genre(name, _metadata0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + internal string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Indexed, Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs new file mode 100644 index 0000000000..ff19e9b019 --- /dev/null +++ b/Jellyfin.Data/Entities/Group.cs @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Group + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Group() + { + GroupPermissions = new System.Collections.Generic.HashSet(); + ProviderMappings = new System.Collections.Generic.HashSet(); + Preferences = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Group CreateGroupUnsafe() + { + return new Group(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public Group(string name, global::Jellyfin.Data.Entities.User _user0) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); + _user0.Groups.Add(this); + + this.GroupPermissions = new System.Collections.Generic.HashSet(); + this.ProviderMappings = new System.Collections.Generic.HashSet(); + this.Preferences = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Group Create(string name, global::Jellyfin.Data.Entities.User _user0) + { + return new Group(name, _user0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id { get; protected set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string Name { get; set; } + + /// + /// Concurrency token + /// + [Timestamp] + public Byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection GroupPermissions { get; protected set; } + + public virtual ICollection ProviderMappings { get; protected set; } + + public virtual ICollection Preferences { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs new file mode 100644 index 0000000000..19ca142947 --- /dev/null +++ b/Jellyfin.Data/Entities/Library.cs @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Library + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Library() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Library CreateLibraryUnsafe() + { + return new Library(); + } + + /// + /// Public constructor with required data + /// + /// + public Library(string name) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + public static Library Create(string name) + { + return new Library(name); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs new file mode 100644 index 0000000000..1987196d69 --- /dev/null +++ b/Jellyfin.Data/Entities/LibraryItem.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public abstract partial class LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to being abstract. + /// + protected LibraryItem() + { + Init(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + protected LibraryItem(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + + Init(); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for UrlId + /// + internal Guid _UrlId; + /// + /// When provided in a partial class, allows value of UrlId to be changed before setting. + /// + partial void SetUrlId(Guid oldValue, ref Guid newValue); + /// + /// When provided in a partial class, allows value of UrlId to be changed before returning. + /// + partial void GetUrlId(ref Guid result); + + /// + /// Indexed, Required + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + [Required] + public Guid UrlId + { + get + { + Guid value = _UrlId; + GetUrlId(ref value); + return (_UrlId = value); + } + set + { + Guid oldValue = _UrlId; + SetUrlId(oldValue, ref value); + if (oldValue != value) + { + _UrlId = value; + } + } + } + + /// + /// Backing field for DateAdded + /// + protected DateTime _DateAdded; + /// + /// When provided in a partial class, allows value of DateAdded to be changed before setting. + /// + partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateAdded to be changed before returning. + /// + partial void GetDateAdded(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateAdded + { + get + { + DateTime value = _DateAdded; + GetDateAdded(ref value); + return (_DateAdded = value); + } + internal set + { + DateTime oldValue = _DateAdded; + SetDateAdded(oldValue, ref value); + if (oldValue != value) + { + _DateAdded = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required + /// + public virtual global::Jellyfin.Data.Entities.LibraryRoot LibraryRoot { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs new file mode 100644 index 0000000000..015fc4ea98 --- /dev/null +++ b/Jellyfin.Data/Entities/LibraryRoot.cs @@ -0,0 +1,202 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class LibraryRoot + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected LibraryRoot() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static LibraryRoot CreateLibraryRootUnsafe() + { + return new LibraryRoot(); + } + + /// + /// Public constructor with required data + /// + /// Absolute Path + public LibraryRoot(string path) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + this.Path = path; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Absolute Path + public static LibraryRoot Create(string path) + { + return new LibraryRoot(path); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Path + /// + protected string _Path; + /// + /// When provided in a partial class, allows value of Path to be changed before setting. + /// + partial void SetPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Path to be changed before returning. + /// + partial void GetPath(ref string result); + + /// + /// Required, Max length = 65535 + /// Absolute Path + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Path + { + get + { + string value = _Path; + GetPath(ref value); + return (_Path = value); + } + set + { + string oldValue = _Path; + SetPath(oldValue, ref value); + if (oldValue != value) + { + _Path = value; + } + } + } + + /// + /// Backing field for NetworkPath + /// + protected string _NetworkPath; + /// + /// When provided in a partial class, allows value of NetworkPath to be changed before setting. + /// + partial void SetNetworkPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of NetworkPath to be changed before returning. + /// + partial void GetNetworkPath(ref string result); + + /// + /// Max length = 65535 + /// Absolute network path, for example for transcoding sattelites. + /// + [MaxLength(65535)] + [StringLength(65535)] + public string NetworkPath + { + get + { + string value = _NetworkPath; + GetNetworkPath(ref value); + return (_NetworkPath = value); + } + set + { + string oldValue = _NetworkPath; + SetNetworkPath(oldValue, ref value); + if (oldValue != value) + { + _NetworkPath = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required + /// + public virtual global::Jellyfin.Data.Entities.Library Library { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs new file mode 100644 index 0000000000..2a47a96325 --- /dev/null +++ b/Jellyfin.Data/Entities/MediaFile.cs @@ -0,0 +1,209 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MediaFile + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MediaFile() + { + MediaFileStreams = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MediaFile CreateMediaFileUnsafe() + { + return new MediaFile(); + } + + /// + /// Public constructor with required data + /// + /// Relative to the LibraryRoot + /// + /// + public MediaFile(string path, global::Jellyfin.Data.Enums.MediaFileKind kind, global::Jellyfin.Data.Entities.Release _release0) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + this.Path = path; + + this.Kind = kind; + + if (_release0 == null) throw new ArgumentNullException(nameof(_release0)); + _release0.MediaFiles.Add(this); + + this.MediaFileStreams = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Relative to the LibraryRoot + /// + /// + public static MediaFile Create(string path, global::Jellyfin.Data.Enums.MediaFileKind kind, global::Jellyfin.Data.Entities.Release _release0) + { + return new MediaFile(path, kind, _release0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Path + /// + protected string _Path; + /// + /// When provided in a partial class, allows value of Path to be changed before setting. + /// + partial void SetPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Path to be changed before returning. + /// + partial void GetPath(ref string result); + + /// + /// Required, Max length = 65535 + /// Relative to the LibraryRoot + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Path + { + get + { + string value = _Path; + GetPath(ref value); + return (_Path = value); + } + set + { + string oldValue = _Path; + SetPath(oldValue, ref value); + if (oldValue != value) + { + _Path = value; + } + } + } + + /// + /// Backing field for Kind + /// + protected global::Jellyfin.Data.Enums.MediaFileKind _Kind; + /// + /// When provided in a partial class, allows value of Kind to be changed before setting. + /// + partial void SetKind(global::Jellyfin.Data.Enums.MediaFileKind oldValue, ref global::Jellyfin.Data.Enums.MediaFileKind newValue); + /// + /// When provided in a partial class, allows value of Kind to be changed before returning. + /// + partial void GetKind(ref global::Jellyfin.Data.Enums.MediaFileKind result); + + /// + /// Required + /// + [Required] + public global::Jellyfin.Data.Enums.MediaFileKind Kind + { + get + { + global::Jellyfin.Data.Enums.MediaFileKind value = _Kind; + GetKind(ref value); + return (_Kind = value); + } + set + { + global::Jellyfin.Data.Enums.MediaFileKind oldValue = _Kind; + SetKind(oldValue, ref value); + if (oldValue != value) + { + _Kind = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection MediaFileStreams { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs new file mode 100644 index 0000000000..6593d3cf75 --- /dev/null +++ b/Jellyfin.Data/Entities/MediaFileStream.cs @@ -0,0 +1,160 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MediaFileStream + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MediaFileStream() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MediaFileStream CreateMediaFileStreamUnsafe() + { + return new MediaFileStream(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public MediaFileStream(int streamnumber, global::Jellyfin.Data.Entities.MediaFile _mediafile0) + { + this.StreamNumber = streamnumber; + + if (_mediafile0 == null) throw new ArgumentNullException(nameof(_mediafile0)); + _mediafile0.MediaFileStreams.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static MediaFileStream Create(int streamnumber, global::Jellyfin.Data.Entities.MediaFile _mediafile0) + { + return new MediaFileStream(streamnumber, _mediafile0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for StreamNumber + /// + protected int _StreamNumber; + /// + /// When provided in a partial class, allows value of StreamNumber to be changed before setting. + /// + partial void SetStreamNumber(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of StreamNumber to be changed before returning. + /// + partial void GetStreamNumber(ref int result); + + /// + /// Required + /// + [Required] + public int StreamNumber + { + get + { + int value = _StreamNumber; + GetStreamNumber(ref value); + return (_StreamNumber = value); + } + set + { + int oldValue = _StreamNumber; + SetStreamNumber(oldValue, ref value); + if (oldValue != value) + { + _StreamNumber = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs new file mode 100644 index 0000000000..6057017e94 --- /dev/null +++ b/Jellyfin.Data/Entities/Metadata.cs @@ -0,0 +1,385 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public abstract partial class Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to being abstract. + /// + protected Metadata() + { + PersonRoles = new System.Collections.Generic.HashSet(); + Genres = new System.Collections.Generic.HashSet(); + Artwork = new System.Collections.Generic.HashSet(); + Ratings = new System.Collections.Generic.HashSet(); + Sources = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + protected Metadata(string title, string language, DateTime dateadded, DateTime datemodified) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + this.PersonRoles = new System.Collections.Generic.HashSet(); + this.Genres = new System.Collections.Generic.HashSet(); + this.Artwork = new System.Collections.Generic.HashSet(); + this.Ratings = new System.Collections.Generic.HashSet(); + this.Sources = new System.Collections.Generic.HashSet(); + + Init(); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Title + /// + protected string _Title; + /// + /// When provided in a partial class, allows value of Title to be changed before setting. + /// + partial void SetTitle(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Title to be changed before returning. + /// + partial void GetTitle(ref string result); + + /// + /// Required, Max length = 1024 + /// The title or name of the object + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Title + { + get + { + string value = _Title; + GetTitle(ref value); + return (_Title = value); + } + set + { + string oldValue = _Title; + SetTitle(oldValue, ref value); + if (oldValue != value) + { + _Title = value; + } + } + } + + /// + /// Backing field for OriginalTitle + /// + protected string _OriginalTitle; + /// + /// When provided in a partial class, allows value of OriginalTitle to be changed before setting. + /// + partial void SetOriginalTitle(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of OriginalTitle to be changed before returning. + /// + partial void GetOriginalTitle(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string OriginalTitle + { + get + { + string value = _OriginalTitle; + GetOriginalTitle(ref value); + return (_OriginalTitle = value); + } + set + { + string oldValue = _OriginalTitle; + SetOriginalTitle(oldValue, ref value); + if (oldValue != value) + { + _OriginalTitle = value; + } + } + } + + /// + /// Backing field for SortTitle + /// + protected string _SortTitle; + /// + /// When provided in a partial class, allows value of SortTitle to be changed before setting. + /// + partial void SetSortTitle(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of SortTitle to be changed before returning. + /// + partial void GetSortTitle(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string SortTitle + { + get + { + string value = _SortTitle; + GetSortTitle(ref value); + return (_SortTitle = value); + } + set + { + string oldValue = _SortTitle; + SetSortTitle(oldValue, ref value); + if (oldValue != value) + { + _SortTitle = value; + } + } + } + + /// + /// Backing field for Language + /// + protected string _Language; + /// + /// When provided in a partial class, allows value of Language to be changed before setting. + /// + partial void SetLanguage(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Language to be changed before returning. + /// + partial void GetLanguage(ref string result); + + /// + /// Required, Min length = 3, Max length = 3 + /// ISO-639-3 3-character language codes + /// + [Required] + [MinLength(3)] + [MaxLength(3)] + [StringLength(3)] + public string Language + { + get + { + string value = _Language; + GetLanguage(ref value); + return (_Language = value); + } + set + { + string oldValue = _Language; + SetLanguage(oldValue, ref value); + if (oldValue != value) + { + _Language = value; + } + } + } + + /// + /// Backing field for ReleaseDate + /// + protected DateTimeOffset? _ReleaseDate; + /// + /// When provided in a partial class, allows value of ReleaseDate to be changed before setting. + /// + partial void SetReleaseDate(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); + /// + /// When provided in a partial class, allows value of ReleaseDate to be changed before returning. + /// + partial void GetReleaseDate(ref DateTimeOffset? result); + + public DateTimeOffset? ReleaseDate + { + get + { + DateTimeOffset? value = _ReleaseDate; + GetReleaseDate(ref value); + return (_ReleaseDate = value); + } + set + { + DateTimeOffset? oldValue = _ReleaseDate; + SetReleaseDate(oldValue, ref value); + if (oldValue != value) + { + _ReleaseDate = value; + } + } + } + + /// + /// Backing field for DateAdded + /// + protected DateTime _DateAdded; + /// + /// When provided in a partial class, allows value of DateAdded to be changed before setting. + /// + partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateAdded to be changed before returning. + /// + partial void GetDateAdded(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateAdded + { + get + { + DateTime value = _DateAdded; + GetDateAdded(ref value); + return (_DateAdded = value); + } + internal set + { + DateTime oldValue = _DateAdded; + SetDateAdded(oldValue, ref value); + if (oldValue != value) + { + _DateAdded = value; + } + } + } + + /// + /// Backing field for DateModified + /// + protected DateTime _DateModified; + /// + /// When provided in a partial class, allows value of DateModified to be changed before setting. + /// + partial void SetDateModified(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateModified to be changed before returning. + /// + partial void GetDateModified(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateModified + { + get + { + DateTime value = _DateModified; + GetDateModified(ref value); + return (_DateModified = value); + } + internal set + { + DateTime oldValue = _DateModified; + SetDateModified(oldValue, ref value); + if (oldValue != value) + { + _DateModified = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection PersonRoles { get; protected set; } + + public virtual ICollection Genres { get; protected set; } + + public virtual ICollection Artwork { get; protected set; } + + public virtual ICollection Ratings { get; protected set; } + + public virtual ICollection Sources { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs new file mode 100644 index 0000000000..3a8f5854eb --- /dev/null +++ b/Jellyfin.Data/Entities/MetadataProvider.cs @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MetadataProvider + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MetadataProvider() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MetadataProvider CreateMetadataProviderUnsafe() + { + return new MetadataProvider(); + } + + /// + /// Public constructor with required data + /// + /// + public MetadataProvider(string name) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + public static MetadataProvider Create(string name) + { + return new MetadataProvider(name); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs new file mode 100644 index 0000000000..87ff19e26d --- /dev/null +++ b/Jellyfin.Data/Entities/MetadataProviderId.cs @@ -0,0 +1,189 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MetadataProviderId + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MetadataProviderId() + { + // NOTE: This class has one-to-one associations with MetadataProviderId. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MetadataProviderId CreateMetadataProviderIdUnsafe() + { + return new MetadataProviderId(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public MetadataProviderId(string providerid, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.Person _person1, global::Jellyfin.Data.Entities.PersonRole _personrole2, global::Jellyfin.Data.Entities.RatingSource _ratingsource3) + { + // NOTE: This class has one-to-one associations with MetadataProviderId. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + if (string.IsNullOrEmpty(providerid)) throw new ArgumentNullException(nameof(providerid)); + this.ProviderId = providerid; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Sources.Add(this); + + if (_person1 == null) throw new ArgumentNullException(nameof(_person1)); + _person1.Sources.Add(this); + + if (_personrole2 == null) throw new ArgumentNullException(nameof(_personrole2)); + _personrole2.Sources.Add(this); + + if (_ratingsource3 == null) throw new ArgumentNullException(nameof(_ratingsource3)); + _ratingsource3.Source = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static MetadataProviderId Create(string providerid, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.Person _person1, global::Jellyfin.Data.Entities.PersonRole _personrole2, global::Jellyfin.Data.Entities.RatingSource _ratingsource3) + { + return new MetadataProviderId(providerid, _metadata0, _person1, _personrole2, _ratingsource3); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for ProviderId + /// + protected string _ProviderId; + /// + /// When provided in a partial class, allows value of ProviderId to be changed before setting. + /// + partial void SetProviderId(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of ProviderId to be changed before returning. + /// + partial void GetProviderId(ref string result); + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string ProviderId + { + get + { + string value = _ProviderId; + GetProviderId(ref value); + return (_ProviderId = value); + } + set + { + string oldValue = _ProviderId; + SetProviderId(oldValue, ref value); + if (oldValue != value) + { + _ProviderId = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required + /// + public virtual global::Jellyfin.Data.Entities.MetadataProvider MetadataProvider { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Movie.cs b/Jellyfin.Data/Entities/Movie.cs new file mode 100644 index 0000000000..dfcc05a943 --- /dev/null +++ b/Jellyfin.Data/Entities/Movie.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Movie: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Movie(): base() + { + Releases = new System.Collections.Generic.HashSet(); + MovieMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Movie CreateMovieUnsafe() + { + return new Movie(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public Movie(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + this.Releases = new System.Collections.Generic.HashSet(); + this.MovieMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Movie Create(Guid urlid, DateTime dateadded) + { + return new Movie(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Releases { get; protected set; } + + public virtual ICollection MovieMetadata { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs new file mode 100644 index 0000000000..bd847da8fa --- /dev/null +++ b/Jellyfin.Data/Entities/MovieMetadata.cs @@ -0,0 +1,239 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MovieMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MovieMetadata(): base() + { + Studios = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MovieMetadata CreateMovieMetadataUnsafe() + { + return new MovieMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public MovieMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Movie _movie0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0)); + _movie0.MovieMetadata.Add(this); + + this.Studios = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static MovieMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Movie _movie0) + { + return new MovieMetadata(title, language, dateadded, datemodified, _movie0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get + { + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); + } + set + { + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } + } + } + + /// + /// Backing field for Plot + /// + protected string _Plot; + /// + /// When provided in a partial class, allows value of Plot to be changed before setting. + /// + partial void SetPlot(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Plot to be changed before returning. + /// + partial void GetPlot(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Plot + { + get + { + string value = _Plot; + GetPlot(ref value); + return (_Plot = value); + } + set + { + string oldValue = _Plot; + SetPlot(oldValue, ref value); + if (oldValue != value) + { + _Plot = value; + } + } + } + + /// + /// Backing field for Tagline + /// + protected string _Tagline; + /// + /// When provided in a partial class, allows value of Tagline to be changed before setting. + /// + partial void SetTagline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Tagline to be changed before returning. + /// + partial void GetTagline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Tagline + { + get + { + string value = _Tagline; + GetTagline(ref value); + return (_Tagline = value); + } + set + { + string oldValue = _Tagline; + SetTagline(oldValue, ref value); + if (oldValue != value) + { + _Tagline = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get + { + string value = _Country; + GetCountry(ref value); + return (_Country = value); + } + set + { + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Studios { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs new file mode 100644 index 0000000000..417f2595bd --- /dev/null +++ b/Jellyfin.Data/Entities/MusicAlbum.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MusicAlbum: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MusicAlbum(): base() + { + MusicAlbumMetadata = new System.Collections.Generic.HashSet(); + Tracks = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MusicAlbum CreateMusicAlbumUnsafe() + { + return new MusicAlbum(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public MusicAlbum(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + this.MusicAlbumMetadata = new System.Collections.Generic.HashSet(); + this.Tracks = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static MusicAlbum Create(Guid urlid, DateTime dateadded) + { + return new MusicAlbum(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection MusicAlbumMetadata { get; protected set; } + + public virtual ICollection Tracks { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs new file mode 100644 index 0000000000..cd72ecba51 --- /dev/null +++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs @@ -0,0 +1,202 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class MusicAlbumMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MusicAlbumMetadata(): base() + { + Labels = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MusicAlbumMetadata CreateMusicAlbumMetadataUnsafe() + { + return new MusicAlbumMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public MusicAlbumMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0)); + _musicalbum0.MusicAlbumMetadata.Add(this); + + this.Labels = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static MusicAlbumMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) + { + return new MusicAlbumMetadata(title, language, dateadded, datemodified, _musicalbum0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Barcode + /// + protected string _Barcode; + /// + /// When provided in a partial class, allows value of Barcode to be changed before setting. + /// + partial void SetBarcode(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Barcode to be changed before returning. + /// + partial void GetBarcode(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string Barcode + { + get + { + string value = _Barcode; + GetBarcode(ref value); + return (_Barcode = value); + } + set + { + string oldValue = _Barcode; + SetBarcode(oldValue, ref value); + if (oldValue != value) + { + _Barcode = value; + } + } + } + + /// + /// Backing field for LabelNumber + /// + protected string _LabelNumber; + /// + /// When provided in a partial class, allows value of LabelNumber to be changed before setting. + /// + partial void SetLabelNumber(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of LabelNumber to be changed before returning. + /// + partial void GetLabelNumber(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string LabelNumber + { + get + { + string value = _LabelNumber; + GetLabelNumber(ref value); + return (_LabelNumber = value); + } + set + { + string oldValue = _LabelNumber; + SetLabelNumber(oldValue, ref value); + if (oldValue != value) + { + _LabelNumber = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get + { + string value = _Country; + GetCountry(ref value); + return (_Country = value); + } + set + { + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Labels { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs new file mode 100644 index 0000000000..a717fc83fe --- /dev/null +++ b/Jellyfin.Data/Entities/Permission.cs @@ -0,0 +1,152 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Permission + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Permission() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Permission CreatePermissionUnsafe() + { + return new Permission(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + public Permission(global::Jellyfin.Data.Enums.PermissionKind kind, bool value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) + { + this.Kind = kind; + + this.Value = value; + + if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); + _user0.Permissions.Add(this); + + if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); + _group1.GroupPermissions.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + public static Permission Create(global::Jellyfin.Data.Enums.PermissionKind kind, bool value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) + { + return new Permission(kind, value, _user0, _group1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id { get; protected set; } + + /// + /// Backing field for Kind + /// + protected global::Jellyfin.Data.Enums.PermissionKind _Kind; + /// + /// When provided in a partial class, allows value of Kind to be changed before setting. + /// + partial void SetKind(global::Jellyfin.Data.Enums.PermissionKind oldValue, ref global::Jellyfin.Data.Enums.PermissionKind newValue); + /// + /// When provided in a partial class, allows value of Kind to be changed before returning. + /// + partial void GetKind(ref global::Jellyfin.Data.Enums.PermissionKind result); + + /// + /// Required + /// + [Required] + public global::Jellyfin.Data.Enums.PermissionKind Kind + { + get + { + global::Jellyfin.Data.Enums.PermissionKind value = _Kind; + GetKind(ref value); + return (_Kind = value); + } + set + { + global::Jellyfin.Data.Enums.PermissionKind oldValue = _Kind; + SetKind(oldValue, ref value); + if (oldValue != value) + { + _Kind = value; + OnPropertyChanged(); + } + } + } + + /// + /// Required + /// + [Required] + public bool Value { get; set; } + + /// + /// Concurrency token + /// + [Timestamp] + public Byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + } +} + diff --git a/Jellyfin.Data/Entities/PermissionKind.cs b/Jellyfin.Data/Entities/PermissionKind.cs new file mode 100644 index 0000000000..971298674a --- /dev/null +++ b/Jellyfin.Data/Entities/PermissionKind.cs @@ -0,0 +1,40 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; + +namespace Jellyfin.Data.Enums +{ + public enum PermissionKind : Int32 + { + IsAdministrator, + IsHidden, + IsDisabled, + BlockUnrateditems, + EnbleSharedDeviceControl, + EnableRemoteAccess, + EnableLiveTvManagement, + EnableLiveTvAccess, + EnableMediaPlayback, + EnableAudioPlaybackTranscoding, + EnableVideoPlaybackTranscoding, + EnableContentDeletion, + EnableContentDownloading, + EnableSyncTranscoding, + EnableMediaConversion, + EnableAllDevices, + EnableAllChannels, + EnableAllFolders, + EnablePublicSharing, + AccessSchedules + } +} diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs new file mode 100644 index 0000000000..3437b9581d --- /dev/null +++ b/Jellyfin.Data/Entities/Person.cs @@ -0,0 +1,312 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Person + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Person() + { + Sources = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Person CreatePersonUnsafe() + { + return new Person(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public Person(Guid urlid, string name, DateTime dateadded, DateTime datemodified) + { + this.UrlId = urlid; + + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + this.Sources = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Person Create(Guid urlid, string name, DateTime dateadded, DateTime datemodified) + { + return new Person(urlid, name, dateadded, datemodified); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for UrlId + /// + protected Guid _UrlId; + /// + /// When provided in a partial class, allows value of UrlId to be changed before setting. + /// + partial void SetUrlId(Guid oldValue, ref Guid newValue); + /// + /// When provided in a partial class, allows value of UrlId to be changed before returning. + /// + partial void GetUrlId(ref Guid result); + + /// + /// Required + /// + [Required] + public Guid UrlId + { + get + { + Guid value = _UrlId; + GetUrlId(ref value); + return (_UrlId = value); + } + set + { + Guid oldValue = _UrlId; + SetUrlId(oldValue, ref value); + if (oldValue != value) + { + _UrlId = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Backing field for SourceId + /// + protected string _SourceId; + /// + /// When provided in a partial class, allows value of SourceId to be changed before setting. + /// + partial void SetSourceId(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of SourceId to be changed before returning. + /// + partial void GetSourceId(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string SourceId + { + get + { + string value = _SourceId; + GetSourceId(ref value); + return (_SourceId = value); + } + set + { + string oldValue = _SourceId; + SetSourceId(oldValue, ref value); + if (oldValue != value) + { + _SourceId = value; + } + } + } + + /// + /// Backing field for DateAdded + /// + protected DateTime _DateAdded; + /// + /// When provided in a partial class, allows value of DateAdded to be changed before setting. + /// + partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateAdded to be changed before returning. + /// + partial void GetDateAdded(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateAdded + { + get + { + DateTime value = _DateAdded; + GetDateAdded(ref value); + return (_DateAdded = value); + } + internal set + { + DateTime oldValue = _DateAdded; + SetDateAdded(oldValue, ref value); + if (oldValue != value) + { + _DateAdded = value; + } + } + } + + /// + /// Backing field for DateModified + /// + protected DateTime _DateModified; + /// + /// When provided in a partial class, allows value of DateModified to be changed before setting. + /// + partial void SetDateModified(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateModified to be changed before returning. + /// + partial void GetDateModified(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateModified + { + get + { + DateTime value = _DateModified; + GetDateModified(ref value); + return (_DateModified = value); + } + internal set + { + DateTime oldValue = _DateModified; + SetDateModified(oldValue, ref value); + if (oldValue != value) + { + _DateModified = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Sources { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs new file mode 100644 index 0000000000..d8e2dbc11a --- /dev/null +++ b/Jellyfin.Data/Entities/PersonRole.cs @@ -0,0 +1,215 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class PersonRole + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected PersonRole() + { + // NOTE: This class has one-to-one associations with PersonRole. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Sources = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static PersonRole CreatePersonRoleUnsafe() + { + return new PersonRole(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public PersonRole(global::Jellyfin.Data.Enums.PersonRoleType type, global::Jellyfin.Data.Entities.Metadata _metadata0) + { + // NOTE: This class has one-to-one associations with PersonRole. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.Type = type; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.PersonRoles.Add(this); + + this.Sources = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static PersonRole Create(global::Jellyfin.Data.Enums.PersonRoleType type, global::Jellyfin.Data.Entities.Metadata _metadata0) + { + return new PersonRole(type, _metadata0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Role + /// + protected string _Role; + /// + /// When provided in a partial class, allows value of Role to be changed before setting. + /// + partial void SetRole(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Role to be changed before returning. + /// + partial void GetRole(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Role + { + get + { + string value = _Role; + GetRole(ref value); + return (_Role = value); + } + set + { + string oldValue = _Role; + SetRole(oldValue, ref value); + if (oldValue != value) + { + _Role = value; + } + } + } + + /// + /// Backing field for Type + /// + protected global::Jellyfin.Data.Enums.PersonRoleType _Type; + /// + /// When provided in a partial class, allows value of Type to be changed before setting. + /// + partial void SetType(global::Jellyfin.Data.Enums.PersonRoleType oldValue, ref global::Jellyfin.Data.Enums.PersonRoleType newValue); + /// + /// When provided in a partial class, allows value of Type to be changed before returning. + /// + partial void GetType(ref global::Jellyfin.Data.Enums.PersonRoleType result); + + /// + /// Required + /// + [Required] + public global::Jellyfin.Data.Enums.PersonRoleType Type + { + get + { + global::Jellyfin.Data.Enums.PersonRoleType value = _Type; + GetType(ref value); + return (_Type = value); + } + set + { + global::Jellyfin.Data.Enums.PersonRoleType oldValue = _Type; + SetType(oldValue, ref value); + if (oldValue != value) + { + _Type = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required + /// + public virtual global::Jellyfin.Data.Entities.Person Person { get; set; } + + public virtual global::Jellyfin.Data.Entities.Artwork Artwork { get; set; } + + public virtual ICollection Sources { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs new file mode 100644 index 0000000000..16c97fef54 --- /dev/null +++ b/Jellyfin.Data/Entities/Photo.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Photo: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Photo(): base() + { + PhotoMetadata = new System.Collections.Generic.HashSet(); + Releases = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Photo CreatePhotoUnsafe() + { + return new Photo(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public Photo(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + this.PhotoMetadata = new System.Collections.Generic.HashSet(); + this.Releases = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Photo Create(Guid urlid, DateTime dateadded) + { + return new Photo(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection PhotoMetadata { get; protected set; } + + public virtual ICollection Releases { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs new file mode 100644 index 0000000000..9c47d022e9 --- /dev/null +++ b/Jellyfin.Data/Entities/PhotoMetadata.cs @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class PhotoMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected PhotoMetadata(): base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static PhotoMetadata CreatePhotoMetadataUnsafe() + { + return new PhotoMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public PhotoMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Photo _photo0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_photo0 == null) throw new ArgumentNullException(nameof(_photo0)); + _photo0.PhotoMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static PhotoMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Photo _photo0) + { + return new PhotoMetadata(title, language, dateadded, datemodified, _photo0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs new file mode 100644 index 0000000000..3d69ea2f3a --- /dev/null +++ b/Jellyfin.Data/Entities/Preference.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Preference + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Preference() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Preference CreatePreferenceUnsafe() + { + return new Preference(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + public Preference(global::Jellyfin.Data.Enums.PreferenceKind kind, string value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) + { + this.Kind = kind; + + if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); + this.Value = value; + + if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); + _user0.Preferences.Add(this); + + if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); + _group1.Preferences.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + public static Preference Create(global::Jellyfin.Data.Enums.PreferenceKind kind, string value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) + { + return new Preference(kind, value, _user0, _group1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id { get; protected set; } + + /// + /// Required + /// + [Required] + public global::Jellyfin.Data.Enums.PreferenceKind Kind { get; set; } + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Value { get; set; } + + /// + /// Concurrency token + /// + [Timestamp] + public Byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/PreferenceKind.cs b/Jellyfin.Data/Entities/PreferenceKind.cs new file mode 100644 index 0000000000..e6673afb1b --- /dev/null +++ b/Jellyfin.Data/Entities/PreferenceKind.cs @@ -0,0 +1,27 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; + +namespace Jellyfin.Data.Enums +{ + public enum PreferenceKind : Int32 + { + MaxParentalRating, + BlockedTags, + RemoteClientBitrateLimit, + EnabledDevices, + EnabledChannels, + EnabledFolders, + EnableContentDeletionFromFolders + } +} diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs new file mode 100644 index 0000000000..e50a01489c --- /dev/null +++ b/Jellyfin.Data/Entities/ProviderMapping.cs @@ -0,0 +1,133 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class ProviderMapping + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected ProviderMapping() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static ProviderMapping CreateProviderMappingUnsafe() + { + return new ProviderMapping(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public ProviderMapping(string providername, string providersecrets, string providerdata, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) + { + if (string.IsNullOrEmpty(providername)) throw new ArgumentNullException(nameof(providername)); + this.ProviderName = providername; + + if (string.IsNullOrEmpty(providersecrets)) throw new ArgumentNullException(nameof(providersecrets)); + this.ProviderSecrets = providersecrets; + + if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata)); + this.ProviderData = providerdata; + + if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); + _user0.ProviderMappings.Add(this); + + if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); + _group1.ProviderMappings.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static ProviderMapping Create(string providername, string providersecrets, string providerdata, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) + { + return new ProviderMapping(providername, providersecrets, providerdata, _user0, _group1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id { get; protected set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string ProviderName { get; set; } + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string ProviderSecrets { get; set; } + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string ProviderData { get; set; } + + /// + /// Concurrency token + /// + [Timestamp] + public Byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs new file mode 100644 index 0000000000..b1098a1d7d --- /dev/null +++ b/Jellyfin.Data/Entities/Rating.cs @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Rating + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Rating() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Rating CreateRatingUnsafe() + { + return new Rating(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public Rating(double value, global::Jellyfin.Data.Entities.Metadata _metadata0) + { + this.Value = value; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Ratings.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Rating Create(double value, global::Jellyfin.Data.Entities.Metadata _metadata0) + { + return new Rating(value, _metadata0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Value + /// + protected double _Value; + /// + /// When provided in a partial class, allows value of Value to be changed before setting. + /// + partial void SetValue(double oldValue, ref double newValue); + /// + /// When provided in a partial class, allows value of Value to be changed before returning. + /// + partial void GetValue(ref double result); + + /// + /// Required + /// + [Required] + public double Value + { + get + { + double value = _Value; + GetValue(ref value); + return (_Value = value); + } + set + { + double oldValue = _Value; + SetValue(oldValue, ref value); + if (oldValue != value) + { + _Value = value; + } + } + } + + /// + /// Backing field for Votes + /// + protected int? _Votes; + /// + /// When provided in a partial class, allows value of Votes to be changed before setting. + /// + partial void SetVotes(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of Votes to be changed before returning. + /// + partial void GetVotes(ref int? result); + + public int? Votes + { + get + { + int? value = _Votes; + GetVotes(ref value); + return (_Votes = value); + } + set + { + int? oldValue = _Votes; + SetVotes(oldValue, ref value); + if (oldValue != value) + { + _Votes = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// If this is NULL it's the internal user rating. + /// + public virtual global::Jellyfin.Data.Entities.RatingSource RatingType { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs new file mode 100644 index 0000000000..32d5634c2b --- /dev/null +++ b/Jellyfin.Data/Entities/RatingSource.cs @@ -0,0 +1,242 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + /// + /// This is the entity to store review ratings, not age ratings + /// + public partial class RatingSource + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected RatingSource() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static RatingSource CreateRatingSourceUnsafe() + { + return new RatingSource(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + public RatingSource(double maximumvalue, double minimumvalue, global::Jellyfin.Data.Entities.Rating _rating0) + { + this.MaximumValue = maximumvalue; + + this.MinimumValue = minimumvalue; + + if (_rating0 == null) throw new ArgumentNullException(nameof(_rating0)); + _rating0.RatingType = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + public static RatingSource Create(double maximumvalue, double minimumvalue, global::Jellyfin.Data.Entities.Rating _rating0) + { + return new RatingSource(maximumvalue, minimumvalue, _rating0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Backing field for MaximumValue + /// + protected double _MaximumValue; + /// + /// When provided in a partial class, allows value of MaximumValue to be changed before setting. + /// + partial void SetMaximumValue(double oldValue, ref double newValue); + /// + /// When provided in a partial class, allows value of MaximumValue to be changed before returning. + /// + partial void GetMaximumValue(ref double result); + + /// + /// Required + /// + [Required] + public double MaximumValue + { + get + { + double value = _MaximumValue; + GetMaximumValue(ref value); + return (_MaximumValue = value); + } + set + { + double oldValue = _MaximumValue; + SetMaximumValue(oldValue, ref value); + if (oldValue != value) + { + _MaximumValue = value; + } + } + } + + /// + /// Backing field for MinimumValue + /// + protected double _MinimumValue; + /// + /// When provided in a partial class, allows value of MinimumValue to be changed before setting. + /// + partial void SetMinimumValue(double oldValue, ref double newValue); + /// + /// When provided in a partial class, allows value of MinimumValue to be changed before returning. + /// + partial void GetMinimumValue(ref double result); + + /// + /// Required + /// + [Required] + public double MinimumValue + { + get + { + double value = _MinimumValue; + GetMinimumValue(ref value); + return (_MinimumValue = value); + } + set + { + double oldValue = _MinimumValue; + SetMinimumValue(oldValue, ref value); + if (oldValue != value) + { + _MinimumValue = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual global::Jellyfin.Data.Entities.MetadataProviderId Source { get; set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs new file mode 100644 index 0000000000..e02f70be89 --- /dev/null +++ b/Jellyfin.Data/Entities/Release.cs @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Release + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Release() + { + MediaFiles = new System.Collections.Generic.HashSet(); + Chapters = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Release CreateReleaseUnsafe() + { + return new Release(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + /// + /// + public Release(string name, global::Jellyfin.Data.Entities.Movie _movie0, global::Jellyfin.Data.Entities.Episode _episode1, global::Jellyfin.Data.Entities.Track _track2, global::Jellyfin.Data.Entities.CustomItem _customitem3, global::Jellyfin.Data.Entities.Book _book4, global::Jellyfin.Data.Entities.Photo _photo5) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0)); + _movie0.Releases.Add(this); + + if (_episode1 == null) throw new ArgumentNullException(nameof(_episode1)); + _episode1.Releases.Add(this); + + if (_track2 == null) throw new ArgumentNullException(nameof(_track2)); + _track2.Releases.Add(this); + + if (_customitem3 == null) throw new ArgumentNullException(nameof(_customitem3)); + _customitem3.Releases.Add(this); + + if (_book4 == null) throw new ArgumentNullException(nameof(_book4)); + _book4.Releases.Add(this); + + if (_photo5 == null) throw new ArgumentNullException(nameof(_photo5)); + _photo5.Releases.Add(this); + + this.MediaFiles = new System.Collections.Generic.HashSet(); + this.Chapters = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + /// + /// + public static Release Create(string name, global::Jellyfin.Data.Entities.Movie _movie0, global::Jellyfin.Data.Entities.Episode _episode1, global::Jellyfin.Data.Entities.Track _track2, global::Jellyfin.Data.Entities.CustomItem _customitem3, global::Jellyfin.Data.Entities.Book _book4, global::Jellyfin.Data.Entities.Photo _photo5) + { + return new Release(name, _movie0, _episode1, _track2, _customitem3, _book4, _photo5); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] Timestamp { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection MediaFiles { get; protected set; } + + public virtual ICollection Chapters { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs new file mode 100644 index 0000000000..fdfdf24091 --- /dev/null +++ b/Jellyfin.Data/Entities/Season.cs @@ -0,0 +1,127 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Season: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Season(): base() + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + SeasonMetadata = new System.Collections.Generic.HashSet(); + Episodes = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Season CreateSeasonUnsafe() + { + return new Season(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public Season(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Series _series0) + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.UrlId = urlid; + + if (_series0 == null) throw new ArgumentNullException(nameof(_series0)); + _series0.Seasons.Add(this); + + this.SeasonMetadata = new System.Collections.Generic.HashSet(); + this.Episodes = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public static Season Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Series _series0) + { + return new Season(urlid, dateadded, _series0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for SeasonNumber + /// + protected int? _SeasonNumber; + /// + /// When provided in a partial class, allows value of SeasonNumber to be changed before setting. + /// + partial void SetSeasonNumber(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of SeasonNumber to be changed before returning. + /// + partial void GetSeasonNumber(ref int? result); + + public int? SeasonNumber + { + get + { + int? value = _SeasonNumber; + GetSeasonNumber(ref value); + return (_SeasonNumber = value); + } + set + { + int? oldValue = _SeasonNumber; + SetSeasonNumber(oldValue, ref value); + if (oldValue != value) + { + _SeasonNumber = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection SeasonMetadata { get; protected set; } + + public virtual ICollection Episodes { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs new file mode 100644 index 0000000000..5939cbbca1 --- /dev/null +++ b/Jellyfin.Data/Entities/SeasonMetadata.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class SeasonMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected SeasonMetadata(): base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static SeasonMetadata CreateSeasonMetadataUnsafe() + { + return new SeasonMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public SeasonMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Season _season0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_season0 == null) throw new ArgumentNullException(nameof(_season0)); + _season0.SeasonMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static SeasonMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Season _season0) + { + return new SeasonMetadata(title, language, dateadded, datemodified, _season0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get + { + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); + } + set + { + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs new file mode 100644 index 0000000000..a57064824c --- /dev/null +++ b/Jellyfin.Data/Entities/Series.cs @@ -0,0 +1,183 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Series: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Series(): base() + { + SeriesMetadata = new System.Collections.Generic.HashSet(); + Seasons = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Series CreateSeriesUnsafe() + { + return new Series(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public Series(Guid urlid, DateTime dateadded) + { + this.UrlId = urlid; + + this.SeriesMetadata = new System.Collections.Generic.HashSet(); + this.Seasons = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Series Create(Guid urlid, DateTime dateadded) + { + return new Series(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for AirsDayOfWeek + /// + protected global::Jellyfin.Data.Enums.Weekday? _AirsDayOfWeek; + /// + /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting. + /// + partial void SetAirsDayOfWeek(global::Jellyfin.Data.Enums.Weekday? oldValue, ref global::Jellyfin.Data.Enums.Weekday? newValue); + /// + /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning. + /// + partial void GetAirsDayOfWeek(ref global::Jellyfin.Data.Enums.Weekday? result); + + public global::Jellyfin.Data.Enums.Weekday? AirsDayOfWeek + { + get + { + global::Jellyfin.Data.Enums.Weekday? value = _AirsDayOfWeek; + GetAirsDayOfWeek(ref value); + return (_AirsDayOfWeek = value); + } + set + { + global::Jellyfin.Data.Enums.Weekday? oldValue = _AirsDayOfWeek; + SetAirsDayOfWeek(oldValue, ref value); + if (oldValue != value) + { + _AirsDayOfWeek = value; + } + } + } + + /// + /// Backing field for AirsTime + /// + protected DateTimeOffset? _AirsTime; + /// + /// When provided in a partial class, allows value of AirsTime to be changed before setting. + /// + partial void SetAirsTime(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); + /// + /// When provided in a partial class, allows value of AirsTime to be changed before returning. + /// + partial void GetAirsTime(ref DateTimeOffset? result); + + /// + /// The time the show airs, ignore the date portion + /// + public DateTimeOffset? AirsTime + { + get + { + DateTimeOffset? value = _AirsTime; + GetAirsTime(ref value); + return (_AirsTime = value); + } + set + { + DateTimeOffset? oldValue = _AirsTime; + SetAirsTime(oldValue, ref value); + if (oldValue != value) + { + _AirsTime = value; + } + } + } + + /// + /// Backing field for FirstAired + /// + protected DateTimeOffset? _FirstAired; + /// + /// When provided in a partial class, allows value of FirstAired to be changed before setting. + /// + partial void SetFirstAired(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); + /// + /// When provided in a partial class, allows value of FirstAired to be changed before returning. + /// + partial void GetFirstAired(ref DateTimeOffset? result); + + public DateTimeOffset? FirstAired + { + get + { + DateTimeOffset? value = _FirstAired; + GetFirstAired(ref value); + return (_FirstAired = value); + } + set + { + DateTimeOffset? oldValue = _FirstAired; + SetFirstAired(oldValue, ref value); + if (oldValue != value) + { + _FirstAired = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection SeriesMetadata { get; protected set; } + + public virtual ICollection Seasons { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs new file mode 100644 index 0000000000..9a91371dfe --- /dev/null +++ b/Jellyfin.Data/Entities/SeriesMetadata.cs @@ -0,0 +1,239 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class SeriesMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected SeriesMetadata(): base() + { + Networks = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static SeriesMetadata CreateSeriesMetadataUnsafe() + { + return new SeriesMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public SeriesMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Series _series0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_series0 == null) throw new ArgumentNullException(nameof(_series0)); + _series0.SeriesMetadata.Add(this); + + this.Networks = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static SeriesMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Series _series0) + { + return new SeriesMetadata(title, language, dateadded, datemodified, _series0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get + { + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); + } + set + { + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } + } + } + + /// + /// Backing field for Plot + /// + protected string _Plot; + /// + /// When provided in a partial class, allows value of Plot to be changed before setting. + /// + partial void SetPlot(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Plot to be changed before returning. + /// + partial void GetPlot(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Plot + { + get + { + string value = _Plot; + GetPlot(ref value); + return (_Plot = value); + } + set + { + string oldValue = _Plot; + SetPlot(oldValue, ref value); + if (oldValue != value) + { + _Plot = value; + } + } + } + + /// + /// Backing field for Tagline + /// + protected string _Tagline; + /// + /// When provided in a partial class, allows value of Tagline to be changed before setting. + /// + partial void SetTagline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Tagline to be changed before returning. + /// + partial void GetTagline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Tagline + { + get + { + string value = _Tagline; + GetTagline(ref value); + return (_Tagline = value); + } + set + { + string oldValue = _Tagline; + SetTagline(oldValue, ref value); + if (oldValue != value) + { + _Tagline = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get + { + string value = _Country; + GetCountry(ref value); + return (_Country = value); + } + set + { + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Networks { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs new file mode 100644 index 0000000000..1d3ad372fb --- /dev/null +++ b/Jellyfin.Data/Entities/Track.cs @@ -0,0 +1,127 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class Track: global::Jellyfin.Data.Entities.LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Track(): base() + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Releases = new System.Collections.Generic.HashSet(); + TrackMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Track CreateTrackUnsafe() + { + return new Track(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public Track(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.UrlId = urlid; + + if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0)); + _musicalbum0.Tracks.Add(this); + + this.Releases = new System.Collections.Generic.HashSet(); + this.TrackMetadata = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public static Track Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) + { + return new Track(urlid, dateadded, _musicalbum0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for TrackNumber + /// + protected int? _TrackNumber; + /// + /// When provided in a partial class, allows value of TrackNumber to be changed before setting. + /// + partial void SetTrackNumber(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of TrackNumber to be changed before returning. + /// + partial void GetTrackNumber(ref int? result); + + public int? TrackNumber + { + get + { + int? value = _TrackNumber; + GetTrackNumber(ref value); + return (_TrackNumber = value); + } + set + { + int? oldValue = _TrackNumber; + SetTrackNumber(oldValue, ref value); + if (oldValue != value) + { + _TrackNumber = value; + } + } + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Releases { get; protected set; } + + public virtual ICollection TrackMetadata { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs new file mode 100644 index 0000000000..f4c61459c8 --- /dev/null +++ b/Jellyfin.Data/Entities/TrackMetadata.cs @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class TrackMetadata: global::Jellyfin.Data.Entities.Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected TrackMetadata(): base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static TrackMetadata CreateTrackMetadataUnsafe() + { + return new TrackMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public TrackMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Track _track0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_track0 == null) throw new ArgumentNullException(nameof(_track0)); + _track0.TrackMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static TrackMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Track _track0) + { + return new TrackMetadata(title, language, dateadded, datemodified, _track0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } +} + diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs new file mode 100644 index 0000000000..2ee3c8f4f2 --- /dev/null +++ b/Jellyfin.Data/Entities/User.cs @@ -0,0 +1,242 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + public partial class User + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected User() + { + Groups = new System.Collections.Generic.HashSet(); + Permissions = new System.Collections.Generic.HashSet(); + ProviderMappings = new System.Collections.Generic.HashSet(); + Preferences = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static User CreateUserUnsafe() + { + return new User(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + /// + /// + public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) + { + if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); + this.Username = username; + + this.MustUpdatePassword = mustupdatepassword; + + if (string.IsNullOrEmpty(audiolanguagepreference)) throw new ArgumentNullException(nameof(audiolanguagepreference)); + this.AudioLanguagePreference = audiolanguagepreference; + + if (string.IsNullOrEmpty(authenticationproviderid)) throw new ArgumentNullException(nameof(authenticationproviderid)); + this.AuthenticationProviderId = authenticationproviderid; + + this.InvalidLoginAttemptCount = invalidloginattemptcount; + + if (string.IsNullOrEmpty(subtitlemode)) throw new ArgumentNullException(nameof(subtitlemode)); + this.SubtitleMode = subtitlemode; + + this.PlayDefaultAudioTrack = playdefaultaudiotrack; + + this.Groups = new System.Collections.Generic.HashSet(); + this.Permissions = new System.Collections.Generic.HashSet(); + this.ProviderMappings = new System.Collections.Generic.HashSet(); + this.Preferences = new System.Collections.Generic.HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + /// + /// + public static User Create(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) + { + return new User(username, mustupdatepassword, audiolanguagepreference, authenticationproviderid, invalidloginattemptcount, subtitlemode, playdefaultaudiotrack); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public Guid Id { get; protected set; } + + /// + /// Required + /// + [ConcurrencyCheck] + [Required] + public byte[] LastLoginTimestamp { get; set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string Username { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Password { get; set; } + + /// + /// Required + /// + [Required] + public bool MustUpdatePassword { get; set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string AudioLanguagePreference { get; set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string AuthenticationProviderId { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string GroupedFolders { get; set; } + + /// + /// Required + /// + [Required] + public int InvalidLoginAttemptCount { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string LatestItemExcludes { get; set; } + + public int? LoginAttemptsBeforeLockout { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string MyMediaExcludes { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string OrderedViews { get; set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string SubtitleMode { get; set; } + + /// + /// Required + /// + [Required] + public bool PlayDefaultAudioTrack { get; set; } + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string SubtitleLanguagePrefernce { get; set; } + + public bool? DisplayMissingEpisodes { get; set; } + + public bool? DisplayCollectionsView { get; set; } + + public bool? HidePlayedInLatest { get; set; } + + public bool? RememberAudioSelections { get; set; } + + public bool? RememberSubtitleSelections { get; set; } + + public bool? EnableNextEpisodeAutoPlay { get; set; } + + public bool? EnableUserPreferenceAccess { get; set; } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual ICollection Groups { get; protected set; } + + public virtual ICollection Permissions { get; protected set; } + + public virtual ICollection ProviderMappings { get; protected set; } + + public virtual ICollection Preferences { get; protected set; } + + } +} + diff --git a/Jellyfin.Data/Enums/ArtKind.cs b/Jellyfin.Data/Enums/ArtKind.cs new file mode 100644 index 0000000000..52e33048e2 --- /dev/null +++ b/Jellyfin.Data/Enums/ArtKind.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; + +namespace Jellyfin.Data.Enums +{ + public enum ArtKind : Int32 + { + Other, + Poster, + Banner, + Thumbnail, + Logo + } +} diff --git a/Jellyfin.Data/Enums/MediaFileKind.cs b/Jellyfin.Data/Enums/MediaFileKind.cs new file mode 100644 index 0000000000..34d1b20f59 --- /dev/null +++ b/Jellyfin.Data/Enums/MediaFileKind.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; + +namespace Jellyfin.Data.Enums +{ + public enum MediaFileKind : Int32 + { + Main, + Sidecar, + AdditionalPart, + AlternativeFormat, + AdditionalStream + } +} diff --git a/Jellyfin.Data/Enums/PersonRoleType.cs b/Jellyfin.Data/Enums/PersonRoleType.cs new file mode 100644 index 0000000000..f5c8f43c51 --- /dev/null +++ b/Jellyfin.Data/Enums/PersonRoleType.cs @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; + +namespace Jellyfin.Data.Enums +{ + public enum PersonRoleType : Int32 + { + Other, + Director, + Artist, + OriginalArtist, + Actor, + VoiceActor, + Producer, + Remixer, + Conductor, + Composer, + Author, + Editor + } +} diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs new file mode 100644 index 0000000000..ce0c6e4ce8 --- /dev/null +++ b/Jellyfin.Data/Enums/Weekday.cs @@ -0,0 +1,27 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +// Produced by Entity Framework Visual Editor +// https://github.com/msawczyn/EFDesigner +// +//------------------------------------------------------------------------------ + +using System; + +namespace Jellyfin.Data.Enums +{ + public enum Weekday : Int32 + { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday + } +} diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj new file mode 100644 index 0000000000..73ea593b0b --- /dev/null +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/Jellyfin.Data/Structs/.gitkeep b/Jellyfin.Data/Structs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 270cdeaaf5..88114d9994 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -43,6 +43,8 @@ + + diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 3120999bfa..fba1db584c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -158,23 +158,7 @@ namespace Jellyfin.Server ApplicationHost.LogEnvironmentInfo(_logger, appPaths); - // Make sure we have all the code pages we can get - // Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - // Increase the max http request limit - // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. - ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); - - // Disable the "Expect: 100-Continue" header by default - // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c - ServicePointManager.Expect100Continue = false; - - Batteries_V2.Init(); - if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK) - { - _logger.LogWarning("Failed to enable shared cache for SQLite"); - } + PerformStaticInitialization(); var appHost = new CoreAppHost( appPaths, @@ -202,7 +186,7 @@ namespace Jellyfin.Server ServiceCollection serviceCollection = new ServiceCollection(); appHost.Init(serviceCollection); - var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build(); + var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build(); // Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = webHost.Services; @@ -247,14 +231,49 @@ namespace Jellyfin.Server } } - private static IWebHostBuilder CreateWebHostBuilder( + /// + /// Call static initialization methods for the application. + /// + public static void PerformStaticInitialization() + { + // Make sure we have all the code pages we can get + // Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + // Increase the max http request limit + // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. + ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); + + // Disable the "Expect: 100-Continue" header by default + // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c + ServicePointManager.Expect100Continue = false; + + Batteries_V2.Init(); + if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK) + { + _logger.LogWarning("Failed to enable shared cache for SQLite"); + } + } + + /// + /// Configure the web host builder. + /// + /// The builder to configure. + /// The application host. + /// The application service collection. + /// The command line options passed to the application. + /// The application configuration. + /// The application paths. + /// The configured web host builder. + public static IWebHostBuilder ConfigureWebHostBuilder( + this IWebHostBuilder builder, ApplicationHost appHost, IServiceCollection serviceCollection, StartupOptions commandLineOpts, IConfiguration startupConfig, IApplicationPaths appPaths) { - return new WebHostBuilder() + return builder .UseKestrel((builderContext, options) => { var addresses = appHost.ServerConfigurationManager @@ -275,8 +294,7 @@ namespace Jellyfin.Server { _logger.LogInformation("Kestrel listening on {IpAddress}", address); options.Listen(address, appHost.HttpPort); - - if (appHost.ListenWithHttps) + if (appHost.ListenWithHttps && appHost.Certificate != null) { options.Listen(address, appHost.HttpsPort, listenOptions => { @@ -286,11 +304,18 @@ namespace Jellyfin.Server } else if (builderContext.HostingEnvironment.IsDevelopment()) { - options.Listen(address, appHost.HttpsPort, listenOptions => + try { - listenOptions.UseHttps(); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); + options.Listen(address, appHost.HttpsPort, listenOptions => + { + listenOptions.UseHttps(); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted."); + } } } } @@ -309,11 +334,18 @@ namespace Jellyfin.Server } else if (builderContext.HostingEnvironment.IsDevelopment()) { - options.ListenAnyIP(appHost.HttpsPort, listenOptions => + try { - listenOptions.UseHttps(); - listenOptions.Protocols = HttpProtocols.Http1AndHttp2; - }); + options.ListenAnyIP(appHost.HttpsPort, listenOptions => + { + listenOptions.UseHttps(); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted."); + } } } }) @@ -493,7 +525,9 @@ namespace Jellyfin.Server /// Initialize the logging configuration file using the bundled resource file as a default if it doesn't exist /// already. /// - private static async Task InitLoggingConfigFile(IApplicationPaths appPaths) + /// The application paths. + /// A task representing the creation of the configuration file, or a completed task if the file already exists. + public static async Task InitLoggingConfigFile(IApplicationPaths appPaths) { // Do nothing if the config file already exists string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault); @@ -513,7 +547,13 @@ namespace Jellyfin.Server await resource.CopyToAsync(dst).ConfigureAwait(false); } - private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths) + /// + /// Create the application configuration. + /// + /// The command line options passed to the program. + /// The application paths. + /// The application configuration. + public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths) { return new ConfigurationBuilder() .ConfigureAppConfiguration(commandLineOpts, appPaths) diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 4d7d56e9d4..8bcfd13504 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Prometheus; namespace Jellyfin.Server { @@ -69,9 +70,19 @@ namespace Jellyfin.Server app.UseJellyfinApiSwagger(); app.UseRouting(); app.UseAuthorization(); + if (_serverConfigurationManager.Configuration.EnableMetrics) + { + // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad + app.UseHttpMetrics(); + } + app.UseEndpoints(endpoints => { endpoints.MapControllers(); + if (_serverConfigurationManager.Configuration.EnableMetrics) + { + endpoints.MapMetrics(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/metrics"); + } }); app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index 222bb34d31..23bf547125 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -265,17 +265,20 @@ namespace MediaBrowser.Api.Images { Url = url, BufferContent = false - }).ConfigureAwait(false); - var ext = result.ContentType.Split('/').Last(); + var ext = result.ContentType.Split('/')[^1]; var fullCachePath = GetFullCachePath(urlHash + "." + ext); Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) + var stream = result.Content; + await using (stream.ConfigureAwait(false)) { - using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(filestream).ConfigureAwait(false); + var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + await using (filestream.ConfigureAwait(false)) + { + await stream.CopyToAsync(filestream).ConfigureAwait(false); + } } Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 0bbe7e1cfa..68e3dfa59c 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -299,22 +299,26 @@ namespace MediaBrowser.Api { var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false); - var ext = result.ContentType.Split('/').Last(); + var ext = result.ContentType.Split('/')[^1]; var fullCachePath = GetFullCachePath(urlHash + "." + ext); Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) + var stream = result.Content; + + await using (stream.ConfigureAwait(false)) { - using var fileStream = new FileStream( + var fileStream = new FileStream( fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - - await stream.CopyToAsync(fileStream).ConfigureAwait(false); + await using (fileStream.ConfigureAwait(false)) + { + await stream.CopyToAsync(fileStream).ConfigureAwait(false); + } } Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 7705393576..928ca16128 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -321,7 +321,7 @@ namespace MediaBrowser.Api.Playback var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); // enable throttling when NOT using hardware acceleration - if (encodingOptions.HardwareAccelerationType == string.Empty) + if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { return state.InputProtocol == MediaProtocol.File && state.RunTimeTicks.HasValue && @@ -330,6 +330,7 @@ namespace MediaBrowser.Api.Playback state.VideoType == VideoType.VideoFile && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase); } + return false; } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 52962366c6..4213193bac 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -209,24 +209,28 @@ namespace MediaBrowser.Api.Playback.Hls try { // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using var fileStream = GetPlaylistFileStream(playlist); - using var reader = new StreamReader(fileStream); - var count = 0; - - while (!reader.EndOfStream) + var fileStream = GetPlaylistFileStream(playlist); + await using (fileStream.ConfigureAwait(false)) { - var line = reader.ReadLine(); + using var reader = new StreamReader(fileStream); + var count = 0; - if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) + while (!reader.EndOfStream) { - count++; - if (count >= segmentCount) + var line = await reader.ReadLineAsync().ConfigureAwait(false); + + if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) { - Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist); - return; + count++; + if (count >= segmentCount) + { + Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist); + return; + } } } } + await Task.Delay(100, cancellationToken).ConfigureAwait(false); } catch (IOException) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 20e18cc265..7f74e85e9b 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -720,22 +720,203 @@ namespace MediaBrowser.Api.Playback.Hls //return state.VideoRequest.VideoBitRate.HasValue; } + /// + /// Get the H.26X level of the output video stream. + /// + /// StreamState of the current stream. + /// H.26X level of the output video stream. + private int? GetOutputVideoCodecLevel(StreamState state) + { + string levelString; + if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) + && state.VideoStream.Level.HasValue) + { + levelString = state.VideoStream?.Level.ToString(); + } + else + { + levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec); + } + + if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel)) + { + return parsedLevel; + } + + return null; + } + + /// + /// Gets a formatted string of the output audio codec, for use in the CODECS field. + /// + /// + /// + /// StreamState of the current stream. + /// Formatted audio codec string. + private string GetPlaylistAudioCodecs(StreamState state) + { + + if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase)) + { + string profile = state.GetRequestedProfiles("aac").FirstOrDefault(); + + return HlsCodecStringFactory.GetAACString(profile); + } + else if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringFactory.GetMP3String(); + } + else if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringFactory.GetAC3String(); + } + else if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringFactory.GetEAC3String(); + } + + return string.Empty; + } + + /// + /// Gets a formatted string of the output video codec, for use in the CODECS field. + /// + /// + /// + /// StreamState of the current stream. + /// Formatted video codec string. + private string GetPlaylistVideoCodecs(StreamState state, string codec, int level) + { + if (level == 0) + { + // This is 0 when there's no requested H.26X level in the device profile + // and the source is not encoded in H.26X + Logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist"); + return string.Empty; + } + + if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + string profile = state.GetRequestedProfiles("h264").FirstOrDefault(); + + return HlsCodecStringFactory.GetH264String(profile, level); + } + else if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + string profile = state.GetRequestedProfiles("h265").FirstOrDefault(); + + return HlsCodecStringFactory.GetH265String(profile, level); + } + + return string.Empty; + } + + /// + /// Appends a CODECS field containing formatted strings of + /// the active streams output video and audio codecs. + /// + /// + /// + /// + /// StringBuilder to append the field to. + /// StreamState of the current stream. + private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state) + { + // Video + string videoCodecs = string.Empty; + int? videoCodecLevel = GetOutputVideoCodecLevel(state); + if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue) + { + videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value); + } + + // Audio + string audioCodecs = string.Empty; + if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec)) + { + audioCodecs = GetPlaylistAudioCodecs(state); + } + + StringBuilder codecs = new StringBuilder(); + + codecs.Append(videoCodecs) + .Append(',') + .Append(audioCodecs); + + if (codecs.Length > 1) + { + builder.Append(",CODECS=\"") + .Append(codecs) + .Append('"'); + } + } + + /// + /// Appends a FRAME-RATE field containing the framerate of the output stream. + /// + /// + /// StringBuilder to append the field to. + /// StreamState of the current stream. + private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state) + { + double? framerate = null; + if (state.TargetFramerate.HasValue) + { + framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3); + } + else if (state.VideoStream.RealFrameRate.HasValue) + { + framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3); + } + + if (framerate.HasValue) + { + builder.Append(",FRAME-RATE=\"") + .Append(framerate.Value) + .Append('"'); + } + } + + /// + /// Appends a RESOLUTION field containing the resolution of the output stream. + /// + /// + /// StringBuilder to append the field to. + /// StreamState of the current stream. + private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state) + { + if (state.OutputWidth.HasValue && state.OutputHeight.HasValue) + { + builder.Append(",RESOLUTION=\"") + .Append(state.OutputWidth.GetValueOrDefault()) + .Append('x') + .Append(state.OutputHeight.GetValueOrDefault()) + .Append('"'); + } + } + private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup) { - var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture); + builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=") + .Append(bitrate.ToString(CultureInfo.InvariantCulture)) + .Append(",AVERAGE-BANDWIDTH=") + .Append(bitrate.ToString(CultureInfo.InvariantCulture)); - // tvos wants resolution, codecs, framerate - //if (state.TargetFramerate.HasValue) - //{ - // header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture)); - //} + AppendPlaylistCodecsField(builder, state); + + AppendPlaylistResolutionField(builder, state); + + AppendPlaylistFramerateField(builder, state); if (!string.IsNullOrWhiteSpace(subtitleGroup)) { - header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup); + builder.Append(",SUBTITLES=\"") + .Append(subtitleGroup) + .Append('"'); } - builder.AppendLine(header); + builder.Append(Environment.NewLine); builder.AppendLine(url); } diff --git a/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs new file mode 100644 index 0000000000..3bbb77a65e --- /dev/null +++ b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs @@ -0,0 +1,126 @@ +using System; +using System.Text; + + +namespace MediaBrowser.Api.Playback +{ + /// + /// Get various codec strings for use in HLS playlists. + /// + static class HlsCodecStringFactory + { + + /// + /// Gets a MP3 codec string. + /// + /// MP3 codec string. + public static string GetMP3String() + { + return "mp4a.40.34"; + } + + /// + /// Gets an AAC codec string. + /// + /// AAC profile. + /// AAC codec string. + public static string GetAACString(string profile) + { + StringBuilder result = new StringBuilder("mp4a", 9); + + if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".40.5"); + } + else + { + // Default to LC if profile is invalid + result.Append(".40.2"); + } + + return result.ToString(); + } + + /// + /// Gets a H.264 codec string. + /// + /// H.264 profile. + /// H.264 level. + /// H.264 string. + public static string GetH264String(string profile, int level) + { + StringBuilder result = new StringBuilder("avc1", 11); + + if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".6400"); + } + else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".4D40"); + } + else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".42E0"); + } + else + { + // Default to constrained baseline if profile is invalid + result.Append(".4240"); + } + + string levelHex = level.ToString("X2"); + result.Append(levelHex); + + return result.ToString(); + } + + /// + /// Gets a H.265 codec string. + /// + /// H.265 profile. + /// H.265 level. + /// H.265 string. + public static string GetH265String(string profile, int level) + { + // The h265 syntax is a bit of a mystery at the time this comment was written. + // This is what I've found through various sources: + // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN] + StringBuilder result = new StringBuilder("hev1", 16); + + if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase)) + { + result.Append(".2.6"); + } + else + { + // Default to main if profile is invalid + result.Append(".1.6"); + } + + result.Append(".L") + .Append(level * 3) + .Append(".B0"); + + return result.ToString(); + } + + /// + /// Gets an AC-3 codec string. + /// + /// AC-3 codec string. + public static string GetAC3String() + { + return "mp4a.a5"; + } + + /// + /// Gets an E-AC-3 codec string. + /// + /// E-AC-3 codec string. + public static string GetEAC3String() + { + return "mp4a.a6"; + } + } +} diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 78fc6c6941..7d4d5fcf97 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Api } [Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")] - public class GetPublicUsers : IReturn + public class GetPublicUsers : IReturn { } @@ -266,22 +266,38 @@ namespace MediaBrowser.Api _authContext = authContext; } + /// + /// Gets the public available Users information + /// + /// The request. + /// System.Object. public object Get(GetPublicUsers request) { - // If the startup wizard hasn't been completed then just return all users - if (!ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + var result = _userManager + .Users + .Where(item => !item.Policy.IsDisabled); + + if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { - return Get(new GetUsers + var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; + result = result.Where(item => !item.Policy.IsHidden); + + if (!string.IsNullOrWhiteSpace(deviceId)) { - IsDisabled = false - }); + result = result.Where(i => _deviceManager.CanAccessDevice(i, deviceId)); + } + + if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) + { + result = result.Where(i => i.Policy.EnableRemoteAccess); + } } - return Get(new GetUsers - { - IsHidden = false, - IsDisabled = false - }, true, true); + return ToOptimizedResult(result + .OrderBy(u => u.Name) + .Select(i => _userManager.GetPublicUserDto(i, Request.RemoteIp)) + .ToArray() + ); } /// diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index 08964420e7..40020093b6 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Security.Cryptography; using System.Text; diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs index 2ecbc6539b..94bf7c7401 100644 --- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs +++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs @@ -1,3 +1,5 @@ +#nullable enable + using System.Collections.Generic; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs index 48e758ee4c..258bd6662c 100644 --- a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs +++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs index c747871222..2f52ba196a 100644 --- a/MediaBrowser.Common/Extensions/ProcessExtensions.cs +++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Diagnostics; using System.Threading; diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs index 95802a4626..7c7bdaa92f 100644 --- a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs +++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs @@ -1,3 +1,4 @@ +#nullable enable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs index 22130c5a1e..ebac9d8e6b 100644 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/StringExtensions.cs b/MediaBrowser.Common/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..7643017412 --- /dev/null +++ b/MediaBrowser.Common/Extensions/StringExtensions.cs @@ -0,0 +1,37 @@ +#nullable enable + +using System; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Extensions methods to simplify string operations. + /// + public static class StringExtensions + { + /// + /// Returns the part on the left of the needle. + /// + /// The string to seek. + /// The needle to find. + /// The part left of the . + public static ReadOnlySpan LeftPart(this ReadOnlySpan haystack, char needle) + { + var pos = haystack.IndexOf(needle); + return pos == -1 ? haystack : haystack[..pos]; + } + + /// + /// Returns the part on the left of the needle. + /// + /// The string to seek. + /// The needle to find. + /// One of the enumeration values that specifies the rules for the search. + /// The part left of the needle. + public static ReadOnlySpan LeftPart(this ReadOnlySpan haystack, ReadOnlySpan needle, StringComparison stringComparison = default) + { + var pos = haystack.IndexOf(needle, stringComparison); + return pos == -1 ? haystack : haystack[..pos]; + } + } +} diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index be7b4ce59d..ec6cb35eb9 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -143,6 +143,14 @@ namespace MediaBrowser.Controller.Library /// UserDto. UserDto GetUserDto(User user, string remoteEndPoint = null); + /// + /// Gets the user public dto. + /// + /// Ther user.\ + /// The remote end point. + /// A public UserDto, aka a UserDto stripped of personal data. + PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null); + /// /// Authenticates the user. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5efb10d3a9..61a3306756 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2547,7 +2547,7 @@ namespace MediaBrowser.Controller.MediaEncoding encodingOptions.HardwareDecodingCodecs = Array.Empty(); return null; } - return "-c:v h264_qsv "; + return "-c:v h264_qsv"; } break; case "hevc": @@ -2555,19 +2555,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { //return "-c:v hevc_qsv -load_plugin hevc_hw "; - return "-c:v hevc_qsv "; + return "-c:v hevc_qsv"; } break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg2_qsv "; + return "-c:v mpeg2_qsv"; } break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vc1_qsv "; + return "-c:v vc1_qsv"; } break; } @@ -2587,32 +2587,32 @@ namespace MediaBrowser.Controller.MediaEncoding encodingOptions.HardwareDecodingCodecs = Array.Empty(); return null; } - return "-c:v h264_cuvid "; + return "-c:v h264_cuvid"; } break; case "hevc": case "h265": if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - return "-c:v hevc_cuvid "; + return "-c:v hevc_cuvid"; } break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg2_cuvid "; + return "-c:v mpeg2_cuvid"; } break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vc1_cuvid "; + return "-c:v vc1_cuvid"; } break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg4_cuvid "; + return "-c:v mpeg4_cuvid"; } break; } @@ -2626,38 +2626,38 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) { - return "-c:v h264_mediacodec "; + return "-c:v h264_mediacodec"; } break; case "hevc": case "h265": if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - return "-c:v hevc_mediacodec "; + return "-c:v hevc_mediacodec"; } break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg2_mediacodec "; + return "-c:v mpeg2_mediacodec"; } break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg4_mediacodec "; + return "-c:v mpeg4_mediacodec"; } break; case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vp8_mediacodec "; + return "-c:v vp8_mediacodec"; } break; case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vp9_mediacodec "; + return "-c:v vp9_mediacodec"; } break; } @@ -2671,25 +2671,25 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) { - return "-c:v h264_mmal "; + return "-c:v h264_mmal"; } break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg2_mmal "; + return "-c:v mpeg2_mmal"; } break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg4_mmal "; + return "-c:v mpeg4_mmal"; } break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vc1_mmal "; + return "-c:v vc1_mmal"; } break; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 992ad146d8..1377502dd9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -113,7 +113,7 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableEncoders(validator.GetEncoders()); } - _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty); + _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty); } /// @@ -126,7 +126,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { string newPath; - _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); + _logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty); if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) { @@ -180,7 +180,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!rc) { - _logger.LogWarning("FFmpeg: {0}: Failed version check: {1}", location, path); + _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path); } // ToDo - Enable the ffmpeg validator. At the moment any version can be used. @@ -191,18 +191,18 @@ namespace MediaBrowser.MediaEncoding.Encoder } else { - _logger.LogWarning("FFmpeg: {0}: File not found: {1}", location, path); + _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path); } } return rc; } - private string GetEncoderPathFromDirectory(string path, string filename) + private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false) { try { - var files = _fileSystem.GetFilePaths(path); + var files = _fileSystem.GetFilePaths(path, recursive); var excludeExtensions = new[] { ".c" }; @@ -223,7 +223,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// private string ExistsOnSystemPath(string fileName) { - string inJellyfinPath = GetEncoderPathFromDirectory(System.AppContext.BaseDirectory, fileName); + var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true); if (!string.IsNullOrEmpty(inJellyfinPath)) { return inJellyfinPath; @@ -892,7 +892,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName); } - _logger.LogWarning("Could not determine vob file list for {0} using DvdLib. Will scan using file sizes.", path); + _logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path); } var files = allVobs diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index e4cd6c34ad..22a42322a6 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -19,6 +19,11 @@ namespace MediaBrowser.Model.Configuration /// public bool EnableUPnP { get; set; } + /// + /// Gets or sets a value indicating whether to enable prometheus metrics exporting. + /// + public bool EnableMetrics { get; set; } + /// /// Gets or sets the public mapped port. /// @@ -268,6 +273,7 @@ namespace MediaBrowser.Model.Configuration PublicHttpsPort = DefaultHttpsPort; HttpServerPortNumber = DefaultHttpPort; HttpsPortNumber = DefaultHttpsPort; + EnableMetrics = false; EnableHttps = false; EnableDashboardResponseCaching = true; EnableCaseSensitiveItemIds = true; diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index 756e500dd7..7bb961deb2 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Xml.Serialization; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dlna { @@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Dlna foreach (var val in codec) { - if (ListHelper.ContainsIgnoreCase(codecs, val)) + if (codecs.Contains(val, StringComparer.OrdinalIgnoreCase)) { return true; } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 7423efaf65..0c3bd88829 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Globalization; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna @@ -167,9 +167,7 @@ namespace MediaBrowser.Model.Dlna switch (condition.Condition) { case ProfileConditionType.EqualsAny: - { - return ListHelper.ContainsIgnoreCase(expected.Split('|'), currentValue); - } + return expected.Split('|').Contains(currentValue, StringComparer.OrdinalIgnoreCase); case ProfileConditionType.Equals: return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase); case ProfileConditionType.NotEquals: diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index e6691c5139..cc2417a709 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Xml.Serialization; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dlna { @@ -45,7 +45,7 @@ namespace MediaBrowser.Model.Dlna public static bool ContainsContainer(string profileContainers, string inputContainer) { var isNegativeList = false; - if (profileContainers != null && profileContainers.StartsWith("-")) + if (profileContainers != null && profileContainers.StartsWith("-", StringComparison.Ordinal)) { isNegativeList = true; profileContainers = profileContainers.Substring(1); @@ -72,7 +72,7 @@ namespace MediaBrowser.Model.Dlna foreach (var container in allInputContainers) { - if (ListHelper.ContainsIgnoreCase(profileContainers, container)) + if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) { return false; } @@ -86,7 +86,7 @@ namespace MediaBrowser.Model.Dlna foreach (var container in allInputContainers) { - if (ListHelper.ContainsIgnoreCase(profileContainers, container)) + if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) { return true; } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 0cefbbe012..3813ac5ebb 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Xml.Serialization; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna @@ -93,14 +93,14 @@ namespace MediaBrowser.Model.Dlna public DeviceProfile() { - DirectPlayProfiles = new DirectPlayProfile[] { }; - TranscodingProfiles = new TranscodingProfile[] { }; - ResponseProfiles = new ResponseProfile[] { }; - CodecProfiles = new CodecProfile[] { }; - ContainerProfiles = new ContainerProfile[] { }; + DirectPlayProfiles = Array.Empty(); + TranscodingProfiles = Array.Empty(); + ResponseProfiles = Array.Empty(); + CodecProfiles = Array.Empty(); + ContainerProfiles = Array.Empty(); SubtitleProfiles = Array.Empty(); - XmlRootAttributes = new XmlAttribute[] { }; + XmlRootAttributes = Array.Empty(); SupportedMediaTypes = "Audio,Photo,Video"; MaxStreamingBitrate = 8000000; @@ -129,13 +129,14 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!ListHelper.ContainsIgnoreCase(i.GetAudioCodecs(), audioCodec ?? string.Empty)) + if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } return i; } + return null; } @@ -155,7 +156,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!ListHelper.ContainsIgnoreCase(i.GetAudioCodecs(), audioCodec ?? string.Empty)) + if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } @@ -185,7 +186,7 @@ namespace MediaBrowser.Model.Dlna } var audioCodecs = i.GetAudioCodecs(); - if (audioCodecs.Length > 0 && !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec ?? string.Empty)) + if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } @@ -288,13 +289,13 @@ namespace MediaBrowser.Model.Dlna } var audioCodecs = i.GetAudioCodecs(); - if (audioCodecs.Length > 0 && !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec ?? string.Empty)) + if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } var videoCodecs = i.GetVideoCodecs(); - if (videoCodecs.Length > 0 && !ListHelper.ContainsIgnoreCase(videoCodecs, videoCodec ?? string.Empty)) + if (videoCodecs.Length > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs index 6a8f655ac5..9c28019aad 100644 --- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs +++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs @@ -1,7 +1,8 @@ #pragma warning disable CS1591 +using System; +using System.Linq; using System.Xml.Serialization; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dlna { @@ -40,7 +41,7 @@ namespace MediaBrowser.Model.Dlna } var languages = GetLanguages(); - return languages.Length == 0 || ListHelper.ContainsIgnoreCase(languages, subLanguage); + return languages.Length == 0 || languages.Contains(subLanguage, StringComparer.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.Model/Dto/PublicUserDto.cs b/MediaBrowser.Model/Dto/PublicUserDto.cs new file mode 100644 index 0000000000..b6bfaf2e9b --- /dev/null +++ b/MediaBrowser.Model/Dto/PublicUserDto.cs @@ -0,0 +1,48 @@ +using System; + +namespace MediaBrowser.Model.Dto +{ + /// + /// Class PublicUserDto. Its goal is to show only public information about a user + /// + public class PublicUserDto : IItemDto + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the primary image tag. + /// + /// The primary image tag. + public string PrimaryImageTag { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has password. + /// + /// true if this instance has password; otherwise, false. + public bool HasPassword { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has configured password. + /// Note that in this case this method should not be here, but it is necessary when changing password at the + /// first login. + /// + /// true if this instance has configured password; otherwise, false. + public bool HasConfiguredPassword { get; set; } + + /// + /// Gets or sets the primary image aspect ratio. + /// + /// The primary image aspect ratio. + public double? PrimaryImageAspectRatio { get; set; } + + /// + public override string ToString() + { + return Name ?? base.ToString(); + } + } +} diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index cd387bd546..922eb4ca79 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Gets a provider id + /// Gets a provider id. /// /// The instance. /// The provider. @@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Gets a provider id + /// Gets a provider id. /// /// The instance. /// The name. @@ -53,7 +53,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Sets a provider id + /// Sets a provider id. /// /// The instance. /// The name. @@ -89,7 +89,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Sets a provider id + /// Sets a provider id. /// /// The instance. /// The provider. diff --git a/MediaBrowser.Model/Extensions/ListHelper.cs b/MediaBrowser.Model/Extensions/ListHelper.cs deleted file mode 100644 index 90ce6f2e5e..0000000000 --- a/MediaBrowser.Model/Extensions/ListHelper.cs +++ /dev/null @@ -1,27 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Extensions -{ - // TODO: @bond remove - public static class ListHelper - { - public static bool ContainsIgnoreCase(string[] list, string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - foreach (var item in list) - { - if (string.Equals(item, value, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - return false; - } - } -} diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 06efedb300..fe2fbe7e4f 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -17,115 +17,132 @@ namespace MediaBrowser.Model.Net /// private static readonly HashSet _videoFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { - ".mkv", - ".m2t", - ".m2ts", - ".img", - ".iso", - ".mk3d", - ".ts", - ".rmvb", - ".mov", + ".3gp", + ".asf", ".avi", - ".mpg", - ".mpeg", - ".wmv", - ".mp4", ".divx", ".dvr-ms", - ".wtv", + ".f4v", + ".flv", + ".img", + ".iso", + ".m2t", + ".m2ts", + ".m2v", + ".m4v", + ".mk3d", + ".mkv", + ".mov", + ".mp4", + ".mpg", + ".mpeg", + ".mts", + ".ogg", ".ogm", ".ogv", - ".asf", - ".m4v", - ".flv", - ".f4v", - ".3gp", + ".rec", + ".ts", + ".rmvb", ".webm", - ".mts", - ".m2v", - ".rec" + ".wmv", + ".wtv", }; // http://en.wikipedia.org/wiki/Internet_media_type + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + // http://www.iana.org/assignments/media-types/media-types.xhtml // Add more as needed private static readonly Dictionary _mimeTypeLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { // Type application + { ".7z", "application/x-7z-compressed" }, + { ".azw", "application/vnd.amazon.ebook" }, + { ".azw3", "application/vnd.amazon.ebook" }, { ".cbz", "application/x-cbz" }, { ".cbr", "application/epub+zip" }, { ".eot", "application/vnd.ms-fontobject" }, { ".epub", "application/epub+zip" }, { ".js", "application/x-javascript" }, { ".json", "application/json" }, - { ".map", "application/x-javascript" }, - { ".pdf", "application/pdf" }, - { ".ttml", "application/ttml+xml" }, { ".m3u8", "application/x-mpegURL" }, + { ".map", "application/x-javascript" }, { ".mobi", "application/x-mobipocket-ebook" }, - { ".xml", "application/xml" }, + { ".pdf", "application/pdf" }, + { ".rar", "application/vnd.rar" }, + { ".srt", "application/x-subrip" }, + { ".ttml", "application/ttml+xml" }, { ".wasm", "application/wasm" }, + { ".xml", "application/xml" }, + { ".zip", "application/zip" }, // Type image + { ".bmp", "image/bmp" }, + { ".gif", "image/gif" }, + { ".ico", "image/vnd.microsoft.icon" }, { ".jpg", "image/jpeg" }, { ".jpeg", "image/jpeg" }, - { ".tbn", "image/jpeg" }, { ".png", "image/png" }, - { ".gif", "image/gif" }, - { ".tiff", "image/tiff" }, - { ".webp", "image/webp" }, - { ".ico", "image/vnd.microsoft.icon" }, { ".svg", "image/svg+xml" }, { ".svgz", "image/svg+xml" }, + { ".tbn", "image/jpeg" }, + { ".tif", "image/tiff" }, + { ".tiff", "image/tiff" }, + { ".webp", "image/webp" }, // Type font { ".ttf" , "font/ttf" }, { ".woff" , "font/woff" }, + { ".woff2" , "font/woff2" }, // Type text { ".ass", "text/x-ssa" }, { ".ssa", "text/x-ssa" }, { ".css", "text/css" }, { ".csv", "text/csv" }, + { ".rtf", "text/rtf" }, { ".txt", "text/plain" }, { ".vtt", "text/vtt" }, // Type video - { ".mpg", "video/mpeg" }, - { ".ogv", "video/ogg" }, - { ".mov", "video/quicktime" }, - { ".webm", "video/webm" }, - { ".mkv", "video/x-matroska" }, - { ".wmv", "video/x-ms-wmv" }, - { ".flv", "video/x-flv" }, - { ".avi", "video/x-msvideo" }, - { ".asf", "video/x-ms-asf" }, - { ".m4v", "video/x-m4v" }, - { ".m4s", "video/mp4" }, { ".3gp", "video/3gpp" }, { ".3g2", "video/3gpp2" }, - { ".mpd", "video/vnd.mpeg.dash.mpd" }, - { ".ts", "video/mp2t" }, + { ".asf", "video/x-ms-asf" }, + { ".avi", "video/x-msvideo" }, + { ".flv", "video/x-flv" }, + { ".mp4", "video/mp4" }, + { ".m4s", "video/mp4" }, + { ".m4v", "video/x-m4v" }, { ".mpegts", "video/mp2t" }, + { ".mpg", "video/mpeg" }, + { ".mkv", "video/x-matroska" }, + { ".mov", "video/quicktime" }, + { ".mpd", "video/vnd.mpeg.dash.mpd" }, + { ".ogv", "video/ogg" }, + { ".ts", "video/mp2t" }, + { ".webm", "video/webm" }, + { ".wmv", "video/x-ms-wmv" }, // Type audio - { ".mp3", "audio/mpeg" }, - { ".m4a", "audio/mp4" }, { ".aac", "audio/mp4" }, - { ".webma", "audio/webm" }, - { ".wav", "audio/wav" }, - { ".wma", "audio/x-ms-wma" }, - { ".ogg", "audio/ogg" }, - { ".oga", "audio/ogg" }, - { ".opus", "audio/ogg" }, { ".ac3", "audio/ac3" }, + { ".ape", "audio/x-ape" }, { ".dsf", "audio/dsf" }, - { ".m4b", "audio/m4b" }, - { ".xsp", "audio/xsp" }, { ".dsp", "audio/dsp" }, { ".flac", "audio/flac" }, - { ".ape", "audio/x-ape" }, + { ".m4a", "audio/mp4" }, + { ".m4b", "audio/m4b" }, + { ".mid", "audio/midi" }, + { ".midi", "audio/midi" }, + { ".mp3", "audio/mpeg" }, + { ".oga", "audio/ogg" }, + { ".ogg", "audio/ogg" }, + { ".opus", "audio/ogg" }, + { ".vorbis", "audio/vorbis" }, + { ".wav", "audio/wav" }, + { ".webma", "audio/webm" }, + { ".wma", "audio/x-ms-wma" }, { ".wv", "audio/x-wavpack" }, + { ".xsp", "audio/xsp" }, }; private static readonly Dictionary _extensionLookup = CreateExtensionLookup(); diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 79a128e9be..9c54bd70e0 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Extensions; +using System.Linq; using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications @@ -81,8 +81,12 @@ namespace MediaBrowser.Model.Notifications { foreach (NotificationOption i in Options) { - if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) return i; + if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) + { + return i; + } } + return null; } @@ -98,7 +102,7 @@ namespace MediaBrowser.Model.Notifications NotificationOption opt = GetOptions(notificationType); return opt == null || - !ListHelper.ContainsIgnoreCase(opt.DisabledServices, service); + !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase); } public bool IsEnabledToMonitorUser(string type, Guid userId) @@ -106,7 +110,7 @@ namespace MediaBrowser.Model.Notifications NotificationOption opt = GetOptions(type); return opt != null && opt.Enabled && - !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId.ToString("")); + !opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase); } public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy) @@ -125,7 +129,7 @@ namespace MediaBrowser.Model.Notifications return true; } - return ListHelper.ContainsIgnoreCase(opt.SendToUsers, userId); + return opt.SendToUsers.Contains(userId, StringComparer.OrdinalIgnoreCase); } return false; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 1c84622ac0..6a22fd02f5 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,7 +1,9 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" @@ -36,31 +38,37 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + jellyfin.ruleset = jellyfin.ruleset SharedVersion.cs = SharedVersion.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}" + ProjectSection(SolutionItems) = preProject + tests\jellyfin-tests.ruleset = tests\jellyfin-tests.ruleset + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api.Tests", "tests\MediaBrowser.Api.Tests\MediaBrowser.Api.Tests.csproj", "{7C93C84F-105C-48E5-A878-406FA0A5B296}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -112,10 +120,6 @@ Global {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -176,6 +180,14 @@ Global {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.Build.0 = Release|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU + {7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -208,5 +220,6 @@ Global {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {7C93C84F-105C-48E5-A878-406FA0A5B296} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection EndGlobal diff --git a/SharedVersion.cs b/SharedVersion.cs index d741f379d2..6981c1ca9d 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.5.0")] -[assembly: AssemblyFileVersion("10.5.0")] +[assembly: AssemblyVersion("10.6.0")] +[assembly: AssemblyFileVersion("10.6.0")] diff --git a/build b/build deleted file mode 100755 index 95d5d5c495..0000000000 --- a/build +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env bash - -# build - build Jellyfin binaries or packages - -set -o errexit -set -o pipefail - -# The list of possible package actions (except 'clean') -declare -a actions=( 'build' 'package' 'sign' 'publish' ) - -# The list of possible platforms, based on directories under 'deployment/' -declare -a platforms=( $( - find deployment/ -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | sort -) ) - -# The list of standard dependencies required by all build scripts; individual -# action scripts may specify their own dependencies -declare -a dependencies=( 'tar' 'zip' ) - -usage() { - echo -e "build - build Jellyfin binaries or packages" - echo -e "" - echo -e "Usage:" - echo -e " $ build --list-platforms" - echo -e " $ build --list-actions " - echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch ] " - echo -e "" - echo -e "The 'keep-artifacts' option preserves build artifacts, e.g. Docker images for system package builds." - echo -e "The web_branch defaults to the same branch name as the current main branch or can be 'local' to not touch the submodule branching." - echo -e "To build all platforms, use 'all'." - echo -e "To perform all build actions, use 'all'." - echo -e "Build output files are collected at '../bin/'." -} - -# Show usage on stderr with exit 1 on argless -if [[ -z $1 ]]; then - usage >&2 - exit 1 -fi -# Show usage if -h or --help are specified in the args -if [[ $@ =~ '-h' || $@ =~ '--help' ]]; then - usage - exit 0 -fi - -# List all available platforms then exit -if [[ $1 == '--list-platforms' ]]; then - echo -e "Available platforms:" - for platform in ${platforms[@]}; do - echo -e " ${platform}" - done - exit 0 -fi - -# List all available actions for a given platform then exit -if [[ $1 == '--list-actions' ]]; then - platform="$2" - if [[ ! " ${platforms[@]} " =~ " ${platform} " ]]; then - echo "ERROR: Platform ${platform} does not exist." - exit 1 - fi - echo -e "Available actions for platform ${platform}:" - for action in ${actions[@]}; do - if [[ -f deployment/${platform}/${action}.sh ]]; then - echo -e " ${action}" - fi - done - exit 0 -fi - -# Parse keep-artifacts option -if [[ $1 == '-k' || $1 == '--keep-artifacts' ]]; then - keep_artifacts="y" - shift 1 -else - keep_artifacts="n" -fi - -# Parse branch option -if [[ $1 == '-b' || $1 == '--web-branch' ]]; then - web_branch="$2" - shift 2 -else - web_branch="$( git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' )" -fi - -# Parse platform option -if [[ -n $1 ]]; then - cli_platform="$1" - shift -else - echo "ERROR: A platform must be specified. Use 'all' to specify all platforms." - exit 1 -fi -if [[ ${cli_platform} == 'all' ]]; then - declare -a platform=( ${platforms[@]} ) -else - if [[ ! " ${platforms[@]} " =~ " ${cli_platform} " ]]; then - echo "ERROR: Platform ${cli_platform} is invalid. Use the '--list-platforms' option to list available platforms." - exit 1 - else - declare -a platform=( "${cli_platform}" ) - fi -fi - -# Parse action option -if [[ -n $1 ]]; then - cli_action="$1" - shift -else - echo "ERROR: An action must be specified. Use 'all' to specify all actions." - exit 1 -fi -if [[ ${cli_action} == 'all' ]]; then - declare -a action=( ${actions[@]} ) -else - if [[ ! " ${actions[@]} " =~ " ${cli_action} " ]]; then - echo "ERROR: Action ${cli_action} is invalid. Use the '--list-actions ' option to list available actions." - exit 1 - else - declare -a action=( "${cli_action}" ) - fi -fi - -# Verify required utilities are installed -missing_deps=() -for utility in ${dependencies[@]}; do - if ! which ${utility} &>/dev/null; then - missing_deps+=( ${utility} ) - fi -done - -# Error if we're missing anything -if [[ ${#missing_deps[@]} -gt 0 ]]; then - echo -e "ERROR: This script requires the following missing utilities:" - for utility in ${missing_deps[@]}; do - echo -e " ${utility}" - done - exit 1 -fi - -# Parse platform-specific dependencies -for target_platform in ${platform[@]}; do - # Read platform-specific dependencies - if [[ -f deployment/${target_platform}/dependencies.txt ]]; then - platform_dependencies="$( grep -v '^#' deployment/${target_platform}/dependencies.txt )" - - # Verify required utilities are installed - missing_deps=() - for utility in ${platform_dependencies[@]}; do - if ! which ${utility} &>/dev/null; then - missing_deps+=( ${utility} ) - fi - done - - # Error if we're missing anything - if [[ ${#missing_deps[@]} -gt 0 ]]; then - echo -e "ERROR: The ${target_platform} platform requires the following utilities:" - for utility in ${missing_deps[@]}; do - echo -e " ${utility}" - done - exit 1 - fi - fi -done - -# Execute each platform and action in order, if said action is enabled -pushd deployment/ -for target_platform in ${platform[@]}; do - echo -e "> Processing platform ${target_platform}" - date_start=$( date +%s ) - pushd ${target_platform} - cleanup() { - echo -e ">> Processing action clean" - if [[ -f clean.sh && -x clean.sh ]]; then - ./clean.sh ${keep_artifacts} - fi - } - trap cleanup EXIT INT - for target_action in ${action[@]}; do - echo -e ">> Processing action ${target_action}" - if [[ -f ${target_action}.sh && -x ${target_action}.sh ]]; then - ./${target_action}.sh web_branch=${web_branch} - fi - done - if [[ -d pkg-dist/ ]]; then - echo -e ">> Collecting build artifacts" - target_dir="../../../bin/${target_platform}" - mkdir -p ${target_dir} - mv pkg-dist/* ${target_dir}/ - fi - cleanup - date_end=$( date +%s ) - echo -e "> Completed platform ${target_platform} in $( expr ${date_end} - ${date_start} ) seconds." - popd -done -popd diff --git a/build b/build new file mode 120000 index 0000000000..c07a74de4f --- /dev/null +++ b/build @@ -0,0 +1 @@ +build.sh \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000000..1db02af983 --- /dev/null +++ b/build.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +# build.sh - Build Jellyfin binary packages +# Part of the Jellyfin Project + +set -o errexit +set -o pipefail + +usage() { + echo -e "build.sh - Build Jellyfin binary packages" + echo -e "Usage:" + echo -e " $0 -t/--type -p/--platform [-k/--keep-artifacts] [-l/--list-platforms]" + echo -e "Notes:" + echo -e " * BUILD_TYPE can be one of: [native, docker] and must be specified" + echo -e " * native: Build using the build script in the host OS" + echo -e " * docker: Build using the build script in a standardized Docker container" + echo -e " * PLATFORM can be any platform shown by -l/--list-platforms and must be specified" + echo -e " * If -k/--keep-artifacts is specified, transient artifacts (e.g. Docker containers) will be" + echo -e " retained after the build is finished; the source directory will still be cleaned" + echo -e " * If -l/--list-platforms is specified, all other arguments are ignored; the script will print" + echo -e " the list of supported platforms and exit" +} + +list_platforms() { + declare -a platforms + platforms=( + $( find deployment -maxdepth 1 -mindepth 1 -name "build.*" | awk -F'.' '{ $1=""; printf $2; if ($3 != ""){ printf "." $3; }; if ($4 != ""){ printf "." $4; }; print ""; }' | sort ) + ) + echo -e "Valid platforms:" + echo + for platform in ${platforms[@]}; do + echo -e "* ${platform} : $( grep '^#=' deployment/build.${platform} | sed 's/^#= //' )" + done +} + +do_build_native() { + if [[ ! -f $( which dpkg ) || $( dpkg --print-architecture | head -1 ) != "${PLATFORM##*.}" ]]; then + echo "Cross-building is not supported for native builds, use 'docker' builds on amd64 for cross-building." + exit 1 + fi + export IS_DOCKER=NO + deployment/build.${PLATFORM} +} + +do_build_docker() { + if [[ -f $( which dpkg ) && $( dpkg --print-architecture | head -1 ) != "amd64" ]]; then + echo "Docker-based builds only support amd64-based cross-building; use a 'native' build instead." + exit 1 + fi + if [[ ! -f deployment/Dockerfile.${PLATFORM} ]]; then + echo "Missing Dockerfile for platform ${PLATFORM}" + exit 1 + fi + if [[ ${KEEP_ARTIFACTS} == YES ]]; then + docker_args="" + else + docker_args="--rm" + fi + + docker build . -t "jellyfin-builder.${PLATFORM}" -f deployment/Dockerfile.${PLATFORM} + mkdir -p ${ARTIFACT_DIR} + docker run $docker_args -v "${SOURCE_DIR}:/jellyfin" -v "${ARTIFACT_DIR}:/dist" "jellyfin-builder.${PLATFORM}" +} + +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + -t|--type) + BUILD_TYPE="$2" + shift # past argument + shift # past value + ;; + -p|--platform) + PLATFORM="$2" + shift # past argument + shift # past value + ;; + -k|--keep-artifacts) + KEEP_ARTIFACTS=YES + shift # past argument + ;; + -l|--list-platforms) + list_platforms + exit 0 + ;; + -h|--help) + usage + exit 0 + ;; + *) # unknown option + echo "Unknown option $1" + usage + exit 1 + ;; + esac +done + +if [[ -z ${BUILD_TYPE} || -z ${PLATFORM} ]]; then + usage + exit 1 +fi + +export SOURCE_DIR="$( pwd )" +export ARTIFACT_DIR="${SOURCE_DIR}/../bin/${PLATFORM}" + +# Determine build type +case ${BUILD_TYPE} in + native) + do_build_native + ;; + docker) + do_build_docker + ;; +esac diff --git a/build.yaml b/build.yaml index 123f77fb89..9e590e5a01 100644 --- a/build.yaml +++ b/build.yaml @@ -1,18 +1,17 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.5.0" +version: "10.6.0" packages: - - debian-package-x64 - - debian-package-armhf - - debian-package-arm64 - - ubuntu-package-x64 - - ubuntu-package-armhf - - ubuntu-package-arm64 - - fedora-package-x64 - - centos-package-x64 - - linux-x64 + - debian.amd64 + - debian.arm64 + - debian.armhf + - ubuntu.amd64 + - ubuntu.arm64 + - ubuntu.armhf + - fedora.amd64 + - centos.amd64 + - linux.amd64 + - windows.amd64 - macos - portable - - win-x64 - - win-x86 diff --git a/bump_version b/bump_version index 106dd7a78e..46b7f86e05 100755 --- a/bump_version +++ b/bump_version @@ -10,10 +10,6 @@ usage() { echo -e "" echo -e "Usage:" echo -e " $ bump_version " - echo -e "" - echo -e "The web_branch defaults to the same branch name as the current main branch." - echo -e "This helps facilitate releases where both branches would be called release-X.Y.Z" - echo -e "and would already be created before running this script." } if [[ -z $1 ]]; then @@ -54,37 +50,26 @@ else new_version_deb="${new_version}-1" fi -# Set the Dockerfile web version to the specified new_version -old_version="$( - grep "JELLYFIN_WEB_VERSION=" Dockerfile \ - | sed -E 's/ARG JELLYFIN_WEB_VERSION=v([0-9\.]+[-a-z0-9]*)/\1/' -)" -echo $old_version - -old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars -sed -i "s/${old_version_sed}/${new_version}/g" Dockerfile* +# Update the metapackage equivs file +debian_equivs_file="debian/metapackage/jellyfin" +sed -i "s/${old_version_sed}/${new_version}/g" ${debian_equivs_file} # Write out a temporary Debian changelog with our new stuff appended and some templated formatting -debian_changelog_file="deployment/debian-package-x64/pkg-src/changelog" +debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "### DEBIAN PACKAGE CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit. -jellyfin (${new_version_deb}) unstable; urgency=medium +echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} -- Jellyfin Packaging Team $( date --rfc-2822 ) " >> ${debian_changelog_temp} cat ${debian_changelog_file} >> ${debian_changelog_temp} -# Edit the file to verify -$EDITOR ${debian_changelog_temp} # Move into place mv ${debian_changelog_temp} ${debian_changelog_file} -# Clean up -rm -f ${debian_changelog_temp} # Write out a temporary Yum changelog with our new stuff prepended and some templated formatting -fedora_spec_file="deployment/fedora-package-x64/pkg-src/jellyfin.spec" +fedora_spec_file="fedora/jellyfin.spec" fedora_changelog_temp="$( mktemp )" fedora_spec_temp_dir="$( mktemp -d )" fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp" @@ -98,21 +83,18 @@ sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 # Remove the header from xx01 sed -i '/^%changelog/d' xx01 # Create new temp file with our changelog -echo -e "### YUM SPEC CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit. -%changelog +echo -e "%changelog * $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team - New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}" >> ${fedora_changelog_temp} cat xx01 >> ${fedora_changelog_temp} -# Edit the file to verify -$EDITOR ${fedora_changelog_temp} # Reassembble cat xx00 ${fedora_changelog_temp} > ${fedora_spec_temp} popd # Move into place mv ${fedora_spec_temp} ${fedora_spec_file} # Clean up -rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir} +rm -rf ${fedora_spec_temp_dir} # Stage the changed files for commit -git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile* +git add ${shared_version_file} ${build_file} ${debian_equivs_file} ${debian_changelog_file} ${fedora_spec_file} git status diff --git a/deployment/debian-package-x64/pkg-src/bin/restart.sh b/debian/bin/restart.sh similarity index 100% rename from deployment/debian-package-x64/pkg-src/bin/restart.sh rename to debian/bin/restart.sh diff --git a/deployment/debian-package-x64/pkg-src/changelog b/debian/changelog similarity index 93% rename from deployment/debian-package-x64/pkg-src/changelog rename to debian/changelog index 51c4822370..35fb659571 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +jellyfin-server (10.6.0-1) unstable; urgency=medium + + * Forthcoming stable release + + -- Jellyfin Packaging Team Mon, 23 Mar 2020 14:46:05 -0400 + jellyfin (10.5.0-1) unstable; urgency=medium * New upstream version 10.5.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.5.0 diff --git a/deployment/debian-package-x64/pkg-src/compat b/debian/compat similarity index 100% rename from deployment/debian-package-x64/pkg-src/compat rename to debian/compat diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/debian/conf/jellyfin similarity index 81% rename from deployment/debian-package-x64/pkg-src/conf/jellyfin rename to debian/conf/jellyfin index c6e595f15a..64c98520cb 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/debian/conf/jellyfin @@ -18,6 +18,9 @@ JELLYFIN_CONFIG_DIR="/etc/jellyfin" JELLYFIN_LOG_DIR="/var/log/jellyfin" JELLYFIN_CACHE_DIR="/var/cache/jellyfin" +# web client path, installed by the jellyfin-web package +JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin/web" + # Restart script for in-app server control JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh" @@ -37,4 +40,4 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" +JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers b/debian/conf/jellyfin-sudoers similarity index 100% rename from deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers rename to debian/conf/jellyfin-sudoers diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin.service.conf b/debian/conf/jellyfin.service.conf similarity index 100% rename from deployment/debian-package-x64/pkg-src/conf/jellyfin.service.conf rename to debian/conf/jellyfin.service.conf diff --git a/deployment/debian-package-x64/pkg-src/conf/logging.json b/debian/conf/logging.json similarity index 100% rename from deployment/debian-package-x64/pkg-src/conf/logging.json rename to debian/conf/logging.json diff --git a/deployment/debian-package-x64/pkg-src/control b/debian/control similarity index 59% rename from deployment/debian-package-x64/pkg-src/control rename to debian/control index 13fd3ecabb..896d8286b7 100644 --- a/deployment/debian-package-x64/pkg-src/control +++ b/debian/control @@ -1,4 +1,4 @@ -Source: jellyfin +Source: jellyfin-server Section: misc Priority: optional Maintainer: Jellyfin Team @@ -8,24 +8,23 @@ Build-Depends: debhelper (>= 9), libcurl4-openssl-dev, libfontconfig1-dev, libfreetype6-dev, - libssl-dev, - wget, - npm | nodejs + libssl-dev Standards-Version: 3.9.4 -Homepage: https://jellyfin.media/ +Homepage: https://jellyfin.org/ Vcs-Git: https://github.org/jellyfin/jellyfin.git Vcs-Browser: https://github.org/jellyfin/jellyfin -Package: jellyfin +Package: jellyfin-server Replaces: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server Breaks: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server Conflicts: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server Architecture: any Depends: at, libsqlite3-0, - jellyfin-ffmpeg, + jellyfin-ffmpeg (>= 4.2.1-2), libfontconfig1, libfreetype6, libssl1.1 -Description: Jellyfin is a home media server. - It is built on top of other popular open source technologies such as Service Stack, jQuery, jQuery mobile, and Mono. It features a REST-based api with built-in documentation to facilitate client development. We also have client libraries for our api to enable rapid development. +Recommends: jellyfin-web +Description: Jellyfin is the Free Software Media System. + This package provides the Jellyfin server backend and API. diff --git a/deployment/debian-package-x64/pkg-src/copyright b/debian/copyright similarity index 100% rename from deployment/debian-package-x64/pkg-src/copyright rename to debian/copyright diff --git a/deployment/debian-package-x64/pkg-src/gbp.conf b/debian/gbp.conf similarity index 100% rename from deployment/debian-package-x64/pkg-src/gbp.conf rename to debian/gbp.conf diff --git a/deployment/debian-package-x64/pkg-src/install b/debian/install similarity index 100% rename from deployment/debian-package-x64/pkg-src/install rename to debian/install diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.init b/debian/jellyfin.init similarity index 100% rename from deployment/debian-package-x64/pkg-src/jellyfin.init rename to debian/jellyfin.init diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/debian/jellyfin.service similarity index 60% rename from deployment/debian-package-x64/pkg-src/jellyfin.service rename to debian/jellyfin.service index 1305e238b0..f1a8f4652c 100644 --- a/deployment/debian-package-x64/pkg-src/jellyfin.service +++ b/debian/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} Restart = on-failure TimeoutSec = 15 diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.upstart b/debian/jellyfin.upstart similarity index 100% rename from deployment/debian-package-x64/pkg-src/jellyfin.upstart rename to debian/jellyfin.upstart diff --git a/debian/metapackage/jellyfin b/debian/metapackage/jellyfin new file mode 100644 index 0000000000..9a41eafaed --- /dev/null +++ b/debian/metapackage/jellyfin @@ -0,0 +1,13 @@ +Source: jellyfin +Section: misc +Priority: optional +Homepage: https://jellyfin.org +Standards-Version: 3.9.2 + +Package: jellyfin +Version: 10.6.0 +Maintainer: Jellyfin Packaging Team +Depends: jellyfin-server, jellyfin-web +Description: Provides the Jellyfin Free Software Media System + Provides the full Jellyfin experience, including both the server and web interface. + diff --git a/deployment/debian-package-x64/pkg-src/po/POTFILES.in b/debian/po/POTFILES.in similarity index 100% rename from deployment/debian-package-x64/pkg-src/po/POTFILES.in rename to debian/po/POTFILES.in diff --git a/deployment/debian-package-x64/pkg-src/po/templates.pot b/debian/po/templates.pot similarity index 100% rename from deployment/debian-package-x64/pkg-src/po/templates.pot rename to debian/po/templates.pot diff --git a/deployment/debian-package-x64/pkg-src/postinst b/debian/postinst similarity index 100% rename from deployment/debian-package-x64/pkg-src/postinst rename to debian/postinst diff --git a/deployment/debian-package-x64/pkg-src/postrm b/debian/postrm similarity index 100% rename from deployment/debian-package-x64/pkg-src/postrm rename to debian/postrm diff --git a/deployment/debian-package-x64/pkg-src/preinst b/debian/preinst similarity index 100% rename from deployment/debian-package-x64/pkg-src/preinst rename to debian/preinst diff --git a/deployment/debian-package-x64/pkg-src/prerm b/debian/prerm similarity index 100% rename from deployment/debian-package-x64/pkg-src/prerm rename to debian/prerm diff --git a/deployment/debian-package-x64/pkg-src/rules b/debian/rules similarity index 64% rename from deployment/debian-package-x64/pkg-src/rules rename to debian/rules index c2d57dfb22..2a5d41a696 100755 --- a/deployment/debian-package-x64/pkg-src/rules +++ b/debian/rules @@ -2,8 +2,6 @@ CONFIG := Release TERM := xterm SHELL := /bin/bash -WEB_TARGET := $(CURDIR)/MediaBrowser.WebDashboard/jellyfin-web -WEB_VERSION := $(shell sed -n -e 's/^version: "\(.*\)"/\1/p' $(CURDIR)/build.yaml) HOST_ARCH := $(shell arch) BUILD_ARCH := ${DEB_HOST_MULTIARCH} @@ -41,25 +39,12 @@ override_dh_auto_test: override_dh_clistrip: override_dh_auto_build: - echo $(WEB_VERSION) - # Clone down and build Web frontend - mkdir -p $(WEB_TARGET) - wget -O web-src.tgz https://github.com/jellyfin/jellyfin-web/archive/v$(WEB_VERSION).tar.gz || wget -O web-src.tgz https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz - mkdir -p $(CURDIR)/web - tar -xzf web-src.tgz -C $(CURDIR)/web/ --strip 1 - cd $(CURDIR)/web/ && npm install yarn - cd $(CURDIR)/web/ && node_modules/yarn/bin/yarn install - mv $(CURDIR)/web/dist/* $(WEB_TARGET)/ - # Build the application dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server override_dh_auto_clean: dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true - rm -f '$(CURDIR)/web-src.tgz' rm -rf '$(CURDIR)/usr' - rm -rf '$(CURDIR)/web' - rm -rf '$(WEB_TARGET)' # Force the service name to jellyfin even if we're building jellyfin-nightly override_dh_installinit: diff --git a/deployment/debian-package-x64/pkg-src/source.lintian-overrides b/debian/source.lintian-overrides similarity index 100% rename from deployment/debian-package-x64/pkg-src/source.lintian-overrides rename to debian/source.lintian-overrides diff --git a/deployment/debian-package-x64/pkg-src/source/format b/debian/source/format similarity index 100% rename from deployment/debian-package-x64/pkg-src/source/format rename to debian/source/format diff --git a/deployment/debian-package-x64/pkg-src/source/options b/debian/source/options similarity index 100% rename from deployment/debian-package-x64/pkg-src/source/options rename to debian/source/options diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 new file mode 100644 index 0000000000..39788cc0ee --- /dev/null +++ b/deployment/Dockerfile.centos.amd64 @@ -0,0 +1,32 @@ +FROM centos:7 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=3.1 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV IS_DOCKER=YES + +# Prepare CentOS environment +RUN yum update -y \ + && yum install -y epel-release \ + && yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git + +# Install DotNET SDK +RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ + && rpmdev-setuptree \ + && yum install -y dotnet-sdk-${SDK_VERSION} + +# Create symlinks and directories +RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.amd64 /build.sh \ + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES + +VOLUME ${SOURCE_DIR}/ + +VOLUME ${ARTIFACT_DIR}/ + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/win-x64/Dockerfile b/deployment/Dockerfile.debian.amd64 similarity index 63% rename from deployment/win-x64/Dockerfile rename to deployment/Dockerfile.debian.amd64 index 8a33749541..b5a0380489 100644 --- a/deployment/win-x64/Dockerfile +++ b/deployment/Dockerfile.debian.amd64 @@ -1,7 +1,6 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/win-x64 ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,10 +8,11 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 zip + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current @@ -21,17 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install yarn package manager -RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt update \ - && apt install -y yarn +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.amd64 /build.sh -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/debian-package-arm64/Dockerfile.amd64 b/deployment/Dockerfile.debian.arm64 similarity index 78% rename from deployment/debian-package-arm64/Dockerfile.amd64 rename to deployment/Dockerfile.debian.arm64 index b63e08b7dd..cfe562df33 100644 --- a/deployment/debian-package-arm64/Dockerfile.amd64 +++ b/deployment/Dockerfile.debian.arm64 @@ -1,7 +1,6 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64 ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,10 +8,11 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current @@ -29,15 +29,11 @@ RUN dpkg --add-architecture arm64 \ && cd cross-gcc-packages-amd64/cross-gcc-8-arm64 \ && apt-get install -y gcc-8-source libstdc++-8-dev-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-8-dev:arm64 -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.arm64 /build.sh -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] -#ENTRYPOINT ["/bin/bash"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/debian-package-armhf/Dockerfile.amd64 b/deployment/Dockerfile.debian.armhf similarity index 79% rename from deployment/debian-package-armhf/Dockerfile.amd64 rename to deployment/Dockerfile.debian.armhf index 1b64b53148..ea8c8c8e62 100644 --- a/deployment/debian-package-armhf/Dockerfile.amd64 +++ b/deployment/Dockerfile.debian.armhf @@ -1,7 +1,6 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-armhf ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,10 +8,11 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current @@ -29,14 +29,11 @@ RUN dpkg --add-architecture armhf \ && cd cross-gcc-packages-amd64/cross-gcc-8-armhf \ && apt-get install -y gcc-8-source libstdc++-8-dev-armhf-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip binutils-arm-linux-gnueabihf libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf libssl-dev:armhf liblttng-ust0:armhf libstdc++-8-dev:armhf -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/Dockerfile.fedora.amd64 similarity index 60% rename from deployment/fedora-package-x64/Dockerfile rename to deployment/Dockerfile.fedora.amd64 index 87120f3a05..01b99deb6d 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/Dockerfile.fedora.amd64 @@ -1,18 +1,16 @@ FROM fedora:31 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64 ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist +ENV IS_DOCKER=YES # Prepare Fedora environment -RUN dnf update -y - -# Install build dependencies -RUN dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel nodejs-yarn +RUN dnf update -y \ + && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel # Install DotNET SDK RUN rpm --import https://packages.microsoft.com/keys/microsoft.asc \ @@ -20,14 +18,14 @@ RUN rpm --import https://packages.microsoft.com/keys/microsoft.asc \ && dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} # Create symlinks and directories -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ +RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \ && mkdir -p ${SOURCE_DIR}/SPECS \ - && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ && mkdir -p ${SOURCE_DIR}/SOURCES \ - && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES + && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES + +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/macos/Dockerfile b/deployment/Dockerfile.linux.amd64 similarity index 70% rename from deployment/macos/Dockerfile rename to deployment/Dockerfile.linux.amd64 index b522df8848..d8bec92145 100644 --- a/deployment/macos/Dockerfile +++ b/deployment/Dockerfile.linux.amd64 @@ -1,7 +1,6 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/macos ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ @@ -21,17 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install yarn package manager -RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt update \ - && apt install -y yarn - # Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64 /build.sh + +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/portable/Dockerfile b/deployment/Dockerfile.macos similarity index 70% rename from deployment/portable/Dockerfile rename to deployment/Dockerfile.macos index 965eb82b86..ba5da40190 100644 --- a/deployment/portable/Dockerfile +++ b/deployment/Dockerfile.macos @@ -1,7 +1,6 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/portable ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ @@ -21,17 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install yarn package manager -RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt update \ - && apt install -y yarn - # Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +RUN ln -sf ${SOURCE_DIR}/deployment/build.macos /build.sh + +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/linux-x64/Dockerfile b/deployment/Dockerfile.portable similarity index 69% rename from deployment/linux-x64/Dockerfile rename to deployment/Dockerfile.portable index c47057546d..2893e140df 100644 --- a/deployment/linux-x64/Dockerfile +++ b/deployment/Dockerfile.portable @@ -1,14 +1,13 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/linux-x64 ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ @@ -21,17 +20,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install yarn package manager -RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt update \ - && apt install -y yarn - # Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh + +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 new file mode 100644 index 0000000000..e61be4efcc --- /dev/null +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -0,0 +1,31 @@ +FROM ubuntu:bionic +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=3.1 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs +ENV ARCH=amd64 +ENV IS_DOCKER=YES + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4c7c-8ea0-fad5605b077a/49497b5420eecbd905158d86d738af64/dotnet-sdk-3.1.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.amd64 /build.sh + +VOLUME ${SOURCE_DIR}/ + +VOLUME ${ARTIFACT_DIR}/ + +ENTRYPOINT ["/build.sh"] diff --git a/deployment/ubuntu-package-arm64/Dockerfile.amd64 b/deployment/Dockerfile.ubuntu.arm64 similarity index 81% rename from deployment/ubuntu-package-arm64/Dockerfile.amd64 rename to deployment/Dockerfile.ubuntu.arm64 index b11994a18a..f91b91cd46 100644 --- a/deployment/ubuntu-package-arm64/Dockerfile.amd64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -1,7 +1,6 @@ FROM ubuntu:bionic # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64 ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ @@ -21,12 +21,6 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install npm package manager -RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \ - && echo "deb https://deb.nodesource.com/node_10.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \ - && apt update \ - && apt install -y nodejs - # Prepare the cross-toolchain RUN rm /etc/apt/sources.list \ && export CODENAME="$( lsb_release -c -s )" \ @@ -46,14 +40,11 @@ RUN rm /etc/apt/sources.list \ && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ && apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 libssl-dev:arm64 -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.arm64 /build.sh -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/Dockerfile.ubuntu.armhf similarity index 81% rename from deployment/ubuntu-package-armhf/Dockerfile.amd64 rename to deployment/Dockerfile.ubuntu.armhf index e475b14389..85414614c0 100644 --- a/deployment/ubuntu-package-armhf/Dockerfile.amd64 +++ b/deployment/Dockerfile.ubuntu.armhf @@ -1,7 +1,6 @@ FROM ubuntu:bionic # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment @@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ @@ -21,12 +21,6 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install npm package manager -RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \ - && echo "deb https://deb.nodesource.com/node_10.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \ - && apt update \ - && apt install -y nodejs - # Prepare the cross-toolchain RUN rm /etc/apt/sources.list \ && export CODENAME="$( lsb_release -c -s )" \ @@ -46,14 +40,11 @@ RUN rm /etc/apt/sources.list \ && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \ && apt-get install -y gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf libssl-dev:armhf -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +# Link to build script +RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/win-x86/Dockerfile b/deployment/Dockerfile.windows.amd64 similarity index 69% rename from deployment/win-x86/Dockerfile rename to deployment/Dockerfile.windows.amd64 index f8dc5be83d..0397a023e2 100644 --- a/deployment/win-x86/Dockerfile +++ b/deployment/Dockerfile.windows.amd64 @@ -1,14 +1,13 @@ FROM debian:10 # Docker build arguments ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/win-x86 ARG ARTIFACT_DIR=/dist ARG SDK_VERSION=3.1 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=amd64 +ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ @@ -21,17 +20,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4 && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet -# Install yarn package manager -RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt update \ - && apt install -y yarn - # Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh +RUN ln -sf ${SOURCE_DIR}/deployment/build.windows.amd64 /build.sh + +VOLUME ${SOURCE_DIR}/ VOLUME ${ARTIFACT_DIR}/ -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] +ENTRYPOINT ["/build.sh"] diff --git a/deployment/README.md b/deployment/README.md deleted file mode 100644 index a805f59ca3..0000000000 --- a/deployment/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Jellyfin Packaging - -This directory contains the packaging configuration of Jellyfin for multiple platforms. The specification is below; all package platforms must follow the specification to be compatable with the central `build` script. - -## Package List - -### Operating System Packages - -* `debian-package-x64`: Package for Debian and Ubuntu amd64 systems. -* `fedora-package-x64`: Package for Fedora, CentOS, and Red Hat Enterprise Linux amd64 systems. - -### Portable Builds (archives) - -* `linux-x64`: Portable binary archive for generic Linux amd64 systems. -* `macos`: Portable binary archive for MacOS amd64 systems. -* `win-x64`: Portable binary archive for Windows amd64 systems. -* `win-x86`: Portable binary archive for Windows i386 systems. - -### Other Builds - -These builds are not necessarily run from the `build` script, but are present for other platforms. - -* `portable`: Compiled `.dll` for use with .NET Core runtime on any system. -* `docker`: Docker manifests for auto-publishing. -* `unraid`: unRaid Docker template; not built by `build` but imported into unRaid directly. -* `windows`: Support files and scripts for Windows CI build. - -## Package Specification - -### Dependencies - -* If a platform requires additional build dependencies, the required binary names, i.e. to validate `which `, should be specified in a `dependencies.txt` file inside the platform directory. - -* Each dependency should be present on its own line. - -### Action Scripts - -* Actions are defined in BASH scripts with the name `.sh` within the platform directory. - -* The list of valid actions are: - - 1. `build`: Builds a set of binaries. - 2. `package`: Assembles the compiled binaries into a package. - 3. `sign`: Performs signing actions on a package. - 4. `publish`: Performs a publishing action for a package. - 5. `clean`: Cleans up any artifacts from the previous actions. - -* All package actions are optional, however at least one should generate output files, and any that do should contain a `clean` action. - -* Actions are executed in the order specified above, and later actions may depend on former actions. - -* Actions except for `clean` should `set -o errexit` to terminate on failed actions. - -* The `clean` action should always `exit 0` even if no work is done or it fails. - -* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`, containing either the value `y` or `n`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories. - -### Output Files - -* Upon completion of the defined actions, at least one output file must be created in the `/pkg-dist` directory. - -* Output files will be moved to the directory `jellyfin-build/` one directory above the repository root upon completion. diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64 new file mode 100755 index 0000000000..939bbc45a4 --- /dev/null +++ b/deployment/build.centos.amd64 @@ -0,0 +1,24 @@ +#!/bin/bash + +#= CentOS/RHEL 7+ amd64 .rpm + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Build RPM +make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS +rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm + +# Move the artifacts out +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +rm -f fedora/jellyfin*.tar.gz + +popd diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64 new file mode 100755 index 0000000000..f44c6a7d1d --- /dev/null +++ b/deployment/build.debian.amd64 @@ -0,0 +1,28 @@ +#!/bin/bash + +#= Debian 10+ amd64 .deb + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + # Remove build-dep for dotnet-sdk-3.1, since it's installed manually + cp -a debian/control /tmp/control.orig + sed -i '/dotnet-sdk-3.1,/d' debian/control +fi + +# Build DEB +dpkg-buildpackage -us -uc --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR}/ +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + cp -a /tmp/control.orig debian/control + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64 new file mode 100755 index 0000000000..0127671f3d --- /dev/null +++ b/deployment/build.debian.arm64 @@ -0,0 +1,29 @@ +#!/bin/bash + +#= Debian 10+ arm64 .deb + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + # Remove build-dep for dotnet-sdk-3.1, since it's installed manually + cp -a debian/control /tmp/control.orig + sed -i '/dotnet-sdk-3.1,/d' debian/control +fi + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR}/ +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + cp -a /tmp/control.orig debian/control + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf new file mode 100755 index 0000000000..02e3db4fca --- /dev/null +++ b/deployment/build.debian.armhf @@ -0,0 +1,29 @@ +#!/bin/bash + +#= Debian 10+ arm64 .deb + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + # Remove build-dep for dotnet-sdk-3.1, since it's installed manually + cp -a debian/control /tmp/control.orig + sed -i '/dotnet-sdk-3.1,/d' debian/control +fi + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR}/ +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + cp -a /tmp/control.orig debian/control + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64 new file mode 100755 index 0000000000..8ac99decc1 --- /dev/null +++ b/deployment/build.fedora.amd64 @@ -0,0 +1,24 @@ +#!/bin/bash + +#= Fedora 29+ amd64 .rpm + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Build RPM +make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS +rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm + +# Move the artifacts out +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +rm -f fedora/jellyfin*.tar.gz + +popd diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64 new file mode 100755 index 0000000000..0cbbd05cf9 --- /dev/null +++ b/deployment/build.linux.amd64 @@ -0,0 +1,27 @@ +#!/bin/bash + +#= Generic Linux amd64 .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.macos b/deployment/build.macos new file mode 100755 index 0000000000..16be29eeef --- /dev/null +++ b/deployment/build.macos @@ -0,0 +1,27 @@ +#!/bin/bash + +#= MacOS 10.13+ .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.portable b/deployment/build.portable new file mode 100755 index 0000000000..1e8a4ab623 --- /dev/null +++ b/deployment/build.portable @@ -0,0 +1,27 @@ +#!/bin/bash + +#= Portable .NET DLL .tar.gz + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" + +# Build archives +dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" +tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version} +rm -rf dist/jellyfin-server_${version} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64 new file mode 100755 index 0000000000..107ddbe02d --- /dev/null +++ b/deployment/build.ubuntu.amd64 @@ -0,0 +1,28 @@ +#!/bin/bash + +#= Ubuntu 18.04+ amd64 .deb + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + # Remove build-dep for dotnet-sdk-3.1, since it's installed manually + cp -a debian/control /tmp/control.orig + sed -i '/dotnet-sdk-3.1,/d' debian/control +fi + +# Build DEB +dpkg-buildpackage -us -uc --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR}/ +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + cp -a /tmp/control.orig debian/control + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64 new file mode 100755 index 0000000000..b13868f44b --- /dev/null +++ b/deployment/build.ubuntu.arm64 @@ -0,0 +1,29 @@ +#!/bin/bash + +#= Ubuntu 18.04+ arm64 .deb + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + # Remove build-dep for dotnet-sdk-3.1, since it's installed manually + cp -a debian/control /tmp/control.orig + sed -i '/dotnet-sdk-3.1,/d' debian/control +fi + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR}/ +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + cp -a /tmp/control.orig debian/control + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf new file mode 100755 index 0000000000..0b4dd308a2 --- /dev/null +++ b/deployment/build.ubuntu.armhf @@ -0,0 +1,29 @@ +#!/bin/bash + +#= Ubuntu 18.04+ arm64 .deb + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +if [[ ${IS_DOCKER} == YES ]]; then + # Remove build-dep for dotnet-sdk-3.1, since it's installed manually + cp -a debian/control /tmp/control.orig + sed -i '/dotnet-sdk-3.1,/d' debian/control +fi + +# Build DEB +export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} +dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean + +mkdir -p ${ARTIFACT_DIR}/ +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + cp -a /tmp/control.orig debian/control + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64 new file mode 100755 index 0000000000..39bd41f990 --- /dev/null +++ b/deployment/build.windows.amd64 @@ -0,0 +1,54 @@ +#!/bin/bash + +#= Windows 7+ amd64 (x64) .zip + +set -o errexit +set -o xtrace + +# Version variables +NSSM_VERSION="nssm-2.24-101-g897c7ad" +NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip" +FFMPEG_VERSION="ffmpeg-4.2.1-win64-static" +FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip" + +# Move to source directory +pushd ${SOURCE_DIR} + +# Get version +version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" + +output_dir="dist/jellyfin-server_${version}" + +# Build binary +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" + +# Prepare addins +addin_build_dir="$( mktemp -d )" +wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip +wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip +unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir} +cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe +unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir} +cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${output_dir}/ffmpeg.exe +cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe ${output_dir}/ffprobe.exe +rm -rf ${addin_build_dir} + +# Prepare scripts +cp ${SOURCE_DIR}/windows/legacy/install-jellyfin.ps1 ${output_dir}/install-jellyfin.ps1 +cp ${SOURCE_DIR}/windows/legacy/install.bat ${output_dir}/install.bat + +# Create zip package +pushd dist +zip -qr jellyfin-server_${version}.portable.zip jellyfin-server_${version} +popd +rm -rf ${output_dir} + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/ +mv dist/jellyfin[-_]*.zip ${ARTIFACT_DIR}/ + +if [[ ${IS_DOCKER} == YES ]]; then + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} +fi + +popd diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile deleted file mode 100644 index 08219a2e4a..0000000000 --- a/deployment/centos-package-x64/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -FROM centos:7 -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64 -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist - -# Prepare CentOS environment -RUN yum update -y \ - && yum install -y epel-release - -# Install build dependencies -RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git - -# Install recent NodeJS and Yarn -RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \ - && rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \ - && yum install -y yarn - -# Install DotNET SDK -RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ - && rpmdev-setuptree \ - && yum install -y dotnet-sdk-${SDK_VERSION} - -# Create symlinks and directories -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ - && mkdir -p ${SOURCE_DIR}/SPECS \ - && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ - && mkdir -p ${SOURCE_DIR}/SOURCES \ - && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/centos-package-x64/clean.sh b/deployment/centos-package-x64/clean.sh deleted file mode 100755 index 31455de0d4..0000000000 --- a/deployment/centos-package-x64/clean.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" -VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -package_source_dir="${WORKDIR}/pkg-src" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-centos-build" - -rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ - || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/centos-package-x64/dependencies.txt b/deployment/centos-package-x64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/centos-package-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh deleted file mode 100755 index 62dd144e50..0000000000 --- a/deployment/centos-package-x64/docker-build.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# Builds the RPM inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Build RPM -make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS -rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/rpm -mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh deleted file mode 100755 index 1b983f49d9..0000000000 --- a/deployment/centos-package-x64/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-centos-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the RPMs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the RPMs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/rpm/* "${output_dir}" diff --git a/deployment/centos-package-x64/pkg-src b/deployment/centos-package-x64/pkg-src deleted file mode 120000 index 3ff4d3cbf5..0000000000 --- a/deployment/centos-package-x64/pkg-src +++ /dev/null @@ -1 +0,0 @@ -../fedora-package-x64/pkg-src/ \ No newline at end of file diff --git a/deployment/debian-package-arm64/Dockerfile.arm64 b/deployment/debian-package-arm64/Dockerfile.arm64 deleted file mode 100644 index 9ca4868441..0000000000 --- a/deployment/debian-package-arm64/Dockerfile.arm64 +++ /dev/null @@ -1,34 +0,0 @@ -FROM debian:10 -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64 -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist -ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=arm64 - -# Prepare Debian build environment -RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0 - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5a4c8f96-1c73-401c-a6de-8e100403188a/0ce6ab39747e2508366d498f9c0a0669/dotnet-sdk-3.1.100-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-arm64/clean.sh b/deployment/debian-package-arm64/clean.sh deleted file mode 100755 index e7bfdf8b4b..0000000000 --- a/deployment/debian-package-arm64/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-debian_arm64-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/debian-package-arm64/dependencies.txt b/deployment/debian-package-arm64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/debian-package-arm64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh deleted file mode 100755 index 67ab6bd74b..0000000000 --- a/deployment/debian-package-arm64/docker-build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# Builds the DEB inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image -sed -i '/dotnet-sdk-3.1,/d' debian/control - -# Build DEB -export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} -dpkg-buildpackage -us -uc -aarm64 - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/deb -mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/debian-package-arm64/package.sh b/deployment/debian-package-arm64/package.sh deleted file mode 100755 index 2091982187..0000000000 --- a/deployment/debian-package-arm64/package.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -ARCH="$( arch )" -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-debian_arm64-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Determine which Dockerfile to use -case $ARCH in - 'x86_64') - DOCKERFILE="Dockerfile.amd64" - ;; - 'armv7l') - DOCKERFILE="Dockerfile.arm64" - ;; -esac - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/debian-package-arm64/pkg-src b/deployment/debian-package-arm64/pkg-src deleted file mode 120000 index 4c695fea17..0000000000 --- a/deployment/debian-package-arm64/pkg-src +++ /dev/null @@ -1 +0,0 @@ -../debian-package-x64/pkg-src \ No newline at end of file diff --git a/deployment/debian-package-armhf/Dockerfile.armhf b/deployment/debian-package-armhf/Dockerfile.armhf deleted file mode 100644 index dd398b5aa5..0000000000 --- a/deployment/debian-package-armhf/Dockerfile.armhf +++ /dev/null @@ -1,34 +0,0 @@ -FROM debian:10 -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-armhf -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist -ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=armhf - -# Prepare Debian build environment -RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0 - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/67766a96-eb8c-4cd2-bca4-ea63d2cc115c/7bf13840aa2ed88793b7315d5e0d74e6/dotnet-sdk-3.1.100-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-armhf/clean.sh b/deployment/debian-package-armhf/clean.sh deleted file mode 100755 index 35a3d3e9ad..0000000000 --- a/deployment/debian-package-armhf/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-debian_armhf-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/debian-package-armhf/dependencies.txt b/deployment/debian-package-armhf/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/debian-package-armhf/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/debian-package-armhf/docker-build.sh b/deployment/debian-package-armhf/docker-build.sh deleted file mode 100755 index 1bd7fb2911..0000000000 --- a/deployment/debian-package-armhf/docker-build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# Builds the DEB inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image -sed -i '/dotnet-sdk-3.1,/d' debian/control - -# Build DEB -export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} -dpkg-buildpackage -us -uc -aarmhf - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/deb -mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/debian-package-armhf/package.sh b/deployment/debian-package-armhf/package.sh deleted file mode 100755 index 4a27dd8283..0000000000 --- a/deployment/debian-package-armhf/package.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -ARCH="$( arch )" -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-debian_armhf-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Determine which Dockerfile to use -case $ARCH in - 'x86_64') - DOCKERFILE="Dockerfile.amd64" - ;; - 'armv7l') - DOCKERFILE="Dockerfile.armhf" - ;; -esac - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/debian-package-armhf/pkg-src b/deployment/debian-package-armhf/pkg-src deleted file mode 120000 index 0bb6d55249..0000000000 --- a/deployment/debian-package-armhf/pkg-src +++ /dev/null @@ -1 +0,0 @@ -../debian-package-x64/pkg-src/ \ No newline at end of file diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile deleted file mode 100644 index e863d1edf9..0000000000 --- a/deployment/debian-package-x64/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM debian:10 -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64 -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist -ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=amd64 - -# Prepare Debian build environment -RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4c7c-8ea0-fad5605b077a/49497b5420eecbd905158d86d738af64/dotnet-sdk-3.1.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-x64/clean.sh b/deployment/debian-package-x64/clean.sh deleted file mode 100755 index 4e507bcb27..0000000000 --- a/deployment/debian-package-x64/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-debian-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/debian-package-x64/dependencies.txt b/deployment/debian-package-x64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/debian-package-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh deleted file mode 100755 index 962a522ebc..0000000000 --- a/deployment/debian-package-x64/docker-build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Builds the DEB inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image -sed -i '/dotnet-sdk-3.1,/d' debian/control - -# Build DEB -dpkg-buildpackage -us -uc - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/deb -mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh deleted file mode 100755 index 5a416959ab..0000000000 --- a/deployment/debian-package-x64/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-debian-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/fedora-package-x64/clean.sh b/deployment/fedora-package-x64/clean.sh deleted file mode 100755 index 700c8f1bb3..0000000000 --- a/deployment/fedora-package-x64/clean.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" -VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -package_source_dir="${WORKDIR}/pkg-src" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-fedora-build" - -rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ - || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/fedora-package-x64/dependencies.txt b/deployment/fedora-package-x64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/fedora-package-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh deleted file mode 100755 index 740e8d35ca..0000000000 --- a/deployment/fedora-package-x64/docker-build.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# Builds the RPM inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Build RPM -make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS -rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/rpm -mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh deleted file mode 100755 index ae6962dd1f..0000000000 --- a/deployment/fedora-package-x64/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-fedora-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the RPMs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the RPMs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/rpm/* "${output_dir}" diff --git a/deployment/linux-x64/clean.sh b/deployment/linux-x64/clean.sh deleted file mode 100755 index c07501a7bb..0000000000 --- a/deployment/linux-x64/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-linux-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/linux-x64/dependencies.txt b/deployment/linux-x64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/linux-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/linux-x64/docker-build.sh b/deployment/linux-x64/docker-build.sh deleted file mode 100755 index e33328a36a..0000000000 --- a/deployment/linux-x64/docker-build.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Builds the TAR archive inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" - -# Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" -tar -cvzf /jellyfin_${version}.portable.tar.gz -C /dist jellyfin_${version} -rm -rf /dist/jellyfin_${version} - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv /jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/linux-x64/package.sh b/deployment/linux-x64/package.sh deleted file mode 100755 index dfe8a9aa4a..0000000000 --- a/deployment/linux-x64/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-linux-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/* "${output_dir}" diff --git a/deployment/macos/clean.sh b/deployment/macos/clean.sh deleted file mode 100755 index c07501a7bb..0000000000 --- a/deployment/macos/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-linux-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/macos/dependencies.txt b/deployment/macos/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/macos/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/macos/docker-build.sh b/deployment/macos/docker-build.sh deleted file mode 100755 index f9417388d7..0000000000 --- a/deployment/macos/docker-build.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Builds the TAR archive inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" - -# Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" -tar -cvzf /jellyfin_${version}.portable.tar.gz -C /dist jellyfin_${version} -rm -rf /dist/jellyfin_${version} - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv /jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/macos/package.sh b/deployment/macos/package.sh deleted file mode 100755 index 464c0d382f..0000000000 --- a/deployment/macos/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-macos-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/* "${output_dir}" diff --git a/deployment/portable/clean.sh b/deployment/portable/clean.sh deleted file mode 100755 index c07501a7bb..0000000000 --- a/deployment/portable/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-linux-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/portable/dependencies.txt b/deployment/portable/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/portable/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/portable/docker-build.sh b/deployment/portable/docker-build.sh deleted file mode 100755 index 094190bbf6..0000000000 --- a/deployment/portable/docker-build.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Builds the TAR archive inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" - -# Build archives -dotnet publish Jellyfin.Server --configuration Release --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" -tar -cvzf /jellyfin_${version}.portable.tar.gz -C /dist jellyfin_${version} -rm -rf /dist/jellyfin_${version} - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv /jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/portable/package.sh b/deployment/portable/package.sh deleted file mode 100755 index 0ceb54dda1..0000000000 --- a/deployment/portable/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-portable-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/* "${output_dir}" diff --git a/deployment/ubuntu-package-arm64/Dockerfile.arm64 b/deployment/ubuntu-package-arm64/Dockerfile.arm64 deleted file mode 100644 index 8f004b2f1a..0000000000 --- a/deployment/ubuntu-package-arm64/Dockerfile.arm64 +++ /dev/null @@ -1,40 +0,0 @@ -FROM ubuntu:bionic -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64 -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist -ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=arm64 - -# Prepare Debian build environment -RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 libssl-dev - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5a4c8f96-1c73-401c-a6de-8e100403188a/0ce6ab39747e2508366d498f9c0a0669/dotnet-sdk-3.1.100-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - -# Install npm package manager -RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \ - && echo "deb https://deb.nodesource.com/node_10.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \ - && apt update \ - && apt install -y nodejs - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-arm64/clean.sh b/deployment/ubuntu-package-arm64/clean.sh deleted file mode 100755 index 82d427f9e5..0000000000 --- a/deployment/ubuntu-package-arm64/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-ubuntu-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/ubuntu-package-arm64/dependencies.txt b/deployment/ubuntu-package-arm64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/ubuntu-package-arm64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh deleted file mode 100755 index 67ab6bd74b..0000000000 --- a/deployment/ubuntu-package-arm64/docker-build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# Builds the DEB inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image -sed -i '/dotnet-sdk-3.1,/d' debian/control - -# Build DEB -export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} -dpkg-buildpackage -us -uc -aarm64 - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/deb -mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/ubuntu-package-arm64/package.sh b/deployment/ubuntu-package-arm64/package.sh deleted file mode 100755 index d1140a7274..0000000000 --- a/deployment/ubuntu-package-arm64/package.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -ARCH="$( arch )" -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-ubuntu_arm64-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Determine which Dockerfile to use -case $ARCH in - 'x86_64') - DOCKERFILE="Dockerfile.amd64" - ;; - 'armv7l') - DOCKERFILE="Dockerfile.arm64" - ;; -esac - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-arm64/pkg-src b/deployment/ubuntu-package-arm64/pkg-src deleted file mode 120000 index 4c695fea17..0000000000 --- a/deployment/ubuntu-package-arm64/pkg-src +++ /dev/null @@ -1 +0,0 @@ -../debian-package-x64/pkg-src \ No newline at end of file diff --git a/deployment/ubuntu-package-armhf/Dockerfile.armhf b/deployment/ubuntu-package-armhf/Dockerfile.armhf deleted file mode 100644 index 0e71fa6938..0000000000 --- a/deployment/ubuntu-package-armhf/Dockerfile.armhf +++ /dev/null @@ -1,40 +0,0 @@ -FROM ubuntu:bionic -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist -ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=armhf - -# Prepare Debian build environment -RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 libssl-dev - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/67766a96-eb8c-4cd2-bca4-ea63d2cc115c/7bf13840aa2ed88793b7315d5e0d74e6/dotnet-sdk-3.1.100-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - -# Install npm package manager -RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \ - && echo "deb https://deb.nodesource.com/node_10.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \ - && apt update \ - && apt install -y nodejs - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-armhf/clean.sh b/deployment/ubuntu-package-armhf/clean.sh deleted file mode 100755 index 82d427f9e5..0000000000 --- a/deployment/ubuntu-package-armhf/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-ubuntu-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/ubuntu-package-armhf/dependencies.txt b/deployment/ubuntu-package-armhf/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/ubuntu-package-armhf/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh deleted file mode 100755 index 1bd7fb2911..0000000000 --- a/deployment/ubuntu-package-armhf/docker-build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# Builds the DEB inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image -sed -i '/dotnet-sdk-3.1,/d' debian/control - -# Build DEB -export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} -dpkg-buildpackage -us -uc -aarmhf - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/deb -mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/ubuntu-package-armhf/package.sh b/deployment/ubuntu-package-armhf/package.sh deleted file mode 100755 index 2ceb3e8165..0000000000 --- a/deployment/ubuntu-package-armhf/package.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -ARCH="$( arch )" -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-ubuntu_armhf-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Determine which Dockerfile to use -case $ARCH in - 'x86_64') - DOCKERFILE="Dockerfile.amd64" - ;; - 'armv7l') - DOCKERFILE="Dockerfile.armhf" - ;; -esac - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-armhf/pkg-src b/deployment/ubuntu-package-armhf/pkg-src deleted file mode 120000 index 4c695fea17..0000000000 --- a/deployment/ubuntu-package-armhf/pkg-src +++ /dev/null @@ -1 +0,0 @@ -../debian-package-x64/pkg-src \ No newline at end of file diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile deleted file mode 100644 index e2dda6392c..0000000000 --- a/deployment/ubuntu-package-x64/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM ubuntu:bionic -# Docker build arguments -ARG SOURCE_DIR=/jellyfin -ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-x64 -ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=3.1 -# Docker run environment -ENV SOURCE_DIR=/jellyfin -ENV ARTIFACT_DIR=/dist -ENV DEB_BUILD_OPTIONS=noddebs -ENV ARCH=amd64 - -# Prepare Ubuntu build environment -RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0 \ - && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ - && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4c7c-8ea0-fad5605b077a/49497b5420eecbd905158d86d738af64/dotnet-sdk-3.1.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ - && mkdir -p dotnet-sdk \ - && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ - && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet - -# Install npm package manager -RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \ - && echo "deb https://deb.nodesource.com/node_10.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \ - && apt update \ - && apt install -y nodejs - -VOLUME ${ARTIFACT_DIR}/ - -COPY . ${SOURCE_DIR}/ - -ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-x64/clean.sh b/deployment/ubuntu-package-x64/clean.sh deleted file mode 100755 index 82d427f9e5..0000000000 --- a/deployment/ubuntu-package-x64/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-ubuntu-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/ubuntu-package-x64/dependencies.txt b/deployment/ubuntu-package-x64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/ubuntu-package-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh deleted file mode 100755 index 962a522ebc..0000000000 --- a/deployment/ubuntu-package-x64/docker-build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Builds the DEB inside the Docker container - -set -o errexit -set -o xtrace - -# Move to source directory -pushd ${SOURCE_DIR} - -# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image -sed -i '/dotnet-sdk-3.1,/d' debian/control - -# Build DEB -dpkg-buildpackage -us -uc - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/deb -mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/ubuntu-package-x64/package.sh b/deployment/ubuntu-package-x64/package.sh deleted file mode 100755 index 08c003778b..0000000000 --- a/deployment/ubuntu-package-x64/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-ubuntu-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-x64/pkg-src b/deployment/ubuntu-package-x64/pkg-src deleted file mode 120000 index 0bb6d55249..0000000000 --- a/deployment/ubuntu-package-x64/pkg-src +++ /dev/null @@ -1 +0,0 @@ -../debian-package-x64/pkg-src/ \ No newline at end of file diff --git a/deployment/win-x64/clean.sh b/deployment/win-x64/clean.sh deleted file mode 100755 index 6c183f3371..0000000000 --- a/deployment/win-x64/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-windows-x64-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/win-x64/dependencies.txt b/deployment/win-x64/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/win-x64/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/win-x64/docker-build.sh b/deployment/win-x64/docker-build.sh deleted file mode 100755 index 79e5fb0bcd..0000000000 --- a/deployment/win-x64/docker-build.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -# Builds the ZIP archive inside the Docker container - -set -o errexit -set -o xtrace - -# Version variables -NSSM_VERSION="nssm-2.24-101-g897c7ad" -NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip" -FFMPEG_VERSION="ffmpeg-4.2.1-win64-static" -FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip" - -# Move to source directory -pushd ${SOURCE_DIR} - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" - -# Build binary -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" - -# Prepare addins -addin_build_dir="$( mktemp -d )" -wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip -wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip -unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir} -cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe /dist/jellyfin_${version}/nssm.exe -unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir} -cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe /dist/jellyfin_${version}/ffmpeg.exe -cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe /dist/jellyfin_${version}/ffprobe.exe -rm -rf ${addin_build_dir} - -# Prepare scripts -cp ${SOURCE_DIR}/deployment/windows/legacy/install-jellyfin.ps1 /dist/jellyfin_${version}/install-jellyfin.ps1 -cp ${SOURCE_DIR}/deployment/windows/legacy/install.bat /dist/jellyfin_${version}/install.bat - -# Create zip package -pushd /dist -zip -r /jellyfin_${version}.portable.zip jellyfin_${version} -popd -rm -rf /dist/jellyfin_${version} - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv /jellyfin[-_]*.zip ${ARTIFACT_DIR}/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/win-x64/package.sh b/deployment/win-x64/package.sh deleted file mode 100755 index a8ab190fa5..0000000000 --- a/deployment/win-x64/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-windows-x64-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/* "${output_dir}" diff --git a/deployment/win-x86/clean.sh b/deployment/win-x86/clean.sh deleted file mode 100755 index 8b78c5e4b6..0000000000 --- a/deployment/win-x86/clean.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -keep_artifacts="${1}" - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-windows-x86-build" - -rm -rf "${package_temporary_dir}" &>/dev/null \ - || sudo rm -rf "${package_temporary_dir}" &>/dev/null - -rm -rf "${output_dir}" &>/dev/null \ - || sudo rm -rf "${output_dir}" &>/dev/null - -if [[ ${keep_artifacts} == 'n' ]]; then - docker_sudo="" - if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo=sudo - fi - ${docker_sudo} docker image rm ${image_name} --force -fi diff --git a/deployment/win-x86/dependencies.txt b/deployment/win-x86/dependencies.txt deleted file mode 100644 index bdb9670965..0000000000 --- a/deployment/win-x86/dependencies.txt +++ /dev/null @@ -1 +0,0 @@ -docker diff --git a/deployment/win-x86/docker-build.sh b/deployment/win-x86/docker-build.sh deleted file mode 100755 index 977dcf78fa..0000000000 --- a/deployment/win-x86/docker-build.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -# Builds the ZIP archive inside the Docker container - -set -o errexit -set -o xtrace - -# Version variables -NSSM_VERSION="nssm-2.24-101-g897c7ad" -NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip" -FFMPEG_VERSION="ffmpeg-4.2.1-win32-static" -FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win32/static/${FFMPEG_VERSION}.zip" - -# Move to source directory -pushd ${SOURCE_DIR} - -# Clone down and build Web frontend -web_build_dir="$( mktemp -d )" -web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web" -git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/ -pushd ${web_build_dir} -if [[ -n ${web_branch} ]]; then - checkout -b origin/${web_branch} -fi -yarn install -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd -rm -rf ${web_build_dir} - -# Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" - -# Build binary -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x86 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" - -# Prepare addins -addin_build_dir="$( mktemp -d )" -wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip -wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip -unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir} -cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe /dist/jellyfin_${version}/nssm.exe -unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir} -cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe /dist/jellyfin_${version}/ffmpeg.exe -cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe /dist/jellyfin_${version}/ffprobe.exe -rm -rf ${addin_build_dir} - -# Prepare scripts -cp ${SOURCE_DIR}/deployment/windows/legacy/install-jellyfin.ps1 /dist/jellyfin_${version}/install-jellyfin.ps1 -cp ${SOURCE_DIR}/deployment/windows/legacy/install.bat /dist/jellyfin_${version}/install.bat - -# Create zip package -pushd /dist -zip -r /jellyfin_${version}.portable.zip jellyfin_${version} -popd -rm -rf /dist/jellyfin_${version} - -# Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv /jellyfin[-_]*.zip ${ARTIFACT_DIR}/ -chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} diff --git a/deployment/win-x86/package.sh b/deployment/win-x86/package.sh deleted file mode 100755 index 65e7e2928c..0000000000 --- a/deployment/win-x86/package.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -args="${@}" -declare -a docker_envvars -for arg in ${args}; do - docker_envvars+=("-e ${arg}") -done - -WORKDIR="$( pwd )" - -package_temporary_dir="${WORKDIR}/pkg-dist-tmp" -output_dir="${WORKDIR}/pkg-dist" -current_user="$( whoami )" -image_name="jellyfin-windows-x86-build" - -# Determine if sudo should be used for Docker -if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ - && [[ ! ${EUID:-1000} -eq 0 ]] \ - && [[ ! ${USER} == "root" ]] \ - && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then - docker_sudo="sudo" -else - docker_sudo="" -fi - -# Prepare temporary package dir -mkdir -p "${package_temporary_dir}" -# Set up the build environment Docker image -${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile -# Build the DEBs and copy out to ${package_temporary_dir} -${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars} -# Move the DEBs to the output directory -mkdir -p "${output_dir}" -mv "${package_temporary_dir}"/* "${output_dir}" diff --git a/deployment/fedora-package-x64/pkg-src/.gitignore b/fedora/.gitignore similarity index 100% rename from deployment/fedora-package-x64/pkg-src/.gitignore rename to fedora/.gitignore diff --git a/fedora/Makefile b/fedora/Makefile new file mode 100644 index 0000000000..97904ddd35 --- /dev/null +++ b/fedora/Makefile @@ -0,0 +1,26 @@ +VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin.spec) + +srpm: + cd fedora/; \ + SOURCE_DIR=.. \ + WORKDIR="$${PWD}"; \ + tar \ + --transform "s,^\.,jellyfin-server-$(VERSION)," \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude='deployment' \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + --exclude='jellyfin-server-$(VERSION).tar.gz' \ + -czf "jellyfin-server-$(VERSION).tar.gz" \ + -C $${SOURCE_DIR} ./ + cd fedora/; \ + rpmbuild -bs jellyfin.spec \ + --define "_sourcedir $$PWD/" \ + --define "_srcrpmdir $(outdir)" diff --git a/deployment/fedora-package-x64/pkg-src/README.md b/fedora/README.md similarity index 100% rename from deployment/fedora-package-x64/pkg-src/README.md rename to fedora/README.md diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin-firewalld.xml b/fedora/jellyfin-firewalld.xml similarity index 100% rename from deployment/fedora-package-x64/pkg-src/jellyfin-firewalld.xml rename to fedora/jellyfin-firewalld.xml diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/fedora/jellyfin.env similarity index 88% rename from deployment/fedora-package-x64/pkg-src/jellyfin.env rename to fedora/jellyfin.env index de48f13af5..bf64acd3f9 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.env +++ b/fedora/jellyfin.env @@ -20,6 +20,9 @@ JELLYFIN_CONFIG_DIR="/etc/jellyfin" JELLYFIN_LOG_DIR="/var/log/jellyfin" JELLYFIN_CACHE_DIR="/var/cache/jellyfin" +# web client path, installed by the jellyfin-web package +JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin-web" + # In-App service control JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.override.conf b/fedora/jellyfin.override.conf similarity index 100% rename from deployment/fedora-package-x64/pkg-src/jellyfin.override.conf rename to fedora/jellyfin.override.conf diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/fedora/jellyfin.service similarity index 69% rename from deployment/fedora-package-x64/pkg-src/jellyfin.service rename to fedora/jellyfin.service index f3dc594b1c..b092ebf2f0 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.service +++ b/fedora/jellyfin.service @@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of [Service] EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin -ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart=/usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} TimeoutSec=15 Restart=on-failure User=jellyfin diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/fedora/jellyfin.spec similarity index 62% rename from deployment/fedora-package-x64/pkg-src/jellyfin.spec rename to fedora/jellyfin.spec index 33c6f6f648..9311864a63 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -7,15 +7,13 @@ %endif Name: jellyfin -Version: 10.5.0 +Version: 10.6.0 Release: 1%{?dist} -Summary: The Free Software Media Browser -License: GPLv2 -URL: https://jellyfin.media +Summary: The Free Software Media System +License: GPLv3 +URL: https://jellyfin.org # Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` -Source0: https://github.com/%{name}/%{name}/archive/%{name}-%{version}.tar.gz -# Jellyfin Webinterface downloaded by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` -Source1: https://github.com/%{name}/%{name}-web/archive/%{name}-web-%{version}.tar.gz +Source0: jellyfin-server-%{version}.tar.gz Source11: jellyfin.service Source12: jellyfin.env Source13: jellyfin.sudoers @@ -25,43 +23,30 @@ Source16: jellyfin-firewalld.xml %{?systemd_requires} BuildRequires: systemd -Requires(pre): shadow-utils -BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel, git -%if 0%{?fedora} -BuildRequires: nodejs-yarn, git -%else -# Requirements not packaged in main repos -# From https://rpm.nodesource.com/pub_10.x/el/7/x86_64/ -BuildRequires: nodejs >= 10 yarn -%endif -Requires: libcurl, fontconfig, freetype, openssl, glibc libicu +BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel # Requirements not packaged in main repos # COPR @dotnet-sig/dotnet or # https://packages.microsoft.com/rhel/7/prod/ BuildRequires: dotnet-runtime-3.1, dotnet-sdk-3.1 -# RPMfusion free -Requires: ffmpeg - +Requires: %{name}-server = %{version}-%{release}, %{name}-web >= 10.6, %{name}-web < 10.7 # Disable Automatic Dependency Processing AutoReqProv: no %description Jellyfin is a free software media system that puts you in control of managing and streaming your media. +%package server +# RPMfusion free +Summary: The Free Software Media System Server backend +Requires(pre): shadow-utils +Requires: ffmpeg +Requires: libcurl, fontconfig, freetype, openssl, glibc libicu + +%description server +The Jellyfin media server backend. %prep -%autosetup -n %{name}-%{version} -b 0 -b 1 -web_build_dir="$(mktemp -d)" -web_target="$PWD/MediaBrowser.WebDashboard/jellyfin-web" -pushd ../jellyfin-web-%{version} || pushd ../jellyfin-web-master -%if 0%{?fedora} -nodejs-yarn install -%else -yarn install -%endif -mkdir -p ${web_target} -mv dist/* ${web_target}/ -popd +%autosetup -n jellyfin-server-%{version} -b 0 %build @@ -70,81 +55,76 @@ export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server -%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE -%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/%{name}.service.d/override.conf -%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/%{name}/logging.json +%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE +%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf +%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json %{__mkdir} -p %{buildroot}%{_bindir} tee %{buildroot}%{_bindir}/jellyfin << EOF #!/bin/sh -exec %{_libdir}/%{name}/%{name} \${@} +exec %{_libdir}/jellyfin/jellyfin \${@} EOF %{__mkdir} -p %{buildroot}%{_sharedstatedir}/jellyfin -%{__mkdir} -p %{buildroot}%{_sysconfdir}/%{name} +%{__mkdir} -p %{buildroot}%{_sysconfdir}/jellyfin %{__mkdir} -p %{buildroot}%{_var}/log/jellyfin %{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin -%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/%{name}.service -%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/%{name} -%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/%{name}-sudoers -%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/%{name}/restart.sh -%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/%{name}.xml +%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/jellyfin.service +%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/jellyfin +%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/jellyfin-sudoers +%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh +%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml -%files -%{_libdir}/%{name}/jellyfin-web/* -%attr(755,root,root) %{_bindir}/%{name} -%{_libdir}/%{name}/*.json -%{_libdir}/%{name}/*.dll -%{_libdir}/%{name}/*.so -%{_libdir}/%{name}/*.a -%{_libdir}/%{name}/createdump +%files server +%attr(755,root,root) %{_bindir}/jellyfin +%{_libdir}/jellyfin/*.json +%{_libdir}/jellyfin/*.dll +%{_libdir}/jellyfin/*.so +%{_libdir}/jellyfin/*.a +%{_libdir}/jellyfin/createdump # Needs 755 else only root can run it since binary build by dotnet is 722 -%attr(755,root,root) %{_libdir}/%{name}/jellyfin -%{_libdir}/%{name}/SOS_README.md -%{_unitdir}/%{name}.service -%{_libexecdir}/%{name}/restart.sh -%{_prefix}/lib/firewalld/services/%{name}.xml -%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/%{name} -%config %{_sysconfdir}/sysconfig/%{name} -%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/%{name}-sudoers -%config(noreplace) %{_sysconfdir}/systemd/system/%{name}.service.d/override.conf -%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/%{name}/logging.json +%attr(755,root,root) %{_libdir}/jellyfin/jellyfin +%{_libdir}/jellyfin/SOS_README.md +%{_unitdir}/jellyfin.service +%{_libexecdir}/jellyfin/restart.sh +%{_prefix}/lib/firewalld/services/jellyfin.xml +%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/jellyfin +%config %{_sysconfdir}/sysconfig/jellyfin +%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/jellyfin-sudoers +%config(noreplace) %{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf +%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/jellyfin/logging.json %attr(750,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin %attr(-,jellyfin,jellyfin) %dir %{_var}/log/jellyfin %attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin -%if 0%{?fedora} -%license LICENSE -%else -%{_datadir}/licenses/%{name}/LICENSE -%endif +%{_datadir}/licenses/jellyfin/LICENSE -%pre +%pre server getent group jellyfin >/dev/null || groupadd -r jellyfin getent passwd jellyfin >/dev/null || \ useradd -r -g jellyfin -d %{_sharedstatedir}/jellyfin -s /sbin/nologin \ -c "Jellyfin default user" jellyfin exit 0 -%post +%post server # Move existing configuration cache and logs to their new locations and symlink them. if [ $1 -gt 1 ] ; then service_state=$(systemctl is-active jellyfin.service) if [ "${service_state}" = "active" ]; then systemctl stop jellyfin.service fi - if [ ! -L %{_sharedstatedir}/%{name}/config ]; then - mv %{_sharedstatedir}/%{name}/config/* %{_sysconfdir}/%{name}/ - rmdir %{_sharedstatedir}/%{name}/config - ln -sf %{_sysconfdir}/%{name} %{_sharedstatedir}/%{name}/config + if [ ! -L %{_sharedstatedir}/jellyfin/config ]; then + mv %{_sharedstatedir}/jellyfin/config/* %{_sysconfdir}/jellyfin/ + rmdir %{_sharedstatedir}/jellyfin/config + ln -sf %{_sysconfdir}/jellyfin %{_sharedstatedir}/jellyfin/config fi - if [ ! -L %{_sharedstatedir}/%{name}/logs ]; then - mv %{_sharedstatedir}/%{name}/logs/* %{_var}/log/jellyfin - rmdir %{_sharedstatedir}/%{name}/logs - ln -sf %{_var}/log/jellyfin %{_sharedstatedir}/%{name}/logs + if [ ! -L %{_sharedstatedir}/jellyfin/logs ]; then + mv %{_sharedstatedir}/jellyfin/logs/* %{_var}/log/jellyfin + rmdir %{_sharedstatedir}/jellyfin/logs + ln -sf %{_var}/log/jellyfin %{_sharedstatedir}/jellyfin/logs fi - if [ ! -L %{_sharedstatedir}/%{name}/cache ]; then - mv %{_sharedstatedir}/%{name}/cache/* %{_var}/cache/jellyfin - rmdir %{_sharedstatedir}/%{name}/cache - ln -sf %{_var}/cache/jellyfin %{_sharedstatedir}/%{name}/cache + if [ ! -L %{_sharedstatedir}/jellyfin/cache ]; then + mv %{_sharedstatedir}/jellyfin/cache/* %{_var}/cache/jellyfin + rmdir %{_sharedstatedir}/jellyfin/cache + ln -sf %{_var}/cache/jellyfin %{_sharedstatedir}/jellyfin/cache fi if [ "${service_state}" = "active" ]; then systemctl start jellyfin.service @@ -152,13 +132,15 @@ if [ $1 -gt 1 ] ; then fi %systemd_post jellyfin.service -%preun +%preun server %systemd_preun jellyfin.service -%postun +%postun server %systemd_postun_with_restart jellyfin.service %changelog +* Mon Mar 23 2020 Jellyfin Packaging Team +- Forthcoming stable release * Fri Oct 11 2019 Jellyfin Packaging Team - New upstream version 10.5.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.5.0 * Sat Aug 31 2019 Jellyfin Packaging Team diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers b/fedora/jellyfin.sudoers similarity index 100% rename from deployment/fedora-package-x64/pkg-src/jellyfin.sudoers rename to fedora/jellyfin.sudoers diff --git a/deployment/fedora-package-x64/pkg-src/restart.sh b/fedora/restart.sh similarity index 95% rename from deployment/fedora-package-x64/pkg-src/restart.sh rename to fedora/restart.sh index 9b64b6d728..9e53efecd0 100755 --- a/deployment/fedora-package-x64/pkg-src/restart.sh +++ b/fedora/restart.sh @@ -24,13 +24,13 @@ cmd="$( get_service_command )" echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now + echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now ;; 'service') - echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now + echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now ;; 'sysv') - echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now + echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now ;; esac exit 0 diff --git a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs new file mode 100644 index 0000000000..8bf613f05f --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs @@ -0,0 +1,43 @@ +using System; +using MediaBrowser.Common.Extensions; +using Xunit; + +namespace Jellyfin.Common.Tests.Extensions +{ + public class StringExtensionsTests + { + [Theory] + [InlineData("", 'q', "")] + [InlineData("Banana split", ' ', "Banana")] + [InlineData("Banana split", 'q', "Banana split")] + public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult) + { + var result = str.AsSpan().LeftPart(needle).ToString(); + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("", "", "")] + [InlineData("", "q", "")] + [InlineData("Banana split", "", "")] + [InlineData("Banana split", " ", "Banana")] + [InlineData("Banana split test", " split", "Banana")] + public void LeftPart_ValidArgsWithoutStringComparison_Correct(string str, string needle, string expectedResult) + { + var result = str.AsSpan().LeftPart(needle).ToString(); + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("", "", StringComparison.Ordinal, "")] + [InlineData("Banana split", " ", StringComparison.Ordinal, "Banana")] + [InlineData("Banana split test", " split", StringComparison.Ordinal, "Banana")] + [InlineData("Banana split test", " Split", StringComparison.Ordinal, "Banana split test")] + [InlineData("Banana split test", " Splït", StringComparison.InvariantCultureIgnoreCase, "Banana split test")] + public void LeftPart_ValidArgs_Correct(string str, string needle, StringComparison stringComparison, string expectedResult) + { + var result = str.AsSpan().LeftPart(needle, stringComparison).ToString(); + Assert.Equal(expectedResult, result); + } + } +} diff --git a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs new file mode 100644 index 0000000000..51633e157c --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs @@ -0,0 +1,19 @@ +using System; +using MediaBrowser.Model.Extensions; +using Xunit; + +namespace Jellyfin.Model.Tests.Extensions +{ + public class StringHelperTests + { + [Theory] + [InlineData("", "")] + [InlineData("banana", "Banana")] + [InlineData("Banana", "Banana")] + [InlineData("ä", "Ä")] + public void StringHelper_ValidArgs_Success(string input, string expectedResult) + { + Assert.Equal(expectedResult, StringHelper.FirstToUpper(input)); + } + } +} diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj new file mode 100644 index 0000000000..f6c3274986 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + false + true + enable + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs index 9a4b0b5422..c9a295a4ce 100644 --- a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs +++ b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs @@ -6,61 +6,45 @@ namespace Jellyfin.Naming.Tests.Music { public class MultiDiscAlbumTests { - [Fact] - public void TestMultiDiscAlbums() + private readonly NamingOptions _namingOptions = new NamingOptions(); + + [Theory] + [InlineData("", false)] + [InlineData("C:/", false)] + [InlineData("/home/", false)] + [InlineData(@"blah blah", false)] + [InlineData(@"D:/music/weezer/03 Pinkerton", false)] + [InlineData(@"D:/music/michael jackson/Bad (2012 Remaster)", false)] + [InlineData(@"cd1", true)] + [InlineData(@"disc18", true)] + [InlineData(@"disk10", true)] + [InlineData(@"vol7", true)] + [InlineData(@"volume1", true)] + [InlineData(@"cd 1", true)] + [InlineData(@"disc 1", true)] + [InlineData(@"disk 1", true)] + [InlineData(@"disk", false)] + [InlineData(@"disk ·", false)] + [InlineData(@"disk a", false)] + [InlineData(@"disk volume", false)] + [InlineData(@"disc disc", false)] + [InlineData(@"disk disc 6", false)] + [InlineData(@"cd - 1", true)] + [InlineData(@"disc- 1", true)] + [InlineData(@"disk - 1", true)] + [InlineData(@"Disc 01 (Hugo Wolf · 24 Lieder)", true)] + [InlineData(@"Disc 04 (Encores and Folk Songs)", true)] + [InlineData(@"Disc04 (Encores and Folk Songs)", true)] + [InlineData(@"Disc 04(Encores and Folk Songs)", true)] + [InlineData(@"Disc04(Encores and Folk Songs)", true)] + [InlineData(@"D:/Video/MBTestLibrary/VideoTest/music/.38 special/anth/Disc 2", true)] + [InlineData(@"[1985] Opportunities (Let's make lots of money) (1985)", false)] + [InlineData(@"Blah 04(Encores and Folk Songs)", false)] + public void AlbumParser_MultidiscPath_Identifies(string path, bool result) { - Assert.False(IsMultiDiscAlbumFolder(@"blah blah")); - Assert.False(IsMultiDiscAlbumFolder(@"D:/music/weezer/03 Pinkerton")); - Assert.False(IsMultiDiscAlbumFolder(@"D:/music/michael jackson/Bad (2012 Remaster)")); + var parser = new AlbumParser(_namingOptions); - Assert.True(IsMultiDiscAlbumFolder(@"cd1")); - Assert.True(IsMultiDiscAlbumFolder(@"disc18")); - Assert.True(IsMultiDiscAlbumFolder(@"disk10")); - Assert.True(IsMultiDiscAlbumFolder(@"vol7")); - Assert.True(IsMultiDiscAlbumFolder(@"volume1")); - - Assert.True(IsMultiDiscAlbumFolder(@"cd 1")); - Assert.True(IsMultiDiscAlbumFolder(@"disc 1")); - Assert.True(IsMultiDiscAlbumFolder(@"disk 1")); - - Assert.False(IsMultiDiscAlbumFolder(@"disk")); - Assert.False(IsMultiDiscAlbumFolder(@"disk ·")); - Assert.False(IsMultiDiscAlbumFolder(@"disk a")); - - Assert.False(IsMultiDiscAlbumFolder(@"disk volume")); - Assert.False(IsMultiDiscAlbumFolder(@"disc disc")); - Assert.False(IsMultiDiscAlbumFolder(@"disk disc 6")); - - Assert.True(IsMultiDiscAlbumFolder(@"cd - 1")); - Assert.True(IsMultiDiscAlbumFolder(@"disc- 1")); - Assert.True(IsMultiDiscAlbumFolder(@"disk - 1")); - - Assert.True(IsMultiDiscAlbumFolder(@"Disc 01 (Hugo Wolf · 24 Lieder)")); - Assert.True(IsMultiDiscAlbumFolder(@"Disc 04 (Encores and Folk Songs)")); - Assert.True(IsMultiDiscAlbumFolder(@"Disc04 (Encores and Folk Songs)")); - Assert.True(IsMultiDiscAlbumFolder(@"Disc 04(Encores and Folk Songs)")); - Assert.True(IsMultiDiscAlbumFolder(@"Disc04(Encores and Folk Songs)")); - - Assert.True(IsMultiDiscAlbumFolder(@"D:/Video/MBTestLibrary/VideoTest/music/.38 special/anth/Disc 2")); - } - - [Fact] - public void TestMultiDiscAlbums1() - { - Assert.False(IsMultiDiscAlbumFolder(@"[1985] Opportunities (Let's make lots of money) (1985)")); - } - - [Fact] - public void TestMultiDiscAlbums2() - { - Assert.False(IsMultiDiscAlbumFolder(@"Blah 04(Encores and Folk Songs)")); - } - - private bool IsMultiDiscAlbumFolder(string path) - { - var parser = new AlbumParser(new NamingOptions()); - - return parser.IsMultiPart(path); + Assert.Equal(result, parser.IsMultiPart(path)); } } } diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs index 41da889c28..40d80607c8 100644 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -1,4 +1,5 @@ -using Emby.Naming.Common; +using System; +using Emby.Naming.Common; using Emby.Naming.Subtitles; using Xunit; @@ -6,28 +7,19 @@ namespace Jellyfin.Naming.Tests.Subtitles { public class SubtitleParserTests { - private SubtitleParser GetParser() - { - var options = new NamingOptions(); + private readonly NamingOptions _namingOptions = new NamingOptions(); - return new SubtitleParser(options); - } - - [Fact] - public void TestSubtitles() + [Theory] + [InlineData("The Skin I Live In (2011).srt", null, false, false)] + [InlineData("The Skin I Live In (2011).eng.srt", "eng", false, false)] + [InlineData("The Skin I Live In (2011).eng.default.srt", "eng", true, false)] + [InlineData("The Skin I Live In (2011).eng.forced.srt", "eng", false, true)] + [InlineData("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true)] + [InlineData("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true)] + [InlineData("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true)] + public void SubtitleParser_ValidFileName_Parses(string input, string language, bool isDefault, bool isForced) { - Test("The Skin I Live In (2011).srt", null, false, false); - Test("The Skin I Live In (2011).eng.srt", "eng", false, false); - Test("The Skin I Live In (2011).eng.default.srt", "eng", true, false); - Test("The Skin I Live In (2011).eng.forced.srt", "eng", false, true); - Test("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true); - Test("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true); - Test("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true); - } - - private void Test(string input, string language, bool isDefault, bool isForced) - { - var parser = GetParser(); + var parser = new SubtitleParser(_namingOptions); var result = parser.ParseFile(input); @@ -35,5 +27,20 @@ namespace Jellyfin.Naming.Tests.Subtitles Assert.Equal(isDefault, result.IsDefault); Assert.Equal(isForced, result.IsForced); } + + [Theory] + [InlineData("The Skin I Live In (2011).mp4")] + public void SubtitleParser_InvalidFileName_ReturnsNull(string input) + { + var parser = new SubtitleParser(_namingOptions); + + Assert.Null(parser.ParseFile(input)); + } + + [Fact] + public void SubtitleParser_EmptyFileName_ThrowsArgumentException() + { + Assert.Throws(() => new SubtitleParser(_namingOptions).ParseFile(string.Empty)); + } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs deleted file mode 100644 index 0c2978aca9..0000000000 --- a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Emby.Naming.Common; -using Emby.Naming.Video; - -namespace Jellyfin.Naming.Tests.Video -{ - public abstract class BaseVideoTest - { - private readonly NamingOptions _namingOptions = new NamingOptions(); - - protected VideoResolver GetParser() - => new VideoResolver(_namingOptions); - } -} diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs index 49cb2387bb..917d8fb3a9 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs @@ -46,6 +46,7 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)] // FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)] [InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again + [InlineData("3 days to kill (2005).mkv", "3 days to kill", 2005)] public void CleanDateTimeTest(string input, string expectedName, int? expectedYear) { input = Path.GetFileName(input); diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index a64d173496..a2722a1753 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -5,7 +5,7 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class ExtraTests : BaseVideoTest + public class ExtraTests { private readonly NamingOptions _videoOptions = new NamingOptions(); diff --git a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs index ed3112936d..d2b3d6ff0d 100644 --- a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs @@ -4,26 +4,26 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class Format3DTests : BaseVideoTest + public class Format3DTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + [Fact] public void TestKodiFormat3D() { - var options = new NamingOptions(); - - Test("Super movie.3d.mp4", false, null, options); - Test("Super movie.3d.hsbs.mp4", true, "hsbs", options); - Test("Super movie.3d.sbs.mp4", true, "sbs", options); - Test("Super movie.3d.htab.mp4", true, "htab", options); - Test("Super movie.3d.tab.mp4", true, "tab", options); - Test("Super movie 3d hsbs.mp4", true, "hsbs", options); + Test("Super movie.3d.mp4", false, null); + Test("Super movie.3d.hsbs.mp4", true, "hsbs"); + Test("Super movie.3d.sbs.mp4", true, "sbs"); + Test("Super movie.3d.htab.mp4", true, "htab"); + Test("Super movie.3d.tab.mp4", true, "tab"); + Test("Super movie 3d hsbs.mp4", true, "hsbs"); } [Fact] public void Test3DName() { var result = - GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv"); + new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv"); Assert.Equal("hsbs", result.Format3D); Assert.Equal("Oblivion", result.Name); @@ -34,32 +34,31 @@ namespace Jellyfin.Naming.Tests.Video { // These were introduced for Media Browser 3 // Kodi conventions are preferred but these still need to be supported - var options = new NamingOptions(); - Test("Super movie.3d.mp4", false, null, options); - Test("Super movie.3d.hsbs.mp4", true, "hsbs", options); - Test("Super movie.3d.sbs.mp4", true, "sbs", options); - Test("Super movie.3d.htab.mp4", true, "htab", options); - Test("Super movie.3d.tab.mp4", true, "tab", options); + Test("Super movie.3d.mp4", false, null); + Test("Super movie.3d.hsbs.mp4", true, "hsbs"); + Test("Super movie.3d.sbs.mp4", true, "sbs"); + Test("Super movie.3d.htab.mp4", true, "htab"); + Test("Super movie.3d.tab.mp4", true, "tab"); - Test("Super movie.hsbs.mp4", true, "hsbs", options); - Test("Super movie.sbs.mp4", true, "sbs", options); - Test("Super movie.htab.mp4", true, "htab", options); - Test("Super movie.tab.mp4", true, "tab", options); - Test("Super movie.sbs3d.mp4", true, "sbs3d", options); - Test("Super movie.3d.mvc.mp4", true, "mvc", options); + Test("Super movie.hsbs.mp4", true, "hsbs"); + Test("Super movie.sbs.mp4", true, "sbs"); + Test("Super movie.htab.mp4", true, "htab"); + Test("Super movie.tab.mp4", true, "tab"); + Test("Super movie.sbs3d.mp4", true, "sbs3d"); + Test("Super movie.3d.mvc.mp4", true, "mvc"); - Test("Super movie [3d].mp4", false, null, options); - Test("Super movie [hsbs].mp4", true, "hsbs", options); - Test("Super movie [fsbs].mp4", true, "fsbs", options); - Test("Super movie [ftab].mp4", true, "ftab", options); - Test("Super movie [htab].mp4", true, "htab", options); - Test("Super movie [sbs3d].mp4", true, "sbs3d", options); + Test("Super movie [3d].mp4", false, null); + Test("Super movie [hsbs].mp4", true, "hsbs"); + Test("Super movie [fsbs].mp4", true, "fsbs"); + Test("Super movie [ftab].mp4", true, "ftab"); + Test("Super movie [htab].mp4", true, "htab"); + Test("Super movie [sbs3d].mp4", true, "sbs3d"); } - private void Test(string input, bool is3D, string format3D, NamingOptions options) + private void Test(string input, bool is3D, string? format3D) { - var parser = new Format3DParser(options); + var parser = new Format3DParser(_namingOptions); var result = parser.Parse(input); diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index b8fbb2cb25..03fe32b6e1 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -8,6 +8,8 @@ namespace Jellyfin.Naming.Tests.Video { public class MultiVersionTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + // FIXME // [Fact] public void TestMultiEdition1() @@ -430,8 +432,7 @@ namespace Jellyfin.Naming.Tests.Video private VideoListResolver GetResolver() { - var options = new NamingOptions(); - return new VideoListResolver(options); + return new VideoListResolver(_namingOptions); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs index 3e0cbaf0c2..3630a07e46 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs @@ -6,8 +6,10 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class StackTests : BaseVideoTest + public class StackTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + [Fact] public void TestSimpleStack() { @@ -446,7 +448,7 @@ namespace Jellyfin.Naming.Tests.Video private StackResolver GetResolver() { - return new StackResolver(new NamingOptions()); + return new StackResolver(_namingOptions); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs index 8d5ced9a41..e31d97e2e3 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs @@ -4,8 +4,10 @@ using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class StubTests : BaseVideoTest + public class StubTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + [Fact] public void TestStubs() { @@ -27,16 +29,14 @@ namespace Jellyfin.Naming.Tests.Video public void TestStubName() { var result = - GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc"); + new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc"); Assert.Equal("Oblivion", result.Name); } private void Test(string path, bool isStub, string stubType) { - var options = new NamingOptions(); - - var isStubResult = StubResolver.TryResolveFile(path, options, out var stubTypeResult); + var isStubResult = StubResolver.TryResolveFile(path, _namingOptions, out var stubTypeResult); Assert.Equal(isStub, isStubResult); diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index ef8a178989..566dc9f7c1 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -8,6 +8,7 @@ namespace Jellyfin.Naming.Tests.Video { public class VideoListResolverTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); // FIXME // [Fact] public void TestStackAndExtras() @@ -450,8 +451,7 @@ namespace Jellyfin.Naming.Tests.Video private VideoListResolver GetResolver() { - var options = new NamingOptions(); - return new VideoListResolver(options); + return new VideoListResolver(_namingOptions); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 5a3ce88869..114735ceed 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -1,275 +1,200 @@ -using MediaBrowser.Model.Entities; +using System.Collections.Generic; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.Entities; using Xunit; namespace Jellyfin.Naming.Tests.Video { - public class VideoResolverTests : BaseVideoTest + public class VideoResolverTests { - // FIXME - // [Fact] - public void TestSimpleFile() + private readonly NamingOptions _namingOptions = new NamingOptions(); + + public static IEnumerable GetResolveFileTestData() { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("Brave", result.Name); - Assert.Null(result.ExtraType); + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv", + Container = "mkv", + Name = "7 Psychos" + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv", + Container = "mkv", + Name = "3 days to kill", + Year = 2005 + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/American Psycho/American.Psycho.mkv", + Container = "mkv", + Name = "American.Psycho", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv", + Container = "mkv", + Name = "brave", + Year = 2006, + Is3D = true, + Format3D = "sbs", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv", + Container = "mkv", + Name = "300", + Year = 2006 + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv", + Container = "mkv", + Name = "300", + Year = 2006, + Is3D = true, + Format3D = "sbs", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc", + Container = "disc", + Name = "brave", + Year = 2006, + IsStub = true, + StubType = "bluray", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc", + Container = "disc", + Name = "300", + Year = 2006, + IsStub = true, + StubType = "bluray", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc", + Container = "disc", + Name = "Brave", + Year = 2006, + IsStub = true, + StubType = "bluray", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/300 (2007)/300 (2006).bluray.disc", + Container = "disc", + Name = "300", + Year = 2006, + IsStub = true, + StubType = "bluray", + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv", + Container = "mkv", + Name = "300", + Year = 2006, + ExtraType = ExtraType.Trailer, + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv", + Container = "mkv", + Name = "Brave", + Year = 2006, + ExtraType = ExtraType.Trailer, + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/300 (2007)/300 (2006).mkv", + Container = "mkv", + Name = "300", + Year = 2006 + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv", + Container = "mkv", + Name = "Bad Boys", + Year = 1995, + } + }; + yield return new object[] + { + new VideoFileInfo() + { + Path = @"/server/Movies/Brave (2007)/Brave (2006).mkv", + Container = "mkv", + Name = "Brave", + Year = 2006, + } + }; } - // FIXME - // [Fact] - public void TestSimpleFile2() + + [Theory] + [MemberData(nameof(GetResolveFileTestData))] + public void ResolveFile_ValidFileName_Success(VideoFileInfo expectedResult) { - var parser = GetParser(); + var result = new VideoResolver(_namingOptions).ResolveFile(expectedResult.Path); - var result = - parser.ResolveFile(@"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv"); - - Assert.Equal(1995, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("Bad Boys", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestSimpleFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestExtra() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal(ExtraType.Trailer, result.ExtraType); - Assert.Equal("Brave (2006)-trailer", result.Name); - } - - // FIXME - // [Fact] - public void TestExtraWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("300 (2006)-trailer", result.Name); - Assert.Equal(ExtraType.Trailer, result.ExtraType); - } - - // FIXME - // [Fact] - public void TestStubFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestStubFile() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("Brave", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestExtraStubWithNumericNameNotSupported() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestExtraStubNotSupported() - { - // Using a stub for an extra is currently not supported - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc"); - - Assert.Equal(2006, result.Year); - Assert.True(result.IsStub); - Assert.Equal("bluray", result.StubType); - Assert.False(result.Is3D); - Assert.Equal("brave", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void Test3DFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.True(result.Is3D); - Assert.Equal("sbs", result.Format3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestBad3DFileWithNumericName() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("300", result.Name); - Assert.Null(result.ExtraType); - Assert.Null(result.Format3D); - } - - // FIXME - // [Fact] - public void Test3DFile() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv"); - - Assert.Equal(2006, result.Year); - Assert.False(result.IsStub); - Assert.True(result.Is3D); - Assert.Equal("sbs", result.Format3D); - Assert.Equal("brave", result.Name); - Assert.Null(result.ExtraType); - } - - [Fact] - public void TestNameWithoutDate() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/American Psycho/American.Psycho.mkv"); - - Assert.Null(result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Null(result.Format3D); - Assert.Equal("American.Psycho", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestCleanDateAndStringsSequence() - { - var parser = GetParser(); - - // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again - var result = - parser.ResolveFile(@"/server/Movies/3.Days.to.Kill/3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv"); - - Assert.Equal(2014, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Null(result.Format3D); - Assert.Equal("3.Days.to.Kill", result.Name); - Assert.Null(result.ExtraType); - } - - // FIXME - // [Fact] - public void TestCleanDateAndStringsSequence1() - { - var parser = GetParser(); - - // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again - var result = - parser.ResolveFile(@"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv"); - - Assert.Equal(2005, result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Null(result.Format3D); - Assert.Equal("3 days to kill", result.Name); - Assert.Null(result.ExtraType); - } - - [Fact] - public void TestFolderNameWithExtension() - { - var parser = GetParser(); - - var result = - parser.ResolveFile(@"/server/Movies/7 Psychos.mkv/7 Psychos.mkv"); - - Assert.Null(result.Year); - Assert.False(result.IsStub); - Assert.False(result.Is3D); - Assert.Equal("7 Psychos", result.Name); - Assert.Null(result.ExtraType); + Assert.NotNull(result); + Assert.Equal(result.Path, expectedResult.Path); + Assert.Equal(result.Container, expectedResult.Container); + Assert.Equal(result.Name, expectedResult.Name); + Assert.Equal(result.Year, expectedResult.Year); + Assert.Equal(result.ExtraType, expectedResult.ExtraType); + Assert.Equal(result.Format3D, expectedResult.Format3D); + Assert.Equal(result.Is3D, expectedResult.Is3D); + Assert.Equal(result.IsStub, expectedResult.IsStub); + Assert.Equal(result.StubType, expectedResult.StubType); + Assert.Equal(result.IsDirectory, expectedResult.IsDirectory); + Assert.Equal(result.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension); } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs new file mode 100644 index 0000000000..39bd94b598 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs @@ -0,0 +1,18 @@ +using Emby.Server.Implementations.HttpServer; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.HttpServer +{ + public class ResponseFilterTests + { + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + [InlineData("This is a clean string.", "This is a clean string.")] + [InlineData("This isn't \n\ra clean string.", "This isn't a clean string.")] + public void RemoveControlCharacters_ValidArgs_Correct(string? input, string? result) + { + Assert.Equal(result, ResponseFilter.RemoveControlCharacters(input)); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs new file mode 100644 index 0000000000..c771f5f4ae --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -0,0 +1,27 @@ +using System; +using Emby.Server.Implementations.Library; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library +{ + public class PathExtensionsTests + { + [Theory] + [InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")] + [InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")] + [InlineData("Superman: Red Son", "imdbid", null)] + public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) + { + Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); + } + + [Theory] + [InlineData("", "")] + [InlineData("Superman: Red Son [imdbid=tt10985510]", "")] + [InlineData("", "imdbid")] + public void GetAttributeValue_EmptyString_ThrowsArgumentException(string input, string attribute) + { + Assert.Throws(() => PathExtensions.GetAttributeValue(input, attribute)); + } + } +} diff --git a/tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs b/tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs new file mode 100644 index 0000000000..34698fe251 --- /dev/null +++ b/tests/MediaBrowser.Api.Tests/BrandingServiceTests.cs @@ -0,0 +1,49 @@ +using System.Text.Json; +using System.Threading.Tasks; +using MediaBrowser.Model.Branding; +using Xunit; + +namespace MediaBrowser.Api.Tests +{ + public sealed class BrandingServiceTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public BrandingServiceTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetConfiguration_ReturnsCorrectResponse() + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync("/Branding/Configuration"); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString()); + var responseBody = await response.Content.ReadAsStreamAsync(); + _ = await JsonSerializer.DeserializeAsync(responseBody); + } + + [Theory] + [InlineData("/Branding/Css")] + [InlineData("/Branding/Css.css")] + public async Task GetCss_ReturnsCorrectResponse(string url) + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync(url); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal("text/css", response.Content.Headers.ContentType.ToString()); + } + } +} diff --git a/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs b/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs new file mode 100644 index 0000000000..c39ed07de3 --- /dev/null +++ b/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using Emby.Server.Implementations; +using Emby.Server.Implementations.IO; +using Emby.Server.Implementations.Networking; +using Jellyfin.Drawing.Skia; +using Jellyfin.Server; +using MediaBrowser.Common; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Extensions.Logging; + +namespace MediaBrowser.Api.Tests +{ + /// + /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests. + /// + public class JellyfinApplicationFactory : WebApplicationFactory + { + private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); + private static readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); + + /// + /// Initializes a new instance of the class. + /// + public JellyfinApplicationFactory() + { + // Perform static initialization that only needs to happen once per test-run + Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); + Program.PerformStaticInitialization(); + } + + /// + protected override IWebHostBuilder CreateWebHostBuilder() + { + return new WebHostBuilder(); + } + + /// + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Specify the startup command line options + var commandLineOpts = new StartupOptions + { + NoWebClient = true, + NoAutoRunWebApp = true + }; + + // Use a temporary directory for the application paths + var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName())); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "logs")); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "config")); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "cache")); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "jellyfin-web")); + var appPaths = new ServerApplicationPaths( + webHostPathRoot, + Path.Combine(webHostPathRoot, "logs"), + Path.Combine(webHostPathRoot, "config"), + Path.Combine(webHostPathRoot, "cache"), + Path.Combine(webHostPathRoot, "jellyfin-web")); + + // Create the logging config file + // TODO: We shouldn't need to do this since we are only logging to console + Program.InitLoggingConfigFile(appPaths).GetAwaiter().GetResult(); + + // Create a copy of the application configuration to use for startup + var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths); + + ILoggerFactory loggerFactory = new SerilogLoggerFactory(); + _disposableComponents.Add(loggerFactory); + + // Create the app host and initialize it + var appHost = new CoreAppHost( + appPaths, + loggerFactory, + commandLineOpts, + new ManagedFileSystem(loggerFactory.CreateLogger(), appPaths), + new NetworkManager(loggerFactory.CreateLogger())); + _disposableComponents.Add(appHost); + var serviceCollection = new ServiceCollection(); + appHost.Init(serviceCollection); + + // Configure the web host builder + Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths); + } + + /// + protected override TestServer CreateServer(IWebHostBuilder builder) + { + // Create the test server using the base implementation + var testServer = base.CreateServer(builder); + + // Finish initializing the app host + var appHost = (CoreAppHost)testServer.Services.GetRequiredService(); + appHost.ServiceProvider = testServer.Services; + appHost.InitializeServices().GetAwaiter().GetResult(); + appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); + + return testServer; + } + + /// + protected override void Dispose(bool disposing) + { + foreach (var disposable in _disposableComponents) + { + disposable.Dispose(); + } + + _disposableComponents.Clear(); + + base.Dispose(disposing); + } + } +} diff --git a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj new file mode 100644 index 0000000000..f30e486900 --- /dev/null +++ b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp3.1 + false + true + enable + + + + + + + + + + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + + + diff --git a/tests/jellyfin-tests.ruleset b/tests/jellyfin-tests.ruleset new file mode 100644 index 0000000000..5a113e955e --- /dev/null +++ b/tests/jellyfin-tests.ruleset @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/windows/build-jellyfin.ps1 b/windows/build-jellyfin.ps1 new file mode 100644 index 0000000000..c762137a75 --- /dev/null +++ b/windows/build-jellyfin.ps1 @@ -0,0 +1,190 @@ +[CmdletBinding()] +param( + [switch]$MakeNSIS, + [switch]$InstallNSIS, + [switch]$InstallFFMPEG, + [switch]$InstallNSSM, + [switch]$SkipJellyfinBuild, + [switch]$GenerateZip, + [string]$InstallLocation = "./dist/jellyfin-win-nsis", + [string]$UXLocation = "../jellyfin-ux", + [switch]$InstallTrayApp, + [ValidateSet('Debug','Release')][string]$BuildType = 'Release', + [ValidateSet('Quiet','Minimal', 'Normal')][string]$DotNetVerbosity = 'Minimal', + [ValidateSet('win','win7', 'win8','win81','win10')][string]$WindowsVersion = 'win', + [ValidateSet('x64','x86', 'arm', 'arm64')][string]$Architecture = 'x64' +) + +$ProgressPreference = 'SilentlyContinue' # Speedup all downloads by hiding progress bars. + +#PowershellCore and *nix check to make determine which temp dir to use. +if(($PSVersionTable.PSEdition -eq 'Core') -and (-not $IsWindows)){ + $TempDir = mktemp -d +}else{ + $TempDir = $env:Temp +} +#Create staging dir +New-Item -ItemType Directory -Force -Path $InstallLocation +$ResolvedInstallLocation = Resolve-Path $InstallLocation +$ResolvedUXLocation = Resolve-Path $UXLocation + +function Build-JellyFin { + if(($Architecture -eq 'arm64') -and ($WindowsVersion -ne 'win10')){ + Write-Error "arm64 only supported with Windows10 Version" + exit + } + if(($Architecture -eq 'arm') -and ($WindowsVersion -notin @('win10','win81','win8'))){ + Write-Error "arm only supported with Windows 8 or higher" + exit + } + Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture" + Write-Verbose "InstallLocation: $ResolvedInstallLocation" + Write-Verbose "DotNetVerbosity: $DotNetVerbosity" + dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server +} + +function Install-FFMPEG { + param( + [string]$ResolvedInstallLocation, + [string]$Architecture, + [string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared" + ) + Write-Verbose "Checking Architecture" + if($Architecture -notin @('x86','x64')){ + Write-Warning "No builds available for your selected architecture of $Architecture" + Write-Warning "FFMPEG will not be installed" + }elseif($Architecture -eq 'x64'){ + Write-Verbose "Downloading 64 bit FFMPEG" + Invoke-WebRequest -Uri https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose + }else{ + Write-Verbose "Downloading 32 bit FFMPEG" + Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/shared/$FFMPEGVersionX86.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose + } + + Expand-Archive "$tempdir/ffmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" -Force | Write-Verbose + if($Architecture -eq 'x64'){ + Write-Verbose "Copying Binaries to Jellyfin location" + Get-ChildItem "$tempdir/ffmpeg" | ForEach-Object { + Copy-Item $_.FullName -Destination $installLocation | Write-Verbose + } + }else{ + Write-Verbose "Copying Binaries to Jellyfin location" + Get-ChildItem "$tempdir/ffmpeg/$FFMPEGVersionX86/bin" | ForEach-Object { + Copy-Item $_.FullName -Destination $installLocation | Write-Verbose + } + } + Remove-Item "$tempdir/ffmpeg/" -Recurse -Force -ErrorAction Continue | Write-Verbose + Remove-Item "$tempdir/ffmpeg.zip" -Force -ErrorAction Continue | Write-Verbose +} + +function Install-NSSM { + param( + [string]$ResolvedInstallLocation, + [string]$Architecture + ) + Write-Verbose "Checking Architecture" + if($Architecture -notin @('x86','x64')){ + Write-Warning "No builds available for your selected architecture of $Architecture" + Write-Warning "NSSM will not be installed" + }else{ + Write-Verbose "Downloading NSSM" + # [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + # Temporary workaround, file is hosted in an azure blob with a custom domain in front for brevity + Invoke-WebRequest -Uri http://files.evilt.win/nssm/nssm-2.24-101-g897c7ad.zip -UseBasicParsing -OutFile "$tempdir/nssm.zip" | Write-Verbose + } + + Expand-Archive "$tempdir/nssm.zip" -DestinationPath "$tempdir/nssm/" -Force | Write-Verbose + if($Architecture -eq 'x64'){ + Write-Verbose "Copying Binaries to Jellyfin location" + Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win64" | ForEach-Object { + Copy-Item $_.FullName -Destination $installLocation | Write-Verbose + } + }else{ + Write-Verbose "Copying Binaries to Jellyfin location" + Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win32" | ForEach-Object { + Copy-Item $_.FullName -Destination $installLocation | Write-Verbose + } + } + Remove-Item "$tempdir/nssm/" -Recurse -Force -ErrorAction Continue | Write-Verbose + Remove-Item "$tempdir/nssm.zip" -Force -ErrorAction Continue | Write-Verbose +} + +function Make-NSIS { + param( + [string]$ResolvedInstallLocation + ) + + $env:InstallLocation = $ResolvedInstallLocation + if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ + & "$tempdir/nsis/nsis-3.04/makensis.exe" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi" + } else { + & "makensis" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi" + } + Copy-Item .\deployment\windows\jellyfin_*.exe $ResolvedInstallLocation\..\ +} + + +function Install-NSIS { + Write-Verbose "Downloading NSIS" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri https://nchc.dl.sourceforge.net/project/nsis/NSIS%203/3.04/nsis-3.04.zip -UseBasicParsing -OutFile "$tempdir/nsis.zip" | Write-Verbose + + Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" -Force | Write-Verbose +} + +function Cleanup-NSIS { + Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose + Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose +} + +function Install-TrayApp { + param( + [string]$ResolvedInstallLocation, + [string]$Architecture + ) + Write-Verbose "Checking Architecture" + if($Architecture -ne 'x64'){ + Write-Warning "No builds available for your selected architecture of $Architecture" + Write-Warning "The tray app will not be available." + }else{ + Write-Verbose "Downloading Tray App and copying to Jellyfin location" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri https://github.com/jellyfin/jellyfin-windows-tray/releases/latest/download/JellyfinTray.exe -UseBasicParsing -OutFile "$installLocation/JellyfinTray.exe" | Write-Verbose + } +} + +if(-not $SkipJellyfinBuild.IsPresent -and -not ($InstallNSIS -eq $true)){ + Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture" + Build-JellyFin +} +if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){ + Write-Verbose "Starting FFMPEG Install" + Install-FFMPEG $ResolvedInstallLocation $Architecture +} +if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){ + Write-Verbose "Starting NSSM Install" + Install-NSSM $ResolvedInstallLocation $Architecture +} +if($InstallTrayApp.IsPresent -or ($InstallTrayApp -eq $true)){ + Write-Verbose "Downloading Windows Tray App" + Install-TrayApp $ResolvedInstallLocation $Architecture +} +#Copy-Item .\deployment\windows\install-jellyfin.ps1 $ResolvedInstallLocation\install-jellyfin.ps1 +#Copy-Item .\deployment\windows\install.bat $ResolvedInstallLocation\install.bat +Copy-Item .\LICENSE $ResolvedInstallLocation\LICENSE +if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ + Write-Verbose "Installing NSIS" + Install-NSIS +} +if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){ + Write-Verbose "Starting NSIS Package creation" + Make-NSIS $ResolvedInstallLocation +} +if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ + Write-Verbose "Cleanup NSIS" + Cleanup-NSIS +} +if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){ + Compress-Archive -Path $ResolvedInstallLocation -DestinationPath "$ResolvedInstallLocation/jellyfin.zip" -Force +} +Write-Verbose "Finished" diff --git a/windows/dependencies.txt b/windows/dependencies.txt new file mode 100644 index 0000000000..16f77cce7c --- /dev/null +++ b/windows/dependencies.txt @@ -0,0 +1,2 @@ +dotnet +nsis diff --git a/windows/dialogs/confirmation.nsddef b/windows/dialogs/confirmation.nsddef new file mode 100644 index 0000000000..969ebacd62 --- /dev/null +++ b/windows/dialogs/confirmation.nsddef @@ -0,0 +1,24 @@ + + + + !include "helpers\StrSlash.nsh" + ${StrSlash} '$0' $INSTDIR + + ${StrSlash} '$1' $_JELLYFINDATADIR_ + + ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ + \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ + Installation Folder:\b0 $0\line\b \ + Service install:\b0 $_INSTALLSERVICE_\line\b \ + Service start:\b0 $_SERVICESTART_\line\b \ + Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \ + Jellyfin Data Folder:\b0 $1\par \ +\ + \pard\sa200\sl276\slmult1\f1\lang1043\par \ + }" + + diff --git a/windows/dialogs/confirmation.nsdinc b/windows/dialogs/confirmation.nsdinc new file mode 100644 index 0000000000..f00e9b43ab --- /dev/null +++ b/windows/dialogs/confirmation.nsdinc @@ -0,0 +1,61 @@ +; ========================================================= +; This file was generated by NSISDialogDesigner 1.4.4.0 +; http://coolsoft.altervista.org/nsisdialogdesigner +; +; Do not edit it manually, use NSISDialogDesigner instead! +; Modified by EraYaN (2019-09-01) +; ========================================================= + +; handle variables +Var hCtl_confirmation +Var hCtl_confirmation_ConfirmRichText + +; HeaderCustomScript +!include "helpers\StrSlash.nsh" + + + +; dialog create function +Function fnc_confirmation_Create + + ; === confirmation (type: Dialog) === + nsDialogs::Create 1018 + Pop $hCtl_confirmation + ${If} $hCtl_confirmation == error + Abort + ${EndIf} + !insertmacro MUI_HEADER_TEXT "Confirmation Page" "Please confirm your choices for Jellyfin Server installation" + + ; === ConfirmRichText (type: RichText) === + nsDialogs::CreateControl /NOUNLOAD "RichEdit20A" ${ES_READONLY}|${WS_VISIBLE}|${WS_CHILD}|${WS_TABSTOP}|${WS_VSCROLL}|${ES_MULTILINE}|${ES_WANTRETURN} ${WS_EX_STATICEDGE} 8u 7u 280u 126u "" + Pop $hCtl_confirmation_ConfirmRichText + ${NSD_AddExStyle} $hCtl_confirmation_ConfirmRichText ${WS_EX_STATICEDGE} + + ; CreateFunctionCustomScript + ${StrSlash} '$0' $INSTDIR + + ${StrSlash} '$1' $_JELLYFINDATADIR_ + + ${If} $_INSTALLSERVICE_ == "Yes" + ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ + \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ + Installation Folder:\b0 $0\line\b \ + Service install:\b0 $_INSTALLSERVICE_\line\b \ + Service start:\b0 $_SERVICESTART_\line\b \ + Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \ + Jellyfin Data Folder:\b0 $1\par \ + \ + \pard\sa200\sl276\slmult1\f1\lang1043\par \ + }" + ${Else} + ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ + \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ + Installation Folder:\b0 $0\line\b \ + Service install:\b0 $_INSTALLSERVICE_\line\b \ + Jellyfin Data Folder:\b0 $1\par \ + \ + \pard\sa200\sl276\slmult1\f1\lang1043\par \ + }" + ${EndIf} + +FunctionEnd diff --git a/windows/dialogs/service-config.nsddef b/windows/dialogs/service-config.nsddef new file mode 100644 index 0000000000..3509ada249 --- /dev/null +++ b/windows/dialogs/service-config.nsddef @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/windows/dialogs/service-config.nsdinc b/windows/dialogs/service-config.nsdinc new file mode 100644 index 0000000000..58c350f2ec --- /dev/null +++ b/windows/dialogs/service-config.nsdinc @@ -0,0 +1,56 @@ +; ========================================================= +; This file was generated by NSISDialogDesigner 1.4.4.0 +; http://coolsoft.altervista.org/nsisdialogdesigner +; +; Do not edit it manually, use NSISDialogDesigner instead! +; ========================================================= + +; handle variables +Var hCtl_service_config +Var hCtl_service_config_StartServiceAfterInstall +Var hCtl_service_config_LocalSystemAccountLabel +Var hCtl_service_config_NetworkServiceAccountLabel +Var hCtl_service_config_UseLocalSystemAccount +Var hCtl_service_config_UseNetworkServiceAccount +Var hCtl_service_config_Font1 + + +; dialog create function +Function fnc_service_config_Create + + ; custom font definitions + CreateFont $hCtl_service_config_Font1 "Microsoft Sans Serif" "8.25" "700" + + ; === service_config (type: Dialog) === + nsDialogs::Create 1018 + Pop $hCtl_service_config + ${If} $hCtl_service_config == error + Abort + ${EndIf} + !insertmacro MUI_HEADER_TEXT "Configure the service" "This controls what type of access the server gets to this system." + + ; === StartServiceAfterInstall (type: Checkbox) === + ${NSD_CreateCheckbox} 8u 118u 280u 15u "Start Service after Install" + Pop $hCtl_service_config_StartServiceAfterInstall + ${NSD_Check} $hCtl_service_config_StartServiceAfterInstall + + ; === LocalSystemAccountLabel (type: Label) === + ${NSD_CreateLabel} 8u 71u 280u 28u "The Local System account has full access to every resource and file on the system. This can have very real security implications, do not use unless absolutely neseccary." + Pop $hCtl_service_config_LocalSystemAccountLabel + + ; === NetworkServiceAccountLabel (type: Label) === + ${NSD_CreateLabel} 8u 24u 280u 28u "The NetworkService account is a predefined local account used by the service control manager. It is the recommended way to install the Jellyfin Server service." + Pop $hCtl_service_config_NetworkServiceAccountLabel + + ; === UseLocalSystemAccount (type: RadioButton) === + ${NSD_CreateRadioButton} 8u 54u 280u 15u "Use Local System account" + Pop $hCtl_service_config_UseLocalSystemAccount + ${NSD_AddStyle} $hCtl_service_config_UseLocalSystemAccount ${WS_GROUP} + + ; === UseNetworkServiceAccount (type: RadioButton) === + ${NSD_CreateRadioButton} 8u 7u 280u 15u "Use Network Service account (Recommended)" + Pop $hCtl_service_config_UseNetworkServiceAccount + SendMessage $hCtl_service_config_UseNetworkServiceAccount ${WM_SETFONT} $hCtl_service_config_Font1 0 + ${NSD_Check} $hCtl_service_config_UseNetworkServiceAccount + +FunctionEnd diff --git a/windows/dialogs/setuptype.nsddef b/windows/dialogs/setuptype.nsddef new file mode 100644 index 0000000000..b55ceeaaa6 --- /dev/null +++ b/windows/dialogs/setuptype.nsddef @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/windows/dialogs/setuptype.nsdinc b/windows/dialogs/setuptype.nsdinc new file mode 100644 index 0000000000..8746ad2cc6 --- /dev/null +++ b/windows/dialogs/setuptype.nsdinc @@ -0,0 +1,50 @@ +; ========================================================= +; This file was generated by NSISDialogDesigner 1.4.4.0 +; http://coolsoft.altervista.org/nsisdialogdesigner +; +; Do not edit it manually, use NSISDialogDesigner instead! +; ========================================================= + +; handle variables +Var hCtl_setuptype +Var hCtl_setuptype_InstallasaServiceLabel +Var hCtl_setuptype_InstallasaService +Var hCtl_setuptype_BasicInstallLabel +Var hCtl_setuptype_BasicInstall +Var hCtl_setuptype_Font1 + + +; dialog create function +Function fnc_setuptype_Create + + ; custom font definitions + CreateFont $hCtl_setuptype_Font1 "Microsoft Sans Serif" "8.25" "700" + + ; === setuptype (type: Dialog) === + nsDialogs::Create 1018 + Pop $hCtl_setuptype + ${If} $hCtl_setuptype == error + Abort + ${EndIf} + !insertmacro MUI_HEADER_TEXT "Setup Type" "Control how Jellyfin is installed." + + ; === InstallasaServiceLabel (type: Label) === + ${NSD_CreateLabel} 8u 71u 280u 28u "Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." + Pop $hCtl_setuptype_InstallasaServiceLabel + + ; === InstallasaService (type: RadioButton) === + ${NSD_CreateRadioButton} 8u 54u 280u 15u "Install as a Service (Advanced Users)" + Pop $hCtl_setuptype_InstallasaService + ${NSD_AddStyle} $hCtl_setuptype_InstallasaService ${WS_GROUP} + + ; === BasicInstallLabel (type: Label) === + ${NSD_CreateLabel} 8u 24u 280u 28u "The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." + Pop $hCtl_setuptype_BasicInstallLabel + + ; === BasicInstall (type: RadioButton) === + ${NSD_CreateRadioButton} 8u 7u 280u 15u "Basic Install (Recommended)" + Pop $hCtl_setuptype_BasicInstall + SendMessage $hCtl_setuptype_BasicInstall ${WM_SETFONT} $hCtl_setuptype_Font1 0 + ${NSD_Check} $hCtl_setuptype_BasicInstall + +FunctionEnd diff --git a/windows/helpers/ShowError.nsh b/windows/helpers/ShowError.nsh new file mode 100644 index 0000000000..6e09b1e407 --- /dev/null +++ b/windows/helpers/ShowError.nsh @@ -0,0 +1,10 @@ +; Show error +!macro ShowError TEXT RETRYLABEL + MessageBox MB_ABORTRETRYIGNORE|MB_ICONSTOP "${TEXT}" IDIGNORE +2 IDRETRY ${RETRYLABEL} + Abort +!macroend + +!macro ShowErrorFinal TEXT + MessageBox MB_OK|MB_ICONSTOP "${TEXT}" + Abort +!macroend diff --git a/windows/helpers/StrSlash.nsh b/windows/helpers/StrSlash.nsh new file mode 100644 index 0000000000..b8aa771aa6 --- /dev/null +++ b/windows/helpers/StrSlash.nsh @@ -0,0 +1,47 @@ +; Adapted from: https://nsis.sourceforge.io/Another_String_Replace_(and_Slash/BackSlash_Converter) (2019-08-31) + +!macro _StrSlashConstructor out in + Push "${in}" + Push "\" + Call StrSlash + Pop ${out} +!macroend + +!define StrSlash '!insertmacro "_StrSlashConstructor"' + +; Push $filenamestring (e.g. 'c:\this\and\that\filename.htm') +; Push "\" +; Call StrSlash +; Pop $R0 +; ;Now $R0 contains 'c:/this/and/that/filename.htm' +Function StrSlash + Exch $R3 ; $R3 = needle ("\" or "/") + Exch + Exch $R1 ; $R1 = String to replacement in (haystack) + Push $R2 ; Replaced haystack + Push $R4 ; $R4 = not $R3 ("/" or "\") + Push $R6 + Push $R7 ; Scratch reg + StrCpy $R2 "" + StrLen $R6 $R1 + StrCpy $R4 "\" + StrCmp $R3 "/" loop + StrCpy $R4 "/" +loop: + StrCpy $R7 $R1 1 + StrCpy $R1 $R1 $R6 1 + StrCmp $R7 $R3 found + StrCpy $R2 "$R2$R7" + StrCmp $R1 "" done loop +found: + StrCpy $R2 "$R2$R4" + StrCmp $R1 "" done loop +done: + StrCpy $R3 $R2 + Pop $R7 + Pop $R6 + Pop $R4 + Pop $R2 + Pop $R1 + Exch $R3 +FunctionEnd diff --git a/windows/jellyfin.nsi b/windows/jellyfin.nsi new file mode 100644 index 0000000000..fada62d981 --- /dev/null +++ b/windows/jellyfin.nsi @@ -0,0 +1,575 @@ +!verbose 3 +SetCompressor /SOLID bzip2 +ShowInstDetails show +ShowUninstDetails show +Unicode True + +;-------------------------------- +!define SF_USELECTED 0 ; used to check selected options status, rest are inherited from Sections.nsh + + !include "MUI2.nsh" + !include "Sections.nsh" + !include "LogicLib.nsh" + + !include "helpers\ShowError.nsh" + +; Global variables that we'll use + Var _JELLYFINVERSION_ + Var _JELLYFINDATADIR_ + Var _SETUPTYPE_ + Var _INSTALLSERVICE_ + Var _SERVICESTART_ + Var _SERVICEACCOUNTTYPE_ + Var _EXISTINGINSTALLATION_ + Var _EXISTINGSERVICE_ + Var _MAKESHORTCUTS_ + Var _FOLDEREXISTS_ +; +!ifdef x64 + !define ARCH "x64" + !define NAMESUFFIX "(64 bit)" + !define INSTALL_DIRECTORY "$PROGRAMFILES64\Jellyfin\Server" +!endif + +!ifdef x84 + !define ARCH "x86" + !define NAMESUFFIX "(32 bit)" + !define INSTALL_DIRECTORY "$PROGRAMFILES32\Jellyfin\Server" +!endif + +!ifndef ARCH + !error "Set the Arch with /Dx86 or /Dx64" +!endif + +;-------------------------------- + + !define REG_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\JellyfinServer" ;Registry to show up in Add/Remove Programs + !define REG_CONFIG_KEY "Software\Jellyfin\Server" ;Registry to store all configuration + + !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ ;Align installer version with jellyfin.dll version + + Name "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} ${NAMESUFFIX}" ; This is referred in various header text labels + OutFile "jellyfin_${ver_1}.${ver_2}.${ver_3}_windows-${ARCH}.exe" ; Naming convention jellyfin_{version}_windows-{arch].exe + BrandingText "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} Installer" ; This shows in just over the buttons + +; installer attributes, these show up in details tab on installer properties + VIProductVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIProductVersion format, should be X.X.X.X + VIFileVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIFileVersion format, should be X.X.X.X + VIAddVersionKey "ProductName" "Jellyfin Server" + VIAddVersionKey "FileVersion" "${ver_1}.${ver_2}.${ver_3}.0" + VIAddVersionKey "LegalCopyright" "(c) 2019 Jellyfin Contributors. Code released under the GNU General Public License" + VIAddVersionKey "FileDescription" "Jellyfin Server: The Free Software Media System" + +;TODO, check defaults + InstallDir ${INSTALL_DIRECTORY} ;Default installation folder + InstallDirRegKey HKLM "${REG_CONFIG_KEY}" "InstallFolder" ;Read the registry for install folder, + + RequestExecutionLevel admin ; ask it upfront for service control, and installing in priv folders + + CRCCheck on ; make sure the installer wasn't corrupted while downloading + + !define MUI_ABORTWARNING ;Prompts user in case of aborting install + +; TODO: Replace with nice Jellyfin Icons +!ifdef UXPATH + !define MUI_ICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Installer Icon + !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Uninstaller Icon + + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "${UXPATH}\branding\NSIS\installer-header.bmp" + !define MUI_WELCOMEFINISHPAGE_BITMAP "${UXPATH}\branding\NSIS\installer-right.bmp" + !define MUI_UNWELCOMEFINISHPAGE_BITMAP "${UXPATH}\branding\NSIS\installer-right.bmp" +!endif + +;-------------------------------- +;Pages + +; Welcome Page + !define MUI_WELCOMEPAGE_TEXT "The installer will ask for details to install Jellyfin Server." + !insertmacro MUI_PAGE_WELCOME +; License Page + !insertmacro MUI_PAGE_LICENSE "$%InstallLocation%\LICENSE" ; picking up generic GPL + +; Setup Type Page + Page custom ShowSetupTypePage SetupTypePage_Config + +; Components Page + !define MUI_PAGE_CUSTOMFUNCTION_PRE HideComponentsPage + !insertmacro MUI_PAGE_COMPONENTS + !define MUI_PAGE_CUSTOMFUNCTION_PRE HideInstallDirectoryPage ; Controls when to hide / show + !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Install folder" ; shows just above the folder selection dialog + !insertmacro MUI_PAGE_DIRECTORY + +; Data folder Page + !define MUI_PAGE_CUSTOMFUNCTION_PRE HideDataDirectoryPage ; Controls when to hide / show + !define MUI_PAGE_HEADER_TEXT "Choose Data Location" + !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install the Jellyfin Server data." + !define MUI_DIRECTORYPAGE_TEXT_TOP "The installer will set the following folder for Jellyfin Server data. To install in a different folder, click Browse and select another folder. Please make sure the folder exists and is accessible. Click Next to continue." + !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data folder" + !define MUI_DIRECTORYPAGE_VARIABLE $_JELLYFINDATADIR_ + !insertmacro MUI_PAGE_DIRECTORY + +; Custom Dialogs + !include "dialogs\setuptype.nsdinc" + !include "dialogs\service-config.nsdinc" + !include "dialogs\confirmation.nsdinc" + +; Select service account type + #!define MUI_PAGE_CUSTOMFUNCTION_PRE HideServiceConfigPage ; Controls when to hide / show (This does not work for Page, might need to go PageEx) + #!define MUI_PAGE_CUSTOMFUNCTION_SHOW fnc_service_config_Show + #!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ServiceConfigPage_Config + #!insertmacro MUI_PAGE_CUSTOM ServiceAccountType + Page custom ShowServiceConfigPage ServiceConfigPage_Config + +; Confirmation Page + Page custom ShowConfirmationPage ; just letting the user know what they chose to install + +; Actual Installion Page + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + #!insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +;Languages; Add more languages later here if needed + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections +Section "!Jellyfin Server (required)" InstallJellyfinServer + SectionIn RO ; Mandatory section, isn't this the whole purpose to run the installer. + + StrCmp "$_EXISTINGINSTALLATION_" "Yes" RunUninstaller CarryOn ; Silently uninstall in case of previous installation + + RunUninstaller: + DetailPrint "Looking for uninstaller at $INSTDIR" + FindFirst $0 $1 "$INSTDIR\Uninstall.exe" + FindClose $0 + StrCmp $1 "" CarryOn ; the registry key was there but uninstaller was not found + + DetailPrint "Silently running the uninstaller at $INSTDIR" + ExecWait '"$INSTDIR\Uninstall.exe" /S _?=$INSTDIR' $0 + DetailPrint "Uninstall finished, $0" + + CarryOn: + ${If} $_EXISTINGSERVICE_ == 'Yes' + ExecWait '"$INSTDIR\nssm.exe" stop JellyfinServer' $0 + ${If} $0 <> 0 + MessageBox MB_OK|MB_ICONSTOP "Could not stop the Jellyfin Server service." + Abort + ${EndIf} + DetailPrint "Stopped Jellyfin Server service, $0" + ${EndIf} + + SetOutPath "$INSTDIR" + + File "/oname=icon.ico" "${UXPATH}\branding\NSIS\modern-install.ico" + File /r $%InstallLocation%\* + + +; Write the InstallFolder, DataFolder, Network Service info into the registry for later use + WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "InstallFolder" "$INSTDIR" + WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "DataFolder" "$_JELLYFINDATADIR_" + WriteRegStr HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" "$_SERVICEACCOUNTTYPE_" + + !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ + StrCpy $_JELLYFINVERSION_ "${ver_1}.${ver_2}.${ver_3}" ; + +; Write the uninstall keys for Windows + WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayName" "Jellyfin Server $_JELLYFINVERSION_ ${NAMESUFFIX}" + WriteRegExpandStr HKLM "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"' + WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayIcon" '"$INSTDIR\Uninstall.exe",0' + WriteRegStr HKLM "${REG_UNINST_KEY}" "Publisher" "The Jellyfin Project" + WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.org/" + WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayVersion" "$_JELLYFINVERSION_" + WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoModify" 1 + WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoRepair" 1 + +;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" +SectionEnd + +Section "Jellyfin Server Service" InstallService +${If} $_INSTALLSERVICE_ == "Yes" ; Only run this if we're going to install the service! + ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 + DetailPrint "Jellyfin Server service statuscode, $0" + ${If} $0 == 0 + InstallRetry: + ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --service --datadir \"$_JELLYFINDATADIR_\"' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not install the Jellyfin Server service." InstallRetry + ${EndIf} + DetailPrint "Jellyfin Server Service install, $0" + ${Else} + DetailPrint "Jellyfin Server Service exists, updating..." + + ConfigureApplicationRetry: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Application "$INSTDIR\jellyfin.exe"' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureApplicationRetry + ${EndIf} + DetailPrint "Jellyfin Server Service setting (Application), $0" + + ConfigureAppParametersRetry: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --service --datadir \"$_JELLYFINDATADIR_\"' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureAppParametersRetry + ${EndIf} + DetailPrint "Jellyfin Server Service setting (AppParameters), $0" + ${EndIf} + + + Sleep 3000 ; Give time for Windows to catchup + ConfigureStartRetry: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Start SERVICE_DELAYED_AUTO_START' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureStartRetry + ${EndIf} + DetailPrint "Jellyfin Server Service setting (Start), $0" + + ConfigureDescriptionRetry: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Description "Jellyfin Server: The Free Software Media System"' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureDescriptionRetry + ${EndIf} + DetailPrint "Jellyfin Server Service setting (Description), $0" + ConfigureDisplayNameRetry: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer DisplayName "Jellyfin Server"' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureDisplayNameRetry + + ${EndIf} + DetailPrint "Jellyfin Server Service setting (DisplayName), $0" + + Sleep 3000 + ${If} $_SERVICEACCOUNTTYPE_ == "NetworkService" ; the default install using NSSM is Local System + ConfigureNetworkServiceRetry: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Objectname "Network Service"' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service account." ConfigureNetworkServiceRetry + ${EndIf} + DetailPrint "Jellyfin Server service account change, $0" + ${EndIf} + + Sleep 3000 + ConfigureDefaultAppExit: + ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppExit Default Exit' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not configure the Jellyfin Server service app exit action." ConfigureDefaultAppExit + ${EndIf} + DetailPrint "Jellyfin Server service exit action set, $0" +${EndIf} + +SectionEnd + +Section "-start service" StartService +${If} $_SERVICESTART_ == "Yes" +${AndIf} $_INSTALLSERVICE_ == "Yes" + StartRetry: + ExecWait '"$INSTDIR\nssm.exe" start JellyfinServer' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not start the Jellyfin Server service." StartRetry + ${EndIf} + DetailPrint "Jellyfin Server service start, $0" +${EndIf} +SectionEnd + +Section "Create Shortcuts" CreateWinShortcuts + ${If} $_MAKESHORTCUTS_ == "Yes" + CreateDirectory "$SMPROGRAMS\Jellyfin Server" + CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin (View Console).lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMAXIMIZED + CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin Tray App.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 + ;CreateShortCut "$DESKTOP\Jellyfin Server.lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMINIMIZED + CreateShortCut "$DESKTOP\Jellyfin Server\Jellyfin Server.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 + ${EndIf} +SectionEnd + +;-------------------------------- +;Descriptions + +;Language strings + LangString DESC_InstallJellyfinServer ${LANG_ENGLISH} "Install Jellyfin Server" + LangString DESC_InstallService ${LANG_ENGLISH} "Install As a Service" + +;Assign language strings to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${InstallJellyfinServer} $(DESC_InstallJellyfinServer) + !insertmacro MUI_DESCRIPTION_TEXT ${InstallService} $(DESC_InstallService) + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ReadRegStr $INSTDIR HKLM "${REG_CONFIG_KEY}" "InstallFolder" ; read the installation folder + ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; read the data folder + ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; read the account name + + DetailPrint "Jellyfin Install location: $INSTDIR" + DetailPrint "Jellyfin Data folder: $_JELLYFINDATADIR_" + + MessageBox MB_YESNO|MB_ICONINFORMATION "Do you want to retain the Jellyfin Server data folder? The media will not be touched. $\r$\nIf unsure choose YES." /SD IDYES IDYES PreserveData + + RMDir /r /REBOOTOK "$_JELLYFINDATADIR_" + + PreserveData: + + ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 + DetailPrint "Jellyfin Server service statuscode, $0" + IntCmp $0 0 NoServiceUninstall ; service doesn't exist, may be run from desktop shortcut + + Sleep 3000 ; Give time for Windows to catchup + + UninstallStopRetry: + ExecWait '"$INSTDIR\nssm.exe" stop JellyfinServer' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not stop the Jellyfin Server service." UninstallStopRetry + ${EndIf} + DetailPrint "Stopped Jellyfin Server service, $0" + + UninstallRemoveRetry: + ExecWait '"$INSTDIR\nssm.exe" remove JellyfinServer confirm' $0 + ${If} $0 <> 0 + !insertmacro ShowError "Could not remove the Jellyfin Server service." UninstallRemoveRetry + ${EndIf} + DetailPrint "Removed Jellyfin Server service, $0" + + Sleep 3000 ; Give time for Windows to catchup + + NoServiceUninstall: ; existing install was present but no service was detected. Remove shortcuts if account is set to none + ${If} $_SERVICEACCOUNTTYPE_ == "None" + RMDir /r "$SMPROGRAMS\Jellyfin Server" + Delete "$DESKTOP\Jellyfin Server.lnk" + DetailPrint "Removed old shortcuts..." + ${EndIf} + + Delete "$INSTDIR\*.*" + RMDir /r /REBOOTOK "$INSTDIR\jellyfin-web" + Delete "$INSTDIR\Uninstall.exe" + RMDir /r /REBOOTOK "$INSTDIR" + + DeleteRegKey HKLM "Software\Jellyfin" + DeleteRegKey HKLM "${REG_UNINST_KEY}" + +SectionEnd + +Function .onInit +; Setting up defaults + StrCpy $_INSTALLSERVICE_ "Yes" + StrCpy $_SERVICESTART_ "Yes" + StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" + StrCpy $_EXISTINGINSTALLATION_ "No" + StrCpy $_EXISTINGSERVICE_ "No" + StrCpy $_MAKESHORTCUTS_ "No" + + SetShellVarContext current + StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" + + System::Call 'kernel32::CreateMutex(p 0, i 0, t "JellyfinServerMutex") p .r1 ?e' + Pop $R0 + + StrCmp $R0 0 +3 + !insertmacro ShowErrorFinal "The installer is already running." + +;Detect if Jellyfin is already installed. +; In case it is installed, let the user choose either +; 1. Exit installer +; 2. Upgrade without messing with data +; 2a. Don't ask for any details, uninstall and install afresh with old settings + +; Read Registry for previous installation + ClearErrors + ReadRegStr "$0" HKLM "${REG_CONFIG_KEY}" "InstallFolder" + IfErrors NoExisitingInstall + + DetailPrint "Existing Jellyfin Server detected at: $0" + StrCpy "$INSTDIR" "$0" ; set the location fro registry as new default + + StrCpy $_EXISTINGINSTALLATION_ "Yes" ; Set our flag to be used later + SectionSetText ${InstallJellyfinServer} "Upgrade Jellyfin Server (required)" ; Change install text to "Upgrade" + + ; check if service was run using Network Service account + ClearErrors + ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default + + ClearErrors + ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds + + ; Hide sections which will not be needed in case of previous install + ; SectionSetText ${InstallService} "" + +; check if there is a service called Jellyfin, there should be +; hack : nssm statuscode Jellyfin will return non zero return code in case it exists + ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 + DetailPrint "Jellyfin Server service statuscode, $0" + IntCmp $0 0 NoService ; service doesn't exist, may be run from desktop shortcut + + ; if service was detected, set defaults going forward. + StrCpy $_EXISTINGSERVICE_ "Yes" + StrCpy $_INSTALLSERVICE_ "Yes" + StrCpy $_SERVICESTART_ "Yes" + StrCpy $_MAKESHORTCUTS_ "No" + SectionSetText ${CreateWinShortcuts} "" + + + NoService: ; existing install was present but no service was detected + ${If} $_SERVICEACCOUNTTYPE_ == "None" + StrCpy $_SETUPTYPE_ "Basic" + StrCpy $_INSTALLSERVICE_ "No" + StrCpy $_SERVICESTART_ "No" + StrCpy $_MAKESHORTCUTS_ "Yes" + ${EndIf} + +; Let the user know that we'll upgrade and provide an option to quit. + MessageBox MB_OKCANCEL|MB_ICONINFORMATION "Existing installation of Jellyfin Server was detected, it'll be upgraded, settings will be retained. \ + $\r$\nClick OK to proceed, Cancel to exit installer." /SD IDOK IDOK ProceedWithUpgrade + Quit ; Quit if the user is not sure about upgrade + + ProceedWithUpgrade: + + NoExisitingInstall: ; by this time, the variables have been correctly set to reflect previous install details + +FunctionEnd + +Function HideInstallDirectoryPage + ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder + Abort + ${EndIf} +FunctionEnd + +Function HideDataDirectoryPage + ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder + Abort + ${EndIf} +FunctionEnd + +Function HideServiceConfigPage + ${If} $_INSTALLSERVICE_ == "No" ; Not running as a service, don't ask for service type + ${OrIf} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder + Abort + ${EndIf} +FunctionEnd + +Function HideConfirmationPage + ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder + Abort + ${EndIf} +FunctionEnd + +Function HideSetupTypePage + ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for SetupType + Abort + ${EndIf} +FunctionEnd + +Function HideComponentsPage + ${If} $_SETUPTYPE_ == "Basic" ; Basic installation chosen, don't show components choice + Abort + ${EndIf} +FunctionEnd + +; Setup Type dialog show function +Function ShowSetupTypePage + Call HideSetupTypePage + Call fnc_setuptype_Create + nsDialogs::Show +FunctionEnd + +; Service Config dialog show function +Function ShowServiceConfigPage + Call HideServiceConfigPage + Call fnc_service_config_Create + nsDialogs::Show +FunctionEnd + +; Confirmation dialog show function +Function ShowConfirmationPage + Call HideConfirmationPage + Call fnc_confirmation_Create + nsDialogs::Show +FunctionEnd + +; Declare temp variables to read the options from the custom page. +Var StartServiceAfterInstall +Var UseNetworkServiceAccount +Var UseLocalSystemAccount +Var BasicInstall + + +Function SetupTypePage_Config +${NSD_GetState} $hCtl_setuptype_BasicInstall $BasicInstall + IfFileExists "$LOCALAPPDATA\Jellyfin" folderfound foldernotfound ; if the folder exists, use this, otherwise, go with new default + folderfound: + StrCpy $_FOLDEREXISTS_ "Yes" + Goto InstallCheck + foldernotfound: + StrCpy $_FOLDEREXISTS_ "No" + Goto InstallCheck + +InstallCheck: +${If} $BasicInstall == 1 + StrCpy $_SETUPTYPE_ "Basic" + StrCpy $_INSTALLSERVICE_ "No" + StrCpy $_SERVICESTART_ "No" + StrCpy $_SERVICEACCOUNTTYPE_ "None" + StrCpy $_MAKESHORTCUTS_ "Yes" + ${If} $_FOLDEREXISTS_ == "Yes" + StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\Jellyfin\" + ${EndIf} +${Else} + StrCpy $_SETUPTYPE_ "Advanced" + StrCpy $_INSTALLSERVICE_ "Yes" + StrCpy $_MAKESHORTCUTS_ "No" + ${If} $_FOLDEREXISTS_ == "Yes" + MessageBox MB_OKCANCEL|MB_ICONINFORMATION "An existing data folder was detected.\ + $\r$\nBasic Setup is highly recommended.\ + $\r$\nIf you proceed, you will need to set up Jellyfin again." IDOK GoAhead IDCANCEL GoBack + GoBack: + Abort + ${EndIf} + GoAhead: + StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" + SectionSetText ${CreateWinShortcuts} "" +${EndIf} + +FunctionEnd + +Function ServiceConfigPage_Config +${NSD_GetState} $hCtl_service_config_StartServiceAfterInstall $StartServiceAfterInstall +${If} $StartServiceAfterInstall == 1 + StrCpy $_SERVICESTART_ "Yes" +${Else} + StrCpy $_SERVICESTART_ "No" +${EndIf} +${NSD_GetState} $hCtl_service_config_UseNetworkServiceAccount $UseNetworkServiceAccount +${NSD_GetState} $hCtl_service_config_UseLocalSystemAccount $UseLocalSystemAccount + +${If} $UseNetworkServiceAccount == 1 + StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" +${ElseIf} $UseLocalSystemAccount == 1 + StrCpy $_SERVICEACCOUNTTYPE_ "LocalSystem" +${Else} + !insertmacro ShowErrorFinal "Service account type not properly configured." +${EndIf} + +FunctionEnd + +; This function handles the choices during component selection +Function .onSelChange + +; If we are not installing service, we don't need to set the NetworkService account or StartService + SectionGetFlags ${InstallService} $0 + ${If} $0 = ${SF_SELECTED} + StrCpy $_INSTALLSERVICE_ "Yes" + ${Else} + StrCpy $_INSTALLSERVICE_ "No" + StrCpy $_SERVICESTART_ "No" + StrCpy $_SERVICEACCOUNTTYPE_ "None" + ${EndIf} +FunctionEnd + +Function .onInstSuccess + #ExecShell "open" "http://localhost:8096" +FunctionEnd diff --git a/windows/legacy/install-jellyfin.ps1 b/windows/legacy/install-jellyfin.ps1 new file mode 100644 index 0000000000..e909a0468e --- /dev/null +++ b/windows/legacy/install-jellyfin.ps1 @@ -0,0 +1,460 @@ +[CmdletBinding()] + +param( + [Switch]$Quiet, + [Switch]$InstallAsService, + [System.Management.Automation.pscredential]$ServiceUser, + [switch]$CreateDesktopShorcut, + [switch]$LaunchJellyfin, + [switch]$MigrateEmbyLibrary, + [string]$InstallLocation, + [string]$EmbyLibraryLocation, + [string]$JellyfinLibraryLocation +) +<# This form was created using POSHGUI.com a free online gui designer for PowerShell +.NAME + Install-Jellyfin +#> + +#This doesn't need to be used by default anymore, but I am keeping it in as a function for future use. +function Elevate-Window { + # Get the ID and security principal of the current user account + $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent() + $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID) + + # Get the security principal for the Administrator role + $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator + + # Check to see if we are currently running "as Administrator" + if ($myWindowsPrincipal.IsInRole($adminRole)) + { + # We are running "as Administrator" - so change the title and background color to indicate this + $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)" + $Host.UI.RawUI.BackgroundColor = "DarkBlue" + clear-host + } + else + { + # We are not running "as Administrator" - so relaunch as administrator + + # Create a new process object that starts PowerShell + $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell"; + + # Specify the current script path and name as a parameter + $newProcess.Arguments = $myInvocation.MyCommand.Definition; + + # Indicate that the process should be elevated + $newProcess.Verb = "runas"; + + # Start the new process + [System.Diagnostics.Process]::Start($newProcess); + + # Exit from the current, unelevated, process + exit + } +} + +#FIXME The install methods should be a function that takes all the params, the quiet flag should be a paramset + +if($Quiet.IsPresent -or $Quiet -eq $true){ + if([string]::IsNullOrEmpty($JellyfinLibraryLocation)){ + $Script:JellyfinDataDir = "$env:LOCALAPPDATA\jellyfin\" + }else{ + $Script:JellyfinDataDir = $JellyfinLibraryLocation + } + if([string]::IsNullOrEmpty($InstallLocation)){ + $Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" + }else{ + $Script:DefaultJellyfinInstallDirectory = $InstallLocation + } + + if([string]::IsNullOrEmpty($EmbyLibraryLocation)){ + $Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\data\" + }else{ + $Script:defaultEmbyDataDir = $EmbyLibraryLocation + } + + if($InstallAsService.IsPresent -or $InstallAsService -eq $true){ + $Script:InstallAsService = $true + }else{$Script:InstallAsService = $false} + if($null -eq $ServiceUser){ + $Script:InstallServiceAsUser = $false + }else{ + $Script:InstallServiceAsUser = $true + $Script:UserCredentials = $ServiceUser + $Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Local\jellyfin\"} + if($CreateDesktopShorcut.IsPresent -or $CreateDesktopShorcut -eq $true) {$Script:CreateShortcut = $true}else{$Script:CreateShortcut = $false} + if($MigrateEmbyLibrary.IsPresent -or $MigrateEmbyLibrary -eq $true){$Script:MigrateLibrary = $true}else{$Script:MigrateLibrary = $false} + if($LaunchJellyfin.IsPresent -or $LaunchJellyfin -eq $true){$Script:StartJellyfin = $true}else{$Script:StartJellyfin = $false} + + if(-not (Test-Path $Script:DefaultJellyfinInstallDirectory)){ + mkdir $Script:DefaultJellyfinInstallDirectory + } + Copy-Item -Path $PSScriptRoot/* -DestinationPath "$Script:DefaultJellyfinInstallDirectory/" -Force -Recurse + if($Script:InstallAsService){ + if($Script:InstallServiceAsUser){ + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" + Start-Sleep -Milliseconds 500 + &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START + }else{ + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" + Start-Sleep -Milliseconds 500 + #&"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin ObjectName $Script:UserCredentials.UserName $Script:UserCredentials.GetNetworkCredential().Password + #Set-Service -Name Jellyfin -Credential $Script:UserCredentials + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START + } + } + if($Script:MigrateLibrary){ + Copy-Item -Path $Script:defaultEmbyDataDir/config -Destination $Script:JellyfinDataDir -force -Recurse + Copy-Item -Path $Script:defaultEmbyDataDir/cache -Destination $Script:JellyfinDataDir -force -Recurse + Copy-Item -Path $Script:defaultEmbyDataDir/data -Destination $Script:JellyfinDataDir -force -Recurse + Copy-Item -Path $Script:defaultEmbyDataDir/metadata -Destination $Script:JellyfinDataDir -force -Recurse + Copy-Item -Path $Script:defaultEmbyDataDir/root -Destination $Script:JellyfinDataDir -force -Recurse + } + if($Script:CreateShortcut){ + $WshShell = New-Object -comObject WScript.Shell + $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Jellyfin.lnk") + $Shortcut.TargetPath = "$Script:DefaultJellyfinInstallDirectory\jellyfin.exe" + $Shortcut.Save() + } + if($Script:StartJellyfin){ + if($Script:InstallAsService){ + Get-Service Jellyfin | Start-Service + }else{ + Start-Process -FilePath $Script:DefaultJellyfinInstallDirectory\jellyfin.exe -PassThru + } + } +}else{ + +} +Add-Type -AssemblyName System.Windows.Forms +[System.Windows.Forms.Application]::EnableVisualStyles() + +$Script:JellyFinDataDir = "$env:LOCALAPPDATA\jellyfin\" +$Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" +$Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\" +$Script:InstallAsService = $False +$Script:InstallServiceAsUser = $false +$Script:CreateShortcut = $false +$Script:MigrateLibrary = $false +$Script:StartJellyfin = $false + +function InstallJellyfin { + Write-Host "Install as service: $Script:InstallAsService" + Write-Host "Install as serviceuser: $Script:InstallServiceAsUser" + Write-Host "Create Shortcut: $Script:CreateShortcut" + Write-Host "MigrateLibrary: $Script:MigrateLibrary" + $GUIElementsCollection | ForEach-Object { + $_.Enabled = $false + } + Write-Host "Making Jellyfin directory" + $ProgressBar.Minimum = 1 + $ProgressBar.Maximum = 100 + $ProgressBar.Value = 1 + if($Script:DefaultJellyfinInstallDirectory -ne $InstallLocationBox.Text){ + Write-Host "Custom Install Location Chosen: $($InstallLocationBox.Text)" + $Script:DefaultJellyfinInstallDirectory = $InstallLocationBox.Text + } + if($Script:JellyfinDataDir -ne $CustomLibraryBox.Text){ + Write-Host "Custom Library Location Chosen: $($CustomLibraryBox.Text)" + $Script:JellyfinDataDir = $CustomLibraryBox.Text + } + if(-not (Test-Path $Script:DefaultJellyfinInstallDirectory)){ + mkdir $Script:DefaultJellyfinInstallDirectory + } + Write-Host "Copying Jellyfin Data" + $progressbar.Value = 10 + Copy-Item -Path $PSScriptRoot/* -Destination $Script:DefaultJellyfinInstallDirectory/ -Force -Recurse + Write-Host "Finished Copying" + $ProgressBar.Value = 50 + if($Script:InstallAsService){ + if($Script:InstallServiceAsUser){ + Write-Host "Installing Service as user $($Script:UserCredentials.UserName)" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" + Start-Sleep -Milliseconds 2000 + &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START + }else{ + Write-Host "Installing Service as LocalSystem" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" + Start-Sleep -Milliseconds 2000 + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START + } + } + $progressbar.Value = 60 + if($Script:MigrateLibrary){ + if($Script:defaultEmbyDataDir -ne $LibraryLocationBox.Text){ + Write-Host "Custom location defined for emby library: $($LibraryLocationBox.Text)" + $Script:defaultEmbyDataDir = $LibraryLocationBox.Text + } + Write-Host "Copying emby library from $Script:defaultEmbyDataDir to $Script:JellyFinDataDir" + Write-Host "This could take a while depending on the size of your library. Please be patient" + Write-Host "Copying config" + Copy-Item -Path $Script:defaultEmbyDataDir/config -Destination $Script:JellyfinDataDir -force -Recurse + Write-Host "Copying cache" + Copy-Item -Path $Script:defaultEmbyDataDir/cache -Destination $Script:JellyfinDataDir -force -Recurse + Write-Host "Copying data" + Copy-Item -Path $Script:defaultEmbyDataDir/data -Destination $Script:JellyfinDataDir -force -Recurse + Write-Host "Copying metadata" + Copy-Item -Path $Script:defaultEmbyDataDir/metadata -Destination $Script:JellyfinDataDir -force -Recurse + Write-Host "Copying root dir" + Copy-Item -Path $Script:defaultEmbyDataDir/root -Destination $Script:JellyfinDataDir -force -Recurse + } + $progressbar.Value = 80 + if($Script:CreateShortcut){ + Write-Host "Creating Shortcut" + $WshShell = New-Object -comObject WScript.Shell + $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Jellyfin.lnk") + $Shortcut.TargetPath = "$Script:DefaultJellyfinInstallDirectory\jellyfin.exe" + $Shortcut.Save() + } + $ProgressBar.Value = 90 + if($Script:StartJellyfin){ + if($Script:InstallAsService){ + Write-Host "Starting Jellyfin Service" + Get-Service Jellyfin | Start-Service + }else{ + Write-Host "Starting Jellyfin" + Start-Process -FilePath $Script:DefaultJellyfinInstallDirectory\jellyfin.exe -PassThru + } + } + $progressbar.Value = 100 + Write-Host Finished + $wshell = New-Object -ComObject Wscript.Shell + $wshell.Popup("Operation Completed",0,"Done",0x1) + $InstallForm.Close() +} +function ServiceBoxCheckChanged { + if($InstallAsServiceCheck.Checked){ + $Script:InstallAsService = $true + $ServiceUserLabel.Visible = $true + $ServiceUserLabel.Enabled = $true + $ServiceUserBox.Visible = $true + $ServiceUserBox.Enabled = $true + }else{ + $Script:InstallAsService = $false + $ServiceUserLabel.Visible = $false + $ServiceUserLabel.Enabled = $false + $ServiceUserBox.Visible = $false + $ServiceUserBox.Enabled = $false + } +} +function UserSelect { + if($ServiceUserBox.Text -eq 'Local System') + { + $Script:InstallServiceAsUser = $false + $Script:UserCredentials = $null + $ServiceUserBox.Items.RemoveAt(1) + $ServiceUserBox.Items.Add("Custom User") + }elseif($ServiceUserBox.Text -eq 'Custom User'){ + $Script:InstallServiceAsUser = $true + $Script:UserCredentials = Get-Credential -Message "Please enter the credentials of the user you with to run Jellyfin Service as" -UserName $env:USERNAME + $ServiceUserBox.Items[1] = "$($Script:UserCredentials.UserName)" + } +} +function CreateShortcutBoxCheckChanged { + if($CreateShortcutCheck.Checked){ + $Script:CreateShortcut = $true + }else{ + $Script:CreateShortcut = $False + } +} +function StartJellyFinBoxCheckChanged { + if($StartProgramCheck.Checked){ + $Script:StartJellyfin = $true + }else{ + $Script:StartJellyfin = $false + } +} + +function CustomLibraryCheckChanged { + if($CustomLibraryCheck.Checked){ + $Script:UseCustomLibrary = $true + $CustomLibraryBox.Enabled = $true + }else{ + $Script:UseCustomLibrary = $false + $CustomLibraryBox.Enabled = $false + } +} + +function MigrateLibraryCheckboxChanged { + + if($MigrateLibraryCheck.Checked){ + $Script:MigrateLibrary = $true + $LibraryMigrationLabel.Visible = $true + $LibraryMigrationLabel.Enabled = $true + $LibraryLocationBox.Visible = $true + $LibraryLocationBox.Enabled = $true + }else{ + $Script:MigrateLibrary = $false + $LibraryMigrationLabel.Visible = $false + $LibraryMigrationLabel.Enabled = $false + $LibraryLocationBox.Visible = $false + $LibraryLocationBox.Enabled = $false + } + +} + + +#region begin GUI{ + +$InstallForm = New-Object system.Windows.Forms.Form +$InstallForm.ClientSize = '320,240' +$InstallForm.text = "Terrible Jellyfin Installer" +$InstallForm.TopMost = $false + +$GUIElementsCollection = @() + +$InstallButton = New-Object system.Windows.Forms.Button +$InstallButton.text = "Install" +$InstallButton.width = 60 +$InstallButton.height = 30 +$InstallButton.location = New-Object System.Drawing.Point(5,5) +$InstallButton.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $InstallButton + +$ProgressBar = New-Object system.Windows.Forms.ProgressBar +$ProgressBar.width = 245 +$ProgressBar.height = 30 +$ProgressBar.location = New-Object System.Drawing.Point(70,5) + +$InstallLocationLabel = New-Object system.Windows.Forms.Label +$InstallLocationLabel.text = "Install Location" +$InstallLocationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft +$InstallLocationLabel.AutoSize = $true +$InstallLocationLabel.width = 100 +$InstallLocationLabel.height = 20 +$InstallLocationLabel.location = New-Object System.Drawing.Point(5,50) +$InstallLocationLabel.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $InstallLocationLabel + +$InstallLocationBox = New-Object system.Windows.Forms.TextBox +$InstallLocationBox.multiline = $false +$InstallLocationBox.width = 205 +$InstallLocationBox.height = 20 +$InstallLocationBox.location = New-Object System.Drawing.Point(110,50) +$InstallLocationBox.Text = $Script:DefaultJellyfinInstallDirectory +$InstallLocationBox.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $InstallLocationBox + +$CustomLibraryCheck = New-Object system.Windows.Forms.CheckBox +$CustomLibraryCheck.text = "Custom Library Location:" +$CustomLibraryCheck.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft +$CustomLibraryCheck.AutoSize = $false +$CustomLibraryCheck.width = 180 +$CustomLibraryCheck.height = 20 +$CustomLibraryCheck.location = New-Object System.Drawing.Point(5,75) +$CustomLibraryCheck.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $CustomLibraryCheck + +$CustomLibraryBox = New-Object system.Windows.Forms.TextBox +$CustomLibraryBox.multiline = $false +$CustomLibraryBox.width = 130 +$CustomLibraryBox.height = 20 +$CustomLibraryBox.location = New-Object System.Drawing.Point(185,75) +$CustomLibraryBox.Text = $Script:JellyFinDataDir +$CustomLibraryBox.Font = 'Microsoft Sans Serif,10' +$CustomLibraryBox.Enabled = $false +$GUIElementsCollection += $CustomLibraryBox + +$InstallAsServiceCheck = New-Object system.Windows.Forms.CheckBox +$InstallAsServiceCheck.text = "Install as Service" +$InstallAsServiceCheck.AutoSize = $false +$InstallAsServiceCheck.width = 140 +$InstallAsServiceCheck.height = 20 +$InstallAsServiceCheck.location = New-Object System.Drawing.Point(5,125) +$InstallAsServiceCheck.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $InstallAsServiceCheck + +$ServiceUserLabel = New-Object system.Windows.Forms.Label +$ServiceUserLabel.text = "Run Service As:" +$ServiceUserLabel.AutoSize = $true +$ServiceUserLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft +$ServiceUserLabel.width = 100 +$ServiceUserLabel.height = 20 +$ServiceUserLabel.location = New-Object System.Drawing.Point(15,145) +$ServiceUserLabel.Font = 'Microsoft Sans Serif,10' +$ServiceUserLabel.Visible = $false +$ServiceUserLabel.Enabled = $false +$GUIElementsCollection += $ServiceUserLabel + +$ServiceUserBox = New-Object system.Windows.Forms.ComboBox +$ServiceUserBox.text = "Run Service As" +$ServiceUserBox.width = 195 +$ServiceUserBox.height = 20 +@('Local System','Custom User') | ForEach-Object {[void] $ServiceUserBox.Items.Add($_)} +$ServiceUserBox.location = New-Object System.Drawing.Point(120,145) +$ServiceUserBox.Font = 'Microsoft Sans Serif,10' +$ServiceUserBox.Visible = $false +$ServiceUserBox.Enabled = $false +$ServiceUserBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList +$GUIElementsCollection += $ServiceUserBox + +$MigrateLibraryCheck = New-Object system.Windows.Forms.CheckBox +$MigrateLibraryCheck.text = "Import Emby/Old JF Library" +$MigrateLibraryCheck.AutoSize = $false +$MigrateLibraryCheck.width = 160 +$MigrateLibraryCheck.height = 20 +$MigrateLibraryCheck.location = New-Object System.Drawing.Point(5,170) +$MigrateLibraryCheck.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $MigrateLibraryCheck + +$LibraryMigrationLabel = New-Object system.Windows.Forms.Label +$LibraryMigrationLabel.text = "Emby/Old JF Library Path" +$LibraryMigrationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft +$LibraryMigrationLabel.AutoSize = $false +$LibraryMigrationLabel.width = 120 +$LibraryMigrationLabel.height = 20 +$LibraryMigrationLabel.location = New-Object System.Drawing.Point(15,190) +$LibraryMigrationLabel.Font = 'Microsoft Sans Serif,10' +$LibraryMigrationLabel.Visible = $false +$LibraryMigrationLabel.Enabled = $false +$GUIElementsCollection += $LibraryMigrationLabel + +$LibraryLocationBox = New-Object system.Windows.Forms.TextBox +$LibraryLocationBox.multiline = $false +$LibraryLocationBox.width = 175 +$LibraryLocationBox.height = 20 +$LibraryLocationBox.location = New-Object System.Drawing.Point(140,190) +$LibraryLocationBox.Text = $Script:defaultEmbyDataDir +$LibraryLocationBox.Font = 'Microsoft Sans Serif,10' +$LibraryLocationBox.Visible = $false +$LibraryLocationBox.Enabled = $false +$GUIElementsCollection += $LibraryLocationBox + +$CreateShortcutCheck = New-Object system.Windows.Forms.CheckBox +$CreateShortcutCheck.text = "Desktop Shortcut" +$CreateShortcutCheck.AutoSize = $false +$CreateShortcutCheck.width = 150 +$CreateShortcutCheck.height = 20 +$CreateShortcutCheck.location = New-Object System.Drawing.Point(5,215) +$CreateShortcutCheck.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $CreateShortcutCheck + +$StartProgramCheck = New-Object system.Windows.Forms.CheckBox +$StartProgramCheck.text = "Start Jellyfin" +$StartProgramCheck.AutoSize = $false +$StartProgramCheck.width = 160 +$StartProgramCheck.height = 20 +$StartProgramCheck.location = New-Object System.Drawing.Point(160,215) +$StartProgramCheck.Font = 'Microsoft Sans Serif,10' +$GUIElementsCollection += $StartProgramCheck + +$InstallForm.controls.AddRange($GUIElementsCollection) +$InstallForm.Controls.Add($ProgressBar) + +#region gui events { +$InstallButton.Add_Click({ InstallJellyfin }) +$CustomLibraryCheck.Add_CheckedChanged({CustomLibraryCheckChanged}) +$InstallAsServiceCheck.Add_CheckedChanged({ServiceBoxCheckChanged}) +$ServiceUserBox.Add_SelectedValueChanged({ UserSelect }) +$MigrateLibraryCheck.Add_CheckedChanged({MigrateLibraryCheckboxChanged}) +$CreateShortcutCheck.Add_CheckedChanged({CreateShortcutBoxCheckChanged}) +$StartProgramCheck.Add_CheckedChanged({StartJellyFinBoxCheckChanged}) +#endregion events } + +#endregion GUI } + + +[void]$InstallForm.ShowDialog() diff --git a/windows/legacy/install.bat b/windows/legacy/install.bat new file mode 100644 index 0000000000..e21479a79a --- /dev/null +++ b/windows/legacy/install.bat @@ -0,0 +1 @@ +powershell.exe -executionpolicy Bypass -file install-jellyfin.ps1