Merge remote-tracking branch 'jellyfin/master'
This commit is contained in:
commit
918df5e352
96
.ci/azure-pipelines-compat.yml
Normal file
96
.ci/azure-pipelines-compat.yml
Normal file
|
@ -0,0 +1,96 @@
|
|||
parameters:
|
||||
- name: Packages
|
||||
type: object
|
||||
default: {}
|
||||
- name: LinuxImage
|
||||
type: string
|
||||
default: "ubuntu-latest"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: CompatibilityCheck
|
||||
displayName: Compatibility Check
|
||||
pool:
|
||||
vmImage: "${{ parameters.LinuxImage }}"
|
||||
# only execute for pull requests
|
||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each Package in parameters.Packages }}:
|
||||
${{ Package.key }}:
|
||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||
maxParallel: 2
|
||||
dependsOn: MainBuild
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: "Update DotNet"
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: "Download New Assembly Build Artifact"
|
||||
inputs:
|
||||
source: "current"
|
||||
artifact: "$(NugetPackageName)"
|
||||
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
||||
runVersion: "latest"
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: "Copy New Assembly Build Artifact"
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
||||
contents: "**/*.dll"
|
||||
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||
cleanTargetFolder: true
|
||||
overWrite: true
|
||||
flattenFolders: true
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: "Download Reference Assembly Build Artifact"
|
||||
inputs:
|
||||
source: "specific"
|
||||
artifact: "$(NugetPackageName)"
|
||||
path: "$(System.ArtifactsDirectory)/current-artifacts"
|
||||
project: "$(System.TeamProjectId)"
|
||||
pipeline: "$(System.DefinitionId)"
|
||||
runVersion: "latestFromBranch"
|
||||
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: "Copy Reference Assembly Build Artifact"
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
||||
contents: "**/*.dll"
|
||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||
cleanTargetFolder: true
|
||||
overWrite: true
|
||||
flattenFolders: true
|
||||
|
||||
- task: DownloadGitHubRelease@0
|
||||
displayName: "Download ABI Compatibility Check Tool"
|
||||
inputs:
|
||||
connection: Jellyfin Release Download
|
||||
userRepository: EraYaN/dotnet-compatibility
|
||||
defaultVersionType: "latest"
|
||||
itemPattern: "**-ci.zip"
|
||||
downloadPath: "$(System.ArtifactsDirectory)"
|
||||
|
||||
- task: ExtractFiles@1
|
||||
displayName: "Extract ABI Compatibility Check Tool"
|
||||
inputs:
|
||||
archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
|
||||
destinationFolder: $(System.ArtifactsDirectory)/tools
|
||||
cleanDestinationFolder: true
|
||||
|
||||
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
||||
- task: CmdLine@2
|
||||
displayName: "Execute ABI Compatibility Check Tool"
|
||||
inputs:
|
||||
script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
|
||||
workingDirectory: $(System.ArtifactsDirectory)
|
101
.ci/azure-pipelines-main.yml
Normal file
101
.ci/azure-pipelines-main.yml
Normal file
|
@ -0,0 +1,101 @@
|
|||
parameters:
|
||||
LinuxImage: "ubuntu-latest"
|
||||
RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
|
||||
DotNetSdkVersion: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: MainBuild
|
||||
displayName: Main Build
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
Debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
pool:
|
||||
vmImage: "${{ parameters.LinuxImage }}"
|
||||
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')), 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)"
|
||||
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"
|
||||
|
||||
- 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')), eq(variables['BuildConfiguration'], 'Release'), 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')), eq(variables['BuildConfiguration'], 'Release'), 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')), eq(variables['BuildConfiguration'], 'Release'), 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: UseDotNet@2
|
||||
displayName: "Update DotNet"
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: "Publish Server"
|
||||
inputs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: "${{ parameters.RestoreBuildProjects }}"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
artifactName: "Jellyfin.Common"
|
65
.ci/azure-pipelines-test.yml
Normal file
65
.ci/azure-pipelines-test.yml
Normal file
|
@ -0,0 +1,65 @@
|
|||
parameters:
|
||||
- name: ImageNames
|
||||
type: object
|
||||
default:
|
||||
Linux: "ubuntu-latest"
|
||||
Windows: "windows-latest"
|
||||
macOS: "macos-latest"
|
||||
- name: TestProjects
|
||||
type: string
|
||||
default: "tests/**/*Tests.csproj"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: MainTest
|
||||
displayName: Main Test
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each imageName in parameters.ImageNames }}:
|
||||
${{ imageName.key }}:
|
||||
ImageName: ${{ imageName.value }}
|
||||
maxParallel: 3
|
||||
pool:
|
||||
vmImage: "$(ImageName)"
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: false
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: "Update DotNet"
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run .NET Core CLI tests
|
||||
inputs:
|
||||
command: "test"
|
||||
projects: ${{ parameters.TestProjects }}
|
||||
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
|
||||
publishTestResults: true
|
||||
testRunTitle: $(Agent.JobName)
|
||||
workingDirectory: "$(Build.SourcesDirectory)"
|
||||
|
||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||
displayName: ReportGenerator (merge)
|
||||
inputs:
|
||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||
reporttypes: "Cobertura"
|
||||
|
||||
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
||||
- task: PublishCodeCoverageResults@1
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||
displayName: Publish Code Coverage
|
||||
inputs:
|
||||
codeCoverageTool: "cobertura"
|
||||
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||
pathToSources: $(Build.SourcesDirectory)
|
||||
failIfCoverageEmpty: true
|
82
.ci/azure-pipelines-windows.yml
Normal file
82
.ci/azure-pipelines-windows.yml
Normal file
|
@ -0,0 +1,82 @@
|
|||
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"
|
|
@ -2,9 +2,11 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
|
|||
|
||||
variables:
|
||||
- name: TestProjects
|
||||
value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj'
|
||||
value: "tests/**/*Tests.csproj"
|
||||
- name: RestoreBuildProjects
|
||||
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
value: "Jellyfin.Server/Jellyfin.Server.csproj"
|
||||
- name: DotNetSdkVersion
|
||||
value: 3.1.100
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
|
@ -13,232 +15,26 @@ trigger:
|
|||
batch: true
|
||||
|
||||
jobs:
|
||||
- job: main_build
|
||||
displayName: Main Build
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
Debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
- template: azure-pipelines-main.yml
|
||||
parameters:
|
||||
LinuxImage: "ubuntu-latest"
|
||||
RestoreBuildProjects: $(RestoreBuildProjects)
|
||||
|
||||
- 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')) ,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'
|
||||
- template: azure-pipelines-test.yml
|
||||
parameters:
|
||||
ImageNames:
|
||||
Linux: "ubuntu-latest"
|
||||
Windows: "windows-latest"
|
||||
macOS: "macos-latest"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (PR)"
|
||||
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'
|
||||
- template: azure-pipelines-windows.yml
|
||||
parameters:
|
||||
WindowsImage: "windows-latest"
|
||||
TestProjects: $(TestProjects)
|
||||
|
||||
- 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')) ,eq(variables['BuildConfiguration'], 'Release'), 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')) ,eq(variables['BuildConfiguration'], 'Release'), 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')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
||||
contents: '**'
|
||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: false # Optional
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Update DotNet'
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: 3.1.100
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Publish Server'
|
||||
inputs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: '$(RestoreBuildProjects)'
|
||||
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Naming'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||
artifactName: 'Jellyfin.Naming'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Controller'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||
artifactName: 'Jellyfin.Controller'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Model'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||
artifactName: 'Jellyfin.Model'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Common'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
||||
artifactName: 'Jellyfin.Common'
|
||||
|
||||
- job: main_test
|
||||
displayName: Main Test
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: false
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Build
|
||||
inputs:
|
||||
command: build
|
||||
publishWebProjects: false
|
||||
projects: '$(TestProjects)'
|
||||
arguments: '--configuration $(BuildConfiguration)'
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
inputs:
|
||||
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
|
||||
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
|
||||
|
||||
- task: VSTest@2
|
||||
inputs:
|
||||
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
|
||||
testAssemblyVer2: | # Required when testSelector == TestAssemblies
|
||||
**\bin\$(BuildConfiguration)\**\*test*.dll
|
||||
!**\obj\**
|
||||
!**\xunit.runner.visualstudio.testadapter.dll
|
||||
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
|
||||
searchFolder: '$(System.DefaultWorkingDirectory)'
|
||||
runInParallel: True # Optional
|
||||
runTestsInIsolation: True # Optional
|
||||
codeCoverageEnabled: True # Optional
|
||||
configuration: 'Debug' # Optional
|
||||
publishRunAttachments: true # Optional
|
||||
|
||||
- job: main_build_win
|
||||
displayName: Publish Windows
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
maxParallel: 2
|
||||
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')) ,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)"
|
||||
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'
|
||||
|
||||
- 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')) ,eq(variables['BuildConfiguration'], 'Release'), 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')) ,eq(variables['BuildConfiguration'], 'Release'), 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')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
||||
contents: '**'
|
||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: false # Optional
|
||||
|
||||
- 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' # Optional. Options: filePath, inline
|
||||
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
|
||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
||||
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
|
||||
workingDirectory: $(Build.SourcesDirectory) # Optional
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy NSIS Installer'
|
||||
inputs:
|
||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
|
||||
contents: 'jellyfin*.exe'
|
||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: true # Optional
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Setup'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/setup'
|
||||
artifactName: 'Jellyfin Server Setup'
|
||||
|
||||
- job: dotnet_compat
|
||||
displayName: Compatibility Check
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
dependsOn: main_build
|
||||
# only execute for pull requests
|
||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
||||
strategy:
|
||||
matrix:
|
||||
- template: azure-pipelines-compat.yml
|
||||
parameters:
|
||||
Packages:
|
||||
Naming:
|
||||
NugetPackageName: Jellyfin.Naming
|
||||
AssemblyFileName: Emby.Naming.dll
|
||||
|
@ -251,74 +47,4 @@ jobs:
|
|||
Common:
|
||||
NugetPackageName: Jellyfin.Common
|
||||
AssemblyFileName: MediaBrowser.Common.dll
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Update DotNet'
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: 3.1.100
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download New Assembly Build Artifact'
|
||||
inputs:
|
||||
source: 'current' # Options: current, specific
|
||||
artifact: '$(NugetPackageName)' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/new-artifacts'
|
||||
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy New Assembly Build Artifact'
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
|
||||
contents: '**/*.dll'
|
||||
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: true # Optional
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Reference Assembly Build Artifact'
|
||||
inputs:
|
||||
source: 'specific' # Options: current, specific
|
||||
artifact: '$(NugetPackageName)' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/current-artifacts'
|
||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||
pipeline: '$(System.DefinitionId)' # Required when source == Specific
|
||||
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy Reference Assembly Build Artifact'
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
|
||||
contents: '**/*.dll'
|
||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: true # Optional
|
||||
|
||||
- task: DownloadGitHubRelease@0
|
||||
displayName: 'Download ABI Compatibility Check Tool'
|
||||
inputs:
|
||||
connection: Jellyfin Release Download
|
||||
userRepository: EraYaN/dotnet-compatibility
|
||||
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
|
||||
itemPattern: '**-ci.zip' # Optional
|
||||
downloadPath: '$(System.ArtifactsDirectory)'
|
||||
|
||||
- task: ExtractFiles@1
|
||||
displayName: 'Extract ABI Compatibility Check Tool'
|
||||
inputs:
|
||||
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
|
||||
destinationFolder: $(System.ArtifactsDirectory)/tools
|
||||
cleanDestinationFolder: true
|
||||
|
||||
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
||||
- task: CmdLine@2
|
||||
displayName: 'Execute ABI Compatibility Check Tool'
|
||||
inputs:
|
||||
script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
|
||||
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
||||
LinuxImage: "ubuntu-latest"
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
name: Nightly-$(date:yyyyMMdd).$(rev:r)
|
||||
|
||||
variables:
|
||||
- name: Version
|
||||
value: '1.0.0'
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
- job: publish_artifacts_nightly
|
||||
displayName: Publish Artifacts Nightly
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download the Windows Setup Artifact
|
||||
inputs:
|
||||
source: 'specific' # Options: current, specific
|
||||
artifact: 'Jellyfin Server Setup' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/win-installer'
|
||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||
pipelineId: 1 # Required when source == Specific
|
||||
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Create Drop directory'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
|
||||
|
||||
- task: CopyFilesOverSSH@0
|
||||
displayName: 'Copy the Windows Setup to the Repo'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
||||
contents: 'jellyfin_*.exe'
|
||||
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Clean up SCP symlink'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'
|
|
@ -1,48 +0,0 @@
|
|||
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
|
||||
|
||||
variables:
|
||||
- name: Version
|
||||
value: '1.0.0'
|
||||
- name: UsedRunId
|
||||
value: 0
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
- job: publish_artifacts_release
|
||||
displayName: Publish Artifacts Release
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download the Windows Setup Artifact
|
||||
inputs:
|
||||
source: 'specific' # Options: current, specific
|
||||
artifact: 'Jellyfin Server Setup' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/win-installer'
|
||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||
pipelineId: 1 # Required when source == Specific
|
||||
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
runId: $(UsedRunId)
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Create Drop directory'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
|
||||
|
||||
- task: CopyFilesOverSSH@0
|
||||
displayName: 'Copy the Windows Setup to the Repo'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
||||
contents: 'jellyfin_*.exe'
|
||||
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Clean up SCP symlink'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'
|
6
.github/ISSUE_TEMPLATE/media_playback.md
vendored
6
.github/ISSUE_TEMPLATE/media_playback.md
vendored
|
@ -11,7 +11,10 @@ assignees: ''
|
|||
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
|
||||
|
||||
**Logs**
|
||||
<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
|
||||
<!-- Please paste any log messages from during the playback issue. -->
|
||||
|
||||
**FFmpeg Logs**
|
||||
<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
|
||||
|
||||
**Stats for Nerds Screenshots**
|
||||
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
|
||||
|
@ -29,4 +32,3 @@ assignees: ''
|
|||
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
|
||||
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
|
||||
- Client and Browser Version: [e.g. 10.3.4 and 68.0]
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
- [nevado](https://github.com/nevado)
|
||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
||||
- [ullmie02](https://github.com/ullmie02)
|
||||
- [pR0Ps](https://github.com/pR0Ps)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
|
|||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
||||
FROM debian:buster-slim
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.1
|
||||
|
||||
|
||||
|
@ -23,11 +21,10 @@ RUN find . -type d -name obj | xargs -r rm -r
|
|||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM debian:stretch-slim-arm32v7
|
||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.1
|
||||
|
||||
|
||||
|
@ -23,11 +21,10 @@ RUN find . -type d -name obj | xargs -r rm -r
|
|||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM debian:stretch-slim-arm64v8
|
||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace DvdLib.Ifo
|
|||
}
|
||||
else
|
||||
{
|
||||
using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
|
||||
using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
|
||||
{
|
||||
|
@ -95,7 +95,7 @@ namespace DvdLib.Ifo
|
|||
{
|
||||
VTSPaths[vtsNum] = vtsPath;
|
||||
|
||||
using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
|
||||
using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
|
||||
{
|
||||
|
|
|
@ -170,32 +170,32 @@ namespace Emby.Dlna.Api
|
|||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Post(ProcessMediaReceiverRegistrarControlRequest request)
|
||||
public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
|
||||
var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
|
||||
|
||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Post(ProcessContentDirectoryControlRequest request)
|
||||
public async Task<object> Post(ProcessContentDirectoryControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request.RequestStream, ContentDirectory);
|
||||
var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
|
||||
|
||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Post(ProcessConnectionManagerControlRequest request)
|
||||
public async Task<object> Post(ProcessConnectionManagerControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request.RequestStream, ConnectionManager);
|
||||
var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
|
||||
|
||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||
}
|
||||
|
||||
private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
|
||||
private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
|
||||
{
|
||||
var id = GetPathValue(2).ToString();
|
||||
|
||||
return service.ProcessControlRequest(new ControlRequest
|
||||
return service.ProcessControlRequestAsync(new ControlRequest
|
||||
{
|
||||
Headers = Request.Headers,
|
||||
InputXml = requestStream,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -20,17 +21,19 @@ namespace Emby.Dlna.ConnectionManager
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ConnectionManagerXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
|
||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -66,12 +67,14 @@ namespace Emby.Dlna.ContentDirectory
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ContentDirectoryXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
@ -96,7 +99,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
_userViewManager,
|
||||
_mediaEncoder,
|
||||
_tvSeriesManager)
|
||||
.ProcessControlRequest(request);
|
||||
.ProcessControlRequestAsync(request);
|
||||
}
|
||||
|
||||
private User GetUser(DeviceProfile profile)
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
_profile = profile;
|
||||
_config = config;
|
||||
|
||||
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder);
|
||||
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder);
|
||||
}
|
||||
|
||||
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||
|
@ -771,11 +771,11 @@ namespace Emby.Dlna.ContentDirectory
|
|||
})
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
return ApplyPaging(new QueryResult<ServerItem>
|
||||
{
|
||||
Items = folders,
|
||||
TotalRecordCount = folders.Length
|
||||
};
|
||||
}, startIndex, limit);
|
||||
}
|
||||
|
||||
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
||||
|
@ -1336,7 +1336,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
};
|
||||
}
|
||||
|
||||
_logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
||||
Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
||||
|
||||
return new ServerItem(_libraryManager.GetUserRootFolder());
|
||||
}
|
||||
|
|
|
@ -385,7 +385,7 @@ namespace Emby.Dlna
|
|||
{
|
||||
Directory.CreateDirectory(systemProfilesPath);
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public interface IUpnpService
|
||||
|
@ -13,6 +15,6 @@ namespace Emby.Dlna
|
|||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>ControlResponse.</returns>
|
||||
ControlResponse ProcessControlRequest(ControlRequest request);
|
||||
Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -15,17 +16,19 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||
_config = config;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
return new ControlHandler(
|
||||
_config,
|
||||
Logger)
|
||||
.ProcessControlRequest(request);
|
||||
.ProcessControlRequestAsync(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Emby.Dlna.Didl;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -15,44 +16,34 @@ namespace Emby.Dlna.Service
|
|||
{
|
||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||
|
||||
protected readonly IServerConfigurationManager Config;
|
||||
protected readonly ILogger _logger;
|
||||
protected IServerConfigurationManager Config { get; }
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||
{
|
||||
Config = config;
|
||||
_logger = logger;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLog;
|
||||
|
||||
if (enableDebugLogging)
|
||||
{
|
||||
LogRequest(request);
|
||||
}
|
||||
|
||||
var response = ProcessControlRequestInternal(request);
|
||||
|
||||
if (enableDebugLogging)
|
||||
{
|
||||
LogResponse(response);
|
||||
}
|
||||
LogRequest(request);
|
||||
|
||||
var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
|
||||
LogResponse(response);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing control request");
|
||||
Logger.LogError(ex, "Error processing control request");
|
||||
|
||||
return new ControlErrorHandler().GetResponse(ex);
|
||||
return ControlErrorHandler.GetResponse(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ControlResponse ProcessControlRequestInternal(ControlRequest request)
|
||||
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
||||
{
|
||||
ControlRequestInfo requestInfo = null;
|
||||
|
||||
|
@ -63,16 +54,17 @@ namespace Emby.Dlna.Service
|
|||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
IgnoreComments = true,
|
||||
Async = true
|
||||
};
|
||||
|
||||
using (var reader = XmlReader.Create(streamReader, readerSettings))
|
||||
{
|
||||
requestInfo = ParseRequest(reader);
|
||||
requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("Received control request {0}", requestInfo.LocalName);
|
||||
Logger.LogDebug("Received control request {0}", requestInfo.LocalName);
|
||||
|
||||
var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
|
||||
|
||||
|
@ -114,17 +106,15 @@ namespace Emby.Dlna.Service
|
|||
IsSuccessful = true
|
||||
};
|
||||
|
||||
//logger.LogDebug(xml);
|
||||
|
||||
controlResponse.Headers.Add("EXT", string.Empty);
|
||||
|
||||
return controlResponse;
|
||||
}
|
||||
|
||||
private ControlRequestInfo ParseRequest(XmlReader reader)
|
||||
private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
|
@ -139,37 +129,38 @@ namespace Emby.Dlna.Service
|
|||
{
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
return ParseBodyTag(subReader);
|
||||
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
await reader.SkipAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new ControlRequestInfo();
|
||||
}
|
||||
|
||||
private ControlRequestInfo ParseBodyTag(XmlReader reader)
|
||||
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
|
||||
{
|
||||
var result = new ControlRequestInfo();
|
||||
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
|
@ -183,28 +174,28 @@ namespace Emby.Dlna.Service
|
|||
{
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
ParseFirstBodyChild(subReader, result.Headers);
|
||||
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ParseFirstBodyChild(XmlReader reader, IDictionary<string, string> headers)
|
||||
private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
|
@ -212,20 +203,20 @@ namespace Emby.Dlna.Service
|
|||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
// TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
|
||||
headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString();
|
||||
headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ControlRequestInfo
|
||||
{
|
||||
public string LocalName;
|
||||
public string NamespaceURI;
|
||||
public IDictionary<string, string> Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
public string LocalName { get; set; }
|
||||
public string NamespaceURI { get; set; }
|
||||
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
|
||||
|
@ -237,10 +228,7 @@ namespace Emby.Dlna.Service
|
|||
return;
|
||||
}
|
||||
|
||||
var originalHeaders = request.Headers;
|
||||
var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
|
||||
|
||||
_logger.LogDebug("Control request. Headers: {0}", headers);
|
||||
Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
|
||||
}
|
||||
|
||||
private void LogResponse(ControlResponse response)
|
||||
|
@ -250,11 +238,7 @@ namespace Emby.Dlna.Service
|
|||
return;
|
||||
}
|
||||
|
||||
var originalHeaders = response.Headers;
|
||||
var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
|
||||
//builder.Append(response.Xml);
|
||||
|
||||
_logger.LogDebug("Control response. Headers: {0}", headers);
|
||||
Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ using Emby.Dlna.Didl;
|
|||
|
||||
namespace Emby.Dlna.Service
|
||||
{
|
||||
public class ControlErrorHandler
|
||||
public static class ControlErrorHandler
|
||||
{
|
||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||
|
||||
public ControlResponse GetResponse(Exception ex)
|
||||
public static ControlResponse GetResponse(Exception ex)
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
|
|
|
@ -14,7 +14,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -129,7 +128,7 @@ namespace Emby.Drawing
|
|||
{
|
||||
var file = await ProcessImage(options).ConfigureAwait(false);
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(file.Item1, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
|
||||
using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
||||
{
|
||||
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Naming.Common
|
||||
{
|
||||
|
@ -176,13 +177,12 @@ namespace Emby.Naming.Common
|
|||
|
||||
CleanDateTimes = new[]
|
||||
{
|
||||
@"(.+[^ _\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9][0-9]|20[0-1][0-9])([ _\,\.\(\)\[\]\-][^0-9]|$)"
|
||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
||||
};
|
||||
|
||||
CleanStrings = new[]
|
||||
{
|
||||
@"[ _\,\.\(\)\[\]\-](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||
@"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||
@"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||
@"(\[.*\])"
|
||||
};
|
||||
|
||||
|
@ -339,7 +339,7 @@ namespace Emby.Naming.Common
|
|||
|
||||
// *** End Kodi Standard Naming
|
||||
|
||||
// [bar] Foo - 1 [baz]
|
||||
// [bar] Foo - 1 [baz]
|
||||
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
|
||||
{
|
||||
IsNamed = true
|
||||
|
@ -423,126 +423,126 @@ namespace Emby.Naming.Common
|
|||
{
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "trailer",
|
||||
ExtraType = ExtraType.Trailer,
|
||||
RuleType = ExtraRuleType.Filename,
|
||||
Token = "trailer",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "trailer",
|
||||
ExtraType = ExtraType.Trailer,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-trailer",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "trailer",
|
||||
ExtraType = ExtraType.Trailer,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = ".trailer",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "trailer",
|
||||
ExtraType = ExtraType.Trailer,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "_trailer",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "trailer",
|
||||
ExtraType = ExtraType.Trailer,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = " trailer",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "sample",
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.Filename,
|
||||
Token = "sample",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "sample",
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-sample",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "sample",
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = ".sample",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "sample",
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "_sample",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "sample",
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = " sample",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "themesong",
|
||||
ExtraType = ExtraType.ThemeSong,
|
||||
RuleType = ExtraRuleType.Filename,
|
||||
Token = "theme",
|
||||
MediaType = MediaType.Audio
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "scene",
|
||||
ExtraType = ExtraType.Scene,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-scene",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "clip",
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-clip",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "interview",
|
||||
ExtraType = ExtraType.Interview,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-interview",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "behindthescenes",
|
||||
ExtraType = ExtraType.BehindTheScenes,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-behindthescenes",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "deletedscene",
|
||||
ExtraType = ExtraType.DeletedScene,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-deleted",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "featurette",
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-featurette",
|
||||
MediaType = MediaType.Video
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = "short",
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-short",
|
||||
MediaType = MediaType.Video
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace Emby.Naming.Subtitles
|
|||
}
|
||||
|
||||
var flags = GetFlags(path);
|
||||
|
||||
var info = new SubtitleInfo
|
||||
{
|
||||
Path = path,
|
||||
|
@ -45,7 +44,7 @@ namespace Emby.Naming.Subtitles
|
|||
// Should have a name, language and file extension
|
||||
if (parts.Count >= 3)
|
||||
{
|
||||
info.Language = parts[parts.Count - 2];
|
||||
info.Language = parts[^2];
|
||||
}
|
||||
|
||||
return info;
|
||||
|
|
|
@ -131,7 +131,7 @@ namespace Emby.Naming.TV
|
|||
var endingNumberGroup = match.Groups["endingepnumber"];
|
||||
if (endingNumberGroup.Success)
|
||||
{
|
||||
// Will only set EndingEpsiodeNumber if the captured number is not followed by additional numbers
|
||||
// Will only set EndingEpisodeNumber if the captured number is not followed by additional numbers
|
||||
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
||||
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
||||
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
||||
|
|
|
@ -1,89 +1,48 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Common;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
||||
/// </summary>
|
||||
public class CleanDateTimeParser
|
||||
public static class CleanDateTimeParser
|
||||
{
|
||||
private readonly NamingOptions _options;
|
||||
|
||||
public CleanDateTimeParser(NamingOptions options)
|
||||
public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public CleanDateTimeResult Clean(string name)
|
||||
{
|
||||
var originalName = name;
|
||||
|
||||
try
|
||||
CleanDateTimeResult result = new CleanDateTimeResult(name);
|
||||
var len = cleanDateTimeRegexes.Count;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var extension = Path.GetExtension(name) ?? string.Empty;
|
||||
// Check supported extensions
|
||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
|
||||
&& !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (TryClean(name, cleanDateTimeRegexes[i], ref result))
|
||||
{
|
||||
// Dummy up a file extension because the expressions will fail without one
|
||||
// This is tricky because we can't just check Path.GetExtension for empty
|
||||
// If the input is "St. Vincent (2014)", it will produce ". Vincent (2014)" as the extension
|
||||
name += ".mkv";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
|
||||
var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
|
||||
.FirstOrDefault(i => i.HasChanged) ??
|
||||
new CleanDateTimeResult { Name = originalName };
|
||||
|
||||
if (result.HasChanged)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Make a second pass, running clean string first
|
||||
var cleanStringResult = new CleanStringParser().Clean(name, _options.CleanStringRegexes);
|
||||
|
||||
if (!cleanStringResult.HasChanged)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return _options.CleanDateTimeRegexes.Select(i => Clean(cleanStringResult.Name, i))
|
||||
.FirstOrDefault(i => i.HasChanged) ??
|
||||
result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static CleanDateTimeResult Clean(string name, Regex expression)
|
||||
private static bool TryClean(string name, Regex expression, ref CleanDateTimeResult result)
|
||||
{
|
||||
var result = new CleanDateTimeResult();
|
||||
|
||||
var match = expression.Match(name);
|
||||
|
||||
if (match.Success
|
||||
&& match.Groups.Count == 4
|
||||
&& match.Groups.Count == 5
|
||||
&& match.Groups[1].Success
|
||||
&& match.Groups[2].Success
|
||||
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
|
||||
{
|
||||
name = match.Groups[1].Value;
|
||||
result.Year = year;
|
||||
result.HasChanged = true;
|
||||
result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year);
|
||||
return true;
|
||||
}
|
||||
|
||||
result.Name = name;
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,33 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
public class CleanDateTimeResult
|
||||
public readonly struct CleanDateTimeResult
|
||||
{
|
||||
public CleanDateTimeResult(string name, int? year)
|
||||
{
|
||||
Name = name;
|
||||
Year = year;
|
||||
}
|
||||
|
||||
public CleanDateTimeResult(string name)
|
||||
{
|
||||
Name = name;
|
||||
Year = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the year.
|
||||
/// Gets the year.
|
||||
/// </summary>
|
||||
/// <value>The year.</value>
|
||||
public int? Year { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has changed.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
|
||||
public bool HasChanged { get; set; }
|
||||
public int? Year { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,45 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// http://kodi.wiki/view/Advancedsettings.xml#video
|
||||
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
||||
/// </summary>
|
||||
public class CleanStringParser
|
||||
public static class CleanStringParser
|
||||
{
|
||||
public CleanStringResult Clean(string name, IEnumerable<Regex> expressions)
|
||||
public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
|
||||
{
|
||||
var hasChanged = false;
|
||||
|
||||
foreach (var exp in expressions)
|
||||
var len = expressions.Count;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var result = Clean(name, exp);
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Name))
|
||||
if (TryClean(name, expressions[i], out newName))
|
||||
{
|
||||
name = result.Name;
|
||||
hasChanged = hasChanged || result.HasChanged;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new CleanStringResult
|
||||
{
|
||||
Name = name,
|
||||
HasChanged = hasChanged
|
||||
};
|
||||
newName = ReadOnlySpan<char>.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static CleanStringResult Clean(string name, Regex expression)
|
||||
private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName)
|
||||
{
|
||||
var result = new CleanStringResult();
|
||||
|
||||
var match = expression.Match(name);
|
||||
|
||||
if (match.Success)
|
||||
int index = match.Index;
|
||||
if (match.Success && index != 0)
|
||||
{
|
||||
result.HasChanged = true;
|
||||
name = name.Substring(0, match.Index);
|
||||
newName = name.AsSpan().Slice(0, match.Index);
|
||||
return true;
|
||||
}
|
||||
|
||||
result.Name = name;
|
||||
return result;
|
||||
newName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
public class CleanStringResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has changed.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
|
||||
public bool HasChanged { get; set; }
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
return _options.VideoExtraRules
|
||||
.Select(i => GetExtraInfo(path, i))
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i.ExtraType)) ?? new ExtraResult();
|
||||
.FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
|
||||
}
|
||||
|
||||
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
public class ExtraResult
|
||||
|
@ -9,7 +11,7 @@ namespace Emby.Naming.Video
|
|||
/// Gets or sets the type of the extra.
|
||||
/// </summary>
|
||||
/// <value>The type of the extra.</value>
|
||||
public string ExtraType { get; set; }
|
||||
public ExtraType? ExtraType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rule.
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaType = Emby.Naming.Common.MediaType;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
|
@ -17,7 +18,7 @@ namespace Emby.Naming.Video
|
|||
/// Gets or sets the type of the extra.
|
||||
/// </summary>
|
||||
/// <value>The type of the extra.</value>
|
||||
public string ExtraType { get; set; }
|
||||
public ExtraType ExtraType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the rule.
|
||||
|
|
|
@ -14,14 +14,14 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
if (path == null)
|
||||
{
|
||||
return default(StubResult);
|
||||
return default;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return default(StubResult);
|
||||
return default;
|
||||
}
|
||||
|
||||
var result = new StubResult()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -30,10 +32,10 @@ namespace Emby.Naming.Video
|
|||
public int? Year { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
|
||||
/// Gets or sets the type of the extra, e.g. trailer, theme song, behind the scenes, etc.
|
||||
/// </summary>
|
||||
/// <value>The type of the extra.</value>
|
||||
public string ExtraType { get; set; }
|
||||
public ExtraType? ExtraType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extra rule.
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
|
@ -32,7 +33,7 @@ namespace Emby.Naming.Video
|
|||
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
||||
// See the unit test TestStackedWithTrailer
|
||||
var nonExtras = videoInfos
|
||||
.Where(i => string.IsNullOrEmpty(i.ExtraType))
|
||||
.Where(i => i.ExtraType == null)
|
||||
.Select(i => new FileSystemMetadata
|
||||
{
|
||||
FullName = i.Path,
|
||||
|
@ -79,7 +80,7 @@ namespace Emby.Naming.Video
|
|||
}
|
||||
|
||||
var standaloneMedia = remainingFiles
|
||||
.Where(i => string.IsNullOrEmpty(i.ExtraType))
|
||||
.Where(i => i.ExtraType == null)
|
||||
.ToList();
|
||||
|
||||
foreach (var media in standaloneMedia)
|
||||
|
@ -148,7 +149,7 @@ namespace Emby.Naming.Video
|
|||
if (list.Count == 1)
|
||||
{
|
||||
var trailers = remainingFiles
|
||||
.Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(i => i.ExtraType == ExtraType.Trailer)
|
||||
.ToList();
|
||||
|
||||
list[0].Extras.AddRange(trailers);
|
||||
|
@ -229,7 +230,7 @@ namespace Emby.Naming.Video
|
|||
}
|
||||
|
||||
return remainingFiles
|
||||
.Where(i => !string.IsNullOrEmpty(i.ExtraType))
|
||||
.Where(i => i.ExtraType == null)
|
||||
.Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
}
|
||||
|
|
|
@ -94,9 +94,10 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
var cleanDateTimeResult = CleanDateTime(name);
|
||||
|
||||
if (string.IsNullOrEmpty(extraResult.ExtraType))
|
||||
if (extraResult.ExtraType == null
|
||||
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
|
||||
{
|
||||
name = CleanString(cleanDateTimeResult.Name).Name;
|
||||
name = newName.ToString();
|
||||
}
|
||||
|
||||
year = cleanDateTimeResult.Year;
|
||||
|
@ -130,14 +131,14 @@ namespace Emby.Naming.Video
|
|||
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public CleanStringResult CleanString(string name)
|
||||
public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
|
||||
{
|
||||
return new CleanStringParser().Clean(name, _options.CleanStringRegexes);
|
||||
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
|
||||
}
|
||||
|
||||
public CleanDateTimeResult CleanDateTime(string name)
|
||||
{
|
||||
return new CleanDateTimeParser(_options).Clean(name);
|
||||
return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,10 @@
|
|||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -177,11 +178,7 @@ namespace Emby.Server.Implementations
|
|||
/// Gets the plugins.
|
||||
/// </summary>
|
||||
/// <value>The plugins.</value>
|
||||
public IPlugin[] Plugins
|
||||
{
|
||||
get => _plugins;
|
||||
protected set => _plugins = value;
|
||||
}
|
||||
public IReadOnlyList<IPlugin> Plugins => _plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger factory.
|
||||
|
@ -602,7 +599,7 @@ namespace Emby.Server.Implementations
|
|||
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
||||
}
|
||||
|
||||
JsonSerializer = new JsonSerializer(FileSystemManager);
|
||||
JsonSerializer = new JsonSerializer();
|
||||
|
||||
if (Plugins != null)
|
||||
{
|
||||
|
@ -1056,7 +1053,7 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
|
||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||
Plugins = GetExports<IPlugin>()
|
||||
_plugins = GetExports<IPlugin>()
|
||||
.Select(LoadPlugin)
|
||||
.Where(i => i != null)
|
||||
.ToArray();
|
||||
|
@ -1705,9 +1702,9 @@ namespace Emby.Server.Implementations
|
|||
/// <param name="plugin">The plugin.</param>
|
||||
public void RemovePlugin(IPlugin plugin)
|
||||
{
|
||||
var list = Plugins.ToList();
|
||||
var list = _plugins.ToList();
|
||||
list.Remove(plugin);
|
||||
Plugins = list.ToArray();
|
||||
_plugins = list.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -242,7 +243,7 @@ namespace Emby.Server.Implementations.Devices
|
|||
|
||||
try
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
await stream.CopyToAsync(fs).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -29,13 +29,13 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -51,10 +51,10 @@
|
|||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
using MediaBrowser.Controller;
|
||||
|
@ -12,7 +13,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
/// <summary>
|
||||
/// Class UdpServerEntryPoint.
|
||||
/// </summary>
|
||||
public class UdpServerEntryPoint : IServerEntryPoint
|
||||
public sealed class UdpServerEntryPoint : IServerEntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The port of the UDP server.
|
||||
|
@ -31,61 +32,44 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
/// The UDP server.
|
||||
/// </summary>
|
||||
private UdpServer _udpServer;
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
|
||||
/// </summary>
|
||||
public UdpServerEntryPoint(
|
||||
ILogger logger,
|
||||
IServerApplicationHost appHost,
|
||||
IJsonSerializer json,
|
||||
ISocketFactory socketFactory)
|
||||
ILogger<UdpServerEntryPoint> logger,
|
||||
IServerApplicationHost appHost)
|
||||
{
|
||||
_logger = logger;
|
||||
_appHost = appHost;
|
||||
_json = json;
|
||||
_socketFactory = socketFactory;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
public async Task RunAsync()
|
||||
{
|
||||
var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory);
|
||||
|
||||
try
|
||||
{
|
||||
udpServer.Start(PortNumber);
|
||||
|
||||
_udpServer = udpServer;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to start UDP Server");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
_udpServer = new UdpServer(_logger, _appHost);
|
||||
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (dispose)
|
||||
if (_disposed)
|
||||
{
|
||||
if (_udpServer != null)
|
||||
{
|
||||
_udpServer.Dispose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
_udpServer.Dispose();
|
||||
|
||||
_cancellationTokenSource = null;
|
||||
_udpServer = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
if (File.Exists(responseCachePath)
|
||||
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
||||
{
|
||||
var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
|
||||
var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||
|
||||
return new HttpResponseInfo
|
||||
{
|
||||
|
@ -220,7 +220,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
StreamDefaults.DefaultFileStreamBufferSize,
|
||||
IODefaults.FileStreamBufferSize,
|
||||
true))
|
||||
{
|
||||
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -71,7 +72,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
SetRangeValues();
|
||||
}
|
||||
|
||||
FileShare = FileShareMode.Read;
|
||||
FileShare = FileShare.Read;
|
||||
Cookies = new List<Cookie>();
|
||||
}
|
||||
|
||||
|
@ -93,7 +94,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
public List<Cookie> Cookies { get; private set; }
|
||||
|
||||
public FileShareMode FileShare { get; set; }
|
||||
public FileShare FileShare { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the options.
|
||||
|
@ -221,17 +222,17 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
}
|
||||
|
||||
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShare fileShare, CancellationToken cancellationToken)
|
||||
{
|
||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
||||
var fileOptions = FileOptions.SequentialScan;
|
||||
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
||||
fileOptions |= FileOptions.Asynchronous;
|
||||
}
|
||||
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, fileOptions))
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
|
@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
else
|
||||
{
|
||||
await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
await fs.CopyToAsync(stream, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -218,7 +219,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
case FileNotFoundException _:
|
||||
case ResourceNotFoundException _: return 404;
|
||||
case MethodNotAllowedException _: return 405;
|
||||
case RemoteServiceUnavailableException _: return 502;
|
||||
default: return 500;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -439,7 +440,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
public Task<object> GetStaticFileResult(IRequest requestContext,
|
||||
string path,
|
||||
FileShareMode fileShare = FileShareMode.Read)
|
||||
FileShare fileShare = FileShare.Read)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
|
@ -463,7 +464,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
throw new ArgumentException("Path can't be empty.", nameof(options));
|
||||
}
|
||||
|
||||
if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
|
||||
if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite)
|
||||
{
|
||||
throw new ArgumentException("FileShare must be either Read or ReadWrite");
|
||||
}
|
||||
|
@ -491,9 +492,9 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <param name="path">The path.</param>
|
||||
/// <param name="fileShare">The file share.</param>
|
||||
/// <returns>Stream.</returns>
|
||||
private Stream GetFileStream(string path, FileShareMode fileShare)
|
||||
private Stream GetFileStream(string path, FileShare fileShare)
|
||||
{
|
||||
return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare);
|
||||
return new FileStream(path, FileMode.Open, FileAccess.Read, fileShare);
|
||||
}
|
||||
|
||||
public Task<object> GetStaticResult(IRequest requestContext,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
@ -16,7 +17,7 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
|||
namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ManagedFileSystem
|
||||
/// Class ManagedFileSystem.
|
||||
/// </summary>
|
||||
public class ManagedFileSystem : IFileSystem
|
||||
{
|
||||
|
@ -79,20 +80,20 @@ namespace Emby.Server.Implementations.IO
|
|||
|
||||
public virtual string MakeAbsolutePath(string folderPath, string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath)
|
||||
// stream
|
||||
|| filePath.Contains("://"))
|
||||
// path is actually a stream
|
||||
if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal))
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/')
|
||||
{
|
||||
return filePath; // absolute local path
|
||||
// absolute local path
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// unc path
|
||||
if (filePath.StartsWith("\\\\"))
|
||||
if (filePath.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
@ -100,13 +101,16 @@ namespace Emby.Server.Implementations.IO
|
|||
var firstChar = filePath[0];
|
||||
if (firstChar == '/')
|
||||
{
|
||||
// For this we don't really know.
|
||||
// for this we don't really know
|
||||
return filePath;
|
||||
}
|
||||
if (firstChar == '\\') //relative path
|
||||
|
||||
// relative path
|
||||
if (firstChar == '\\')
|
||||
{
|
||||
filePath = filePath.Substring(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(folderPath, filePath));
|
||||
|
@ -130,11 +134,7 @@ namespace Emby.Server.Implementations.IO
|
|||
/// </summary>
|
||||
/// <param name="shortcutPath">The shortcut path.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// shortcutPath
|
||||
/// or
|
||||
/// target
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
|
||||
public virtual void CreateShortcut(string shortcutPath, string target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutPath))
|
||||
|
@ -280,11 +280,11 @@ namespace Emby.Server.Implementations.IO
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a filename and removes invalid characters
|
||||
/// Takes a filename and removes invalid characters.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentNullException">filename</exception>
|
||||
/// <exception cref="ArgumentNullException">The filename is null.</exception>
|
||||
public virtual string GetValidFilename(string filename)
|
||||
{
|
||||
var builder = new StringBuilder(filename);
|
||||
|
@ -365,90 +365,9 @@ namespace Emby.Server.Implementations.IO
|
|||
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file stream.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <param name="access">The access.</param>
|
||||
/// <param name="share">The share.</param>
|
||||
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
|
||||
/// <returns>FileStream.</returns>
|
||||
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
|
||||
{
|
||||
if (isAsync)
|
||||
{
|
||||
return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
|
||||
}
|
||||
|
||||
return GetFileStream(path, mode, access, share, FileOpenOptions.None);
|
||||
}
|
||||
|
||||
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
|
||||
=> new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
|
||||
|
||||
private static FileOptions GetFileOptions(FileOpenOptions mode)
|
||||
{
|
||||
var val = (int)mode;
|
||||
return (FileOptions)val;
|
||||
}
|
||||
|
||||
private static FileMode GetFileMode(FileOpenMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
//case FileOpenMode.Append:
|
||||
// return FileMode.Append;
|
||||
case FileOpenMode.Create:
|
||||
return FileMode.Create;
|
||||
case FileOpenMode.CreateNew:
|
||||
return FileMode.CreateNew;
|
||||
case FileOpenMode.Open:
|
||||
return FileMode.Open;
|
||||
case FileOpenMode.OpenOrCreate:
|
||||
return FileMode.OpenOrCreate;
|
||||
//case FileOpenMode.Truncate:
|
||||
// return FileMode.Truncate;
|
||||
default:
|
||||
throw new Exception("Unrecognized FileOpenMode");
|
||||
}
|
||||
}
|
||||
|
||||
private static FileAccess GetFileAccess(FileAccessMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
//case FileAccessMode.ReadWrite:
|
||||
// return FileAccess.ReadWrite;
|
||||
case FileAccessMode.Write:
|
||||
return FileAccess.Write;
|
||||
case FileAccessMode.Read:
|
||||
return FileAccess.Read;
|
||||
default:
|
||||
throw new Exception("Unrecognized FileAccessMode");
|
||||
}
|
||||
}
|
||||
|
||||
private static FileShare GetFileShare(FileShareMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case FileShareMode.ReadWrite:
|
||||
return FileShare.ReadWrite;
|
||||
case FileShareMode.Write:
|
||||
return FileShare.Write;
|
||||
case FileShareMode.Read:
|
||||
return FileShare.Read;
|
||||
case FileShareMode.None:
|
||||
return FileShare.None;
|
||||
default:
|
||||
throw new Exception("Unrecognized FileShareMode");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetHidden(string path, bool isHidden)
|
||||
{
|
||||
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
|
||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -472,7 +391,7 @@ namespace Emby.Server.Implementations.IO
|
|||
|
||||
public virtual void SetReadOnly(string path, bool isReadOnly)
|
||||
{
|
||||
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
|
||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -496,7 +415,7 @@ namespace Emby.Server.Implementations.IO
|
|||
|
||||
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
||||
{
|
||||
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
|
||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -779,7 +698,7 @@ namespace Emby.Server.Implementations.IO
|
|||
|
||||
public virtual void SetExecutable(string path)
|
||||
{
|
||||
if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin)
|
||||
if (OperatingSystem.Id == OperatingSystemId.Darwin)
|
||||
{
|
||||
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -70,9 +70,9 @@ namespace Emby.Server.Implementations.Library
|
|||
byte[] calculatedHash = _cryptographyProvider.ComputeHash(
|
||||
readyHash.Id,
|
||||
passwordbytes,
|
||||
readyHash.Salt);
|
||||
readyHash.Salt.ToArray());
|
||||
|
||||
if (calculatedHash.SequenceEqual(readyHash.Hash))
|
||||
if (readyHash.Hash.SequenceEqual(calculatedHash))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
|
@ -148,17 +148,18 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
// TODO: make use of iterations parameter?
|
||||
PasswordHash passwordHash = PasswordHash.Parse(user.Password);
|
||||
var salt = passwordHash.Salt.ToArray();
|
||||
return new PasswordHash(
|
||||
passwordHash.Id,
|
||||
_cryptographyProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(str),
|
||||
passwordHash.Salt),
|
||||
passwordHash.Salt,
|
||||
salt),
|
||||
salt,
|
||||
passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString();
|
||||
}
|
||||
|
||||
public byte[] GetHashed(User user, string str)
|
||||
public ReadOnlySpan<byte> GetHashed(User user, string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
|
@ -170,7 +171,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return _cryptographyProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(str),
|
||||
passwordHash.Salt);
|
||||
passwordHash.Salt.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -35,7 +36,6 @@ using MediaBrowser.Controller.Sorting;
|
|||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Library;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
@ -53,6 +53,9 @@ namespace Emby.Server.Implementations.Library
|
|||
/// </summary>
|
||||
public class LibraryManager : ILibraryManager
|
||||
{
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the postscan tasks.
|
||||
/// </summary>
|
||||
|
@ -2507,21 +2510,11 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
public NamingOptions GetNamingOptions()
|
||||
{
|
||||
return GetNamingOptionsInternal();
|
||||
}
|
||||
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
|
||||
private NamingOptions GetNamingOptionsInternal()
|
||||
{
|
||||
if (_namingOptions == null)
|
||||
{
|
||||
var options = new NamingOptions();
|
||||
|
||||
_namingOptions = options;
|
||||
_videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
|
||||
_namingOptions = new NamingOptions();
|
||||
_videoFileExtensions = _namingOptions.VideoFileExtensions;
|
||||
}
|
||||
|
||||
return _namingOptions;
|
||||
|
@ -2532,11 +2525,10 @@ namespace Emby.Server.Implementations.Library
|
|||
var resolver = new VideoResolver(GetNamingOptions());
|
||||
|
||||
var result = resolver.CleanDateTime(name);
|
||||
var cleanName = resolver.CleanString(result.Name);
|
||||
|
||||
return new ItemLookupInfo
|
||||
{
|
||||
Name = cleanName.Name,
|
||||
Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
|
||||
Year = result.Year
|
||||
};
|
||||
}
|
||||
|
@ -2558,7 +2550,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
files.AddRange(currentVideo.Extras.Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
}
|
||||
|
||||
var resolvers = new IItemResolver[]
|
||||
|
@ -2608,7 +2600,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
files.AddRange(currentVideo.Extras.Where(i => !string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
}
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
|
||||
|
@ -2712,7 +2704,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
if (!string.Equals(newPath, path, StringComparison.Ordinal))
|
||||
{
|
||||
if (to.IndexOf('/') != -1)
|
||||
if (to.IndexOf('/', StringComparison.Ordinal) != -1)
|
||||
{
|
||||
newPath = newPath.Replace('\\', '/');
|
||||
}
|
||||
|
@ -2733,30 +2725,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var result = resolver.GetExtraInfo(item.Path);
|
||||
|
||||
if (string.Equals(result.ExtraType, "deletedscene", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ExtraType = ExtraType.DeletedScene;
|
||||
}
|
||||
else if (string.Equals(result.ExtraType, "behindthescenes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ExtraType = ExtraType.BehindTheScenes;
|
||||
}
|
||||
else if (string.Equals(result.ExtraType, "interview", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ExtraType = ExtraType.Interview;
|
||||
}
|
||||
else if (string.Equals(result.ExtraType, "scene", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ExtraType = ExtraType.Scene;
|
||||
}
|
||||
else if (string.Equals(result.ExtraType, "sample", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ExtraType = ExtraType.Sample;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.ExtraType = ExtraType.Clip;
|
||||
}
|
||||
item.ExtraType = result.ExtraType;
|
||||
}
|
||||
|
||||
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Library
|
|||
});
|
||||
}
|
||||
|
||||
public async Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||
|
||||
|
@ -307,7 +308,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
|
||||
var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user