mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 04:05:50 -04:00
Merge remote-tracking branch 'upstream/master' into register-services-correctly
This commit is contained in:
commit
f815059698
14
.vscode/extensions.json
vendored
Normal file
14
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
|
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||||
|
|
||||||
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
|
"recommendations": [
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"editorconfig.editorconfig"
|
||||||
|
],
|
||||||
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
- [cvium](https://github.com/cvium)
|
- [cvium](https://github.com/cvium)
|
||||||
- [dannymichel](https://github.com/dannymichel)
|
- [dannymichel](https://github.com/dannymichel)
|
||||||
- [DaveChild](https://github.com/DaveChild)
|
- [DaveChild](https://github.com/DaveChild)
|
||||||
|
- [Delgan](https://github.com/Delgan)
|
||||||
- [dcrdev](https://github.com/dcrdev)
|
- [dcrdev](https://github.com/dcrdev)
|
||||||
- [dhartung](https://github.com/dhartung)
|
- [dhartung](https://github.com/dhartung)
|
||||||
- [dinki](https://github.com/dinki)
|
- [dinki](https://github.com/dinki)
|
||||||
|
28
Dockerfile
28
Dockerfile
@ -1,5 +1,4 @@
|
|||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=3.1
|
||||||
ARG FFMPEG_VERSION=latest
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
@ -17,7 +16,6 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
@ -27,32 +25,26 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
|||||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||||
|
|
||||||
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
# Install dependencies:
|
# Install dependencies:
|
||||||
# libfontconfig1: needed for Skia
|
# mesa-va-drivers: needed for AMD VAAPI
|
||||||
# libgomp1: needed for ffmpeg
|
|
||||||
# libva-drm2: needed for ffmpeg
|
|
||||||
# mesa-va-drivers: needed for VAAPI
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
|
||||||
|
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
|
||||||
|
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||||
|
&& apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
libfontconfig1 \
|
|
||||||
libgomp1 \
|
|
||||||
libva-drm2 \
|
|
||||||
mesa-va-drivers \
|
mesa-va-drivers \
|
||||||
|
jellyfin-ffmpeg \
|
||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
|
||||||
vainfo \
|
|
||||||
i965-va-driver \
|
|
||||||
locales \
|
locales \
|
||||||
&& apt-get clean autoclean -y\
|
&& apt-get remove gnupg wget apt-transport-https -y \
|
||||||
&& apt-get autoremove -y\
|
&& apt-get clean autoclean -y \
|
||||||
|
&& apt-get autoremove -y \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media \
|
&& chmod 777 /cache /config /media \
|
||||||
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
|
|
||||||
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin \
|
|
||||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
@ -65,4 +57,4 @@ VOLUME /cache /config /media
|
|||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
"--ffmpeg", "/usr/local/bin/ffmpeg"]
|
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System.Buffers.Binary;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib
|
namespace DvdLib
|
||||||
@ -12,19 +12,12 @@ namespace DvdLib
|
|||||||
|
|
||||||
public override ushort ReadUInt16()
|
public override ushort ReadUInt16()
|
||||||
{
|
{
|
||||||
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
|
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override uint ReadUInt32()
|
public override uint ReadUInt32()
|
||||||
{
|
{
|
||||||
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
|
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] ReadAndReverseBytes(int count)
|
|
||||||
{
|
|
||||||
byte[] val = base.ReadBytes(count);
|
|
||||||
Array.Reverse(val, 0, count);
|
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,6 @@
|
|||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
@ -13,13 +12,10 @@ namespace DvdLib.Ifo
|
|||||||
|
|
||||||
private ushort _titleCount;
|
private ushort _titleCount;
|
||||||
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
||||||
private readonly IFileSystem _fileSystem;
|
public Dvd(string path)
|
||||||
|
|
||||||
public Dvd(string path, IFileSystem fileSystem)
|
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
|
||||||
Titles = new List<Title>();
|
Titles = new List<Title>();
|
||||||
var allFiles = _fileSystem.GetFiles(path, true).ToList();
|
var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
|
||||||
|
|
||||||
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
||||||
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
||||||
@ -76,7 +72,7 @@ namespace DvdLib.Ifo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
|
||||||
{
|
{
|
||||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||||
|
|
||||||
|
@ -1018,19 +1018,58 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
|
// For audio tracks without art use album art if available.
|
||||||
|
if (item is Audio audioItem)
|
||||||
if (item != null)
|
|
||||||
{
|
{
|
||||||
if (item.HasImage(ImageType.Primary))
|
var album = audioItem.AlbumEntity;
|
||||||
{
|
return album != null && album.HasImage(ImageType.Primary)
|
||||||
return GetImageInfo(item, ImageType.Primary);
|
? GetImageInfo(album, ImageType.Primary)
|
||||||
}
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
|
||||||
|
if (item is MusicAlbum || item is Playlist)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
|
||||||
|
var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
|
||||||
|
if (parentWithImage != null)
|
||||||
|
{
|
||||||
|
return GetImageInfo(parentWithImage, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = item.GetParent();
|
||||||
|
if (parent is UserRootFolder)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminate in case we went past user root folder (unlikely?)
|
||||||
|
if (parent is Folder folder && folder.IsRoot)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFirstParentWithImageBelowUserRoot(parent);
|
||||||
|
}
|
||||||
|
|
||||||
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
||||||
{
|
{
|
||||||
var imageInfo = item.GetImageInfo(type, 0);
|
var imageInfo = item.GetImageInfo(type, 0);
|
||||||
|
@ -136,7 +136,8 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
|
||||||
|
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -27,6 +25,9 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point for the activity logger.
|
||||||
|
/// </summary>
|
||||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@ -42,16 +43,15 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="sessionManager"></param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
/// <param name="deviceManager"></param>
|
/// <param name="deviceManager">The device manager.</param>
|
||||||
/// <param name="taskManager"></param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="activityManager"></param>
|
/// <param name="activityManager">The activity manager.</param>
|
||||||
/// <param name="localization"></param>
|
/// <param name="localization">The localization manager.</param>
|
||||||
/// <param name="installationManager"></param>
|
/// <param name="installationManager">The installation manager.</param>
|
||||||
/// <param name="subManager"></param>
|
/// <param name="subManager">The subtitle manager.</param>
|
||||||
/// <param name="userManager"></param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="appHost"></param>
|
|
||||||
public ActivityLogEntryPoint(
|
public ActivityLogEntryPoint(
|
||||||
ILogger<ActivityLogEntryPoint> logger,
|
ILogger<ActivityLogEntryPoint> logger,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||||
@ -168,7 +169,12 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
Name = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||||
|
user.Name,
|
||||||
|
GetItemName(item),
|
||||||
|
e.DeviceName),
|
||||||
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
||||||
UserId = user.Id
|
UserId = user.Id
|
||||||
});
|
});
|
||||||
@ -416,7 +422,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
|
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
@ -428,8 +434,8 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.Item2.versionStr),
|
e.Argument.Item2.version),
|
||||||
Overview = e.Argument.Item2.description
|
Overview = e.Argument.Item2.changelog
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,7 +451,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
{
|
{
|
||||||
@ -457,7 +463,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.versionStr)
|
e.Argument.version)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,8 +491,8 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
var result = e.Result;
|
var result = e.Result;
|
||||||
var task = e.Task;
|
var task = e.Task;
|
||||||
|
|
||||||
var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
|
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
|
||||||
if (activityTask != null && !activityTask.IsLogged)
|
&& !activityTask.IsLogged)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -560,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a user-friendly string for this TimeSpan instance.
|
/// Constructs a user-friendly string for this TimeSpan instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ToUserFriendlyString(TimeSpan span)
|
private static string ToUserFriendlyString(TimeSpan span)
|
||||||
{
|
{
|
||||||
const int DaysInYear = 365;
|
const int DaysInYear = 365;
|
||||||
const int DaysInMonth = 30;
|
const int DaysInMonth = 30;
|
||||||
|
@ -11,22 +11,17 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
public class ActivityManager : IActivityManager
|
public class ActivityManager : IActivityManager
|
||||||
{
|
{
|
||||||
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
|
||||||
|
|
||||||
private readonly IActivityRepository _repo;
|
private readonly IActivityRepository _repo;
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
public ActivityManager(
|
public ActivityManager(IActivityRepository repo, IUserManager userManager)
|
||||||
ILogger<ActivityManager> logger,
|
|
||||||
IActivityRepository repo,
|
|
||||||
IUserManager userManager)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
entry.Date = DateTime.UtcNow;
|
entry.Date = DateTime.UtcNow;
|
||||||
|
@ -17,7 +17,8 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
||||||
{
|
{
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
@ -76,8 +77,6 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
public void Create(ActivityLogEntry entry)
|
||||||
{
|
{
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
@ -87,32 +86,34 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(
|
||||||
{
|
db =>
|
||||||
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
|
|
||||||
{
|
{
|
||||||
statement.TryBind("@Name", entry.Name);
|
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
|
||||||
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
{
|
||||||
statement.TryBindNull("@UserId");
|
statement.TryBind("@Name", entry.Name);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
statement.TryBind("@Overview", entry.Overview);
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||||
|
statement.TryBind("@Type", entry.Type);
|
||||||
|
statement.TryBind("@ItemId", entry.ItemId);
|
||||||
|
|
||||||
statement.MoveNext();
|
if (entry.UserId.Equals(Guid.Empty))
|
||||||
}
|
{
|
||||||
}, TransactionMode);
|
statement.TryBindNull("@UserId");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
|
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||||
|
|
||||||
|
statement.MoveNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,33 +126,35 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(
|
||||||
{
|
db =>
|
||||||
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
|
|
||||||
{
|
{
|
||||||
statement.TryBind("@Id", entry.Id);
|
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
|
||||||
|
|
||||||
statement.TryBind("@Name", entry.Name);
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
{
|
||||||
statement.TryBindNull("@UserId");
|
statement.TryBind("@Id", entry.Id);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
statement.TryBind("@Name", entry.Name);
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
statement.TryBind("@Overview", entry.Overview);
|
||||||
|
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||||
|
statement.TryBind("@Type", entry.Type);
|
||||||
|
statement.TryBind("@ItemId", entry.ItemId);
|
||||||
|
|
||||||
statement.MoveNext();
|
if (entry.UserId.Equals(Guid.Empty))
|
||||||
}
|
{
|
||||||
}, TransactionMode);
|
statement.TryBindNull("@UserId");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
|
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||||
|
|
||||||
|
statement.MoveNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +167,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
{
|
{
|
||||||
whereClauses.Add("DateCreated>=@DateCreated");
|
whereClauses.Add("DateCreated>=@DateCreated");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserId.HasValue)
|
if (hasUserId.HasValue)
|
||||||
{
|
{
|
||||||
if (hasUserId.Value)
|
if (hasUserId.Value)
|
||||||
@ -204,7 +208,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
|
|
||||||
if (limit.HasValue)
|
if (limit.HasValue)
|
||||||
{
|
{
|
||||||
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
|
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
var statementTexts = new[]
|
var statementTexts = new[]
|
||||||
@ -304,7 +308,7 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
index++;
|
index++;
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||||
{
|
{
|
||||||
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
|
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
@ -213,19 +213,6 @@ namespace Emby.Server.Implementations
|
|||||||
/// <value>The configuration manager.</value>
|
/// <value>The configuration manager.</value>
|
||||||
protected IConfigurationManager ConfigurationManager { get; set; }
|
protected IConfigurationManager ConfigurationManager { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public PackageVersionClass SystemUpdateLevel
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
#if BETA
|
|
||||||
return PackageVersionClass.Beta;
|
|
||||||
#else
|
|
||||||
return PackageVersionClass.Release;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the service provider.
|
/// Gets or sets the service provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1170,7 +1157,6 @@ namespace Emby.Server.Implementations
|
|||||||
SupportsLibraryMonitor = true,
|
SupportsLibraryMonitor = true,
|
||||||
EncoderLocation = _mediaEncoder.EncoderLocation,
|
EncoderLocation = _mediaEncoder.EncoderLocation,
|
||||||
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
||||||
SystemUpdateLevel = SystemUpdateLevel,
|
|
||||||
PackageName = _startupOptions.PackageName
|
PackageName = _startupOptions.PackageName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = ZipReader.Open(source))
|
using (var reader = ZipReader.Open(source))
|
||||||
@ -66,6 +67,7 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = GZipReader.Open(source))
|
using (var reader = GZipReader.Open(source))
|
||||||
@ -82,6 +84,7 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
||||||
{
|
{
|
||||||
using (var reader = GZipReader.Open(source))
|
using (var reader = GZipReader.Open(source))
|
||||||
|
@ -20,6 +20,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Name => "Channel Image Provider";
|
||||||
|
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetChannel(item).GetSupportedChannelImages();
|
return GetChannel(item).GetSupportedChannelImages();
|
||||||
@ -32,8 +34,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return channel.GetChannelImage(type, cancellationToken);
|
return channel.GetChannelImage(type, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Channel Image Provider";
|
|
||||||
|
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
return item is Channel;
|
return item is Channel;
|
||||||
|
@ -31,8 +31,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
public class ChannelManager : IChannelManager
|
public class ChannelManager : IChannelManager
|
||||||
{
|
{
|
||||||
internal IChannel[] Channels { get; private set; }
|
|
||||||
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
@ -43,11 +41,16 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
||||||
|
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
public ChannelManager(
|
public ChannelManager(
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<ChannelManager> logger,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
@ -57,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ChannelManager));
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
@ -65,6 +68,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal IChannel[] Channels { get; private set; }
|
||||||
|
|
||||||
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
||||||
|
|
||||||
public void AddParts(IEnumerable<IChannel> channels)
|
public void AddParts(IEnumerable<IChannel> channels)
|
||||||
@ -85,8 +90,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||||
|
|
||||||
var supportsDelete = channel as ISupportsDelete;
|
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
|
||||||
return supportsDelete != null && supportsDelete.CanDelete(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableMediaProbe(BaseItem item)
|
public bool EnableMediaProbe(BaseItem item)
|
||||||
@ -146,15 +150,13 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
|
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
||||||
|
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
||||||
return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +173,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,9 +189,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsFavorite.HasValue)
|
if (query.IsFavorite.HasValue)
|
||||||
{
|
{
|
||||||
var val = query.IsFavorite.Value;
|
var val = query.IsFavorite.Value;
|
||||||
@ -215,7 +216,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +226,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
all = all.Skip(query.StartIndex.Value).ToList();
|
all = all.Skip(query.StartIndex.Value).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.Limit.HasValue)
|
if (query.Limit.HasValue)
|
||||||
{
|
{
|
||||||
all = all.Take(query.Limit.Value).ToList();
|
all = all.Take(query.Limit.Value).ToList();
|
||||||
@ -256,11 +257,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var internalResult = GetChannelsInternal(query);
|
var internalResult = GetChannelsInternal(query);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions()
|
var dtoOptions = new DtoOptions();
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>
|
||||||
@ -341,8 +340,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,11 +364,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
var channel = GetChannel(item.ChannelId);
|
var channel = GetChannel(item.ChannelId);
|
||||||
var channelPlugin = GetChannelProvider(channel);
|
var channelPlugin = GetChannelProvider(channel);
|
||||||
|
|
||||||
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
|
|
||||||
|
|
||||||
IEnumerable<MediaSourceInfo> results;
|
IEnumerable<MediaSourceInfo> results;
|
||||||
|
|
||||||
if (requiresCallback != null)
|
if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
|
||||||
{
|
{
|
||||||
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
@ -384,9 +381,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
|
||||||
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
|
||||||
|
|
||||||
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
||||||
@ -444,18 +438,21 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Path = path;
|
item.Path = path;
|
||||||
|
|
||||||
if (!item.ChannelId.Equals(id))
|
if (!item.ChannelId.Equals(id))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ChannelId = id;
|
item.ChannelId = id;
|
||||||
|
|
||||||
if (item.ParentId != parentFolderId)
|
if (item.ParentId != parentFolderId)
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
|
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
|
||||||
@ -472,10 +469,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_libraryManager.CreateItem(item, null);
|
_libraryManager.CreateItem(item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
await item.RefreshMetadata(
|
||||||
{
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
ForceSave = !isNew && forceUpdate
|
{
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
ForceSave = !isNew && forceUpdate
|
||||||
|
},
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -509,12 +508,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
public ChannelFeatures[] GetAllChannelFeatures()
|
public ChannelFeatures[] GetAllChannelFeatures()
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemIds(new InternalItemsQuery
|
return _libraryManager.GetItemIds(
|
||||||
{
|
new InternalItemsQuery
|
||||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
{
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||||
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||||
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelFeatures GetChannelFeatures(string id)
|
public ChannelFeatures GetChannelFeatures(string id)
|
||||||
@ -532,13 +531,13 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
public bool SupportsExternalTransfer(Guid channelId)
|
public bool SupportsExternalTransfer(Guid channelId)
|
||||||
{
|
{
|
||||||
//var channel = GetChannel(channelId);
|
|
||||||
var channelProvider = GetChannelProvider(channelId);
|
var channelProvider = GetChannelProvider(channelId);
|
||||||
|
|
||||||
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelFeatures GetChannelFeaturesDto(Channel channel,
|
public ChannelFeatures GetChannelFeaturesDto(
|
||||||
|
Channel channel,
|
||||||
IChannel provider,
|
IChannel provider,
|
||||||
InternalChannelFeatures features)
|
InternalChannelFeatures features)
|
||||||
{
|
{
|
||||||
@ -567,6 +566,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(name));
|
throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
|
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +614,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
query.IsFolder = false;
|
query.IsFolder = false;
|
||||||
|
|
||||||
// hack for trailers, figure out a better way later
|
// hack for trailers, figure out a better way later
|
||||||
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
|
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
|
||||||
|
|
||||||
if (sortByPremiereDate)
|
if (sortByPremiereDate)
|
||||||
{
|
{
|
||||||
@ -640,10 +640,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var query = new InternalItemsQuery();
|
var query = new InternalItemsQuery
|
||||||
query.Parent = internalChannel;
|
{
|
||||||
query.EnableTotalRecordCount = false;
|
Parent = internalChannel,
|
||||||
query.ChannelIds = new Guid[] { internalChannel.Id };
|
EnableTotalRecordCount = false,
|
||||||
|
ChannelIds = new Guid[] { internalChannel.Id }
|
||||||
|
};
|
||||||
|
|
||||||
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -651,13 +653,15 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
if (item is Folder folder)
|
if (item is Folder folder)
|
||||||
{
|
{
|
||||||
await GetChannelItemsInternal(new InternalItemsQuery
|
await GetChannelItemsInternal(
|
||||||
{
|
new InternalItemsQuery
|
||||||
Parent = folder,
|
{
|
||||||
EnableTotalRecordCount = false,
|
Parent = folder,
|
||||||
ChannelIds = new Guid[] { internalChannel.Id }
|
EnableTotalRecordCount = false,
|
||||||
|
ChannelIds = new Guid[] { internalChannel.Id }
|
||||||
}, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
},
|
||||||
|
new SimpleProgress<double>(),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -672,7 +676,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
||||||
|
|
||||||
var itemsResult = await GetChannelItems(channelProvider,
|
var itemsResult = await GetChannelItems(
|
||||||
|
channelProvider,
|
||||||
query.User,
|
query.User,
|
||||||
parentItem is Channel ? null : parentItem.ExternalId,
|
parentItem is Channel ? null : parentItem.ExternalId,
|
||||||
null,
|
null,
|
||||||
@ -684,13 +689,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
query.Parent = channel;
|
query.Parent = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
query.ChannelIds = Array.Empty<Guid>();
|
query.ChannelIds = Array.Empty<Guid>();
|
||||||
|
|
||||||
// Not yet sure why this is causing a problem
|
// Not yet sure why this is causing a problem
|
||||||
query.GroupByPresentationUniqueKey = false;
|
query.GroupByPresentationUniqueKey = false;
|
||||||
|
|
||||||
//_logger.LogDebug("GetChannelItemsInternal");
|
|
||||||
|
|
||||||
// null if came from cache
|
// null if came from cache
|
||||||
if (itemsResult != null)
|
if (itemsResult != null)
|
||||||
{
|
{
|
||||||
@ -707,12 +711,15 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
var deadItem = _libraryManager.GetItemById(deadId);
|
var deadItem = _libraryManager.GetItemById(deadId);
|
||||||
if (deadItem != null)
|
if (deadItem != null)
|
||||||
{
|
{
|
||||||
_libraryManager.DeleteItem(deadItem, new DeleteOptions
|
_libraryManager.DeleteItem(
|
||||||
{
|
deadItem,
|
||||||
DeleteFileLocation = false,
|
new DeleteOptions
|
||||||
DeleteFromExternalProvider = false
|
{
|
||||||
|
DeleteFileLocation = false,
|
||||||
}, parentItem, false);
|
DeleteFromExternalProvider = false
|
||||||
|
},
|
||||||
|
parentItem,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -735,7 +742,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
|
||||||
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
||||||
User user,
|
User user,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
@ -743,7 +749,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
bool sortDescending,
|
bool sortDescending,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
|
var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var cacheLength = CacheLength;
|
var cacheLength = CacheLength;
|
||||||
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
||||||
@ -761,11 +767,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@ -785,11 +789,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = new InternalChannelItemQuery
|
var query = new InternalChannelItemQuery
|
||||||
@ -833,7 +835,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetChannelDataCachePath(IChannel channel,
|
private string GetChannelDataCachePath(
|
||||||
|
IChannel channel,
|
||||||
string userId,
|
string userId,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
ChannelItemSortField? sortField,
|
ChannelItemSortField? sortField,
|
||||||
@ -843,8 +846,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var userCacheKey = string.Empty;
|
var userCacheKey = string.Empty;
|
||||||
|
|
||||||
var hasCacheKey = channel as IHasCacheKey;
|
if (channel is IHasCacheKey hasCacheKey)
|
||||||
if (hasCacheKey != null)
|
|
||||||
{
|
{
|
||||||
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
||||||
}
|
}
|
||||||
@ -858,6 +860,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
filename += "-sortField-" + sortField.Value;
|
filename += "-sortField-" + sortField.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortDescending)
|
if (sortDescending)
|
||||||
{
|
{
|
||||||
filename += "-sortDescending";
|
filename += "-sortDescending";
|
||||||
@ -865,7 +868,8 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return Path.Combine(_config.ApplicationPaths.CachePath,
|
return Path.Combine(
|
||||||
|
_config.ApplicationPaths.CachePath,
|
||||||
"channels",
|
"channels",
|
||||||
channelId,
|
channelId,
|
||||||
version,
|
version,
|
||||||
@ -981,7 +985,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
item.RunTimeTicks = null;
|
item.RunTimeTicks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (isNew || !enableMediaProbe)
|
else if (isNew || !enableMediaProbe)
|
||||||
{
|
{
|
||||||
item.RunTimeTicks = info.RunTimeTicks;
|
item.RunTimeTicks = info.RunTimeTicks;
|
||||||
@ -1014,26 +1017,24 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasArtists = item as IHasArtist;
|
if (item is IHasArtist hasArtists)
|
||||||
if (hasArtists != null)
|
|
||||||
{
|
{
|
||||||
hasArtists.Artists = info.Artists.ToArray();
|
hasArtists.Artists = info.Artists.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
if (item is IHasAlbumArtist hasAlbumArtists)
|
||||||
if (hasAlbumArtists != null)
|
|
||||||
{
|
{
|
||||||
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
|
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var trailer = item as Trailer;
|
if (item is Trailer trailer)
|
||||||
if (trailer != null)
|
|
||||||
{
|
{
|
||||||
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
|
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
|
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
trailer.TrailerTypes = info.TrailerTypes.ToArray();
|
trailer.TrailerTypes = info.TrailerTypes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1057,6 +1058,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
|
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ChannelId = internalChannelId;
|
item.ChannelId = internalChannelId;
|
||||||
|
|
||||||
if (!item.ParentId.Equals(parentFolderId))
|
if (!item.ParentId.Equals(parentFolderId))
|
||||||
@ -1064,16 +1066,17 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
|
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
var hasSeries = item as IHasSeries;
|
if (item is IHasSeries hasSeries)
|
||||||
if (hasSeries != null)
|
|
||||||
{
|
{
|
||||||
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
|
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSeries.SeriesName = info.SeriesName;
|
hasSeries.SeriesName = info.SeriesName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1082,24 +1085,23 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
|
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ExternalId = info.Id;
|
item.ExternalId = info.Id;
|
||||||
|
|
||||||
var channelAudioItem = item as Audio;
|
if (item is Audio channelAudioItem)
|
||||||
if (channelAudioItem != null)
|
|
||||||
{
|
{
|
||||||
channelAudioItem.ExtraType = info.ExtraType;
|
channelAudioItem.ExtraType = info.ExtraType;
|
||||||
|
|
||||||
var mediaSource = info.MediaSources.FirstOrDefault();
|
var mediaSource = info.MediaSources.FirstOrDefault();
|
||||||
item.Path = mediaSource == null ? null : mediaSource.Path;
|
item.Path = mediaSource?.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelVideoItem = item as Video;
|
if (item is Video channelVideoItem)
|
||||||
if (channelVideoItem != null)
|
|
||||||
{
|
{
|
||||||
channelVideoItem.ExtraType = info.ExtraType;
|
channelVideoItem.ExtraType = info.ExtraType;
|
||||||
|
|
||||||
var mediaSource = info.MediaSources.FirstOrDefault();
|
var mediaSource = info.MediaSources.FirstOrDefault();
|
||||||
item.Path = mediaSource == null ? null : mediaSource.Path;
|
item.Path = mediaSource?.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
|
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
|
||||||
@ -1156,7 +1158,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
|
if (isNew || forceUpdate || item.DateLastRefreshed == default)
|
||||||
{
|
{
|
||||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,12 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
public class ChannelPostScanTask
|
public class ChannelPostScanTask
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
|
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
@ -7,29 +7,26 @@ using System.Threading.Tasks;
|
|||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
public RefreshChannelsScheduledTask(
|
public RefreshChannelsScheduledTask(
|
||||||
IChannelManager channelManager,
|
IChannelManager channelManager,
|
||||||
IUserManager userManager,
|
|
||||||
ILogger<RefreshChannelsScheduledTask> logger,
|
ILogger<RefreshChannelsScheduledTask> logger,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILocalizationManager localization)
|
ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
@ -63,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
|
await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +69,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
|
@ -46,9 +46,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
{
|
{
|
||||||
var subItem = i;
|
var subItem = i;
|
||||||
|
|
||||||
var episode = subItem as Episode;
|
if (subItem is Episode episode)
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
{
|
||||||
var series = episode.Series;
|
var series = episode.Series;
|
||||||
if (series != null && series.HasImage(ImageType.Primary))
|
if (series != null && series.HasImage(ImageType.Primary))
|
||||||
|
@ -52,7 +52,9 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
|
||||||
|
|
||||||
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
|
||||||
|
|
||||||
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
|
||||||
|
|
||||||
private IEnumerable<Folder> FindFolders(string path)
|
private IEnumerable<Folder> FindFolders(string path)
|
||||||
@ -109,9 +111,9 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
{
|
{
|
||||||
var folder = GetCollectionsFolder(false).Result;
|
var folder = GetCollectionsFolder(false).Result;
|
||||||
|
|
||||||
return folder == null ?
|
return folder == null
|
||||||
new List<BoxSet>() :
|
? Enumerable.Empty<BoxSet>()
|
||||||
folder.GetChildren(user, true).OfType<BoxSet>();
|
: folder.GetChildren(user, true).OfType<BoxSet>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoxSet CreateCollection(CollectionCreationOptions options)
|
public BoxSet CreateCollection(CollectionCreationOptions options)
|
||||||
@ -191,7 +193,6 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
|
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
|
||||||
{
|
{
|
||||||
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
|
||||||
|
|
||||||
if (collection == null)
|
if (collection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("No collection exists with the supplied Id");
|
throw new ArgumentException("No collection exists with the supplied Id");
|
||||||
@ -289,10 +290,13 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
||||||
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
_providerManager.QueueRefresh(
|
||||||
{
|
collection.Id,
|
||||||
ForceSave = true
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
}, RefreshPriority.High);
|
{
|
||||||
|
ForceSave = true
|
||||||
|
},
|
||||||
|
RefreshPriority.High);
|
||||||
|
|
||||||
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
|
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
|
||||||
{
|
{
|
||||||
|
@ -18,7 +18,7 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
{ HostWebClientKey, bool.TrueString },
|
{ HostWebClientKey, bool.TrueString },
|
||||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||||
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
|
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
|
||||||
{ FfmpegProbeSizeKey, "1G" },
|
{ FfmpegProbeSizeKey, "1G" },
|
||||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Data
|
namespace Emby.Server.Implementations.Data
|
||||||
@ -109,25 +107,6 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes to bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>System.Byte[][].</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">obj</exception>
|
|
||||||
public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
json.SerializeToStream(obj, stream);
|
|
||||||
return stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
|
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
|
||||||
{
|
{
|
||||||
var commandText = string.Format(
|
var commandText = string.Format(
|
||||||
@ -383,11 +362,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
|
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
|
||||||
{
|
{
|
||||||
while (This.MoveNext())
|
while (statement.MoveNext())
|
||||||
{
|
{
|
||||||
yield return This.Current;
|
yield return statement.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private readonly object _createdRulesLock = new object();
|
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
|
||||||
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
|
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private string _lastConfigIdentifier;
|
private string _configIdentifier;
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
return new StringBuilder(32)
|
return new StringBuilder(32)
|
||||||
.Append(config.EnableUPnP).Append(Separator)
|
.Append(config.EnableUPnP).Append(Separator)
|
||||||
.Append(config.PublicPort).Append(Separator)
|
.Append(config.PublicPort).Append(Separator)
|
||||||
|
.Append(config.PublicHttpsPort).Append(Separator)
|
||||||
.Append(_appHost.HttpPort).Append(Separator)
|
.Append(_appHost.HttpPort).Append(Separator)
|
||||||
.Append(_appHost.HttpsPort).Append(Separator)
|
.Append(_appHost.HttpsPort).Append(Separator)
|
||||||
.Append(_appHost.EnableHttps).Append(Separator)
|
.Append(_appHost.EnableHttps).Append(Separator)
|
||||||
@ -69,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
|
|
||||||
private void OnConfigurationUpdated(object sender, EventArgs e)
|
private void OnConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
var oldConfigIdentifier = _configIdentifier;
|
||||||
|
_configIdentifier = GetConfigIdentifier();
|
||||||
|
|
||||||
|
if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
Start();
|
Start();
|
||||||
@ -93,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Starting NAT discovery");
|
_logger.LogInformation("Starting NAT discovery");
|
||||||
|
|
||||||
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
||||||
NatUtility.StartDiscovery();
|
NatUtility.StartDiscovery();
|
||||||
|
|
||||||
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
|
||||||
_lastConfigIdentifier = GetConfigIdentifier();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Stop()
|
private void Stop()
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Stopping NAT discovery");
|
_logger.LogInformation("Stopping NAT discovery");
|
||||||
|
|
||||||
NatUtility.StopDiscovery();
|
NatUtility.StopDiscovery();
|
||||||
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
|
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
|
||||||
@ -117,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearCreatedRules(object state)
|
|
||||||
{
|
|
||||||
lock (_createdRulesLock)
|
|
||||||
{
|
|
||||||
_createdRules.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = e.Device;
|
await CreateRules(e.Device).ConfigureAwait(false);
|
||||||
|
|
||||||
CreateRules(device);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -144,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CreateRules(INatDevice device)
|
private Task CreateRules(INatDevice device)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
@ -153,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
|
|
||||||
// On some systems the device discovered event seems to fire repeatedly
|
// On some systems the device discovered event seems to fire repeatedly
|
||||||
// This check will help ensure we're not trying to port map the same device over and over
|
// This check will help ensure we're not trying to port map the same device over and over
|
||||||
var address = device.DeviceEndpoint;
|
if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
|
||||||
|
|
||||||
lock (_createdRulesLock)
|
|
||||||
{
|
{
|
||||||
if (!_createdRules.Contains(address))
|
return Task.CompletedTask;
|
||||||
{
|
|
||||||
_createdRules.Add(address);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return Task.WhenAll(CreatePortMaps(device));
|
||||||
{
|
}
|
||||||
await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error creating http port map");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
private IEnumerable<Task> CreatePortMaps(INatDevice device)
|
||||||
|
{
|
||||||
|
yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
|
||||||
|
|
||||||
|
if (_appHost.EnableHttps)
|
||||||
{
|
{
|
||||||
await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
|
yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error creating https port map");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(
|
_logger.LogDebug(
|
||||||
"Creating port map on local port {0} to public port {1} with device {2}",
|
"Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
|
||||||
privatePort,
|
privatePort,
|
||||||
publicPort,
|
publicPort,
|
||||||
device.DeviceEndpoint);
|
device.DeviceEndpoint);
|
||||||
|
|
||||||
return device.CreatePortMapAsync(
|
try
|
||||||
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
|
{
|
||||||
|
var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
|
||||||
|
await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
|
||||||
|
privatePort,
|
||||||
|
publicPort,
|
||||||
|
device.DeviceEndpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -28,6 +28,12 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HttpResultFactory : IHttpResultFactory
|
public class HttpResultFactory : IHttpResultFactory
|
||||||
{
|
{
|
||||||
|
// Last-Modified and If-Modified-Since must follow strict date format,
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
|
||||||
|
private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\"";
|
||||||
|
// We specifically use en-US culture because both day of week and month names require it
|
||||||
|
private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -420,7 +426,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
if (!noCache)
|
if (!noCache)
|
||||||
{
|
{
|
||||||
DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
|
if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal, out var ifModifiedSinceHeader))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
||||||
{
|
{
|
||||||
@ -629,7 +639,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
if (lastModifiedDate.HasValue)
|
if (lastModifiedDate.HasValue)
|
||||||
{
|
{
|
||||||
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
|
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2335,7 +2335,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string videoPath,
|
string videoPath,
|
||||||
string[] files)
|
string[] files)
|
||||||
{
|
{
|
||||||
new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
|
new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// for imdbid we also accept pattern matching
|
// for imdbid we also accept pattern matching
|
||||||
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
|
var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
|
||||||
return m.Success ? m.Value : null;
|
return m.Success ? m.Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,6 +258,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(username))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
|
||||||
throw new ArgumentNullException(nameof(username));
|
throw new ArgumentNullException(nameof(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,11 +314,13 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException("Invalid username or password entered.");
|
throw new AuthenticationException("Invalid username or password entered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.Policy.IsDisabled)
|
if (user.Policy.IsDisabled)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException(
|
throw new AuthenticationException(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@ -327,11 +330,13 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException("Forbidden.");
|
throw new AuthenticationException("Forbidden.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.IsParentalScheduleAllowed())
|
if (!user.IsParentalScheduleAllowed())
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException("User is not allowed access at this time.");
|
throw new AuthenticationException("User is not allowed access at this time.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,14 +350,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
ResetInvalidLoginAttemptCount(user);
|
ResetInvalidLoginAttemptCount(user);
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IncrementInvalidLoginAttemptCount(user);
|
IncrementInvalidLoginAttemptCount(user);
|
||||||
|
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
|
|
||||||
|
|
||||||
return success ? user : null;
|
return success ? user : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,10 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
public class LiveTvDtoService
|
public class LiveTvDtoService
|
||||||
{
|
{
|
||||||
|
private const string InternalVersionNumber = "4";
|
||||||
|
|
||||||
|
private const string ServiceName = "Emby";
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
@ -160,7 +164,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (librarySeries != null)
|
if (librarySeries != null)
|
||||||
@ -178,6 +181,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
_logger.LogError(ex, "Error");
|
_logger.LogError(ex, "Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
@ -198,13 +202,12 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||||
ExternalSeriesId = programSeriesId,
|
ExternalSeriesId = programSeriesId,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (program != null)
|
if (program != null)
|
||||||
@ -231,9 +234,10 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
dto.ParentBackdropImageTags = new string[]
|
dto.ParentBackdropImageTags = new string[]
|
||||||
{
|
{
|
||||||
_imageProcessor.GetImageCacheTag(program, image)
|
_imageProcessor.GetImageCacheTag(program, image)
|
||||||
};
|
};
|
||||||
|
|
||||||
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -254,7 +258,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (librarySeries != null)
|
if (librarySeries != null)
|
||||||
@ -272,6 +275,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
_logger.LogError(ex, "Error");
|
_logger.LogError(ex, "Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
@ -297,7 +301,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
||||||
if (program == null)
|
if (program == null)
|
||||||
@ -310,7 +313,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,8 +397,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string InternalVersionNumber = "4";
|
|
||||||
|
|
||||||
public Guid GetInternalChannelId(string serviceName, string externalId)
|
public Guid GetInternalChannelId(string serviceName, string externalId)
|
||||||
{
|
{
|
||||||
var name = serviceName + externalId + InternalVersionNumber;
|
var name = serviceName + externalId + InternalVersionNumber;
|
||||||
@ -404,7 +404,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
|
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string ServiceName = "Emby";
|
|
||||||
public string GetInternalTimerId(string externalId)
|
public string GetInternalTimerId(string externalId)
|
||||||
{
|
{
|
||||||
var name = ServiceName + externalId + InternalVersionNumber;
|
var name = ServiceName + externalId + InternalVersionNumber;
|
||||||
|
@ -41,6 +41,10 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LiveTvManager : ILiveTvManager, IDisposable
|
public class LiveTvManager : ILiveTvManager, IDisposable
|
||||||
{
|
{
|
||||||
|
private const string ExternalServiceTag = "ExternalServiceId";
|
||||||
|
|
||||||
|
private const string EtagKey = "ProgramEtag";
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
@ -173,7 +177,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
Name = i.Name,
|
Name = i.Name,
|
||||||
Id = i.Type
|
Id = i.Type
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
var endTime = DateTime.UtcNow;
|
var endTime = DateTime.UtcNow;
|
||||||
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
|
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
info.RequiresClosing = true;
|
info.RequiresClosing = true;
|
||||||
|
|
||||||
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
|
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
|
||||||
@ -357,30 +361,37 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
stream.BitRate = null;
|
stream.BitRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Channels.HasValue && stream.Channels <= 0)
|
if (stream.Channels.HasValue && stream.Channels <= 0)
|
||||||
{
|
{
|
||||||
stream.Channels = null;
|
stream.Channels = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
|
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
|
||||||
{
|
{
|
||||||
stream.AverageFrameRate = null;
|
stream.AverageFrameRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
|
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
|
||||||
{
|
{
|
||||||
stream.RealFrameRate = null;
|
stream.RealFrameRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Width.HasValue && stream.Width <= 0)
|
if (stream.Width.HasValue && stream.Width <= 0)
|
||||||
{
|
{
|
||||||
stream.Width = null;
|
stream.Width = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Height.HasValue && stream.Height <= 0)
|
if (stream.Height.HasValue && stream.Height <= 0)
|
||||||
{
|
{
|
||||||
stream.Height = null;
|
stream.Height = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
|
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
|
||||||
{
|
{
|
||||||
stream.SampleRate = null;
|
stream.SampleRate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream.Level.HasValue && stream.Level <= 0)
|
if (stream.Level.HasValue && stream.Level <= 0)
|
||||||
{
|
{
|
||||||
stream.Level = null;
|
stream.Level = null;
|
||||||
@ -422,7 +433,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string ExternalServiceTag = "ExternalServiceId";
|
|
||||||
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
|
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var parentFolderId = parentFolder.Id;
|
var parentFolderId = parentFolder.Id;
|
||||||
@ -451,6 +461,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Tags = channelInfo.Tags;
|
item.Tags = channelInfo.Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,6 +469,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
item.ChannelType = channelInfo.ChannelType;
|
item.ChannelType = channelInfo.ChannelType;
|
||||||
@ -467,24 +479,28 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.SetProviderId(ExternalServiceTag, serviceName);
|
item.SetProviderId(ExternalServiceTag, serviceName);
|
||||||
|
|
||||||
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
|
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ExternalId = channelInfo.Id;
|
item.ExternalId = channelInfo.Id;
|
||||||
|
|
||||||
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
|
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Number = channelInfo.Number;
|
item.Number = channelInfo.Number;
|
||||||
|
|
||||||
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
|
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Name = channelInfo.Name;
|
item.Name = channelInfo.Name;
|
||||||
|
|
||||||
if (!item.HasImage(ImageType.Primary))
|
if (!item.HasImage(ImageType.Primary))
|
||||||
@ -513,8 +529,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string EtagKey = "ProgramEtag";
|
|
||||||
|
|
||||||
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
|
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var id = _tvDtoService.GetInternalProgramId(info.Id);
|
var id = _tvDtoService.GetInternalProgramId(info.Id);
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
||||||
"Application": "Aplicació",
|
"Application": "Aplicació",
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament",
|
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
|
||||||
"Books": "Llibres",
|
"Books": "Llibres",
|
||||||
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}",
|
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
|
||||||
"Channels": "Canals",
|
"Channels": "Canals",
|
||||||
"ChapterNameValue": "Episodi {0}",
|
"ChapterNameValue": "Capítol {0}",
|
||||||
"Collections": "Col·leccions",
|
"Collections": "Col·leccions",
|
||||||
"DeviceOfflineWithName": "{0} s'ha desconnectat",
|
"DeviceOfflineWithName": "{0} s'ha desconnectat",
|
||||||
"DeviceOnlineWithName": "{0} està connectat",
|
"DeviceOnlineWithName": "{0} està connectat",
|
||||||
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
|
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
|
||||||
"Favorites": "Preferits",
|
"Favorites": "Preferits",
|
||||||
"Folders": "Directoris",
|
"Folders": "Carpetes",
|
||||||
"Genres": "Gèneres",
|
"Genres": "Gèneres",
|
||||||
"HeaderAlbumArtists": "Artistes dels Àlbums",
|
"HeaderAlbumArtists": "Artistes del Àlbum",
|
||||||
"HeaderCameraUploads": "Pujades de Càmera",
|
"HeaderCameraUploads": "Pujades de Càmera",
|
||||||
"HeaderContinueWatching": "Continua Veient",
|
"HeaderContinueWatching": "Continua Veient",
|
||||||
"HeaderFavoriteAlbums": "Àlbums Preferits",
|
"HeaderFavoriteAlbums": "Àlbums Preferits",
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
"Latest": "Seneste",
|
"Latest": "Seneste",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
|
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret",
|
||||||
"MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
|
"MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret",
|
||||||
"MixedContent": "Blandet indhold",
|
"MixedContent": "Blandet indhold",
|
||||||
"Movies": "Film",
|
"Movies": "Film",
|
||||||
"Music": "Musik",
|
"Music": "Musik",
|
||||||
@ -93,13 +93,13 @@
|
|||||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||||
"ValueSpecialEpisodeName": "Special - {0}",
|
"ValueSpecialEpisodeName": "Special - {0}",
|
||||||
"VersionNumber": "Version {0}",
|
"VersionNumber": "Version {0}",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiration.",
|
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.",
|
||||||
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
|
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
|
||||||
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
|
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
|
||||||
"TaskUpdatePlugins": "Opdater Plugins",
|
"TaskUpdatePlugins": "Opdater Plugins",
|
||||||
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
|
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
|
||||||
"TaskCleanLogs": "Ryd Log Mappe",
|
"TaskCleanLogs": "Ryd Log Mappe",
|
||||||
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdatere metadata.",
|
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.",
|
||||||
"TaskRefreshLibrary": "Scan Medie Bibliotek",
|
"TaskRefreshLibrary": "Scan Medie Bibliotek",
|
||||||
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
|
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
|
||||||
"TaskCleanCache": "Ryd Cache Mappe",
|
"TaskCleanCache": "Ryd Cache Mappe",
|
||||||
@ -108,5 +108,11 @@
|
|||||||
"TasksLibraryCategory": "Bibliotek",
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
"TasksMaintenanceCategory": "Vedligeholdelse",
|
"TasksMaintenanceCategory": "Vedligeholdelse",
|
||||||
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
|
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
|
||||||
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
|
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
|
||||||
|
"TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.",
|
||||||
|
"TaskRefreshChannels": "Genopfrisk Kanaler",
|
||||||
|
"TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
|
||||||
|
"TaskCleanTranscode": "Rengør Transcode Mappen",
|
||||||
|
"TaskRefreshPeople": "Genopfrisk Personer",
|
||||||
|
"TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||||
"Application": "Anwendung",
|
"Application": "Anwendung",
|
||||||
"Artists": "Interpreten",
|
"Artists": "Interpreten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifziert",
|
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
|
||||||
"Books": "Bücher",
|
"Books": "Bücher",
|
||||||
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
||||||
"Channels": "Kanäle",
|
"Channels": "Kanäle",
|
||||||
@ -99,11 +99,11 @@
|
|||||||
"TaskRefreshChannels": "Erneuere Kanäle",
|
"TaskRefreshChannels": "Erneuere Kanäle",
|
||||||
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
|
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
|
||||||
"TaskCleanTranscode": "Lösche Transkodier Pfad",
|
"TaskCleanTranscode": "Lösche Transkodier Pfad",
|
||||||
"TaskUpdatePluginsDescription": "Läd Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
|
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
|
||||||
"TaskUpdatePlugins": "Update Plugins",
|
"TaskUpdatePlugins": "Update Plugins",
|
||||||
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
|
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
|
||||||
"TaskRefreshPeople": "Erneuere Schausteller",
|
"TaskRefreshPeople": "Erneuere Schausteller",
|
||||||
"TaskCleanLogsDescription": "Lösche Log Datein die älter als {0} Tage sind.",
|
"TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
|
||||||
"TaskCleanLogs": "Lösche Log Pfad",
|
"TaskCleanLogs": "Lösche Log Pfad",
|
||||||
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
|
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
|
||||||
"TaskRefreshLibrary": "Scanne alle Bibliotheken",
|
"TaskRefreshLibrary": "Scanne alle Bibliotheken",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"Genres": "Géneros",
|
"Genres": "Géneros",
|
||||||
"HeaderAlbumArtists": "Artistas de álbum",
|
"HeaderAlbumArtists": "Artistas de álbum",
|
||||||
"HeaderCameraUploads": "Subidas de cámara",
|
"HeaderCameraUploads": "Subidas de cámara",
|
||||||
"HeaderContinueWatching": "Continuar viendo",
|
"HeaderContinueWatching": "Seguir viendo",
|
||||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||||
|
@ -11,11 +11,11 @@
|
|||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
"DeviceOfflineWithName": "{0} s'est déconnecté",
|
"DeviceOfflineWithName": "{0} s'est déconnecté",
|
||||||
"DeviceOnlineWithName": "{0} est connecté",
|
"DeviceOnlineWithName": "{0} est connecté",
|
||||||
"FailedLoginAttemptWithUserName": "Échec de connexion de {0}",
|
"FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}",
|
||||||
"Favorites": "Favoris",
|
"Favorites": "Favoris",
|
||||||
"Folders": "Dossiers",
|
"Folders": "Dossiers",
|
||||||
"Genres": "Genres",
|
"Genres": "Genres",
|
||||||
"HeaderAlbumArtists": "Artistes d'album",
|
"HeaderAlbumArtists": "Artistes de l'album",
|
||||||
"HeaderCameraUploads": "Photos transférées",
|
"HeaderCameraUploads": "Photos transférées",
|
||||||
"HeaderContinueWatching": "Continuer à regarder",
|
"HeaderContinueWatching": "Continuer à regarder",
|
||||||
"HeaderFavoriteAlbums": "Albums favoris",
|
"HeaderFavoriteAlbums": "Albums favoris",
|
||||||
@ -69,7 +69,7 @@
|
|||||||
"PluginUpdatedWithName": "{0} a été mis à jour",
|
"PluginUpdatedWithName": "{0} a été mis à jour",
|
||||||
"ProviderValue": "Fournisseur : {0}",
|
"ProviderValue": "Fournisseur : {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} a échoué",
|
"ScheduledTaskFailedWithName": "{0} a échoué",
|
||||||
"ScheduledTaskStartedWithName": "{0} a commencé",
|
"ScheduledTaskStartedWithName": "{0} a démarré",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
||||||
"Shows": "Émissions",
|
"Shows": "Émissions",
|
||||||
"Songs": "Chansons",
|
"Songs": "Chansons",
|
||||||
@ -95,21 +95,21 @@
|
|||||||
"VersionNumber": "Version {0}",
|
"VersionNumber": "Version {0}",
|
||||||
"TasksChannelsCategory": "Chaines en ligne",
|
"TasksChannelsCategory": "Chaines en ligne",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
|
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
|
||||||
"TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant",
|
"TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant",
|
||||||
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
|
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
|
||||||
"TaskRefreshChannels": "Rafraîchit les chaines",
|
"TaskRefreshChannels": "Rafraîchir les chaines",
|
||||||
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
|
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
|
||||||
"TaskCleanTranscode": "Nettoie les dossier des transcodages",
|
"TaskCleanTranscode": "Nettoyer les dossier des transcodages",
|
||||||
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.",
|
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.",
|
||||||
"TaskUpdatePlugins": "Mettre à jour les plugins",
|
"TaskUpdatePlugins": "Mettre à jour les extensions",
|
||||||
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.",
|
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
|
||||||
"TaskRefreshPeople": "Rafraîchit les acteurs",
|
"TaskRefreshPeople": "Rafraîchir les acteurs",
|
||||||
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
|
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
|
||||||
"TaskCleanLogs": "Nettoie le répertoire des journaux",
|
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
|
||||||
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
|
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
|
||||||
"TaskRefreshLibrary": "Scanne toute les Bibliothèques",
|
"TaskRefreshLibrary": "Scanner toute les Bibliothèques",
|
||||||
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
|
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
|
||||||
"TaskRefreshChapterImages": "Extrait les images de chapitre",
|
"TaskRefreshChapterImages": "Extraire les images de chapitre",
|
||||||
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
|
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
|
||||||
"TaskCleanCache": "Vider le répertoire cache",
|
"TaskCleanCache": "Vider le répertoire cache",
|
||||||
"TasksApplicationCategory": "Application",
|
"TasksApplicationCategory": "Application",
|
||||||
|
@ -109,5 +109,8 @@
|
|||||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||||
"TaskUpdatePlugins": "プラグインの更新",
|
"TaskUpdatePlugins": "プラグインの更新",
|
||||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
|
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
|
||||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
|
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
||||||
|
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||||
|
"TaskRefreshChapterImages": "チャプター画像を抽出する"
|
||||||
}
|
}
|
||||||
|
@ -95,5 +95,13 @@
|
|||||||
"TaskCleanCache": "Limpar Diretório de Cache",
|
"TaskCleanCache": "Limpar Diretório de Cache",
|
||||||
"TasksApplicationCategory": "Aplicação",
|
"TasksApplicationCategory": "Aplicação",
|
||||||
"TasksLibraryCategory": "Biblioteca",
|
"TasksLibraryCategory": "Biblioteca",
|
||||||
"TasksMaintenanceCategory": "Manutenção"
|
"TasksMaintenanceCategory": "Manutenção",
|
||||||
|
"TaskRefreshChannels": "Atualizar Canais",
|
||||||
|
"TaskUpdatePlugins": "Atualizar Plugins",
|
||||||
|
"TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.",
|
||||||
|
"TaskCleanLogs": "Limpar diretório de log",
|
||||||
|
"TaskRefreshLibrary": "Escanear biblioteca de mídias",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.",
|
||||||
|
"TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.",
|
||||||
|
"TasksChannelsCategory": "Canais de Internet"
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
"Channels": "Каналы",
|
"Channels": "Каналы",
|
||||||
"ChapterNameValue": "Сцена {0}",
|
"ChapterNameValue": "Сцена {0}",
|
||||||
"Collections": "Коллекции",
|
"Collections": "Коллекции",
|
||||||
"DeviceOfflineWithName": "{0} - подкл. разъ-но",
|
"DeviceOfflineWithName": "{0} - отключено",
|
||||||
"DeviceOnlineWithName": "{0} - подкл. уст-но",
|
"DeviceOnlineWithName": "{0} - подключено",
|
||||||
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
|
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
|
||||||
"Favorites": "Избранное",
|
"Favorites": "Избранное",
|
||||||
"Folders": "Папки",
|
"Folders": "Папки",
|
||||||
@ -26,30 +26,30 @@
|
|||||||
"HeaderLiveTV": "Эфир",
|
"HeaderLiveTV": "Эфир",
|
||||||
"HeaderNextUp": "Очередное",
|
"HeaderNextUp": "Очередное",
|
||||||
"HeaderRecordingGroups": "Группы записей",
|
"HeaderRecordingGroups": "Группы записей",
|
||||||
"HomeVideos": "Дом. видео",
|
"HomeVideos": "Домашнее видео",
|
||||||
"Inherit": "Наследуемое",
|
"Inherit": "Наследуемое",
|
||||||
"ItemAddedWithName": "{0} - добавлено в медиатеку",
|
"ItemAddedWithName": "{0} - добавлено в медиатеку",
|
||||||
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
||||||
"LabelIpAddressValue": "IP-адрес: {0}",
|
"LabelIpAddressValue": "IP-адрес: {0}",
|
||||||
"LabelRunningTimeValue": "Длительность: {0}",
|
"LabelRunningTimeValue": "Длительность: {0}",
|
||||||
"Latest": "Новейшее",
|
"Latest": "Последнее",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
|
||||||
"MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
|
"MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена",
|
||||||
"MixedContent": "Смешанное содержимое",
|
"MixedContent": "Смешанное содержимое",
|
||||||
"Movies": "Кино",
|
"Movies": "Кино",
|
||||||
"Music": "Музыка",
|
"Music": "Музыка",
|
||||||
"MusicVideos": "Муз. видео",
|
"MusicVideos": "Музыкальные клипы",
|
||||||
"NameInstallFailed": "Установка {0} неудачна",
|
"NameInstallFailed": "Установка {0} неудачна",
|
||||||
"NameSeasonNumber": "Сезон {0}",
|
"NameSeasonNumber": "Сезон {0}",
|
||||||
"NameSeasonUnknown": "Сезон неопознан",
|
"NameSeasonUnknown": "Сезон неопознан",
|
||||||
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
|
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
|
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
|
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
|
||||||
"NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
|
"NotificationOptionAudioPlayback": "Воспроизведение аудио запущено",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но",
|
"NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено",
|
||||||
"NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры",
|
"NotificationOptionCameraImageUploaded": "Изображения с камеры загружены",
|
||||||
"NotificationOptionInstallationFailed": "Сбой установки",
|
"NotificationOptionInstallationFailed": "Сбой установки",
|
||||||
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
|
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
|
||||||
"NotificationOptionPluginError": "Сбой плагина",
|
"NotificationOptionPluginError": "Сбой плагина",
|
||||||
@ -59,8 +59,8 @@
|
|||||||
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
|
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
|
||||||
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
|
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
|
||||||
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
|
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
|
||||||
"NotificationOptionVideoPlayback": "Воспр-ие видео зап-но",
|
"NotificationOptionVideoPlayback": "Воспроизведение видео запущено",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Восп-ие видео ост-но",
|
"NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено",
|
||||||
"Photos": "Фото",
|
"Photos": "Фото",
|
||||||
"Playlists": "Плей-листы",
|
"Playlists": "Плей-листы",
|
||||||
"Plugin": "Плагин",
|
"Plugin": "Плагин",
|
||||||
@ -76,21 +76,43 @@
|
|||||||
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
|
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
|
||||||
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
|
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
|
||||||
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
|
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
|
||||||
"Sync": "Синхро",
|
"Sync": "Синхронизация",
|
||||||
"System": "Система",
|
"System": "Система",
|
||||||
"TvShows": "ТВ",
|
"TvShows": "ТВ",
|
||||||
"User": "Польз-ль",
|
"User": "Пользователь",
|
||||||
"UserCreatedWithName": "Пользователь {0} был создан",
|
"UserCreatedWithName": "Пользователь {0} был создан",
|
||||||
"UserDeletedWithName": "Пользователь {0} был удалён",
|
"UserDeletedWithName": "Пользователь {0} был удалён",
|
||||||
"UserDownloadingItemWithValues": "{0} загружает {1}",
|
"UserDownloadingItemWithValues": "{0} загружает {1}",
|
||||||
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
|
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
|
||||||
"UserOfflineFromDevice": "{0} - подкл. с {1} разъ-но",
|
"UserOfflineFromDevice": "{0} отключился с {1}",
|
||||||
"UserOnlineFromDevice": "{0} - подкл. с {1} уст-но",
|
"UserOnlineFromDevice": "{0} подключился с {1}",
|
||||||
"UserPasswordChangedWithName": "Пароль польз-ля {0} был изменён",
|
"UserPasswordChangedWithName": "Пароль пользователя {0} был изменён",
|
||||||
"UserPolicyUpdatedWithName": "Польз-ие политики {0} были обновлены",
|
"UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
|
||||||
"UserStartedPlayingItemWithValues": "{0} - воспр. «{1}» на {2}",
|
"UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}",
|
"UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
|
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
|
||||||
"ValueSpecialEpisodeName": "Спецэпизод - {0}",
|
"ValueSpecialEpisodeName": "Специальный эпизод - {0}",
|
||||||
"VersionNumber": "Версия {0}"
|
"VersionNumber": "Версия {0}",
|
||||||
|
"TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
|
||||||
|
"TaskRefreshChannels": "Обновление каналов",
|
||||||
|
"TaskCleanTranscode": "Очистка каталога перекодировки",
|
||||||
|
"TaskUpdatePlugins": "Обновление плагинов",
|
||||||
|
"TaskRefreshPeople": "Обновление метаданных людей",
|
||||||
|
"TaskCleanLogs": "Очистка каталога журналов",
|
||||||
|
"TaskRefreshLibrary": "Сканирование медиатеки",
|
||||||
|
"TaskRefreshChapterImages": "Извлечение изображений сцен",
|
||||||
|
"TaskCleanCache": "Очистка каталога кеша",
|
||||||
|
"TasksChannelsCategory": "Интернет-каналы",
|
||||||
|
"TasksApplicationCategory": "Приложение",
|
||||||
|
"TasksLibraryCategory": "Медиатека",
|
||||||
|
"TasksMaintenanceCategory": "Обслуживание",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.",
|
||||||
|
"TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.",
|
||||||
|
"TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.",
|
||||||
|
"TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.",
|
||||||
|
"TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.",
|
||||||
|
"TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).",
|
||||||
|
"TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
|
||||||
|
"TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе."
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"HeaderContinueWatching": "繼續觀賞",
|
"HeaderContinueWatching": "繼續觀賞",
|
||||||
"HeaderFavoriteAlbums": "最愛專輯",
|
"HeaderFavoriteAlbums": "最愛專輯",
|
||||||
"HeaderFavoriteArtists": "最愛演出者",
|
"HeaderFavoriteArtists": "最愛演出者",
|
||||||
"HeaderFavoriteEpisodes": "最愛級數",
|
"HeaderFavoriteEpisodes": "最愛影集",
|
||||||
"HeaderFavoriteShows": "最愛節目",
|
"HeaderFavoriteShows": "最愛節目",
|
||||||
"HeaderFavoriteSongs": "最愛歌曲",
|
"HeaderFavoriteSongs": "最愛歌曲",
|
||||||
"HeaderLiveTV": "電視直播",
|
"HeaderLiveTV": "電視直播",
|
||||||
|
@ -26,7 +26,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Server.Implementations.Updates
|
namespace Emby.Server.Implementations.Updates
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages all install, uninstall and update operations (both plugins and system).
|
/// Manages all install, uninstall, and update operations for the system and individual plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InstallationManager : IInstallationManager
|
public class InstallationManager : IInstallationManager
|
||||||
{
|
{
|
||||||
@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
|
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _logger.
|
/// The logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
@ -112,10 +112,10 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
|
public event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
|
public event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
||||||
@ -183,61 +183,56 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
public IEnumerable<VersionInfo> GetCompatibleVersions(
|
||||||
IEnumerable<PackageVersionInfo> availableVersions,
|
IEnumerable<VersionInfo> availableVersions,
|
||||||
Version minVersion = null,
|
Version minVersion = null)
|
||||||
PackageVersionClass classification = PackageVersionClass.Release)
|
|
||||||
{
|
{
|
||||||
var appVer = _applicationHost.ApplicationVersion;
|
var appVer = _applicationHost.ApplicationVersion;
|
||||||
availableVersions = availableVersions
|
availableVersions = availableVersions
|
||||||
.Where(x => x.classification == classification
|
.Where(x => Version.Parse(x.targetAbi) <= appVer);
|
||||||
&& Version.Parse(x.requiredVersionStr) <= appVer);
|
|
||||||
|
|
||||||
if (minVersion != null)
|
if (minVersion != null)
|
||||||
{
|
{
|
||||||
availableVersions = availableVersions.Where(x => x.Version >= minVersion);
|
availableVersions = availableVersions.Where(x => x.version >= minVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableVersions.OrderByDescending(x => x.Version);
|
return availableVersions.OrderByDescending(x => x.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
public IEnumerable<VersionInfo> GetCompatibleVersions(
|
||||||
IEnumerable<PackageInfo> availablePackages,
|
IEnumerable<PackageInfo> availablePackages,
|
||||||
string name = null,
|
string name = null,
|
||||||
Guid guid = default,
|
Guid guid = default,
|
||||||
Version minVersion = null,
|
Version minVersion = null)
|
||||||
PackageVersionClass classification = PackageVersionClass.Release)
|
|
||||||
{
|
{
|
||||||
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
|
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
|
||||||
|
|
||||||
// Package not found.
|
// Package not found in repository
|
||||||
if (package == null)
|
if (package == null)
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<PackageVersionInfo>();
|
return Enumerable.Empty<VersionInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetCompatibleVersions(
|
return GetCompatibleVersions(
|
||||||
package.versions,
|
package.versions,
|
||||||
minVersion,
|
minVersion);
|
||||||
classification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
public async Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
||||||
return GetAvailablePluginUpdates(catalog);
|
return GetAvailablePluginUpdates(catalog);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
|
private IEnumerable<VersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
|
||||||
{
|
{
|
||||||
foreach (var plugin in _applicationHost.Plugins)
|
foreach (var plugin in _applicationHost.Plugins)
|
||||||
{
|
{
|
||||||
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version, _applicationHost.SystemUpdateLevel);
|
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version);
|
||||||
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
|
var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version);
|
||||||
if (version != null
|
if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
||||||
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
yield return version;
|
yield return version;
|
||||||
}
|
}
|
||||||
@ -245,7 +240,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken)
|
public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (package == null)
|
if (package == null)
|
||||||
{
|
{
|
||||||
@ -254,11 +249,9 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
|
|
||||||
var installationInfo = new InstallationInfo
|
var installationInfo = new InstallationInfo
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Guid = package.guid,
|
||||||
Name = package.name,
|
Name = package.name,
|
||||||
AssemblyGuid = package.guid,
|
Version = package.version.ToString()
|
||||||
UpdateClass = package.classification,
|
|
||||||
Version = package.versionStr
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var innerCancellationTokenSource = new CancellationTokenSource();
|
var innerCancellationTokenSource = new CancellationTokenSource();
|
||||||
@ -276,7 +269,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
var installationEventArgs = new InstallationEventArgs
|
var installationEventArgs = new InstallationEventArgs
|
||||||
{
|
{
|
||||||
InstallationInfo = installationInfo,
|
InstallationInfo = installationInfo,
|
||||||
PackageVersionInfo = package
|
VersionInfo = package
|
||||||
};
|
};
|
||||||
|
|
||||||
PackageInstalling?.Invoke(this, installationEventArgs);
|
PackageInstalling?.Invoke(this, installationEventArgs);
|
||||||
@ -301,7 +294,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
_currentInstallations.Remove(tuple);
|
_currentInstallations.Remove(tuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr);
|
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version);
|
||||||
|
|
||||||
PackageInstallationCancelled?.Invoke(this, installationEventArgs);
|
PackageInstallationCancelled?.Invoke(this, installationEventArgs);
|
||||||
|
|
||||||
@ -337,7 +330,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
/// <param name="package">The package.</param>
|
/// <param name="package">The package.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns><see cref="Task" />.</returns>
|
/// <returns><see cref="Task" />.</returns>
|
||||||
private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken)
|
private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Set last update time if we were installed before
|
// Set last update time if we were installed before
|
||||||
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
|
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
|
||||||
@ -349,26 +342,26 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
// Do plugin-specific processing
|
// Do plugin-specific processing
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
|
_logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version);
|
||||||
|
|
||||||
PluginInstalled?.Invoke(this, new GenericEventArgs<PackageVersionInfo>(package));
|
PluginInstalled?.Invoke(this, new GenericEventArgs<VersionInfo>(package));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
|
_logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version);
|
||||||
|
|
||||||
PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package)));
|
PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_applicationHost.NotifyPendingRestart();
|
_applicationHost.NotifyPendingRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken)
|
private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(package.targetFilename);
|
var extension = Path.GetExtension(package.filename);
|
||||||
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
|
_logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,7 +408,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uninstalls a plugin
|
/// Uninstalls a plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plugin">The plugin.</param>
|
/// <param name="plugin">The plugin.</param>
|
||||||
public void UninstallPlugin(IPlugin plugin)
|
public void UninstallPlugin(IPlugin plugin)
|
||||||
@ -473,7 +466,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
{
|
{
|
||||||
lock (_currentInstallationsLock)
|
lock (_currentInstallationsLock)
|
||||||
{
|
{
|
||||||
var install = _currentInstallations.Find(x => x.info.Id == id);
|
var install = _currentInstallations.Find(x => x.info.Guid == id.ToString());
|
||||||
if (install == default((InstallationInfo, CancellationTokenSource)))
|
if (install == default((InstallationInfo, CancellationTokenSource)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -71,6 +71,11 @@ namespace Jellyfin.Server.Extensions
|
|||||||
// Clear app parts to avoid other assemblies being picked up
|
// Clear app parts to avoid other assemblies being picked up
|
||||||
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
|
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
|
||||||
.AddApplicationPart(typeof(StartupController).Assembly)
|
.AddApplicationPart(typeof(StartupController).Assembly)
|
||||||
|
.AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
// Setting the naming policy to null leaves the property names as-is when serializing objects to JSON.
|
||||||
|
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||||
|
})
|
||||||
.AddControllersAsServices();
|
.AddControllersAsServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,23 +42,6 @@ namespace MediaBrowser.Api
|
|||||||
[Authenticated]
|
[Authenticated]
|
||||||
public class GetPackages : IReturn<PackageInfo[]>
|
public class GetPackages : IReturn<PackageInfo[]>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
[ApiMember(Name = "PackageType", Description = "Optional package type filter (System/UserInstalled)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string PackageType { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
|
||||||
public string TargetSystems { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "IsPremium", Description = "Optional. Filter by premium status", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
|
|
||||||
public bool? IsPremium { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "IsAdult", Description = "Optional. Filter by package that contain adult content.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
|
|
||||||
public bool? IsAdult { get; set; }
|
|
||||||
|
|
||||||
public bool? IsAppStoreEnabled { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -88,13 +71,6 @@ namespace MediaBrowser.Api
|
|||||||
/// <value>The version.</value>
|
/// <value>The version.</value>
|
||||||
[ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
[ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the update class.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The update class.</value>
|
|
||||||
[ApiMember(Name = "UpdateClass", Description = "Optional update class (Dev, Beta, Release). Defaults to Release.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public PackageVersionClass UpdateClass { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -154,23 +130,6 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
|
IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(request.TargetSystems))
|
|
||||||
{
|
|
||||||
var apps = request.TargetSystems.Split(',').Select(i => (PackageTargetSystem)Enum.Parse(typeof(PackageTargetSystem), i, true));
|
|
||||||
|
|
||||||
packages = packages.Where(p => apps.Contains(p.targetSystem));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.IsAdult.HasValue)
|
|
||||||
{
|
|
||||||
packages = packages.Where(p => p.adult == request.IsAdult.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.IsAppStoreEnabled.HasValue)
|
|
||||||
{
|
|
||||||
packages = packages.Where(p => p.enableInAppStore == request.IsAppStoreEnabled.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ToOptimizedResult(packages.ToArray());
|
return ToOptimizedResult(packages.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +145,7 @@ namespace MediaBrowser.Api
|
|||||||
packages,
|
packages,
|
||||||
request.Name,
|
request.Name,
|
||||||
string.IsNullOrEmpty(request.AssemblyGuid) ? Guid.Empty : Guid.Parse(request.AssemblyGuid),
|
string.IsNullOrEmpty(request.AssemblyGuid) ? Guid.Empty : Guid.Parse(request.AssemblyGuid),
|
||||||
string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version),
|
string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version)).FirstOrDefault();
|
||||||
request.UpdateClass).FirstOrDefault();
|
|
||||||
|
|
||||||
if (package == null)
|
if (package == null)
|
||||||
{
|
{
|
||||||
|
@ -134,7 +134,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}";
|
var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}";
|
||||||
|
|
||||||
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
var ext = outputFileExtension.ToLowerInvariant();
|
var ext = outputFileExtension?.ToLowerInvariant();
|
||||||
var folder = ServerConfigurationManager.GetTranscodePath();
|
var folder = ServerConfigurationManager.GetTranscodePath();
|
||||||
|
|
||||||
return EnableOutputInSubFolder
|
return EnableOutputInSubFolder
|
||||||
|
@ -520,10 +520,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
streamInfo.StartPositionTicks = startTimeTicks;
|
streamInfo.StartPositionTicks = startTimeTicks;
|
||||||
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
|
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
|
||||||
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
|
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
|
||||||
if (!allowAudioStreamCopy)
|
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
|
||||||
{
|
|
||||||
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
|
|
||||||
}
|
|
||||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||||
|
|
||||||
|
@ -242,11 +242,11 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
|
return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemsArray = folder.GetChildren(user, true).ToArray();
|
var itemsArray = folder.GetChildren(user, true);
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>
|
||||||
{
|
{
|
||||||
Items = itemsArray,
|
Items = itemsArray,
|
||||||
TotalRecordCount = itemsArray.Length,
|
TotalRecordCount = itemsArray.Count,
|
||||||
StartIndex = 0
|
StartIndex = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,6 @@ namespace MediaBrowser.Common
|
|||||||
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
|
||||||
bool CanSelfRestart { get; }
|
bool CanSelfRestart { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the version class of the system.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><see cref="PackageVersionClass.Release" /> or <see cref="PackageVersionClass.Beta" />.</value>
|
|
||||||
PackageVersionClass SystemUpdateLevel { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the application version.
|
/// Gets the application version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -67,7 +67,7 @@ namespace MediaBrowser.Common.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when just before the plugin is uninstalled from the server.
|
/// Called just before the plugin is uninstalled from the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void OnUninstalling()
|
public virtual void OnUninstalling()
|
||||||
{
|
{
|
||||||
@ -101,7 +101,7 @@ namespace MediaBrowser.Common.Plugins
|
|||||||
private readonly object _configurationSyncLock = new object();
|
private readonly object _configurationSyncLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The save lock.
|
/// The configuration save lock.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _configurationSaveLock = new object();
|
private readonly object _configurationSaveLock = new object();
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ namespace MediaBrowser.Common.Plugins
|
|||||||
protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath);
|
protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the plugin's configuration.
|
/// Gets or sets the plugin configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The configuration.</value>
|
/// <value>The configuration.</value>
|
||||||
public TConfigurationType Configuration
|
public TConfigurationType Configuration
|
||||||
@ -186,7 +186,7 @@ namespace MediaBrowser.Common.Plugins
|
|||||||
public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
|
public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin's configuration.
|
/// Gets the plugin configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The configuration.</value>
|
/// <value>The configuration.</value>
|
||||||
BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration;
|
BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration;
|
||||||
|
@ -28,12 +28,12 @@ namespace MediaBrowser.Common.Updates
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a plugin is updated.
|
/// Occurs when a plugin is updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
|
event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a plugin is installed.
|
/// Occurs when a plugin is installed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
|
event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the completed installations.
|
/// Gets the completed installations.
|
||||||
@ -64,12 +64,10 @@ namespace MediaBrowser.Common.Updates
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="availableVersions">The available version of the plugin.</param>
|
/// <param name="availableVersions">The available version of the plugin.</param>
|
||||||
/// <param name="minVersion">The minimum required version of the plugin.</param>
|
/// <param name="minVersion">The minimum required version of the plugin.</param>
|
||||||
/// <param name="classification">The classification of updates.</param>
|
|
||||||
/// <returns>All compatible versions ordered from newest to oldest.</returns>
|
/// <returns>All compatible versions ordered from newest to oldest.</returns>
|
||||||
IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
IEnumerable<VersionInfo> GetCompatibleVersions(
|
||||||
IEnumerable<PackageVersionInfo> availableVersions,
|
IEnumerable<VersionInfo> availableVersions,
|
||||||
Version minVersion = null,
|
Version minVersion = null);
|
||||||
PackageVersionClass classification = PackageVersionClass.Release);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all compatible versions ordered from newest to oldest.
|
/// Returns all compatible versions ordered from newest to oldest.
|
||||||
@ -78,21 +76,19 @@ namespace MediaBrowser.Common.Updates
|
|||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <param name="guid">The guid of the plugin.</param>
|
/// <param name="guid">The guid of the plugin.</param>
|
||||||
/// <param name="minVersion">The minimum required version of the plugin.</param>
|
/// <param name="minVersion">The minimum required version of the plugin.</param>
|
||||||
/// <param name="classification">The classification.</param>
|
|
||||||
/// <returns>All compatible versions ordered from newest to oldest.</returns>
|
/// <returns>All compatible versions ordered from newest to oldest.</returns>
|
||||||
IEnumerable<PackageVersionInfo> GetCompatibleVersions(
|
IEnumerable<VersionInfo> GetCompatibleVersions(
|
||||||
IEnumerable<PackageInfo> availablePackages,
|
IEnumerable<PackageInfo> availablePackages,
|
||||||
string name = null,
|
string name = null,
|
||||||
Guid guid = default,
|
Guid guid = default,
|
||||||
Version minVersion = null,
|
Version minVersion = null);
|
||||||
PackageVersionClass classification = PackageVersionClass.Release);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the available plugin updates.
|
/// Returns the available plugin updates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The available plugin updates.</returns>
|
/// <returns>The available plugin updates.</returns>
|
||||||
Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
|
Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Installs the package.
|
/// Installs the package.
|
||||||
@ -100,7 +96,7 @@ namespace MediaBrowser.Common.Updates
|
|||||||
/// <param name="package">The package.</param>
|
/// <param name="package">The package.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns><see cref="Task" />.</returns>
|
/// <returns><see cref="Task" />.</returns>
|
||||||
Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default);
|
Task InstallPackage(VersionInfo package, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uninstalls a plugin.
|
/// Uninstalls a plugin.
|
||||||
|
@ -8,6 +8,6 @@ namespace MediaBrowser.Common.Updates
|
|||||||
{
|
{
|
||||||
public InstallationInfo InstallationInfo { get; set; }
|
public InstallationInfo InstallationInfo { get; set; }
|
||||||
|
|
||||||
public PackageVersionInfo PackageVersionInfo { get; set; }
|
public VersionInfo VersionInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -549,7 +549,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public DateTime DateModified { get; set; }
|
public DateTime DateModified { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public DateTime DateLastSaved { get; set; }
|
public DateTime DateLastSaved { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
@ -2741,7 +2740,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
var list = GetEtagValues(user);
|
var list = GetEtagValues(user);
|
||||||
|
|
||||||
return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual List<string> GetEtagValues(User user)
|
protected virtual List<string> GetEtagValues(User user)
|
||||||
@ -2784,8 +2783,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var view = this as IHasCollectionType;
|
if (this is IHasCollectionType view)
|
||||||
if (view != null)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -864,7 +864,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return SortItemsByRequest(query, result);
|
return SortItemsByRequest(query, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToArray();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetItemsInternal(query).Items;
|
return GetItemsInternal(query).Items;
|
||||||
|
@ -6,30 +6,34 @@ using MediaBrowser.Model.Entities;
|
|||||||
namespace MediaBrowser.Controller.Persistence
|
namespace MediaBrowser.Controller.Persistence
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface IDisplayPreferencesRepository
|
/// Interface IDisplayPreferencesRepository.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDisplayPreferencesRepository : IRepository
|
public interface IDisplayPreferencesRepository : IRepository
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves display preferences for an item
|
/// Saves display preferences for an item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="displayPreferences">The display preferences.</param>
|
/// <param name="displayPreferences">The display preferences.</param>
|
||||||
/// <param name="userId">The user id.</param>
|
/// <param name="userId">The user id.</param>
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
void SaveDisplayPreferences(
|
||||||
void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client,
|
DisplayPreferences displayPreferences,
|
||||||
CancellationToken cancellationToken);
|
string userId,
|
||||||
|
string client,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves all display preferences for a user
|
/// Saves all display preferences for a user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="displayPreferences">The display preferences.</param>
|
/// <param name="displayPreferences">The display preferences.</param>
|
||||||
/// <param name="userId">The user id.</param>
|
/// <param name="userId">The user id.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
void SaveAllDisplayPreferences(
|
||||||
void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId,
|
IEnumerable<DisplayPreferences> displayPreferences,
|
||||||
CancellationToken cancellationToken);
|
Guid userId,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the display preferences.
|
/// Gets the display preferences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -470,7 +470,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {arguments}", inputArgument);
|
_logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -961,7 +961,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
public int? ExitCode { get; private set; }
|
public int? ExitCode { get; private set; }
|
||||||
|
|
||||||
void OnProcessExited(object sender, EventArgs e)
|
private void OnProcessExited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var process = (Process)sender;
|
var process = (Process)sender;
|
||||||
|
|
||||||
|
@ -15,9 +15,8 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
private string _baseUrl;
|
private string _baseUrl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [enable u pn p].
|
/// Gets or sets a value indicating whether to enable automatic port forwarding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [enable u pn p]; otherwise, <c>false</c>.</value>
|
|
||||||
public bool EnableUPnP { get; set; }
|
public bool EnableUPnP { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -254,7 +253,7 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
AutoRunWebApp = true;
|
AutoRunWebApp = true;
|
||||||
EnableRemoteAccess = true;
|
EnableRemoteAccess = true;
|
||||||
|
|
||||||
EnableUPnP = true;
|
EnableUPnP = false;
|
||||||
MinResumePct = 5;
|
MinResumePct = 5;
|
||||||
MaxResumePct = 90;
|
MaxResumePct = 90;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace MediaBrowser.Model.LiveTv
|
|||||||
public SeriesTimerInfoDto()
|
public SeriesTimerInfoDto()
|
||||||
{
|
{
|
||||||
ImageTags = new Dictionary<ImageType, string>();
|
ImageTags = new Dictionary<ImageType, string>();
|
||||||
Days = new DayOfWeek[] { };
|
Days = Array.Empty<DayOfWeek>();
|
||||||
Type = "SeriesTimer";
|
Type = "SeriesTimer";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,7 @@ namespace MediaBrowser.Model.Net
|
|||||||
{ ".3g2", "video/3gpp2" },
|
{ ".3g2", "video/3gpp2" },
|
||||||
{ ".mpd", "video/vnd.mpeg.dash.mpd" },
|
{ ".mpd", "video/vnd.mpeg.dash.mpd" },
|
||||||
{ ".ts", "video/mp2t" },
|
{ ".ts", "video/mp2t" },
|
||||||
|
{ ".mpegts", "video/mp2t" },
|
||||||
|
|
||||||
// Type audio
|
// Type audio
|
||||||
{ ".mp3", "audio/mpeg" },
|
{ ".mp3", "audio/mpeg" },
|
||||||
@ -123,6 +124,8 @@ namespace MediaBrowser.Model.Net
|
|||||||
{ ".xsp", "audio/xsp" },
|
{ ".xsp", "audio/xsp" },
|
||||||
{ ".dsp", "audio/dsp" },
|
{ ".dsp", "audio/dsp" },
|
||||||
{ ".flac", "audio/flac" },
|
{ ".flac", "audio/flac" },
|
||||||
|
{ ".ape", "audio/x-ape" },
|
||||||
|
{ ".wv", "audio/x-wavpack" },
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
|
private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
|
||||||
|
@ -8,17 +8,17 @@ namespace MediaBrowser.Model.Services
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Order in which Request Filters are executed.
|
/// Order in which Request Filters are executed.
|
||||||
/// <0 Executed before global request filters
|
/// <0 Executed before global request filters.
|
||||||
/// >0 Executed after global request filters
|
/// >0 Executed after global request filters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int Priority { get; }
|
int Priority { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The request filter is executed before the service.
|
/// The request filter is executed before the service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="req">The http request wrapper</param>
|
/// <param name="req">The http request wrapper.</param>
|
||||||
/// <param name="res">The http response wrapper</param>
|
/// <param name="res">The http response wrapper.</param>
|
||||||
/// <param name="requestDto">The request DTO</param>
|
/// <param name="requestDto">The request DTO.</param>
|
||||||
void RequestFilter(IRequest req, HttpResponse res, object requestDto);
|
void RequestFilter(IRequest req, HttpResponse res, object requestDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,6 @@ namespace MediaBrowser.Model.System
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemInfo : PublicSystemInfo
|
public class SystemInfo : PublicSystemInfo
|
||||||
{
|
{
|
||||||
public PackageVersionClass SystemUpdateLevel { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the display name of the operating system.
|
/// Gets or sets the display name of the operating system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class CheckForUpdateResult.
|
|
||||||
/// </summary>
|
|
||||||
public class CheckForUpdateResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance is update available.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is update available; otherwise, <c>false</c>.</value>
|
|
||||||
public bool IsUpdateAvailable { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the available version.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The available version.</value>
|
|
||||||
public string AvailableVersion
|
|
||||||
{
|
|
||||||
get => Package != null ? Package.versionStr : "0.0.0.1";
|
|
||||||
set { } // need this for the serializer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or sets package information for an available update
|
|
||||||
/// </summary>
|
|
||||||
public PackageVersionInfo Package { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,10 +8,10 @@ namespace MediaBrowser.Model.Updates
|
|||||||
public class InstallationInfo
|
public class InstallationInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the id.
|
/// Gets or sets the guid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The id.</value>
|
/// <value>The guid.</value>
|
||||||
public Guid Id { get; set; }
|
public string Guid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
@ -19,22 +19,10 @@ namespace MediaBrowser.Model.Updates
|
|||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the assembly guid.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The guid of the assembly.</value>
|
|
||||||
public string AssemblyGuid { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the version.
|
/// Gets or sets the version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The version.</value>
|
/// <value>The version.</value>
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the update class.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The update class.</value>
|
|
||||||
public PackageVersionClass UpdateClass { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,6 @@ namespace MediaBrowser.Model.Updates
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PackageInfo
|
public class PackageInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The internal id of this package.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The id.</value>
|
|
||||||
public string id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -21,59 +15,17 @@ namespace MediaBrowser.Model.Updates
|
|||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the short description.
|
/// Gets or sets a long description of the plugin containing features or helpful explanations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The short description.</value>
|
/// <value>The description.</value>
|
||||||
public string shortDescription { get; set; }
|
public string description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the overview.
|
/// Gets or sets a short overview of what the plugin does.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The overview.</value>
|
/// <value>The overview.</value>
|
||||||
public string overview { get; set; }
|
public string overview { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance is premium.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is premium; otherwise, <c>false</c>.</value>
|
|
||||||
public bool isPremium { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance is adult only content.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is adult; otherwise, <c>false</c>.</value>
|
|
||||||
public bool adult { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the rich desc URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The rich desc URL.</value>
|
|
||||||
public string richDescUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the thumb image.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The thumb image.</value>
|
|
||||||
public string thumbImage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the preview image.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The preview image.</value>
|
|
||||||
public string previewImage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the type.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The type.</value>
|
|
||||||
public string type { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the target filename.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The target filename.</value>
|
|
||||||
public string targetFilename { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the owner.
|
/// Gets or sets the owner.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -87,90 +39,24 @@ namespace MediaBrowser.Model.Updates
|
|||||||
public string category { get; set; }
|
public string category { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the catalog tile color.
|
/// The guid of the assembly associated with this plugin.
|
||||||
/// </summary>
|
|
||||||
/// <value>The owner.</value>
|
|
||||||
public string tileColor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the feature id of this package (if premium).
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The feature id.</value>
|
|
||||||
public string featureId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the registration info for this package (if premium).
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The registration info.</value>
|
|
||||||
public string regInfo { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the price for this package (if premium).
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The price.</value>
|
|
||||||
public float price { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the target system for this plug-in (Server, MBTheater, MBClassic).
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The target system.</value>
|
|
||||||
public PackageTargetSystem targetSystem { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The guid of the assembly associated with this package (if a plug-in).
|
|
||||||
/// This is used to identify the proper item for automatic updates.
|
/// This is used to identify the proper item for automatic updates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string guid { get; set; }
|
public string guid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the total number of ratings for this package.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The total ratings.</value>
|
|
||||||
public int? totalRatings { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the average rating for this package .
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The rating.</value>
|
|
||||||
public float avgRating { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether or not this package is registered.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>True if registered.</value>
|
|
||||||
public bool isRegistered { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the expiration date for this package.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>Expiration Date.</value>
|
|
||||||
public DateTime expDate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the versions.
|
/// Gets or sets the versions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The versions.</value>
|
/// <value>The versions.</value>
|
||||||
public IReadOnlyList<PackageVersionInfo> versions { get; set; }
|
public IReadOnlyList<VersionInfo> versions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether [enable in application store].
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if [enable in application store]; otherwise, <c>false</c>.</value>
|
|
||||||
public bool enableInAppStore { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the installs.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The installs.</value>
|
|
||||||
public int installs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PackageInfo"/> class.
|
/// Initializes a new instance of the <see cref="PackageInfo"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PackageInfo()
|
public PackageInfo()
|
||||||
{
|
{
|
||||||
versions = Array.Empty<PackageVersionInfo>();
|
versions = Array.Empty<VersionInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enum PackageType.
|
|
||||||
/// </summary>
|
|
||||||
public enum PackageTargetSystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Server.
|
|
||||||
/// </summary>
|
|
||||||
Server,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MB Theater.
|
|
||||||
/// </summary>
|
|
||||||
MBTheater,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MB Classic.
|
|
||||||
/// </summary>
|
|
||||||
MBClassic
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enum PackageVersionClass.
|
|
||||||
/// </summary>
|
|
||||||
public enum PackageVersionClass
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The release.
|
|
||||||
/// </summary>
|
|
||||||
Release = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The beta.
|
|
||||||
/// </summary>
|
|
||||||
Beta = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The dev.
|
|
||||||
/// </summary>
|
|
||||||
Dev = 2
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class PackageVersionInfo.
|
|
||||||
/// </summary>
|
|
||||||
public class PackageVersionInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the guid.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The guid.</value>
|
|
||||||
public string guid { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the version STR.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The version STR.</value>
|
|
||||||
public string versionStr { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _version
|
|
||||||
/// </summary>
|
|
||||||
private Version _version;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the version.
|
|
||||||
/// Had to make this an interpreted property since Protobuf can't handle Version
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The version.</value>
|
|
||||||
[JsonIgnore]
|
|
||||||
public Version Version
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_version == null)
|
|
||||||
{
|
|
||||||
var ver = versionStr;
|
|
||||||
_version = new Version(string.IsNullOrEmpty(ver) ? "0.0.0.1" : ver);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the classification.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The classification.</value>
|
|
||||||
public PackageVersionClass classification { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the description.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The description.</value>
|
|
||||||
public string description { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the required version STR.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The required version STR.</value>
|
|
||||||
public string requiredVersionStr { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the source URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The source URL.</value>
|
|
||||||
public string sourceUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the source URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The source URL.</value>
|
|
||||||
public string checksum { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the target filename.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The target filename.</value>
|
|
||||||
public string targetFilename { get; set; }
|
|
||||||
|
|
||||||
public string infoUrl { get; set; }
|
|
||||||
|
|
||||||
public string runtimes { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
58
MediaBrowser.Model/Updates/VersionInfo.cs
Normal file
58
MediaBrowser.Model/Updates/VersionInfo.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Updates
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class PackageVersionInfo.
|
||||||
|
/// </summary>
|
||||||
|
public class VersionInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The guid.</value>
|
||||||
|
public string guid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The version.</value>
|
||||||
|
public Version version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the changelog for this version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The changelog.</value>
|
||||||
|
public string changelog { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ABI that this version was built against.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The target ABI version.</value>
|
||||||
|
public string targetAbi { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the source URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The source URL.</value>
|
||||||
|
public string sourceUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a checksum for the binary.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The checksum.</value>
|
||||||
|
public string checksum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the target filename for the downloaded binary.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The target filename.</value>
|
||||||
|
public string filename { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly IEncodingManager _encodingManager;
|
private readonly IEncodingManager _encodingManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ISubtitleManager _subtitleManager;
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
private readonly IChapterManager _chapterManager;
|
private readonly IChapterManager _chapterManager;
|
||||||
@ -134,7 +133,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json,
|
||||||
IEncodingManager encodingManager,
|
IEncodingManager encodingManager,
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ISubtitleManager subtitleManager,
|
ISubtitleManager subtitleManager,
|
||||||
IChapterManager chapterManager,
|
IChapterManager chapterManager,
|
||||||
@ -149,7 +147,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_encodingManager = encodingManager;
|
_encodingManager = encodingManager;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_config = config;
|
_config = config;
|
||||||
_subtitleManager = subtitleManager;
|
_subtitleManager = subtitleManager;
|
||||||
_chapterManager = chapterManager;
|
_chapterManager = chapterManager;
|
||||||
@ -157,7 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
|
||||||
_subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem);
|
_subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
||||||
@ -202,7 +199,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
_blurayExaminer,
|
_blurayExaminer,
|
||||||
_localization,
|
_localization,
|
||||||
_encodingManager,
|
_encodingManager,
|
||||||
_fileSystem,
|
|
||||||
_config,
|
_config,
|
||||||
_subtitleManager,
|
_subtitleManager,
|
||||||
_chapterManager,
|
_chapterManager,
|
||||||
|
@ -39,7 +39,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
private readonly IBlurayExaminer _blurayExaminer;
|
private readonly IBlurayExaminer _blurayExaminer;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly IEncodingManager _encodingManager;
|
private readonly IEncodingManager _encodingManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ISubtitleManager _subtitleManager;
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
private readonly IChapterManager _chapterManager;
|
private readonly IChapterManager _chapterManager;
|
||||||
@ -56,7 +55,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
IBlurayExaminer blurayExaminer,
|
IBlurayExaminer blurayExaminer,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IEncodingManager encodingManager,
|
IEncodingManager encodingManager,
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ISubtitleManager subtitleManager,
|
ISubtitleManager subtitleManager,
|
||||||
IChapterManager chapterManager,
|
IChapterManager chapterManager,
|
||||||
@ -68,7 +66,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
_blurayExaminer = blurayExaminer;
|
_blurayExaminer = blurayExaminer;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_encodingManager = encodingManager;
|
_encodingManager = encodingManager;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_config = config;
|
_config = config;
|
||||||
_subtitleManager = subtitleManager;
|
_subtitleManager = subtitleManager;
|
||||||
_chapterManager = chapterManager;
|
_chapterManager = chapterManager;
|
||||||
@ -76,7 +73,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemUpdateType> ProbeVideo<T>(T item,
|
public async Task<ItemUpdateType> ProbeVideo<T>(
|
||||||
|
T item,
|
||||||
MetadataRefreshOptions options,
|
MetadataRefreshOptions options,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
where T : Video
|
where T : Video
|
||||||
@ -99,7 +97,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
return ItemUpdateType.MetadataImport;
|
return ItemUpdateType.MetadataImport;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (item.VideoType == VideoType.BluRay)
|
else if (item.VideoType == VideoType.BluRay)
|
||||||
{
|
{
|
||||||
var inputPath = item.Path;
|
var inputPath = item.Path;
|
||||||
@ -130,7 +127,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
return ItemUpdateType.MetadataImport;
|
return ItemUpdateType.MetadataImport;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(Video item,
|
private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
|
||||||
|
Video item,
|
||||||
string[] streamFileNames,
|
string[] streamFileNames,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -145,22 +143,24 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
protocol = _mediaSourceManager.GetPathProtocol(path);
|
protocol = _mediaSourceManager.GetPathProtocol(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
return _mediaEncoder.GetMediaInfo(
|
||||||
{
|
new MediaInfoRequest
|
||||||
PlayableStreamFileNames = streamFileNames,
|
|
||||||
ExtractChapters = true,
|
|
||||||
MediaType = DlnaProfileType.Video,
|
|
||||||
MediaSource = new MediaSourceInfo
|
|
||||||
{
|
{
|
||||||
Path = path,
|
PlayableStreamFileNames = streamFileNames,
|
||||||
Protocol = protocol,
|
ExtractChapters = true,
|
||||||
VideoType = item.VideoType
|
MediaType = DlnaProfileType.Video,
|
||||||
}
|
MediaSource = new MediaSourceInfo
|
||||||
|
{
|
||||||
}, cancellationToken);
|
Path = path,
|
||||||
|
Protocol = protocol,
|
||||||
|
VideoType = item.VideoType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task Fetch(Video video,
|
protected async Task Fetch(
|
||||||
|
Video video,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Model.MediaInfo.MediaInfo mediaInfo,
|
Model.MediaInfo.MediaInfo mediaInfo,
|
||||||
BlurayDiscInfo blurayInfo,
|
BlurayDiscInfo blurayInfo,
|
||||||
@ -491,12 +491,13 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
/// <param name="options">The refreshOptions.</param>
|
/// <param name="options">The refreshOptions.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task AddExternalSubtitles(Video video,
|
private async Task AddExternalSubtitles(
|
||||||
|
Video video,
|
||||||
List<MediaStream> currentStreams,
|
List<MediaStream> currentStreams,
|
||||||
MetadataRefreshOptions options,
|
MetadataRefreshOptions options,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var subtitleResolver = new SubtitleResolver(_localization, _fileSystem);
|
var subtitleResolver = new SubtitleResolver(_localization);
|
||||||
|
|
||||||
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
|
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
|
||||||
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
|
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
|
||||||
@ -605,7 +606,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
private string[] FetchFromDvdLib(Video item)
|
private string[] FetchFromDvdLib(Video item)
|
||||||
{
|
{
|
||||||
var path = item.Path;
|
var path = item.Path;
|
||||||
var dvd = new Dvd(path, _fileSystem);
|
var dvd = new Dvd(path);
|
||||||
|
|
||||||
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
|
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
public class SubtitleResolver
|
public class SubtitleResolver
|
||||||
{
|
{
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
@ -26,16 +25,16 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
".vtt"
|
".vtt"
|
||||||
};
|
};
|
||||||
|
|
||||||
public SubtitleResolver(ILocalizationManager localization, IFileSystem fileSystem)
|
public SubtitleResolver(ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MediaStream> GetExternalSubtitleStreams(Video video,
|
public List<MediaStream> GetExternalSubtitleStreams(
|
||||||
int startIndex,
|
Video video,
|
||||||
IDirectoryService directoryService,
|
int startIndex,
|
||||||
bool clearCache)
|
IDirectoryService directoryService,
|
||||||
|
bool clearCache)
|
||||||
{
|
{
|
||||||
var streams = new List<MediaStream>();
|
var streams = new List<MediaStream>();
|
||||||
|
|
||||||
|
@ -208,8 +208,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
|||||||
|
|
||||||
protected void ParseProviderLinks(T item, string xml)
|
protected void ParseProviderLinks(T item, string xml)
|
||||||
{
|
{
|
||||||
// Look for a match for the Regex pattern "tt" followed by 7 digits
|
// Look for a match for the Regex pattern "tt" followed by 7 or 8 digits
|
||||||
var m = Regex.Match(xml, @"tt([0-9]{7})", RegexOptions.IgnoreCase);
|
var m = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
|
||||||
if (m.Success)
|
if (m.Success)
|
||||||
{
|
{
|
||||||
item.SetProviderId(MetadataProviders.Imdb, m.Value);
|
item.SetProviderId(MetadataProviders.Imdb, m.Value);
|
||||||
|
96
README.md
96
README.md
@ -69,3 +69,99 @@ Most of the translations can be found in the web client but we have several othe
|
|||||||
<a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget">
|
<a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget">
|
||||||
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-web/multi-auto.svg" alt="Detailed Translation Status"/>
|
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-web/multi-auto.svg" alt="Detailed Translation Status"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
## Jellyfin Server
|
||||||
|
|
||||||
|
This repository contains the code for Jellyfin's backend server. Note that this is only one of many projects under the Jellyfin GitHub [organization](https://github.com/jellyfin/) on GitHub. If you want to contribute, you can start by checking out our [documentation](https://jellyfin.org/docs/general/contributing/index.html) to see what to work on.
|
||||||
|
|
||||||
|
## Server Development
|
||||||
|
|
||||||
|
These instructions will help you get set up with a local development environment in order to contribute to this repository. Before you start, please be sure to completely read our [guidelines on development contributions](https://jellyfin.org/docs/general/contributing/development.html). Note that this project is supported on all major operating systems except FreeBSD, which is still incompatible.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Before the project can be built, you must first install the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) on your system.
|
||||||
|
|
||||||
|
Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download).
|
||||||
|
|
||||||
|
### Cloning the Repository
|
||||||
|
|
||||||
|
After dependencies are installed you will need to clone a local copy of this repository. If you just want to run the server from source you can clone this repository directly, but if you are intending to contribute code changes to the project, you should [set up your own fork](https://jellyfin.org/docs/general/contributing/development.html#set-up-your-copy-of-the-repo) of the repository. The following example shows how you can clone the repository directly over HTTPS.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jellyfin/jellyfin.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installing the Web Client
|
||||||
|
|
||||||
|
The server is configured to host the static files required for the [web client](https://github.com/jellyfin/jellyfin-web) in addition to serving the backend by default. Before you can run the server, you will need to get a copy of the web client since they are not included in this repository directly.
|
||||||
|
|
||||||
|
Note that it is also possible to [host the web client separately](#hosting-the-web-client-separately) from the web server with some additional configuration, in which case you can skip this step.
|
||||||
|
|
||||||
|
There are three options to get the files for the web client.
|
||||||
|
|
||||||
|
1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
|
||||||
|
2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
|
||||||
|
3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
|
||||||
|
|
||||||
|
Once you have a copy of the built web client files, you need to copy them into a specific directory.
|
||||||
|
|
||||||
|
> `<repository root>/Mediabrowser.WebDashboard/jellyfin-web`
|
||||||
|
|
||||||
|
As part of the build process, this folder will be copied to the build output directory, where it can be accessed by the server.
|
||||||
|
|
||||||
|
### Running The Server
|
||||||
|
|
||||||
|
The following instructions will help you get the project up and running via the command line, or your preferred IDE.
|
||||||
|
|
||||||
|
#### Running With Visual Studio
|
||||||
|
|
||||||
|
To run the project with Visual Studio you can open the Solution (`.sln`) file and then press `F5` to run the server.
|
||||||
|
|
||||||
|
#### Running With Visual Studio Code
|
||||||
|
|
||||||
|
To run the project with Visual Studio Code you will first need to open the repository directory with Visual Studio Code using the `Open Folder...` option.
|
||||||
|
|
||||||
|
Second, you need to [install the recommended extensions for the workspace](https://code.visualstudio.com/docs/editor/extension-gallery#_recommended-extensions). Note that extension recommendations are classified as either "Workspace Recommendations" or "Other Recommendations", but only the "Workspace Recommendations" are required.
|
||||||
|
|
||||||
|
After the required extensions are installed, you can can run the server by pressing `F5`.
|
||||||
|
|
||||||
|
#### Running From The Command Line
|
||||||
|
|
||||||
|
To run the server from the command line you can use the `dotnet run` command. The example below shows how to do this if you have cloned the repository into a directory named `jellyfin` (the default directory name) and should work on all operating systems.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd jellyfin # Move into the repository directory
|
||||||
|
dotnet run --project Jellyfin.Server # Run the server startup project
|
||||||
|
```
|
||||||
|
|
||||||
|
A second option is to build the project and then run the resulting executable file directly. When running the executable directly you can easily add command line options. Add the `--help` flag to list details on all the supported command line options.
|
||||||
|
|
||||||
|
1. Build the project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet build # Build the project
|
||||||
|
cd bin/Debug/netcoreapp3.1 # Change into the build output directory
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
|
||||||
|
|
||||||
|
### Running The Tests
|
||||||
|
|
||||||
|
This repository also includes unit tests that are used to validate functionality as part of a CI pipeline on Azure. There are several ways to run these tests.
|
||||||
|
|
||||||
|
1. Run tests from the command line using `dotnet test`
|
||||||
|
2. Run tests in Visual Studio using the [Test Explorer](https://docs.microsoft.com/en-us/visualstudio/test/run-unit-tests-with-test-explorer)
|
||||||
|
3. Run individual tests in Visual Studio Code using the associated [CodeLens annotation](https://github.com/OmniSharp/omnisharp-vscode/wiki/How-to-run-and-debug-unit-tests)
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
The following sections describe some more advanced scenarios for running the server from source that build upon the standard instructions above.
|
||||||
|
|
||||||
|
#### Hosting The Web Client Separately
|
||||||
|
|
||||||
|
It is not necessary to host the frontend web client as part of the backend server. Hosting these two components separately may be useful for frontend developers who would prefer to host the client in a separate webpack development server for a tighter development loop. See the [jellyfin-web](https://github.com/jellyfin/jellyfin-web#getting-started) repo for instructions on how to do this.
|
||||||
|
|
||||||
|
To instruct the server not to host the web content, there is a `nowebcontent` configuration flag that must be set. This can specified using the command line switch `--nowebcontent` or the environment variable `JELLYFIN_NOWEBCONTENT=true`.
|
||||||
|
|
||||||
|
Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar.
|
||||||
|
@ -39,11 +39,11 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
[InlineData(@"[rec].mkv", "[rec].mkv", null)]
|
[InlineData(@"[rec].mkv", "[rec].mkv", null)]
|
||||||
[InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
|
[InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
|
||||||
[InlineData("Super movie(2009).mp4", "Super movie", 2009)]
|
[InlineData("Super movie(2009).mp4", "Super movie", 2009)]
|
||||||
// FIXME: [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
|
[InlineData("Drug War 2013.mp4", "Drug War", 2013)]
|
||||||
[InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
|
[InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
|
||||||
// FIXME: [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
|
[InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
|
||||||
[InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
|
[InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
|
||||||
// FIXME: [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
|
[InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
|
||||||
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
|
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
|
||||||
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
||||||
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
|
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user