Merge branch 'master' into sessionmanager
This commit is contained in:
commit
0a43814596
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/**/*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,234 +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)\**\*tests.dll
|
||||
**\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
|
||||
testRunTitle: $(Agent.JobName)
|
||||
otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"'
|
||||
|
||||
- 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
|
||||
|
@ -253,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'
|
|
@ -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
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
|||
# mesa-va-drivers: needed for VAAPI
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \
|
||||
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl ca-certificates \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
|
|
|
@ -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 ca-certificates \
|
||||
&& 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 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ using MediaBrowser.Controller.Playlists;
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
public Filter(string filter)
|
||||
{
|
||||
_all = StringHelper.EqualsIgnoreCase(filter, "*");
|
||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Didl;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using Emby.Dlna.Common;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace Emby.Dlna.Server
|
||||
{
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
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 +15,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 +53,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 +105,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 +128,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 +173,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 +202,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 +227,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 +237,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);
|
||||
}
|
||||
|
|
|
@ -19,15 +19,13 @@ namespace Emby.Naming.Audio
|
|||
_options = options;
|
||||
}
|
||||
|
||||
public MultiPartResult ParseMultiPart(string path)
|
||||
public bool IsMultiPart(string path)
|
||||
{
|
||||
var result = new MultiPartResult();
|
||||
|
||||
var filename = Path.GetFileName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Move this logic into options object
|
||||
|
@ -57,12 +55,11 @@ namespace Emby.Naming.Audio
|
|||
|
||||
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
result.IsMultiPart = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
namespace Emby.Naming.Audio
|
||||
{
|
||||
public class MultiPartResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the part.
|
||||
/// </summary>
|
||||
/// <value>The part.</value>
|
||||
public string Part { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is multi part.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is multi part; otherwise, <c>false</c>.</value>
|
||||
public bool IsMultiPart { get; set; }
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ namespace Emby.Naming.AudioBook
|
|||
public int? ChapterNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// Gets or sets a value indicating whether this instance is a directory.
|
||||
/// </summary>
|
||||
/// <value>The type.</value>
|
||||
public bool IsDirectory { get; set; }
|
||||
|
|
|
@ -39,9 +39,7 @@ namespace Emby.Naming.AudioBook
|
|||
var stackResult = new StackResolver(_options)
|
||||
.ResolveAudioBooks(metadata);
|
||||
|
||||
var list = new List<AudioBookInfo>();
|
||||
|
||||
foreach (var stack in stackResult.Stacks)
|
||||
foreach (var stack in stackResult)
|
||||
{
|
||||
var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList();
|
||||
stackFiles.Sort();
|
||||
|
@ -50,20 +48,9 @@ namespace Emby.Naming.AudioBook
|
|||
Files = stackFiles,
|
||||
Name = stack.Name
|
||||
};
|
||||
list.Add(info);
|
||||
|
||||
yield return info;
|
||||
}
|
||||
|
||||
// Whatever files are left, just add them
|
||||
/*list.AddRange(remainingFiles.Select(i => new AudioBookInfo
|
||||
{
|
||||
Files = new List<AudioBookFileInfo> { i },
|
||||
Name = i.,
|
||||
Year = i.Year
|
||||
}));*/
|
||||
|
||||
var orderedList = list.OrderBy(i => i.Name);
|
||||
|
||||
return orderedList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,24 @@ namespace Emby.Naming.Common
|
|||
private string _expression;
|
||||
private Regex _regex;
|
||||
|
||||
public EpisodeExpression(string expression, bool byDate)
|
||||
{
|
||||
Expression = expression;
|
||||
IsByDate = byDate;
|
||||
DateTimeFormats = Array.Empty<string>();
|
||||
SupportsAbsoluteEpisodeNumbers = true;
|
||||
}
|
||||
|
||||
public EpisodeExpression(string expression)
|
||||
: this(expression, false)
|
||||
{
|
||||
}
|
||||
|
||||
public EpisodeExpression()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public string Expression
|
||||
{
|
||||
get => _expression;
|
||||
|
@ -32,23 +50,5 @@ namespace Emby.Naming.Common
|
|||
public string[] DateTimeFormats { get; set; }
|
||||
|
||||
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
||||
|
||||
public EpisodeExpression(string expression, bool byDate)
|
||||
{
|
||||
Expression = expression;
|
||||
IsByDate = byDate;
|
||||
DateTimeFormats = Array.Empty<string>();
|
||||
SupportsAbsoluteEpisodeNumbers = true;
|
||||
}
|
||||
|
||||
public EpisodeExpression(string expression)
|
||||
: this(expression, false)
|
||||
{
|
||||
}
|
||||
|
||||
public EpisodeExpression()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,46 +11,6 @@ namespace Emby.Naming.Common
|
|||
{
|
||||
public class NamingOptions
|
||||
{
|
||||
public string[] AudioFileExtensions { get; set; }
|
||||
|
||||
public string[] AlbumStackingPrefixes { get; set; }
|
||||
|
||||
public string[] SubtitleFileExtensions { get; set; }
|
||||
|
||||
public char[] SubtitleFlagDelimiters { get; set; }
|
||||
|
||||
public string[] SubtitleForcedFlags { get; set; }
|
||||
|
||||
public string[] SubtitleDefaultFlags { get; set; }
|
||||
|
||||
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
||||
|
||||
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
||||
|
||||
public string[] EpisodeMultiPartExpressions { get; set; }
|
||||
|
||||
public string[] VideoFileExtensions { get; set; }
|
||||
|
||||
public string[] StubFileExtensions { get; set; }
|
||||
|
||||
public string[] AudioBookPartsExpressions { get; set; }
|
||||
|
||||
public StubTypeRule[] StubTypes { get; set; }
|
||||
|
||||
public char[] VideoFlagDelimiters { get; set; }
|
||||
|
||||
public Format3DRule[] Format3DRules { get; set; }
|
||||
|
||||
public string[] VideoFileStackingExpressions { get; set; }
|
||||
|
||||
public string[] CleanDateTimes { get; set; }
|
||||
|
||||
public string[] CleanStrings { get; set; }
|
||||
|
||||
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
||||
|
||||
public ExtraRule[] VideoExtraRules { get; set; }
|
||||
|
||||
public NamingOptions()
|
||||
{
|
||||
VideoFileExtensions = new[]
|
||||
|
@ -177,13 +137,12 @@ namespace Emby.Naming.Common
|
|||
|
||||
CleanDateTimes = new[]
|
||||
{
|
||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](\d{4})([ _\,\.\(\)\[\]\-][^\d]|).*(\d{4})*"
|
||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](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|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||
@"(\[.*\])"
|
||||
};
|
||||
|
||||
|
@ -340,7 +299,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
|
||||
|
@ -682,11 +641,54 @@ namespace Emby.Naming.Common
|
|||
Compile();
|
||||
}
|
||||
|
||||
public string[] AudioFileExtensions { get; set; }
|
||||
|
||||
public string[] AlbumStackingPrefixes { get; set; }
|
||||
|
||||
public string[] SubtitleFileExtensions { get; set; }
|
||||
|
||||
public char[] SubtitleFlagDelimiters { get; set; }
|
||||
|
||||
public string[] SubtitleForcedFlags { get; set; }
|
||||
|
||||
public string[] SubtitleDefaultFlags { get; set; }
|
||||
|
||||
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
||||
|
||||
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
||||
|
||||
public string[] EpisodeMultiPartExpressions { get; set; }
|
||||
|
||||
public string[] VideoFileExtensions { get; set; }
|
||||
|
||||
public string[] StubFileExtensions { get; set; }
|
||||
|
||||
public string[] AudioBookPartsExpressions { get; set; }
|
||||
|
||||
public StubTypeRule[] StubTypes { get; set; }
|
||||
|
||||
public char[] VideoFlagDelimiters { get; set; }
|
||||
|
||||
public Format3DRule[] Format3DRules { get; set; }
|
||||
|
||||
public string[] VideoFileStackingExpressions { get; set; }
|
||||
|
||||
public string[] CleanDateTimes { get; set; }
|
||||
|
||||
public string[] CleanStrings { get; set; }
|
||||
|
||||
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
||||
|
||||
public ExtraRule[] VideoExtraRules { get; set; }
|
||||
|
||||
public Regex[] VideoFileStackingRegexes { get; private set; }
|
||||
|
||||
public Regex[] CleanDateTimeRegexes { get; private set; }
|
||||
|
||||
public Regex[] CleanStringRegexes { get; private set; }
|
||||
|
||||
public Regex[] EpisodeWithoutSeasonRegexes { get; private set; }
|
||||
|
||||
public Regex[] EpisodeMultiPartRegexes { get; private set; }
|
||||
|
||||
public void Compile()
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -27,7 +24,7 @@
|
|||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<!-- TODO: <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;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -28,7 +29,7 @@ namespace Emby.Naming.TV
|
|||
path += ".mp4";
|
||||
}
|
||||
|
||||
EpisodePathParserResult result = null;
|
||||
EpisodePathParserResult? result = null;
|
||||
|
||||
foreach (var expression in _options.EpisodeExpressions)
|
||||
{
|
||||
|
@ -131,12 +132,12 @@ 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;
|
||||
if (nextIndex >= name.Length
|
||||
|| "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
|
||||
|| !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
|
||||
{
|
||||
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
@ -18,7 +19,7 @@ namespace Emby.Naming.TV
|
|||
_options = options;
|
||||
}
|
||||
|
||||
public EpisodeInfo Resolve(
|
||||
public EpisodeInfo? Resolve(
|
||||
string path,
|
||||
bool isDirectory,
|
||||
bool? isNamed = null,
|
||||
|
@ -26,14 +27,9 @@ namespace Emby.Naming.TV
|
|||
bool? supportsAbsoluteNumbers = null,
|
||||
bool fillExtendedInfo = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
bool isStub = false;
|
||||
string container = null;
|
||||
string stubType = null;
|
||||
string? container = null;
|
||||
string? stubType = null;
|
||||
|
||||
if (!isDirectory)
|
||||
{
|
||||
|
@ -41,17 +37,13 @@ namespace Emby.Naming.TV
|
|||
// Check supported extensions
|
||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var stubResult = StubResolver.ResolveFile(path, _options);
|
||||
|
||||
isStub = stubResult.IsStub;
|
||||
|
||||
// It's not supported. Check stub extensions
|
||||
if (!isStub)
|
||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
stubType = stubResult.StubType;
|
||||
isStub = true;
|
||||
}
|
||||
|
||||
container = extension.TrimStart('.');
|
||||
|
|
|
@ -8,9 +8,24 @@ using System.Linq;
|
|||
|
||||
namespace Emby.Naming.TV
|
||||
{
|
||||
public class SeasonPathParser
|
||||
public static class SeasonPathParser
|
||||
{
|
||||
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
||||
/// <summary>
|
||||
/// A season folder must contain one of these somewhere in the name.
|
||||
/// </summary>
|
||||
private static readonly string[] _seasonFolderNames =
|
||||
{
|
||||
"season",
|
||||
"sæson",
|
||||
"temporada",
|
||||
"saison",
|
||||
"staffel",
|
||||
"series",
|
||||
"сезон",
|
||||
"stagione"
|
||||
};
|
||||
|
||||
public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
||||
{
|
||||
var result = new SeasonPathParserResult();
|
||||
|
||||
|
@ -27,21 +42,6 @@ namespace Emby.Naming.TV
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A season folder must contain one of these somewhere in the name.
|
||||
/// </summary>
|
||||
private static readonly string[] _seasonFolderNames =
|
||||
{
|
||||
"season",
|
||||
"sæson",
|
||||
"temporada",
|
||||
"saison",
|
||||
"staffel",
|
||||
"series",
|
||||
"сезон",
|
||||
"stagione"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season number from path.
|
||||
/// </summary>
|
||||
|
@ -150,6 +150,7 @@ namespace Emby.Naming.TV
|
|||
{
|
||||
numericStart = i;
|
||||
}
|
||||
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
@ -161,11 +162,11 @@ namespace Emby.Naming.TV
|
|||
}
|
||||
|
||||
var currentChar = path[i];
|
||||
if (currentChar.Equals('('))
|
||||
if (currentChar == '(')
|
||||
{
|
||||
hasOpenParenth = true;
|
||||
}
|
||||
else if (currentChar.Equals(')'))
|
||||
else if (currentChar == ')')
|
||||
{
|
||||
hasOpenParenth = false;
|
||||
}
|
||||
|
|
|
@ -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,6 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -9,44 +11,35 @@ namespace Emby.Naming.Video
|
|||
/// <summary>
|
||||
/// <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; }
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ namespace Emby.Naming.Video
|
|||
_options = options;
|
||||
}
|
||||
|
||||
public StackResult ResolveDirectories(IEnumerable<string> files)
|
||||
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
|
||||
{
|
||||
return Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ namespace Emby.Naming.Video
|
|||
}));
|
||||
}
|
||||
|
||||
public StackResult ResolveFiles(IEnumerable<string> files)
|
||||
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
|
||||
{
|
||||
return Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
|
@ -38,9 +38,8 @@ namespace Emby.Naming.Video
|
|||
}));
|
||||
}
|
||||
|
||||
public StackResult ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
|
||||
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
|
||||
{
|
||||
var result = new StackResult();
|
||||
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
|
||||
{
|
||||
var stack = new FileStack()
|
||||
|
@ -58,20 +57,16 @@ namespace Emby.Naming.Video
|
|||
stack.Files.Add(file.FullName);
|
||||
}
|
||||
|
||||
result.Stacks.Add(stack);
|
||||
yield return stack;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public StackResult Resolve(IEnumerable<FileSystemMetadata> files)
|
||||
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||
{
|
||||
var result = new StackResult();
|
||||
|
||||
var resolver = new VideoResolver(_options);
|
||||
|
||||
var list = files
|
||||
.Where(i => i.IsDirectory || (resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName)))
|
||||
.Where(i => i.IsDirectory || resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName))
|
||||
.OrderBy(i => i.FullName)
|
||||
.ToList();
|
||||
|
||||
|
@ -191,14 +186,12 @@ namespace Emby.Naming.Video
|
|||
|
||||
if (stack.Files.Count > 1)
|
||||
{
|
||||
result.Stacks.Add(stack);
|
||||
yield return stack;
|
||||
i += stack.Files.Count - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetRegexInput(FileSystemMetadata file)
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
public class StackResult
|
||||
{
|
||||
public List<FileStack> Stacks { get; set; }
|
||||
|
||||
public StackResult()
|
||||
{
|
||||
Stacks = new List<FileStack>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
@ -10,25 +11,22 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
public static class StubResolver
|
||||
{
|
||||
public static StubResult ResolveFile(string path, NamingOptions options)
|
||||
public static bool TryResolveFile(string path, NamingOptions options, out string? stubType)
|
||||
{
|
||||
stubType = default;
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
return default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = new StubResult()
|
||||
{
|
||||
IsStub = true
|
||||
};
|
||||
|
||||
path = Path.GetFileNameWithoutExtension(path);
|
||||
var token = Path.GetExtension(path).TrimStart('.');
|
||||
|
||||
|
@ -36,12 +34,12 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.StubType = rule.StubType;
|
||||
break;
|
||||
stubType = rule.StubType;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace Emby.Naming.Video
|
|||
public string StubType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// Gets or sets a value indicating whether this instance is a directory.
|
||||
/// </summary>
|
||||
/// <value>The type.</value>
|
||||
public bool IsDirectory { get; set; }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
|
@ -10,11 +11,14 @@ namespace Emby.Naming.Video
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VideoInfo" /> class.
|
||||
/// </summary>
|
||||
public VideoInfo()
|
||||
/// <param name="name">The name.</param>
|
||||
public VideoInfo(string name)
|
||||
{
|
||||
Files = new List<VideoFileInfo>();
|
||||
Extras = new List<VideoFileInfo>();
|
||||
AlternateVersions = new List<VideoFileInfo>();
|
||||
Name = name;
|
||||
|
||||
Files = Array.Empty<VideoFileInfo>();
|
||||
Extras = Array.Empty<VideoFileInfo>();
|
||||
AlternateVersions = Array.Empty<VideoFileInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -33,18 +37,18 @@ namespace Emby.Naming.Video
|
|||
/// Gets or sets the files.
|
||||
/// </summary>
|
||||
/// <value>The files.</value>
|
||||
public List<VideoFileInfo> Files { get; set; }
|
||||
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extras.
|
||||
/// </summary>
|
||||
/// <value>The extras.</value>
|
||||
public List<VideoFileInfo> Extras { get; set; }
|
||||
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alternate versions.
|
||||
/// </summary>
|
||||
/// <value>The alternate versions.</value>
|
||||
public List<VideoFileInfo> AlternateVersions { get; set; }
|
||||
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,20 +41,19 @@ namespace Emby.Naming.Video
|
|||
});
|
||||
|
||||
var stackResult = new StackResolver(_options)
|
||||
.Resolve(nonExtras);
|
||||
.Resolve(nonExtras).ToList();
|
||||
|
||||
var remainingFiles = videoInfos
|
||||
.Where(i => !stackResult.Stacks.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
|
||||
.Where(i => !stackResult.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
|
||||
.ToList();
|
||||
|
||||
var list = new List<VideoInfo>();
|
||||
|
||||
foreach (var stack in stackResult.Stacks)
|
||||
foreach (var stack in stackResult)
|
||||
{
|
||||
var info = new VideoInfo
|
||||
var info = new VideoInfo(stack.Name)
|
||||
{
|
||||
Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList(),
|
||||
Name = stack.Name
|
||||
Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList()
|
||||
};
|
||||
|
||||
info.Year = info.Files[0].Year;
|
||||
|
@ -85,10 +84,9 @@ namespace Emby.Naming.Video
|
|||
|
||||
foreach (var media in standaloneMedia)
|
||||
{
|
||||
var info = new VideoInfo
|
||||
var info = new VideoInfo(media.Name)
|
||||
{
|
||||
Files = new List<VideoFileInfo> { media },
|
||||
Name = media.Name
|
||||
Files = new List<VideoFileInfo> { media }
|
||||
};
|
||||
|
||||
info.Year = info.Files[0].Year;
|
||||
|
@ -128,7 +126,8 @@ namespace Emby.Naming.Video
|
|||
.Except(extras)
|
||||
.ToList();
|
||||
|
||||
info.Extras.AddRange(extras);
|
||||
extras.AddRange(info.Extras);
|
||||
info.Extras = extras;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +140,8 @@ namespace Emby.Naming.Video
|
|||
.Except(extrasByFileName)
|
||||
.ToList();
|
||||
|
||||
info.Extras.AddRange(extrasByFileName);
|
||||
extrasByFileName.AddRange(info.Extras);
|
||||
info.Extras = extrasByFileName;
|
||||
}
|
||||
|
||||
// If there's only one video, accept all trailers
|
||||
|
@ -152,7 +152,8 @@ namespace Emby.Naming.Video
|
|||
.Where(i => i.ExtraType == ExtraType.Trailer)
|
||||
.ToList();
|
||||
|
||||
list[0].Extras.AddRange(trailers);
|
||||
trailers.AddRange(list[0].Extras);
|
||||
list[0].Extras = trailers;
|
||||
|
||||
remainingFiles = remainingFiles
|
||||
.Except(trailers)
|
||||
|
@ -160,14 +161,13 @@ namespace Emby.Naming.Video
|
|||
}
|
||||
|
||||
// Whatever files are left, just add them
|
||||
list.AddRange(remainingFiles.Select(i => new VideoInfo
|
||||
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
||||
{
|
||||
Files = new List<VideoFileInfo> { i },
|
||||
Name = i.Name,
|
||||
Year = i.Year
|
||||
}));
|
||||
|
||||
return list.OrderBy(i => i.Name);
|
||||
return list;
|
||||
}
|
||||
|
||||
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
|
||||
|
@ -191,9 +191,18 @@ namespace Emby.Naming.Video
|
|||
|
||||
list.Add(ordered[0]);
|
||||
|
||||
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
|
||||
var alternateVersionsLen = ordered.Count - 1;
|
||||
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
||||
for (int i = 0; i < alternateVersionsLen; i++)
|
||||
{
|
||||
alternateVersions[i] = ordered[i + 1].Files[0];
|
||||
}
|
||||
|
||||
list[0].AlternateVersions = alternateVersions;
|
||||
list[0].Name = folderName;
|
||||
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
|
||||
var extras = ordered.Skip(1).SelectMany(i => i.Extras).ToList();
|
||||
extras.AddRange(list[0].Extras);
|
||||
list[0].Extras = extras;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
@ -22,7 +23,7 @@ namespace Emby.Naming.Video
|
|||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>VideoFileInfo.</returns>
|
||||
public VideoFileInfo ResolveDirectory(string path)
|
||||
public VideoFileInfo? ResolveDirectory(string path)
|
||||
{
|
||||
return Resolve(path, true);
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ namespace Emby.Naming.Video
|
|||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>VideoFileInfo.</returns>
|
||||
public VideoFileInfo ResolveFile(string path)
|
||||
public VideoFileInfo? ResolveFile(string path)
|
||||
{
|
||||
return Resolve(path, false);
|
||||
}
|
||||
|
@ -42,10 +43,10 @@ namespace Emby.Naming.Video
|
|||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
|
||||
/// <param name="parseName">Whether or not the name should be parsed for info</param>
|
||||
/// <param name="parseName">Whether or not the name should be parsed for info.</param>
|
||||
/// <returns>VideoFileInfo.</returns>
|
||||
/// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
|
||||
public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
|
||||
public VideoFileInfo? Resolve(string path, bool isDirectory, bool parseName = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
|
@ -53,8 +54,8 @@ namespace Emby.Naming.Video
|
|||
}
|
||||
|
||||
bool isStub = false;
|
||||
string container = null;
|
||||
string stubType = null;
|
||||
string? container = null;
|
||||
string? stubType = null;
|
||||
|
||||
if (!isDirectory)
|
||||
{
|
||||
|
@ -63,17 +64,13 @@ namespace Emby.Naming.Video
|
|||
// Check supported extensions
|
||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var stubResult = StubResolver.ResolveFile(path, _options);
|
||||
|
||||
isStub = stubResult.IsStub;
|
||||
|
||||
// It's not supported. Check stub extensions
|
||||
if (!isStub)
|
||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
stubType = stubResult.StubType;
|
||||
isStub = true;
|
||||
}
|
||||
|
||||
container = extension.TrimStart('.');
|
||||
|
@ -94,9 +91,10 @@ namespace Emby.Naming.Video
|
|||
{
|
||||
var cleanDateTimeResult = CleanDateTime(name);
|
||||
|
||||
if (extraResult.ExtraType == null)
|
||||
if (extraResult.ExtraType == null
|
||||
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
|
||||
{
|
||||
name = CleanString(cleanDateTimeResult.Name).Name;
|
||||
name = newName.ToString();
|
||||
}
|
||||
|
||||
year = cleanDateTimeResult.Year;
|
||||
|
@ -130,14 +128,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Events;
|
||||
|
|
|
@ -599,7 +599,7 @@ namespace Emby.Server.Implementations
|
|||
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
||||
}
|
||||
|
||||
JsonSerializer = new JsonSerializer(FileSystemManager);
|
||||
JsonSerializer = new JsonSerializer();
|
||||
|
||||
if (Plugins != null)
|
||||
{
|
||||
|
@ -1018,7 +1018,7 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
||||
.Select(x => Assembly.LoadFrom(x))
|
||||
.Select(Assembly.LoadFrom)
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
||||
.ToArray();
|
||||
|
@ -1718,29 +1718,6 @@ namespace Emby.Server.Implementations
|
|||
_plugins = list.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns localhost in the case of no external dns, and the hostname if the
|
||||
/// dns is prefixed with a valid Uri prefix.
|
||||
/// </summary>
|
||||
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
|
||||
/// <returns>The hostname in <paramref name="externalDns"/>.</returns>
|
||||
private static string GetHostnameFromExternalDns(string externalDns)
|
||||
{
|
||||
if (string.IsNullOrEmpty(externalDns))
|
||||
{
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new Uri(externalDns).Host;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return externalDns;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void LaunchUrl(string url)
|
||||
{
|
||||
if (!CanLaunchWebBrowser)
|
||||
|
|
|
@ -35,14 +35,6 @@ namespace Emby.Server.Implementations.Channels
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static string GetUserDistinctValue(User user)
|
||||
{
|
||||
var channels = user.Policy.EnabledChannels
|
||||
.OrderBy(i => i);
|
||||
|
||||
return string.Join("|", channels);
|
||||
}
|
||||
|
||||
private void CleanDatabase(CancellationToken cancellationToken)
|
||||
{
|
||||
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
|
||||
|
@ -75,19 +67,23 @@ namespace Emby.Server.Implementations.Channels
|
|||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
}, false);
|
||||
_libraryManager.DeleteItem(
|
||||
item,
|
||||
new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
// Finally, delete the channel itself
|
||||
_libraryManager.DeleteItem(channel, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
}, false);
|
||||
_libraryManager.DeleteItem(
|
||||
channel,
|
||||
new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
},
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,18 +28,28 @@ namespace Emby.Server.Implementations.Channels
|
|||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Refresh Channels";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Refreshes internet channel information.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Internet Channels";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "RefreshInternetChannels";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var manager = (ChannelManager)_channelManager;
|
||||
|
@ -50,18 +60,18 @@ namespace Emby.Server.Implementations.Channels
|
|||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
return new[]
|
||||
{
|
||||
|
||||
// Every so often
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public string Key => "RefreshInternetChannels";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Images;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.AppBase;
|
||||
|
|
|
@ -243,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);
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@
|
|||
<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.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.1" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
public class AutomaticRestartEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ITaskManager _iTaskManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
|
||||
private Timer _timer;
|
||||
|
||||
public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_iTaskManager = iTaskManager;
|
||||
_sessionManager = sessionManager;
|
||||
_config = config;
|
||||
_liveTvManager = liveTvManager;
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
{
|
||||
if (_appHost.CanSelfRestart)
|
||||
{
|
||||
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
|
||||
{
|
||||
DisposeTimer();
|
||||
|
||||
if (_appHost.HasPendingRestart)
|
||||
{
|
||||
_timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
|
||||
}
|
||||
}
|
||||
|
||||
private async void TimerCallback(object state)
|
||||
{
|
||||
if (_config.Configuration.EnableAutomaticRestart)
|
||||
{
|
||||
var isIdle = await IsIdle().ConfigureAwait(false);
|
||||
|
||||
if (isIdle)
|
||||
{
|
||||
DisposeTimer();
|
||||
|
||||
_logger.LogInformation("Automatically restarting the system because it is idle and a restart is required.");
|
||||
|
||||
try
|
||||
{
|
||||
_appHost.Restart();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error restarting server");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsIdle()
|
||||
{
|
||||
if (_iTaskManager.ScheduledTasks.Any(i => i.State != TaskState.Idle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_liveTvManager.Services.Count == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var timers = await _liveTvManager.GetTimers(new TimerQuery(), CancellationToken.None).ConfigureAwait(false);
|
||||
if (timers.Items.Any(i => i.Status == RecordingStatus.InProgress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting timers");
|
||||
}
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return !_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 30);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
|
||||
|
||||
DisposeTimer();
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ using MediaBrowser.Controller.Providers;
|
|||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
SetRangeValues();
|
||||
}
|
||||
|
||||
FileShare = FileShareMode.Read;
|
||||
FileShare = FileShare.Read;
|
||||
Cookies = new List<Cookie>();
|
||||
}
|
||||
|
||||
|
@ -94,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.
|
||||
|
@ -222,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)
|
||||
{
|
||||
|
@ -245,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -440,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))
|
||||
{
|
||||
|
@ -464,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");
|
||||
}
|
||||
|
@ -492,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,
|
||||
|
|
|
@ -365,87 +365,6 @@ 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 != OperatingSystemId.Windows)
|
||||
|
|
|
@ -36,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;
|
||||
|
@ -54,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>
|
||||
|
@ -708,10 +710,10 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the root media folder
|
||||
/// Creates the root media folder.
|
||||
/// </summary>
|
||||
/// <returns>AggregateFolder.</returns>
|
||||
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded</exception>
|
||||
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
|
||||
public AggregateFolder CreateRootFolder()
|
||||
{
|
||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
|
||||
|
@ -822,7 +824,6 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
// If this returns multiple items it could be tricky figuring out which one is correct.
|
||||
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
|
@ -842,7 +843,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Person
|
||||
/// Gets the person.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>Task{Person}.</returns>
|
||||
|
@ -852,7 +853,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Studio
|
||||
/// Gets the studio.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>Task{Studio}.</returns>
|
||||
|
@ -877,7 +878,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Genre
|
||||
/// Gets the genre.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>Task{Genre}.</returns>
|
||||
|
@ -887,7 +888,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the genre.
|
||||
/// Gets the music genre.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>Task{MusicGenre}.</returns>
|
||||
|
@ -897,7 +898,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Year
|
||||
/// Gets the year.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Task{Year}.</returns>
|
||||
|
@ -1074,9 +1075,9 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96));
|
||||
|
||||
// Now validate the entire media library
|
||||
// Validate the entire media library
|
||||
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
|
||||
|
||||
progress.Report(96);
|
||||
|
@ -1085,7 +1086,6 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04)));
|
||||
|
||||
// Run post-scan tasks
|
||||
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
progress.Report(100);
|
||||
|
@ -1136,7 +1136,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error running postscan task");
|
||||
_logger.LogError(ex, "Error running post-scan task");
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
|
@ -2382,7 +2382,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
public int? GetSeasonNumberFromPath(string path)
|
||||
{
|
||||
return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
|
||||
return SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||
}
|
||||
|
||||
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
||||
|
@ -2508,21 +2508,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;
|
||||
|
@ -2533,11 +2523,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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the supplied file data points to a music album
|
||||
/// Determine if the supplied file data points to a music album.
|
||||
/// </summary>
|
||||
public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
|
||||
{
|
||||
|
@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the supplied resolve args should be considered a music album
|
||||
/// Determine if the supplied resolve args should be considered a music album.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
|
||||
|
@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the supplied list contains what we should consider music
|
||||
/// Determine if the supplied list contains what we should consider music.
|
||||
/// </summary>
|
||||
private bool ContainsMusic(
|
||||
IEnumerable<FileSystemMetadata> list,
|
||||
|
@ -118,6 +118,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
var discSubfolderCount = 0;
|
||||
var notMultiDisc = false;
|
||||
|
||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||
var parser = new AlbumParser(namingOptions);
|
||||
foreach (var fileSystemInfo in list)
|
||||
{
|
||||
if (fileSystemInfo.IsDirectory)
|
||||
|
@ -134,7 +136,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
|
||||
if (hasMusic)
|
||||
{
|
||||
if (IsMultiDiscFolder(path, libraryOptions))
|
||||
if (parser.IsMultiPart(path))
|
||||
{
|
||||
logger.LogDebug("Found multi-disc folder: " + path);
|
||||
discSubfolderCount++;
|
||||
|
@ -165,15 +167,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
|
||||
return discSubfolderCount > 0;
|
||||
}
|
||||
|
||||
private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions)
|
||||
{
|
||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||
|
||||
var parser = new AlbumParser(namingOptions);
|
||||
var result = parser.ParseMultiPart(path);
|
||||
|
||||
return result.IsMultiPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,28 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
/// </summary>
|
||||
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
||||
{
|
||||
private string[] _validCollectionTypes = new[]
|
||||
{
|
||||
CollectionType.Movies,
|
||||
CollectionType.HomeVideos,
|
||||
CollectionType.MusicVideos,
|
||||
CollectionType.Movies,
|
||||
CollectionType.Photos
|
||||
};
|
||||
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
|
||||
: base(libraryManager)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
|
@ -144,7 +166,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
|
||||
foreach (var video in resolverResult)
|
||||
{
|
||||
var firstVideo = video.Files.First();
|
||||
var firstVideo = video.Files[0];
|
||||
|
||||
var videoItem = new T
|
||||
{
|
||||
|
@ -230,7 +252,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
// Owned items will be caught by the plain video resolver
|
||||
if (args.Parent == null)
|
||||
{
|
||||
//return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
||||
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -275,7 +297,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
{
|
||||
item = ResolveVideo<Movie>(args, true);
|
||||
}
|
||||
|
||||
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -319,7 +340,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
{
|
||||
if (item is Movie || item is MusicVideo)
|
||||
{
|
||||
//we need to only look at the name of this actual item (not parents)
|
||||
// We need to only look at the name of this actual item (not parents)
|
||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
|
||||
|
||||
if (!string.IsNullOrEmpty(justName))
|
||||
|
@ -347,9 +368,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a movie based on a child file system entries
|
||||
/// Finds a movie based on a child file system entries.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>Movie.</returns>
|
||||
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
||||
where T : Video, new()
|
||||
|
@ -377,6 +397,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
Set3DFormat(movie);
|
||||
return movie;
|
||||
}
|
||||
|
||||
if (IsBluRayDirectory(child.FullName, filename, directoryService))
|
||||
{
|
||||
var movie = new T
|
||||
|
@ -407,9 +428,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
}
|
||||
|
||||
// TODO: Allow GetMultiDiscMovie in here
|
||||
const bool supportsMultiVersion = true;
|
||||
const bool SupportsMultiVersion = true;
|
||||
|
||||
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
||||
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
|
||||
new MultiItemResolverResult();
|
||||
|
||||
if (result.Items.Count == 1)
|
||||
|
@ -437,7 +458,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
/// <summary>
|
||||
/// Gets the multi disc movie.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="multiDiscFolders">The folders.</param>
|
||||
/// <param name="directoryService">The directory service.</param>
|
||||
/// <returns>``0.</returns>
|
||||
|
@ -451,7 +471,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
var subFileEntries = directoryService.GetFileSystemEntries(i);
|
||||
|
||||
var subfolders = subFileEntries
|
||||
.Where(e => e.IsDirectory)
|
||||
.Where(e => e.IsDirectory)
|
||||
.ToList();
|
||||
|
||||
if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
|
||||
|
@ -459,6 +479,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
videoTypes.Add(VideoType.Dvd);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
|
||||
{
|
||||
videoTypes.Add(VideoType.BluRay);
|
||||
|
@ -476,7 +497,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
}
|
||||
|
||||
return false;
|
||||
|
||||
}).OrderBy(i => i).ToList();
|
||||
|
||||
// If different video types were found, don't allow this
|
||||
|
@ -491,11 +511,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
}
|
||||
|
||||
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
||||
var resolver = new StackResolver(namingOptions);
|
||||
|
||||
var result = resolver.ResolveDirectories(folderPaths);
|
||||
var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
|
||||
|
||||
if (result.Stacks.Count != 1)
|
||||
if (result.Count != 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -508,7 +527,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
|
||||
VideoType = videoTypes[0],
|
||||
|
||||
Name = result.Stacks[0].Name
|
||||
Name = result[0].Name
|
||||
};
|
||||
|
||||
SetIsoType(returnVideo);
|
||||
|
@ -516,15 +535,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return returnVideo;
|
||||
}
|
||||
|
||||
private string[] ValidCollectionTypes = new[]
|
||||
{
|
||||
CollectionType.Movies,
|
||||
CollectionType.HomeVideos,
|
||||
CollectionType.MusicVideos,
|
||||
CollectionType.Movies,
|
||||
CollectionType.Photos
|
||||
};
|
||||
|
||||
private bool IsInvalid(Folder parent, string collectionType)
|
||||
{
|
||||
if (parent != null)
|
||||
|
@ -540,20 +550,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return false;
|
||||
}
|
||||
|
||||
return !ValidCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private IImageProcessor _imageProcessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
|
||||
: base(libraryManager)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,12 @@ using Microsoft.Extensions.Logging;
|
|||
namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SeasonResolver
|
||||
/// Class SeasonResolver.
|
||||
/// </summary>
|
||||
public class SeasonResolver : FolderResolver<Season>
|
||||
{
|
||||
/// <summary>
|
||||
/// The _config
|
||||
/// </summary>
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
@ -45,14 +40,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
/// <returns>Season.</returns>
|
||||
protected override Season Resolve(ItemResolveArgs args)
|
||||
{
|
||||
if (args.Parent is Series && args.IsDirectory)
|
||||
if (args.Parent is Series series && args.IsDirectory)
|
||||
{
|
||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||
var series = ((Series)args.Parent);
|
||||
|
||||
var path = args.Path;
|
||||
|
||||
var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
|
||||
var seasonParserResult = SeasonPathParser.Parse(path, true, true);
|
||||
|
||||
var season = new Season
|
||||
{
|
||||
|
@ -74,7 +68,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
{
|
||||
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
|
||||
{
|
||||
_logger.LogDebug("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
|
||||
_logger.LogDebug(
|
||||
"Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
|
||||
path,
|
||||
episodeInfo.SeasonNumber.Value,
|
||||
episodeInfo.EpisodeNumber.Value);
|
||||
|
@ -90,7 +85,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
|
||||
season.Name = seasonNumber == 0 ?
|
||||
args.LibraryOptions.SeasonZeroDisplayName :
|
||||
string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage);
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("NameSeasonNumber"),
|
||||
seasonNumber,
|
||||
args.GetLibraryOptions().PreferredMetadataLanguage);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
|
||||
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
|
||||
{
|
||||
var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
|
||||
var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber;
|
||||
|
||||
return seasonNumber.HasValue;
|
||||
}
|
||||
|
|
|
@ -291,10 +291,11 @@ namespace Emby.Server.Implementations.Library
|
|||
&& authenticationProvider != null
|
||||
&& !(authenticationProvider is DefaultAuthenticationProvider))
|
||||
{
|
||||
// We should trust the user that the authprovider says, not what was typed
|
||||
// Trust the username returned by the authentication provider
|
||||
username = updatedUsername;
|
||||
|
||||
// Search the database for the user again; the authprovider might have created it
|
||||
// Search the database for the user again
|
||||
// the authentication provider might have created it
|
||||
user = Users
|
||||
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
|
@ -667,7 +668,7 @@ namespace Emby.Server.Implementations.Library
|
|||
throw new ArgumentException("Invalid username", nameof(newName));
|
||||
}
|
||||
|
||||
if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
||||
if (user.Name.Equals(newName, StringComparison.Ordinal))
|
||||
{
|
||||
throw new ArgumentException("The new and old names must be different.");
|
||||
}
|
||||
|
|
|
@ -15,14 +15,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
|
||||
public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, IStreamHelper streamHelper)
|
||||
public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_fileSystem = fileSystem;
|
||||
_streamHelper = streamHelper;
|
||||
}
|
||||
|
||||
|
@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
onStarted();
|
||||
|
||||
|
@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
onStarted();
|
||||
|
||||
|
|
|
@ -427,7 +427,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
foreach (NameValuePair mapping in mappings)
|
||||
{
|
||||
if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId))
|
||||
if (string.Equals(mapping.Name, channelId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return mapping.Value;
|
||||
}
|
||||
|
@ -1664,10 +1664,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||
{
|
||||
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
||||
}
|
||||
|
||||
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
|
||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||
}
|
||||
|
||||
private void OnSuccessfulRecording(TimerInfo timer, string path)
|
||||
|
@ -1888,7 +1888,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return;
|
||||
}
|
||||
|
||||
using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
|
@ -1952,7 +1952,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return;
|
||||
}
|
||||
|
||||
using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -14,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
|||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -24,7 +22,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
public class EncodedRecorder : IRecorder
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private bool _hasExited;
|
||||
|
@ -38,7 +35,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
public EncodedRecorder(
|
||||
ILogger logger,
|
||||
IFileSystem fileSystem,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IServerApplicationPaths appPaths,
|
||||
IJsonSerializer json,
|
||||
|
@ -46,7 +42,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
|
@ -107,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
||||
|
||||
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
||||
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
|
||||
_logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||
|
||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite,
|
||||
StreamDefaults.DefaultFileStreamBufferSize,
|
||||
IODefaults.FileStreamBufferSize,
|
||||
allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
|
||||
|
||||
public Task DeleteTempFiles()
|
||||
|
@ -199,7 +199,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
await StreamHelper.CopyToAsync(
|
||||
inputStream,
|
||||
stream,
|
||||
StreamDefaults.DefaultCopyToBufferSize,
|
||||
IODefaults.CopyToBufferSize,
|
||||
emptyReadLimit,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ using MediaBrowser.Common.Extensions;
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
|
|
@ -127,12 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
|
||||
using (response)
|
||||
using (var stream = response.Content)
|
||||
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
||||
using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
await StreamHelper.CopyToAsync(
|
||||
stream,
|
||||
fileStream,
|
||||
StreamDefaults.DefaultCopyToBufferSize,
|
||||
IODefaults.CopyToBufferSize,
|
||||
() => Resolve(openTaskCompletionSource),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"Channels": "القنوات",
|
||||
"ChapterNameValue": "الباب {0}",
|
||||
"Collections": "مجموعات",
|
||||
"DeviceOfflineWithName": "تم قطع الاتصال بـ{0}",
|
||||
"DeviceOfflineWithName": "تم قطع اتصال {0}",
|
||||
"DeviceOnlineWithName": "{0} متصل",
|
||||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||
"Favorites": "التفضيلات",
|
||||
|
@ -75,8 +75,8 @@
|
|||
"Songs": "الأغاني",
|
||||
"StartupEmbyServerIsLoading": "سيرفر Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.",
|
||||
"SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}",
|
||||
"SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} لـ {1}",
|
||||
"SubtitlesDownloadedForItem": "تم تحميل الترجمات لـ {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} الى {1}",
|
||||
"SubtitlesDownloadedForItem": "تم تحميل الترجمات الى {0}",
|
||||
"Sync": "مزامنة",
|
||||
"System": "النظام",
|
||||
"TvShows": "البرامج التلفزيونية",
|
||||
|
@ -88,7 +88,7 @@
|
|||
"UserOfflineFromDevice": "تم قطع اتصال {0} من {1}",
|
||||
"UserOnlineFromDevice": "{0} متصل عبر {1}",
|
||||
"UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}",
|
||||
"UserPolicyUpdatedWithName": "سياسة المستخدمين تم تحديثها لـ {0}",
|
||||
"UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}",
|
||||
"UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
|
||||
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} تم اضافتها الى مكتبة الوسائط",
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
||||
"Application": "Aplicació",
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
|
||||
"AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament",
|
||||
"Books": "Llibres",
|
||||
"CameraImageUploadedFrom": "Una nova imatge de càmera ha sigut pujada des de {0}",
|
||||
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}",
|
||||
"Channels": "Canals",
|
||||
"ChapterNameValue": "Episodi {0}",
|
||||
"Collections": "Col·leccions",
|
||||
|
|
95
Emby.Server.Implementations/Localization/Core/fil.json
Normal file
95
Emby.Server.Implementations/Localization/Core/fil.json
Normal file
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"VersionNumber": "Bersyon {0}",
|
||||
"ValueSpecialEpisodeName": "Espesyal - {0}",
|
||||
"ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong media library",
|
||||
"UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}",
|
||||
"UserStartedPlayingItemWithValues": "Si {0} ay nagplaplay ng {1} sa {2}",
|
||||
"UserPolicyUpdatedWithName": "Ang user policy ay naiupdate para kay {0}",
|
||||
"UserPasswordChangedWithName": "Napalitan na ang password ni {0}",
|
||||
"UserOnlineFromDevice": "Si {0} ay nakakonekta galing sa {1}",
|
||||
"UserOfflineFromDevice": "Si {0} ay nadiskonekta galing sa {1}",
|
||||
"UserLockedOutWithName": "Si {0} ay nalock out",
|
||||
"UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}",
|
||||
"UserDeletedWithName": "Natanggal na is user {0}",
|
||||
"UserCreatedWithName": "Nagawa na si user {0}",
|
||||
"User": "User",
|
||||
"TvShows": "Pelikula",
|
||||
"System": "Sistema",
|
||||
"Sync": "Pag-sync",
|
||||
"SubtitlesDownloadedForItem": "Naidownload na ang subtitles {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Hindi naidownload ang subtitles {0} para sa {1}",
|
||||
"StartupEmbyServerIsLoading": "Nagloload ang Jellyfin Server. Sandaling maghintay.",
|
||||
"Songs": "Kanta",
|
||||
"Shows": "Pelikula",
|
||||
"ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}",
|
||||
"ScheduledTaskStartedWithName": "Nagsimula na ang {0}",
|
||||
"ScheduledTaskFailedWithName": "Hindi gumana and {0}",
|
||||
"ProviderValue": "Ang provider ay {0}",
|
||||
"PluginUpdatedWithName": "Naiupdate na ang {0}",
|
||||
"PluginUninstalledWithName": "Naiuninstall na ang {0}",
|
||||
"PluginInstalledWithName": "Nainstall na ang {0}",
|
||||
"Plugin": "Plugin",
|
||||
"Playlists": "Playlists",
|
||||
"Photos": "Larawan",
|
||||
"NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula",
|
||||
"NotificationOptionVideoPlayback": "Nagsimula na ang pelikula",
|
||||
"NotificationOptionUserLockedOut": "Nakalock out ang user",
|
||||
"NotificationOptionTaskFailed": "Hindi gumana ang scheduled task",
|
||||
"NotificationOptionServerRestartRequired": "Kailangan irestart ang server",
|
||||
"NotificationOptionPluginUpdateInstalled": "Naiupdate na ang plugin",
|
||||
"NotificationOptionPluginUninstalled": "Naiuninstall na ang plugin",
|
||||
"NotificationOptionPluginInstalled": "Nainstall na ang plugin",
|
||||
"NotificationOptionPluginError": "Hindi gumagana ang plugin",
|
||||
"NotificationOptionNewLibraryContent": "May bagong content na naidagdag",
|
||||
"NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti",
|
||||
"NotificationOptionCameraImageUploaded": "Naiupload na ang picture",
|
||||
"NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog",
|
||||
"NotificationOptionAudioPlayback": "Nagsimula na ang patugtog",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon",
|
||||
"NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon",
|
||||
"NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede idownload.",
|
||||
"NameSeasonUnknown": "Hindi alam ang season",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameInstallFailed": "Hindi nainstall ang {0}",
|
||||
"MusicVideos": "Music video",
|
||||
"Music": "Kanta",
|
||||
"Movies": "Pelikula",
|
||||
"MixedContent": "Halo-halong content",
|
||||
"MessageServerConfigurationUpdated": "Naiupdate na ang server configuration",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}",
|
||||
"MessageApplicationUpdatedTo": "Ang Jellyfin Server ay naiupdate to {0}",
|
||||
"MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server",
|
||||
"Latest": "Pinakabago",
|
||||
"LabelRunningTimeValue": "Oras: {0}",
|
||||
"LabelIpAddressValue": "Ang IP Address ay {0}",
|
||||
"ItemRemovedWithName": "Naitanggal ang {0} sa library",
|
||||
"ItemAddedWithName": "Naidagdag ang {0} sa library",
|
||||
"Inherit": "Manahin",
|
||||
"HeaderRecordingGroups": "Pagtatalang Grupo",
|
||||
"HeaderNextUp": "Susunod",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderFavoriteSongs": "Paboritong Kanta",
|
||||
"HeaderFavoriteShows": "Paboritong Pelikula",
|
||||
"HeaderFavoriteEpisodes": "Paboritong Episodes",
|
||||
"HeaderFavoriteArtists": "Paboritong Artista",
|
||||
"HeaderFavoriteAlbums": "Paboritong Albums",
|
||||
"HeaderContinueWatching": "Ituloy Manood",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderAlbumArtists": "Artista ng Album",
|
||||
"Genres": "Kategorya",
|
||||
"Folders": "Folders",
|
||||
"Favorites": "Paborito",
|
||||
"FailedLoginAttemptWithUserName": "maling login galing {0}",
|
||||
"DeviceOnlineWithName": "nakakonekta si {0}",
|
||||
"DeviceOfflineWithName": "nadiskonekta si {0}",
|
||||
"Collections": "Koleksyon",
|
||||
"ChapterNameValue": "Kabanata {0}",
|
||||
"Channels": "Channel",
|
||||
"CameraImageUploadedFrom": "May bagong larawan na naupload galing {0}",
|
||||
"Books": "Libro",
|
||||
"AuthenticationSucceededWithUserName": "{0} na patunayan",
|
||||
"Artists": "Artista",
|
||||
"Application": "Aplikasyon",
|
||||
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
||||
"Albums": "Albums"
|
||||
}
|
1
Emby.Server.Implementations/Localization/Core/gl.json
Normal file
1
Emby.Server.Implementations/Localization/Core/gl.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -6,7 +6,7 @@
|
|||
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
|
||||
"Latest": "Terbaru",
|
||||
"LabelIpAddressValue": "IP address: {0}",
|
||||
"LabelIpAddressValue": "Alamat IP: {0}",
|
||||
"ItemRemovedWithName": "{0} sudah dikeluarkan dari perpustakaan",
|
||||
"ItemAddedWithName": "{0} sudah dimasukkan ke dalam perpustakaan",
|
||||
"Inherit": "Warisan",
|
||||
|
@ -28,5 +28,63 @@
|
|||
"Collections": "Koleksi",
|
||||
"Books": "Buku",
|
||||
"Artists": "Artis",
|
||||
"Application": "Aplikasi"
|
||||
"Application": "Aplikasi",
|
||||
"ChapterNameValue": "Bagian {0}",
|
||||
"Channels": "Saluran",
|
||||
"TvShows": "Seri TV",
|
||||
"SubtitleDownloadFailureFromForItem": "Talop gagal diunduh dari {0} untuk {1}",
|
||||
"StartupEmbyServerIsLoading": "Peladen Jellyfin sedang dimuat. Silakan coba kembali beberapa saat lagi.",
|
||||
"Songs": "Lagu",
|
||||
"Playlists": "Daftar putar",
|
||||
"NotificationOptionPluginUninstalled": "Plugin dilepas",
|
||||
"MusicVideos": "Video musik",
|
||||
"VersionNumber": "Versi {0}",
|
||||
"ValueSpecialEpisodeName": "Spesial - {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda",
|
||||
"UserStoppedPlayingItemWithValues": "{0} telah selesai memutar {1} pada {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} sedang memutar {1} pada {2}",
|
||||
"UserPolicyUpdatedWithName": "Kebijakan pengguna telah diperbarui untuk {0}",
|
||||
"UserPasswordChangedWithName": "Kata sandi telah diubah untuk pengguna {0}",
|
||||
"UserOnlineFromDevice": "{0} sedang daring dari {1}",
|
||||
"UserOfflineFromDevice": "{0} telah terputus dari {1}",
|
||||
"UserLockedOutWithName": "Pengguna {0} telah dikunci",
|
||||
"UserDownloadingItemWithValues": "{0} sedang mengunduh {1}",
|
||||
"UserDeletedWithName": "Pengguna {0} telah dihapus",
|
||||
"UserCreatedWithName": "Pengguna {0} telah dibuat",
|
||||
"User": "Pengguna",
|
||||
"System": "Sistem",
|
||||
"Sync": "Sinkron",
|
||||
"SubtitlesDownloadedForItem": "Talop telah diunduh untuk {0}",
|
||||
"Shows": "Tayangan",
|
||||
"ServerNameNeedsToBeRestarted": "{0} perlu dimuat ulang",
|
||||
"ScheduledTaskStartedWithName": "{0} dimulai",
|
||||
"ScheduledTaskFailedWithName": "{0} gagal",
|
||||
"ProviderValue": "Penyedia: {0}",
|
||||
"PluginUpdatedWithName": "{0} telah diperbarui",
|
||||
"PluginInstalledWithName": "{0} telah dipasang",
|
||||
"Plugin": "Plugin",
|
||||
"Photos": "Foto",
|
||||
"NotificationOptionUserLockedOut": "Pengguna terkunci",
|
||||
"NotificationOptionTaskFailed": "Kegagalan tugas terjadwal",
|
||||
"NotificationOptionServerRestartRequired": "Restart peladen dibutuhkan",
|
||||
"NotificationOptionPluginUpdateInstalled": "Pembaruan plugin terpasang",
|
||||
"NotificationOptionPluginInstalled": "Plugin terpasang",
|
||||
"NotificationOptionPluginError": "Kegagalan plugin",
|
||||
"NotificationOptionNewLibraryContent": "Konten baru ditambahkan",
|
||||
"NotificationOptionInstallationFailed": "Kegagalan pemasangan",
|
||||
"NotificationOptionCameraImageUploaded": "Gambar kamera terunggah",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia",
|
||||
"NewVersionIsAvailable": "Sebuah versi baru dari Peladen Jellyfin tersedia untuk diunduh.",
|
||||
"NameSeasonUnknown": "Musim tak diketahui",
|
||||
"NameSeasonNumber": "Musim {0}",
|
||||
"NameInstallFailed": "{0} instalasi gagal",
|
||||
"Music": "Musik",
|
||||
"Movies": "Film",
|
||||
"MessageServerConfigurationUpdated": "Konfigurasi peladen telah diperbarui",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi peladen bagian {0} telah diperbarui",
|
||||
"FailedLoginAttemptWithUserName": "Percobaan login gagal dari {0}",
|
||||
"CameraImageUploadedFrom": "Sebuah gambar baru telah diunggah dari {0}",
|
||||
"DeviceOfflineWithName": "{0} telah terputus",
|
||||
"DeviceOnlineWithName": "{0} telah terhubung"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"Channels": "Canali",
|
||||
"ChapterNameValue": "Capitolo {0}",
|
||||
"Collections": "Collezioni",
|
||||
"DeviceOfflineWithName": "{0} ha disconnesso",
|
||||
"DeviceOfflineWithName": "{0} si è disconnesso",
|
||||
"DeviceOnlineWithName": "{0} è connesso",
|
||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||
"Favorites": "Preferiti",
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
"ItemRemovedWithName": "{0} a fost eliminat din bibliotecă",
|
||||
"ItemAddedWithName": "{0} a fost adăugat în bibliotecă",
|
||||
"Inherit": "Moștenit",
|
||||
"HomeVideos": "Videoclipuri personale",
|
||||
"HomeVideos": "Filme personale",
|
||||
"HeaderRecordingGroups": "Grupuri de înregistrare",
|
||||
"HeaderLiveTV": "TV în Direct",
|
||||
"HeaderFavoriteSongs": "Melodii Favorite",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"DeviceOfflineWithName": "{0} je prekinil povezavo",
|
||||
"DeviceOnlineWithName": "{0} je povezan",
|
||||
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
|
||||
"Favorites": "Priljubljeni",
|
||||
"Favorites": "Priljubljeno",
|
||||
"Folders": "Mape",
|
||||
"Genres": "Zvrsti",
|
||||
"HeaderAlbumArtists": "Izvajalci albuma",
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
"SubtitlesDownloadedForItem": "已为 {0} 下载了字幕",
|
||||
"Sync": "同步",
|
||||
"System": "系统",
|
||||
"TvShows": "电视节目",
|
||||
"TvShows": "电视剧",
|
||||
"User": "用户",
|
||||
"UserCreatedWithName": "用户 {0} 已创建",
|
||||
"UserDeletedWithName": "用户 {0} 已删除",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Emby.Server.Implementations.Net
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Images;
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the task to be executed
|
||||
/// Returns the task to be executed.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
|
@ -89,7 +89,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
SourceTypes = new SourceType[] { SourceType.Library },
|
||||
HasChapterImages = false,
|
||||
IsVirtualItem = false
|
||||
|
||||
})
|
||||
.OfType<Video>()
|
||||
.ToList();
|
||||
|
@ -160,7 +159,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
}
|
||||
}
|
||||
|
||||
public string Name => "Chapter image extraction";
|
||||
public string Name => "Extract Chapter Images";
|
||||
|
||||
public string Description => "Creates thumbnails for videos that have chapters.";
|
||||
|
||||
|
|
|
@ -158,9 +158,9 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
}
|
||||
}
|
||||
|
||||
public string Name => "Cache file cleanup";
|
||||
public string Name => "Clean Cache Directory";
|
||||
|
||||
public string Description => "Deletes cache files no longer needed by the system";
|
||||
public string Description => "Deletes cache files no longer needed by the system.";
|
||||
|
||||
public string Category => "Maintenance";
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ using MediaBrowser.Model.Tasks;
|
|||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes old log files
|
||||
/// Deletes old log files.
|
||||
/// </summary>
|
||||
public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
|
@ -33,20 +33,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// Creates the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
// Every so often
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the task to be executed
|
||||
/// Returns the task to be executed.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
|
@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string Name => "Log file cleanup";
|
||||
public string Name => "Clean Log Directory";
|
||||
|
||||
public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
|
||||
|
||||
|
|
|
@ -125,9 +125,9 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
}
|
||||
}
|
||||
|
||||
public string Name => "Transcode file cleanup";
|
||||
public string Name => "Clean Transcode Directory";
|
||||
|
||||
public string Description => "Deletes transcode files more than 24 hours old.";
|
||||
public string Description => "Deletes transcode files more than one day old.";
|
||||
|
||||
public string Category => "Maintenance";
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@ using MediaBrowser.Model.Tasks;
|
|||
namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PeopleValidationTask
|
||||
/// Class PeopleValidationTask.
|
||||
/// </summary>
|
||||
public class PeopleValidationTask : IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
|
@ -32,13 +32,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// Creates the triggers that define when the task will run.
|
||||
/// </summary>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
// Every so often
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerInterval,
|
||||
|
@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the task to be executed
|
||||
/// Returns the task to be executed.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
|
@ -58,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
return _libraryManager.ValidatePeople(cancellationToken, progress);
|
||||
}
|
||||
|
||||
public string Name => "Refresh people";
|
||||
public string Name => "Refresh People";
|
||||
|
||||
public string Description => "Updates metadata for actors and directors in your media library.";
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Check for plugin updates";
|
||||
public string Name => "Update Plugins";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name => "Scan media library";
|
||||
public string Name => "Scan Media Library";
|
||||
|
||||
public string Description => "Scans your media library for new files and refreshes metadata.";
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.Serialization
|
||||
|
@ -12,13 +11,15 @@ namespace Emby.Server.Implementations.Serialization
|
|||
/// </summary>
|
||||
public class JsonSerializer : IJsonSerializer
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public JsonSerializer(
|
||||
IFileSystem fileSystem)
|
||||
public JsonSerializer()
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
Configure();
|
||||
ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
|
||||
ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
|
||||
ServiceStack.Text.JsConfig.IncludeNullValues = false;
|
||||
ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
|
||||
ServiceStack.Text.JsConfig.AssumeUtc = true;
|
||||
|
||||
ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Serialization
|
|||
throw new ArgumentNullException(nameof(file));
|
||||
}
|
||||
|
||||
using (var stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
SerializeToStream(obj, stream);
|
||||
}
|
||||
|
@ -162,7 +163,6 @@ namespace Emby.Server.Implementations.Serialization
|
|||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
|
||||
return ServiceStack.Text.JsonSerializer.DeserializeFromStreamAsync<T>(stream);
|
||||
}
|
||||
|
||||
|
@ -225,20 +225,6 @@ namespace Emby.Server.Implementations.Serialization
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures this instance.
|
||||
/// </summary>
|
||||
private void Configure()
|
||||
{
|
||||
ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
|
||||
ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
|
||||
ServiceStack.Text.JsConfig.IncludeNullValues = false;
|
||||
ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
|
||||
ServiceStack.Text.JsConfig.AssumeUtc = true;
|
||||
|
||||
ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
|
||||
}
|
||||
|
||||
private static string SerializeGuid(Guid guid)
|
||||
{
|
||||
if (guid.Equals(Guid.Empty))
|
||||
|
|
|
@ -1704,7 +1704,7 @@ namespace Emby.Server.Implementations.Session
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error getting {0} image info", ex, type);
|
||||
_logger.LogError(ex, "Error getting image information for {Type}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user