Merge pull request #3 from jellyfin/master
This commit is contained in:
commit
79e3de1568
|
@ -23,7 +23,7 @@ jobs:
|
|||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||
maxParallel: 2
|
||||
dependsOn: MainBuild
|
||||
dependsOn: Build
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
|
|
|
@ -4,15 +4,14 @@ parameters:
|
|||
DotNetSdkVersion: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: MainBuild
|
||||
displayName: Main Build
|
||||
- job: Build
|
||||
displayName: Build
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
Debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
pool:
|
||||
vmImage: "${{ parameters.LinuxImage }}"
|
||||
steps:
|
||||
|
@ -22,13 +21,13 @@ jobs:
|
|||
persistCredentials: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
||||
displayName: "Clone Web Branch"
|
||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (PR)"
|
||||
displayName: "Clone Web Target"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
@ -37,7 +36,7 @@ jobs:
|
|||
displayName: "Install Node"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
versionSpec: "10.x"
|
||||
versionSpec: "12.x"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Build Web Client"
|
||||
|
@ -69,33 +68,33 @@ jobs:
|
|||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: "${{ parameters.RestoreBuildProjects }}"
|
||||
arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
|
||||
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Naming"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
|
||||
artifactName: "Jellyfin.Naming"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Controller"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
||||
artifactName: "Jellyfin.Controller"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Model"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
||||
artifactName: "Jellyfin.Model"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Common"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
||||
artifactName: "Jellyfin.Common"
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
parameters:
|
||||
WindowsImage: "windows-latest"
|
||||
TestProjects: "tests/**/*Tests.csproj"
|
||||
DotNetSdkVersion: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: PublishWindows
|
||||
displayName: Publish Windows
|
||||
pool:
|
||||
vmImage: ${{ parameters.WindowsImage }}
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (PR)"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: "Install Node"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
versionSpec: "10.x"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Build Web Client"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: yarn install
|
||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: "Copy Web Client"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
||||
contents: "**"
|
||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||
cleanTargetFolder: true
|
||||
overWrite: true
|
||||
flattenFolders: false
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone UX Repository"
|
||||
inputs:
|
||||
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: "Build NSIS Installer"
|
||||
inputs:
|
||||
targetType: "filePath"
|
||||
filePath: ./deployment/windows/build-jellyfin.ps1
|
||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
||||
errorActionPreference: "stop"
|
||||
workingDirectory: $(Build.SourcesDirectory)
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: "Copy NSIS Installer"
|
||||
inputs:
|
||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
|
||||
contents: "jellyfin*.exe"
|
||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
||||
cleanTargetFolder: true
|
||||
overWrite: true
|
||||
flattenFolders: true
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Setup"
|
||||
condition: succeeded()
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/setup"
|
||||
artifactName: "Jellyfin Server Setup"
|
|
@ -27,11 +27,6 @@ jobs:
|
|||
Windows: "windows-latest"
|
||||
macOS: "macos-latest"
|
||||
|
||||
- template: azure-pipelines-windows.yml
|
||||
parameters:
|
||||
WindowsImage: "windows-latest"
|
||||
TestProjects: $(TestProjects)
|
||||
|
||||
- template: azure-pipelines-compat.yml
|
||||
parameters:
|
||||
Packages:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -39,6 +39,7 @@ ProgramData*/
|
|||
CorePlugins*/
|
||||
ProgramData-Server*/
|
||||
ProgramData-UI*/
|
||||
MediaBrowser.WebDashboard/jellyfin-web/**
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
|
|
|
@ -505,7 +505,63 @@ namespace Emby.Naming.Common
|
|||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-short",
|
||||
MediaType = MediaType.Video
|
||||
}
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.BehindTheScenes,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "behind the scenes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.DeletedScene,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "deleted scenes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Interview,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "interviews",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Scene,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "scenes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "samples",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "shorts",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "featurettes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Unknown,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "extras",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
};
|
||||
|
||||
Format3DRules = new[]
|
||||
|
|
|
@ -80,6 +80,15 @@ namespace Emby.Naming.Video
|
|||
result.Rule = rule;
|
||||
}
|
||||
}
|
||||
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
||||
{
|
||||
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
|
||||
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.ExtraType = rule.ExtraType;
|
||||
result.Rule = rule;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -5,30 +5,29 @@ using MediaType = Emby.Naming.Common.MediaType;
|
|||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>.
|
||||
/// </summary>
|
||||
public class ExtraRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the token.
|
||||
/// Gets or sets the token to use for matching against the file path.
|
||||
/// </summary>
|
||||
/// <value>The token.</value>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the extra.
|
||||
/// Gets or sets the type of the extra to return when matched.
|
||||
/// </summary>
|
||||
/// <value>The type of the extra.</value>
|
||||
public ExtraType ExtraType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the rule.
|
||||
/// </summary>
|
||||
/// <value>The type of the rule.</value>
|
||||
public ExtraRuleType RuleType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the media.
|
||||
/// Gets or sets the type of the media to return when matched.
|
||||
/// </summary>
|
||||
/// <value>The type of the media.</value>
|
||||
public MediaType MediaType { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,23 @@ namespace Emby.Naming.Video
|
|||
public enum ExtraRuleType
|
||||
{
|
||||
/// <summary>
|
||||
/// The suffix
|
||||
/// Match <see cref="ExtraRule.Token"/> against a suffix in the file name.
|
||||
/// </summary>
|
||||
Suffix = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The filename
|
||||
/// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension.
|
||||
/// </summary>
|
||||
Filename = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The regex
|
||||
/// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension.
|
||||
/// </summary>
|
||||
Regex = 2
|
||||
Regex = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
|
||||
/// </summary>
|
||||
DirectoryName = 3,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +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;
|
||||
|
||||
|
@ -17,6 +18,7 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
{ HostWebClientKey, bool.TrueString },
|
||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||
|
|
|
@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
public static void TryBind(this IStatement statement, string name, byte[] value)
|
||||
public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
|
||||
{
|
||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||
{
|
||||
|
|
|
@ -3315,7 +3315,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
|
||||
if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -3339,7 +3339,7 @@ namespace Emby.Server.Implementations.Data
|
|||
return IsAlphaNumeric(value);
|
||||
}
|
||||
|
||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
|
||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
|
||||
{
|
||||
if (query.IsResumable ?? false)
|
||||
{
|
||||
|
@ -3351,27 +3351,27 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
if (query.IsHD.HasValue)
|
||||
{
|
||||
var threshold = 1200;
|
||||
const int Threshold = 1200;
|
||||
if (query.IsHD.Value)
|
||||
{
|
||||
minWidth = threshold;
|
||||
minWidth = Threshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxWidth = threshold - 1;
|
||||
maxWidth = Threshold - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (query.Is4K.HasValue)
|
||||
{
|
||||
var threshold = 3800;
|
||||
const int Threshold = 3800;
|
||||
if (query.Is4K.Value)
|
||||
{
|
||||
minWidth = threshold;
|
||||
minWidth = Threshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxWidth = threshold - 1;
|
||||
maxWidth = Threshold - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3380,93 +3380,61 @@ namespace Emby.Server.Implementations.Data
|
|||
if (minWidth.HasValue)
|
||||
{
|
||||
whereClauses.Add("Width>=@MinWidth");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinWidth", minWidth);
|
||||
}
|
||||
statement?.TryBind("@MinWidth", minWidth);
|
||||
}
|
||||
|
||||
if (query.MinHeight.HasValue)
|
||||
{
|
||||
whereClauses.Add("Height>=@MinHeight");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinHeight", query.MinHeight);
|
||||
}
|
||||
statement?.TryBind("@MinHeight", query.MinHeight);
|
||||
}
|
||||
|
||||
if (maxWidth.HasValue)
|
||||
{
|
||||
whereClauses.Add("Width<=@MaxWidth");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxWidth", maxWidth);
|
||||
}
|
||||
statement?.TryBind("@MaxWidth", maxWidth);
|
||||
}
|
||||
|
||||
if (query.MaxHeight.HasValue)
|
||||
{
|
||||
whereClauses.Add("Height<=@MaxHeight");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxHeight", query.MaxHeight);
|
||||
}
|
||||
statement?.TryBind("@MaxHeight", query.MaxHeight);
|
||||
}
|
||||
|
||||
if (query.IsLocked.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsLocked=@IsLocked");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsLocked", query.IsLocked);
|
||||
}
|
||||
statement?.TryBind("@IsLocked", query.IsLocked);
|
||||
}
|
||||
|
||||
var tags = query.Tags.ToList();
|
||||
var excludeTags = query.ExcludeTags.ToList();
|
||||
|
||||
if (query.IsMovie ?? false)
|
||||
if (query.IsMovie == true)
|
||||
{
|
||||
var alternateTypes = new List<string>();
|
||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
|
||||
if (query.IncludeItemTypes.Length == 0
|
||||
|| query.IncludeItemTypes.Contains(nameof(Movie))
|
||||
|| query.IncludeItemTypes.Contains(nameof(Trailer)))
|
||||
{
|
||||
alternateTypes.Add(typeof(Movie).FullName);
|
||||
}
|
||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
|
||||
{
|
||||
alternateTypes.Add(typeof(Trailer).FullName);
|
||||
}
|
||||
|
||||
var programAttribtues = new List<string>();
|
||||
if (alternateTypes.Count == 0)
|
||||
{
|
||||
programAttribtues.Add("IsMovie=@IsMovie");
|
||||
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
}
|
||||
else
|
||||
{
|
||||
programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
whereClauses.Add("IsMovie=@IsMovie");
|
||||
}
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsMovie", true);
|
||||
}
|
||||
|
||||
whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")");
|
||||
statement?.TryBind("@IsMovie", true);
|
||||
}
|
||||
else if (query.IsMovie.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsMovie=@IsMovie");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsMovie", query.IsMovie);
|
||||
}
|
||||
statement?.TryBind("@IsMovie", query.IsMovie);
|
||||
}
|
||||
|
||||
if (query.IsSeries.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsSeries=@IsSeries");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsSeries", query.IsSeries);
|
||||
}
|
||||
statement?.TryBind("@IsSeries", query.IsSeries);
|
||||
}
|
||||
|
||||
if (query.IsSports.HasValue)
|
||||
|
@ -3518,10 +3486,7 @@ namespace Emby.Server.Implementations.Data
|
|||
if (query.IsFolder.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsFolder=@IsFolder");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
statement?.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
|
||||
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
||||
|
@ -3532,10 +3497,7 @@ namespace Emby.Server.Implementations.Data
|
|||
if (excludeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type<>@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", excludeTypes[0]);
|
||||
}
|
||||
statement?.TryBind("@type", excludeTypes[0]);
|
||||
}
|
||||
else if (excludeTypes.Length > 1)
|
||||
{
|
||||
|
@ -3546,10 +3508,7 @@ namespace Emby.Server.Implementations.Data
|
|||
else if (includeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
statement?.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
else if (includeTypes.Length > 1)
|
||||
{
|
||||
|
@ -3560,10 +3519,7 @@ namespace Emby.Server.Implementations.Data
|
|||
if (query.ChannelIds.Length == 1)
|
||||
{
|
||||
whereClauses.Add("ChannelId=@ChannelId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (query.ChannelIds.Length > 1)
|
||||
{
|
||||
|
@ -3574,98 +3530,65 @@ namespace Emby.Server.Implementations.Data
|
|||
if (!query.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
whereClauses.Add("ParentId=@ParentId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentId", query.ParentId);
|
||||
}
|
||||
statement?.TryBind("@ParentId", query.ParentId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Path))
|
||||
{
|
||||
whereClauses.Add("Path=@Path");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Path", GetPathToSave(query.Path));
|
||||
}
|
||||
statement?.TryBind("@Path", GetPathToSave(query.Path));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
|
||||
{
|
||||
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||
}
|
||||
statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||
}
|
||||
|
||||
if (query.MinCommunityRating.HasValue)
|
||||
{
|
||||
whereClauses.Add("CommunityRating>=@MinCommunityRating");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||
}
|
||||
statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||
}
|
||||
|
||||
if (query.MinIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("IndexNumber>=@MinIndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateCreated.HasValue)
|
||||
{
|
||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateLastSaved.HasValue)
|
||||
{
|
||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateLastSavedForUser.HasValue)
|
||||
{
|
||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||
}
|
||||
|
||||
if (query.IndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("IndexNumber=@IndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||
}
|
||||
if (query.ParentIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||
}
|
||||
if (query.ParentIndexNumberNotEquals.HasValue)
|
||||
{
|
||||
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||
}
|
||||
statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||
}
|
||||
|
||||
var minEndDate = query.MinEndDate;
|
||||
|
@ -3686,73 +3609,59 @@ namespace Emby.Server.Implementations.Data
|
|||
if (minEndDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("EndDate>=@MinEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinEndDate", minEndDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinEndDate", minEndDate.Value);
|
||||
}
|
||||
|
||||
if (maxEndDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("EndDate<=@MaxEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||
}
|
||||
|
||||
if (query.MinStartDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("StartDate>=@MinStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||
}
|
||||
|
||||
if (query.MaxStartDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||
}
|
||||
|
||||
if (query.MinPremiereDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("PremiereDate>=@MinPremiereDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||
}
|
||||
|
||||
if (query.MaxPremiereDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||
}
|
||||
|
||||
if (query.TrailerTypes.Length > 0)
|
||||
var trailerTypes = query.TrailerTypes;
|
||||
int trailerTypesLen = trailerTypes.Length;
|
||||
if (trailerTypesLen > 0)
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var type in query.TrailerTypes)
|
||||
const string Or = " OR ";
|
||||
StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32);
|
||||
for (int i = 0; i < trailerTypesLen; i++)
|
||||
{
|
||||
var paramName = "@TrailerTypes" + index;
|
||||
|
||||
clauses.Add("TrailerTypes like " + paramName);
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, "%" + type + "%");
|
||||
}
|
||||
index++;
|
||||
var paramName = "@TrailerTypes" + i;
|
||||
clause.Append("TrailerTypes like ")
|
||||
.Append(paramName)
|
||||
.Append(Or);
|
||||
statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
|
||||
}
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
|
||||
// Remove last " OR "
|
||||
clause.Length -= Or.Length;
|
||||
clause.Append(')');
|
||||
|
||||
whereClauses.Add(clause.ToString());
|
||||
}
|
||||
|
||||
if (query.IsAiring.HasValue)
|
||||
|
@ -3760,24 +3669,15 @@ namespace Emby.Server.Implementations.Data
|
|||
if (query.IsAiring.Value)
|
||||
{
|
||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||
|
||||
whereClauses.Add("EndDate>=@MinEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3792,13 +3692,10 @@ namespace Emby.Server.Implementations.Data
|
|||
var paramName = "@PersonId" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, personId.ToByteArray());
|
||||
}
|
||||
statement?.TryBind(paramName, personId.ToByteArray());
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
|
@ -3806,47 +3703,31 @@ namespace Emby.Server.Implementations.Data
|
|||
if (!string.IsNullOrWhiteSpace(query.Person))
|
||||
{
|
||||
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@PersonName", query.Person);
|
||||
}
|
||||
statement?.TryBind("@PersonName", query.Person);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.MinSortName))
|
||||
{
|
||||
whereClauses.Add("SortName>=@MinSortName");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinSortName", query.MinSortName);
|
||||
}
|
||||
statement?.TryBind("@MinSortName", query.MinSortName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
|
||||
{
|
||||
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||
}
|
||||
statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.ExternalId))
|
||||
{
|
||||
whereClauses.Add("ExternalId=@ExternalId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExternalId", query.ExternalId);
|
||||
}
|
||||
statement?.TryBind("@ExternalId", query.ExternalId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Name))
|
||||
{
|
||||
whereClauses.Add("CleanName=@Name");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Name", GetCleanValue(query.Name));
|
||||
}
|
||||
statement?.TryBind("@Name", GetCleanValue(query.Name));
|
||||
}
|
||||
|
||||
// These are the same, for now
|
||||
|
@ -3865,28 +3746,21 @@ namespace Emby.Server.Implementations.Data
|
|||
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
|
||||
{
|
||||
whereClauses.Add("SortName like @NameStartsWith");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||
}
|
||||
statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
|
||||
{
|
||||
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
|
||||
// lowercase this because SortName is stored as lowercase
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||
}
|
||||
statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
|
||||
{
|
||||
whereClauses.Add("SortName < @NameLessThan");
|
||||
// lowercase this because SortName is stored as lowercase
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||
}
|
||||
statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (query.ImageTypes.Length > 0)
|
||||
|
@ -3902,18 +3776,12 @@ namespace Emby.Server.Implementations.Data
|
|||
if (query.IsLiked.Value)
|
||||
{
|
||||
whereClauses.Add("rating>=@UserRating");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(rating is null or rating<@UserRating)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3927,10 +3795,8 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||
}
|
||||
|
||||
if (query.IsFavorite.HasValue)
|
||||
|
@ -3943,10 +3809,8 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
}
|
||||
|
||||
if (EnableJoinUserData(query))
|
||||
|
@ -3975,10 +3839,8 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
whereClauses.Add("(played is null or played=@IsPlayed)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4010,6 +3872,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
|
@ -4029,6 +3892,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
|
@ -4762,18 +4626,22 @@ namespace Emby.Server.Implementations.Data
|
|||
{
|
||||
list.Add(typeof(Person).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Genre).Name, query))
|
||||
{
|
||||
list.Add(typeof(Genre).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
|
||||
{
|
||||
list.Add(typeof(MusicGenre).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
|
||||
{
|
||||
list.Add(typeof(MusicArtist).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Studio).Name, query))
|
||||
{
|
||||
list.Add(typeof(Studio).Name);
|
||||
|
@ -4847,7 +4715,7 @@ namespace Emby.Server.Implementations.Data
|
|||
return false;
|
||||
}
|
||||
|
||||
private static readonly Type[] KnownTypes =
|
||||
private static readonly Type[] _knownTypes =
|
||||
{
|
||||
typeof(LiveTvProgram),
|
||||
typeof(LiveTvChannel),
|
||||
|
@ -4916,7 +4784,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
{
|
||||
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var t in KnownTypes)
|
||||
foreach (var t in _knownTypes)
|
||||
{
|
||||
dict[t.Name] = new[] { t.FullName };
|
||||
}
|
||||
|
@ -4928,7 +4796,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
}
|
||||
|
||||
// Not crazy about having this all the way down here, but at least it's in one place
|
||||
readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
|
||||
private string[] MapIncludeItemTypes(string value)
|
||||
{
|
||||
|
@ -4945,7 +4813,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void DeleteItem(Guid id, CancellationToken cancellationToken)
|
||||
public void DeleteItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
|
@ -4981,7 +4849,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
}
|
||||
}
|
||||
|
||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value)
|
||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value)
|
||||
{
|
||||
using (var statement = PrepareStatement(db, query))
|
||||
{
|
||||
|
@ -5541,6 +5409,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
{
|
||||
GetWhereClauses(typeSubQuery, null);
|
||||
}
|
||||
|
||||
BindSimilarParams(query, statement);
|
||||
BindSearchParams(query, statement);
|
||||
GetWhereClauses(innerQuery, statement);
|
||||
|
@ -5582,7 +5451,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
}
|
||||
|
||||
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToLookup(i => i);
|
||||
.ToLookup(x => x);
|
||||
|
||||
foreach (var type in allTypes)
|
||||
{
|
||||
|
@ -5673,30 +5542,26 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
|
||||
while (startIndex < values.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
|
||||
|
||||
var endIndex = Math.Min(values.Count, startIndex + limit);
|
||||
var isSubsequentRow = false;
|
||||
var endIndex = Math.Min(values.Count, startIndex + Limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
|
||||
i);
|
||||
isSubsequentRow = true;
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
{
|
||||
statement.TryBind("@ItemId", idBlob);
|
||||
|
@ -5724,7 +5589,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5759,28 +5624,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
var listIndex = 0;
|
||||
|
||||
while (startIndex < people.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
|
||||
|
||||
var endIndex = Math.Min(people.Count, startIndex + limit);
|
||||
var isSubsequentRow = false;
|
||||
|
||||
var endIndex = Math.Min(people.Count, startIndex + Limit);
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture));
|
||||
isSubsequentRow = true;
|
||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
{
|
||||
statement.TryBind("@ItemId", idBlob);
|
||||
|
@ -5804,16 +5664,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var item = new PersonInfo();
|
||||
|
||||
item.ItemId = reader.GetGuid(0);
|
||||
item.Name = reader.GetString(1);
|
||||
var item = new PersonInfo
|
||||
{
|
||||
ItemId = reader.GetGuid(0),
|
||||
Name = reader.GetString(1)
|
||||
};
|
||||
|
||||
if (!reader.IsDBNull(2))
|
||||
{
|
||||
|
@ -5920,20 +5781,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 10;
|
||||
var startIndex = 0;
|
||||
var limit = 10;
|
||||
|
||||
while (startIndex < streams.Count)
|
||||
{
|
||||
var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns)));
|
||||
var insertText = new StringBuilder("insert into mediastreams (");
|
||||
foreach (var column in _mediaStreamSaveColumns)
|
||||
{
|
||||
insertText.Append(column).Append(',');
|
||||
}
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + limit);
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
insertText.Append(") values ");
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (i != startIndex)
|
||||
{
|
||||
insertText.Append(",");
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
var index = i.ToString(CultureInfo.InvariantCulture);
|
||||
|
@ -5941,11 +5810,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
|
||||
foreach (var column in _mediaStreamSaveColumns.Skip(1))
|
||||
{
|
||||
insertText.Append("@" + column + index + ",");
|
||||
insertText.Append('@').Append(column).Append(index).Append(',');
|
||||
}
|
||||
|
||||
insertText.Length -= 1; // Remove the last comma
|
||||
|
||||
insertText.Append(")");
|
||||
insertText.Append(')');
|
||||
}
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
|
@ -6007,7 +5877,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6024,7 +5894,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
Index = reader[1].ToInt()
|
||||
};
|
||||
|
||||
item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true);
|
||||
item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true);
|
||||
|
||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
|
|
|
@ -1056,30 +1056,19 @@ namespace Emby.Server.Implementations.Dto
|
|||
|
||||
if (options.ContainsField(ItemFields.SpecialFeatureCount))
|
||||
{
|
||||
if (allExtras == null)
|
||||
{
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
}
|
||||
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
|
||||
}
|
||||
|
||||
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
||||
{
|
||||
int trailerCount = 0;
|
||||
if (allExtras == null)
|
||||
{
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
}
|
||||
|
||||
trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
|
||||
allExtras ??= item.GetExtras().ToArray();
|
||||
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
|
||||
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
trailerCount += hasTrailers.GetTrailerCount();
|
||||
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
|
||||
}
|
||||
|
||||
dto.LocalTrailerCount = trailerCount;
|
||||
}
|
||||
|
||||
// Add EpisodeInfo
|
||||
|
|
|
@ -35,9 +35,8 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
|
|||
public interface IStartupOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// --ffmpeg
|
||||
/// Gets the value of the --ffmpeg command line option.
|
||||
/// </summary>
|
||||
string FFmpegPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --service
|
||||
/// Gets the value of the --service command line option.
|
||||
/// </summary>
|
||||
bool IsService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --noautorunwebapp
|
||||
/// Gets the value of the --noautorunwebapp command line option.
|
||||
/// </summary>
|
||||
bool NoAutoRunWebApp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --package-name
|
||||
/// Gets the value of the --package-name command line option.
|
||||
/// </summary>
|
||||
string PackageName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartpath
|
||||
/// Gets the value of the --restartpath command line option.
|
||||
/// </summary>
|
||||
string RestartPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartargs
|
||||
/// Gets the value of the --restartargs command line option.
|
||||
/// </summary>
|
||||
string RestartArgs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the --plugin-manifest-url command line option.
|
||||
/// </summary>
|
||||
string PluginManifestUrl { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -437,10 +437,10 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
item.SetParent(null);
|
||||
|
||||
ItemRepository.DeleteItem(item.Id, CancellationToken.None);
|
||||
ItemRepository.DeleteItem(item.Id);
|
||||
foreach (var child in children)
|
||||
{
|
||||
ItemRepository.DeleteItem(child.Id, CancellationToken.None);
|
||||
ItemRepository.DeleteItem(child.Id);
|
||||
}
|
||||
|
||||
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
|
||||
|
@ -2609,14 +2609,12 @@ namespace Emby.Server.Implementations.Library
|
|||
}).OrderBy(i => i.Path);
|
||||
}
|
||||
|
||||
private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
|
||||
|
||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var namingOptions = GetNamingOptions();
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
.Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
|
|
|
@ -102,5 +102,17 @@
|
|||
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
||||
"TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
|
||||
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
||||
"TasksApplicationCategory": "تطبيق"
|
||||
"TasksApplicationCategory": "تطبيق",
|
||||
"TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
|
||||
"TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
|
||||
"TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
|
||||
"TaskRefreshChannels": "إعادة تحديث القنوات",
|
||||
"TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
|
||||
"TaskCleanTranscode": "حذف سجلات الترميز",
|
||||
"TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
|
||||
"TaskUpdatePlugins": "تحديث الإضافات",
|
||||
"TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
|
||||
"TaskRefreshPeople": "إعادة تحميل الأشخاص",
|
||||
"TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
|
||||
"TaskCleanLogs": "حذف دليل السجل"
|
||||
}
|
||||
|
|
|
@ -92,5 +92,19 @@
|
|||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiration.",
|
||||
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
|
||||
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
|
||||
"TaskUpdatePlugins": "Opdater Plugins",
|
||||
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
|
||||
"TaskCleanLogs": "Ryd Log Mappe",
|
||||
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdatere metadata.",
|
||||
"TaskRefreshLibrary": "Scan Medie Bibliotek",
|
||||
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
|
||||
"TaskCleanCache": "Ryd Cache Mappe",
|
||||
"TasksChannelsCategory": "Internet Kanaler",
|
||||
"TasksApplicationCategory": "Applikation",
|
||||
"TasksLibraryCategory": "Bibliotek",
|
||||
"TasksMaintenanceCategory": "Vedligeholdelse"
|
||||
}
|
||||
|
|
|
@ -90,5 +90,13 @@
|
|||
"Artists": "Artista",
|
||||
"Application": "Aplikasyon",
|
||||
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
||||
"Albums": "Albums"
|
||||
"Albums": "Albums",
|
||||
"TaskRefreshLibrary": "Suriin ang nasa librerya",
|
||||
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata",
|
||||
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
|
||||
"TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
|
||||
"TasksChannelsCategory": "Palabas sa internet",
|
||||
"TasksLibraryCategory": "Librerya",
|
||||
"TasksMaintenanceCategory": "Pagpapanatili",
|
||||
"HomeVideos": "Sariling pelikula"
|
||||
}
|
||||
|
|
1
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
1
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -92,5 +92,27 @@
|
|||
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
|
||||
"ValueSpecialEpisodeName": "Speciaal - {0}",
|
||||
"VersionNumber": "Versie {0}"
|
||||
"VersionNumber": "Versie {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.",
|
||||
"TaskDownloadMissingSubtitles": "Download missende ondertitels",
|
||||
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
|
||||
"TaskRefreshChannels": "Vernieuw Kanalen",
|
||||
"TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.",
|
||||
"TaskCleanLogs": "Log Folder Opschonen",
|
||||
"TaskCleanTranscode": "Transcode Folder Opschonen",
|
||||
"TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.",
|
||||
"TaskUpdatePlugins": "Update Plugins",
|
||||
"TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.",
|
||||
"TaskRefreshPeople": "Vernieuw Personen",
|
||||
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
|
||||
"TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.",
|
||||
"TaskRefreshLibrary": "Scan Media Bibliotheek",
|
||||
"TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.",
|
||||
"TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken",
|
||||
"TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.",
|
||||
"TaskCleanCache": "Cache Folder Opschonen",
|
||||
"TasksChannelsCategory": "Internet Kanalen",
|
||||
"TasksApplicationCategory": "Applicatie",
|
||||
"TasksLibraryCategory": "Bibliotheek",
|
||||
"TasksMaintenanceCategory": "Onderhoud"
|
||||
}
|
||||
|
|
|
@ -92,5 +92,26 @@
|
|||
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.",
|
||||
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
|
||||
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
|
||||
"TaskRefreshChannels": "Uppdatera kanaler",
|
||||
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
|
||||
"TaskCleanTranscode": "Töm transkodningskatalog",
|
||||
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
|
||||
"TaskUpdatePlugins": "Uppdatera insticksprogram",
|
||||
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
|
||||
"TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
|
||||
"TaskCleanLogs": "Töm loggkatalog",
|
||||
"TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.",
|
||||
"TaskRefreshLibrary": "Genomsök mediabibliotek",
|
||||
"TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
|
||||
"TaskRefreshChapterImages": "Extrahera kapitelbilder",
|
||||
"TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
|
||||
"TaskCleanCache": "Rensa cachekatalog",
|
||||
"TasksChannelsCategory": "Internetkanaler",
|
||||
"TasksApplicationCategory": "Applikation",
|
||||
"TasksLibraryCategory": "Bibliotek",
|
||||
"TasksMaintenanceCategory": "Underhåll"
|
||||
}
|
||||
|
|
|
@ -55,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
{
|
||||
progress.Report(0);
|
||||
|
||||
var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
|
||||
var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
|
||||
|
||||
progress.Report(10);
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -18,6 +20,7 @@ using MediaBrowser.Model.Events;
|
|||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Updates
|
||||
|
@ -27,6 +30,11 @@ namespace Emby.Server.Implementations.Updates
|
|||
/// </summary>
|
||||
public class InstallationManager : IInstallationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for a setting that specifies a URL for the plugin repository JSON manifest.
|
||||
/// </summary>
|
||||
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
|
||||
|
||||
/// <summary>
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
|
@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
private readonly IApplicationHost _applicationHost;
|
||||
|
||||
private readonly IZipClient _zipClient;
|
||||
private readonly IConfiguration _appConfig;
|
||||
|
||||
private readonly object _currentInstallationsLock = new object();
|
||||
|
||||
|
@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates
|
|||
IJsonSerializer jsonSerializer,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IZipClient zipClient)
|
||||
IZipClient zipClient,
|
||||
IConfiguration appConfig)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
|
@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates
|
|||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_zipClient = zipClient;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates
|
|||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
|
||||
|
||||
try
|
||||
{
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
|
||||
stream).ConfigureAwait(false);
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = manifestUrl,
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
|
||||
}
|
||||
catch (SerializationException ex)
|
||||
{
|
||||
const string LogTemplate =
|
||||
"Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
|
||||
"have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
|
||||
PluginManifestUrlKey + ", please ensure that it is correct.";
|
||||
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
const string LogTemplate =
|
||||
"The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
|
||||
"Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
|
||||
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,16 +224,17 @@ namespace Emby.Server.Implementations.Updates
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
||||
return GetAvailablePluginUpdates(catalog);
|
||||
}
|
||||
|
||||
var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
|
||||
|
||||
// Figure out what needs to be installed
|
||||
private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
|
||||
{
|
||||
foreach (var plugin in _applicationHost.Plugins)
|
||||
{
|
||||
var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
|
||||
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version, _applicationHost.SystemUpdateLevel);
|
||||
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
|
||||
if (version != null
|
||||
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
|
||||
namespace Jellyfin.Server
|
||||
|
@ -76,6 +76,10 @@ namespace Jellyfin.Server
|
|||
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
|
||||
public string? RestartArgs { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
|
||||
public string? PluginManifestUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
|
||||
/// </summary>
|
||||
|
@ -84,6 +88,11 @@ namespace Jellyfin.Server
|
|||
{
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (PluginManifestUrl != null)
|
||||
{
|
||||
config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
|
||||
}
|
||||
|
||||
if (NoWebClient)
|
||||
{
|
||||
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
|
||||
|
|
|
@ -213,7 +213,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
request.IncludeItemTypes = "Playlist";
|
||||
}
|
||||
|
||||
bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id);
|
||||
bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)
|
||||
// Assume all folders inside an EnabledChannel are enabled
|
||||
|| user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id);
|
||||
|
||||
var collectionFolders = _libraryManager.GetCollectionFolders(item);
|
||||
foreach (var collectionFolder in collectionFolders)
|
||||
{
|
||||
|
@ -225,7 +228,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
}
|
||||
}
|
||||
|
||||
if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder)
|
||||
if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels)
|
||||
{
|
||||
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
|
||||
return new QueryResult<BaseItem>
|
||||
|
|
|
@ -361,7 +361,8 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
|
||||
var dtoOptions = GetDtoOptions(_authContext, request);
|
||||
|
||||
var dtos = item.GetDisplayExtras()
|
||||
var dtos = item
|
||||
.GetExtras(BaseItem.DisplayExtraTypes)
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
|
||||
|
||||
return dtos.ToArray();
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace MediaBrowser.Common.Updates
|
|||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The available plugin updates.</returns>
|
||||
IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Installs the package.
|
||||
|
|
|
@ -1326,8 +1326,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
}
|
||||
|
||||
// Use some hackery to get the extra type based on foldername
|
||||
Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
|
||||
item.ExtraType = extraType;
|
||||
item.ExtraType = Enum.TryParse(extraFolderName.Replace(" ", string.Empty), true, out ExtraType extraType)
|
||||
? extraType
|
||||
: Model.Entities.ExtraType.Unknown;
|
||||
|
||||
return item;
|
||||
|
||||
|
@ -2877,14 +2878,29 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <value>The remote trailers.</value>
|
||||
public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all extras associated with this item, sorted by <see cref="SortName"/>.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable containing the items.</returns>
|
||||
public IEnumerable<BaseItem> GetExtras()
|
||||
{
|
||||
return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
|
||||
return ExtraIds
|
||||
.Select(LibraryManager.GetItemById)
|
||||
.Where(i => i != null)
|
||||
.OrderBy(i => i.SortName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all extras with specific types that are associated with this item.
|
||||
/// </summary>
|
||||
/// <param name="extraTypes">The types of extras to retrieve.</param>
|
||||
/// <returns>An enumerable containing the extras.</returns>
|
||||
public IEnumerable<BaseItem> GetExtras(IReadOnlyCollection<ExtraType> extraTypes)
|
||||
{
|
||||
return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i?.ExtraType != null && extraTypes.Contains(i.ExtraType.Value));
|
||||
return ExtraIds
|
||||
.Select(LibraryManager.GetItemById)
|
||||
.Where(i => i != null)
|
||||
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> GetTrailers()
|
||||
|
@ -2895,11 +2911,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return Array.Empty<BaseItem>();
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> GetDisplayExtras()
|
||||
{
|
||||
return GetExtras(DisplayExtraTypes);
|
||||
}
|
||||
|
||||
public virtual bool IsHD => Height >= 720;
|
||||
|
||||
public bool IsShortcut { get; set; }
|
||||
|
@ -2917,8 +2928,19 @@ namespace MediaBrowser.Controller.Entities
|
|||
return RunTimeTicks ?? 0;
|
||||
}
|
||||
|
||||
// Possible types of extra videos
|
||||
public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene };
|
||||
/// <summary>
|
||||
/// Extra types that should be counted and displayed as "Special Features" in the UI.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
|
||||
{
|
||||
Model.Entities.ExtraType.Unknown,
|
||||
Model.Entities.ExtraType.BehindTheScenes,
|
||||
Model.Entities.ExtraType.Clip,
|
||||
Model.Entities.ExtraType.DeletedScene,
|
||||
Model.Entities.ExtraType.Interview,
|
||||
Model.Entities.ExtraType.Sample,
|
||||
Model.Entities.ExtraType.Scene
|
||||
};
|
||||
|
||||
public virtual bool SupportsExternalTransfer => false;
|
||||
|
||||
|
|
|
@ -1636,7 +1636,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
|
||||
|
||||
// When the input may or may not be hardware VAAPI decodable
|
||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
[base]: HW scaling video to OutputSize
|
||||
|
@ -1648,7 +1648,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
|
||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
[base]: SW scaling video to OutputSize
|
||||
|
@ -1996,14 +1997,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||
|
||||
// When the input may or may not be hardware VAAPI decodable
|
||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("format=nv12|vaapi");
|
||||
filters.Add("hwupload");
|
||||
}
|
||||
|
||||
// When the input may or may not be hardware QSV decodable
|
||||
else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!hasTextSubs)
|
||||
{
|
||||
|
@ -2013,25 +2014,19 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
|
||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||
|
||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
|
||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var codec = videoStream.Codec.ToLowerInvariant();
|
||||
var pixelFormat = videoStream.PixelFormat.ToLowerInvariant();
|
||||
|
||||
// Assert 10-bit hardware VAAPI decodable
|
||||
if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
|
||||
&& (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
filters.Add("hwdownload");
|
||||
filters.Add("format=p010le");
|
||||
filters.Add("format=nv12");
|
||||
}
|
||||
|
||||
// Assert 8-bit hardware VAAPI decodable
|
||||
else if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
// Assert hardware VAAPI decodable (Except h264 10-bit and higher color depth)
|
||||
// TODO: a propery way to detect hardware capabilities and falling back when transcoding is failed
|
||||
if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1
|
||||
|| ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
|
||||
&& (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
filters.Add("hwdownload");
|
||||
filters.Add("format=nv12");
|
||||
|
@ -2077,7 +2072,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
|
||||
|
||||
// Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
|
||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (state.SubtitleStream != null
|
||||
&& state.SubtitleStream.IsTextSubtitleStream
|
||||
|
|
|
@ -24,8 +24,7 @@ namespace MediaBrowser.Controller.Persistence
|
|||
/// Deletes the item.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
void DeleteItem(Guid id, CancellationToken cancellationToken);
|
||||
void DeleteItem(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items.
|
||||
|
@ -169,4 +168,3 @@ namespace MediaBrowser.Controller.Persistence
|
|||
List<string> GetAllArtistNames();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,12 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
public bool SupportsVideoCodec(string codec)
|
||||
{
|
||||
return ContainerProfile.ContainsContainer(VideoCodec, codec);
|
||||
return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec);
|
||||
}
|
||||
|
||||
public bool SupportsAudioCodec(string codec)
|
||||
{
|
||||
return ContainerProfile.ContainsContainer(AudioCodec, codec);
|
||||
return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace MediaBrowser.Model.Entities
|
|||
{
|
||||
public enum ExtraType
|
||||
{
|
||||
Unknown = 0,
|
||||
Clip = 1,
|
||||
Trailer = 2,
|
||||
BehindTheScenes = 3,
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -23,7 +23,7 @@ Conflicts: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
|
|||
Architecture: any
|
||||
Depends: at,
|
||||
libsqlite3-0,
|
||||
jellyfin-ffmpeg (>= 4.2.1-2),
|
||||
jellyfin-ffmpeg,
|
||||
libfontconfig1,
|
||||
libfreetype6,
|
||||
libssl1.1
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
[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"
|
|
@ -1,2 +0,0 @@
|
|||
dotnet
|
||||
nsis
|
|
@ -1,24 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This file was created by NSISDialogDesigner 1.4.4.0
|
||||
http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
Do not edit manually!
|
||||
-->
|
||||
<Dialog Name="confirmation" Title="Confirmation Page" Subtitle="Please confirm your choices for Jellyfin Server installation" GenerateShowFunction="False">
|
||||
<HeaderCustomScript>!include "helpers\StrSlash.nsh"</HeaderCustomScript>
|
||||
<CreateFunctionCustomScript>${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 \
|
||||
}"</CreateFunctionCustomScript>
|
||||
<RichText Name="ConfirmRichText" Location="12, 12" Size="426, 204" TabIndex="0" ExStyle="WS_EX_STATICEDGE" />
|
||||
</Dialog>
|
|
@ -1,61 +0,0 @@
|
|||
; =========================================================
|
||||
; 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
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This file was created by NSISDialogDesigner 1.4.4.0
|
||||
http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
Do not edit manually!
|
||||
-->
|
||||
<Dialog Name="service_config" Title="CoOnfigure the service" Subtitle="This controls what type of access the server gets to this system." GenerateShowFunction="False">
|
||||
<CheckBox Name="StartServiceAfterInstall" Location="12, 192" Size="426, 24" Text="Start Service after Install" Checked="True" TabIndex="0" />
|
||||
<Label Name="LocalSystemAccountLabel" Location="12, 115" Size="426, 46" Text="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." TabIndex="1" />
|
||||
<Label Name="NetworkServiceAccountLabel" Location="12, 39" Size="426, 46" Text="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." TabIndex="2" />
|
||||
<RadioButton Name="UseLocalSystemAccount" Location="12, 88" Size="426, 24" Text="Use Local System account" TabIndex="3" />
|
||||
<RadioButton Name="UseNetworkServiceAccount" Location="12, 12" Size="426, 24" Text="Use Network Service account (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="4" />
|
||||
</Dialog>
|
|
@ -1,56 +0,0 @@
|
|||
; =========================================================
|
||||
; 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
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This file was created by NSISDialogDesigner 1.4.4.0
|
||||
http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
Do not edit manually!
|
||||
-->
|
||||
<Dialog Name="setuptype" Title="Setup Type" Subtitle="Control how Jellyfin is installed.">
|
||||
<Label Name="InstallasaServiceLabel" Location="12, 115" Size="426, 46" Text="Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." TabIndex="0" />
|
||||
<RadioButton Name="InstallasaService" Location="12, 88" Size="426, 24" Text="Install as a Service (Advanced Users)" TabIndex="1" />
|
||||
<Label Name="BasicInstallLabel" Location="12, 39" Size="426, 46" Text="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." TabIndex="2" />
|
||||
<RadioButton Name="BasicInstall" Location="12, 12" Size="426, 24" Text="Basic Install (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="3" />
|
||||
</Dialog>
|
|
@ -1,50 +0,0 @@
|
|||
; =========================================================
|
||||
; 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
|
|
@ -1,10 +0,0 @@
|
|||
; 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
|
|
@ -1,47 +0,0 @@
|
|||
; 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
|
|
@ -1,575 +0,0 @@
|
|||
!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
|
|
@ -1,460 +0,0 @@
|
|||
[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()
|
|
@ -1 +0,0 @@
|
|||
powershell.exe -executionpolicy Bypass -file install-jellyfin.ps1
|
|
@ -15,7 +15,7 @@
|
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Loading…
Reference in New Issue
Block a user