Merge branch 'master' into warn17
This commit is contained in:
commit
94fe9b8f6d
148
CONTRIBUTORS.md
148
CONTRIBUTORS.md
|
@ -1,40 +1,132 @@
|
|||
# Jellyfin Contributors
|
||||
|
||||
- [JoshuaBoniface](https://github.com/joshuaboniface)
|
||||
- [nvllsvm](https://github.com/nvllsvm)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
- [dcrdev](https://github.com/dcrdev)
|
||||
- [EraYaN](https://github.com/EraYaN)
|
||||
- [flemse](https://github.com/flemse)
|
||||
- [97carmine](https://github.com/97carmine)
|
||||
- [Abbe98](https://github.com/Abbe98)
|
||||
- [agrenott](https://github.com/agrenott)
|
||||
- [AndreCarvalho](https://github.com/AndreCarvalho)
|
||||
- [anthonylavado](https://github.com/anthonylavado)
|
||||
- [Artiume](https://github.com/Artiume)
|
||||
- [AThomsen](https://github.com/AThomsen)
|
||||
- [bilde2910](https://github.com/bilde2910)
|
||||
- [bfayers](https://github.com/bfayers)
|
||||
- [Bond_009](https://github.com/Bond-009)
|
||||
- [AnthonyLavado](https://github.com/anthonylavado)
|
||||
- [sparky8251](https://github.com/sparky8251)
|
||||
- [LeoVerto](https://github.com/LeoVerto)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
- [BnMcG](https://github.com/BnMcG)
|
||||
- [Bond-009](https://github.com/Bond-009)
|
||||
- [brianjmurrell](https://github.com/brianjmurrell)
|
||||
- [bugfixin](https://github.com/bugfixin)
|
||||
- [chaosinnovator](https://github.com/chaosinnovator)
|
||||
- [ckcr4lyf](https://github.com/ckcr4lyf)
|
||||
- [crankdoofus](https://github.com/crankdoofus)
|
||||
- [crobibero](https://github.com/crobibero)
|
||||
- [cromefire](https://github.com/cromefire)
|
||||
- [cryptobank](https://github.com/cryptobank)
|
||||
- [cvium](https://github.com/cvium)
|
||||
- [wtayl0r](https://github.com/wtayl0r)
|
||||
- [TtheCreator](https://github.com/Tthecreator)
|
||||
- [dannymichel](https://github.com/dannymichel)
|
||||
- [DaveChild](https://github.com/DaveChild)
|
||||
- [dcrdev](https://github.com/dcrdev)
|
||||
- [dhartung](https://github.com/dhartung)
|
||||
- [dinki](https://github.com/dinki)
|
||||
- [dkanada](https://github.com/dkanada)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [fruhnow](https://github.com/fruhnow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [dlahoti](https://github.com/dlahoti)
|
||||
- [dmitrylyzo](https://github.com/dmitrylyzo)
|
||||
- [DMouse10462](https://github.com/DMouse10462)
|
||||
- [DrPandemic](https://github.com/DrPandemic)
|
||||
- [EraYaN](https://github.com/EraYaN)
|
||||
- [escabe](https://github.com/escabe)
|
||||
- [excelite](https://github.com/excelite)
|
||||
- [fasheng](https://github.com/fasheng)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [DrPandemic](https://github.com/drpandemic)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [Khinenw](https://github.com/HelloWorld017)
|
||||
- [ferferga](https://github.com/ferferga)
|
||||
- [fhriley](https://github.com/fhriley)
|
||||
- [nevado](https://github.com/nevado)
|
||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
||||
- [ullmie02](https://github.com/ullmie02)
|
||||
- [flemse](https://github.com/flemse)
|
||||
- [Froghut](https://github.com/Froghut)
|
||||
- [fruhnow](https://github.com/fruhnow)
|
||||
- [geilername](https://github.com/geilername)
|
||||
- [gnattu](https://github.com/gnattu)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
- [h1nk](https://github.com/h1nk)
|
||||
- [hawken93](https://github.com/hawken93)
|
||||
- [HelloWorld017](https://github.com/HelloWorld017)
|
||||
- [jftuga](https://github.com/jftuga)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [joshuaboniface](https://github.com/joshuaboniface)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
- [justinfenn](https://github.com/justinfenn)
|
||||
- [KerryRJ](https://github.com/KerryRJ)
|
||||
- [Larvitar](https://github.com/Larvitar)
|
||||
- [LeoVerto](https://github.com/LeoVerto)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
||||
- [loli10K](https://github.com/loli10K)
|
||||
- [lostmypillow](https://github.com/lostmypillow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [ManfredRichthofen](https://github.com/ManfredRichthofen)
|
||||
- [Marenz](https://github.com/Marenz)
|
||||
- [marius-luca-87](https://github.com/marius-luca-87)
|
||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
||||
- [Matt07211](https://github.com/Matt07211)
|
||||
- [mcarlton00](https://github.com/mcarlton00)
|
||||
- [mitchfizz05](https://github.com/mitchfizz05)
|
||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||
- [n8225](https://github.com/n8225)
|
||||
- [Narfinger](https://github.com/Narfinger)
|
||||
- [NathanPickard](https://github.com/NathanPickard)
|
||||
- [neilsb](https://github.com/neilsb)
|
||||
- [nevado](https://github.com/nevado)
|
||||
- [Nickbert7](https://github.com/Nickbert7)
|
||||
- [nvllsvm](https://github.com/nvllsvm)
|
||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||
- [oddstr13](https://github.com/oddstr13)
|
||||
- [petermcneil](https://github.com/petermcneil)
|
||||
- [Phlogi](https://github.com/Phlogi)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pR0Ps](https://github.com/pR0Ps)
|
||||
|
||||
- [PrplHaz4](https://github.com/PrplHaz4)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [redSpoutnik](https://github.com/redSpoutnik)
|
||||
- [ringmatter](https://github.com/ringmatter)
|
||||
- [ryan-hartzell](https://github.com/ryan-hartzell)
|
||||
- [s0urcelab](https://github.com/s0urcelab)
|
||||
- [sachk](https://github.com/sachk)
|
||||
- [sammyrc34](https://github.com/sammyrc34)
|
||||
- [samuel9554](https://github.com/samuel9554)
|
||||
- [scheidleon](https://github.com/scheidleon)
|
||||
- [sebPomme](https://github.com/sebPomme)
|
||||
- [SenorSmartyPants](https://github.com/SenorSmartyPants)
|
||||
- [shemanaev](https://github.com/shemanaev)
|
||||
- [skaro13](https://github.com/skaro13)
|
||||
- [sl1288](https://github.com/sl1288)
|
||||
- [sorinyo2004](https://github.com/sorinyo2004)
|
||||
- [sparky8251](https://github.com/sparky8251)
|
||||
- [stanionascu](https://github.com/stanionascu)
|
||||
- [stevehayles](https://github.com/stevehayles)
|
||||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
||||
- [teacupx](https://github.com/teacupx)
|
||||
- [Terror-Gene](https://github.com/Terror-Gene)
|
||||
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
|
||||
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
|
||||
- [thornbill](https://github.com/thornbill)
|
||||
- [ThreeFive-O](https://github.com/ThreeFive-O)
|
||||
- [TrisMcC](https://github.com/TrisMcC)
|
||||
- [trumblejoe](https://github.com/trumblejoe)
|
||||
- [TtheCreator](https://github.com/TtheCreator)
|
||||
- [twinkybot](https://github.com/twinkybot)
|
||||
- [Ullmie02](https://github.com/Ullmie02)
|
||||
- [Unhelpful](https://github.com/Unhelpful)
|
||||
- [viaregio](https://github.com/viaregio)
|
||||
- [vitorsemeano](https://github.com/vitorsemeano)
|
||||
- [voodoos](https://github.com/voodoos)
|
||||
- [whooo](https://github.com/whooo)
|
||||
- [WiiPlayer2](https://github.com/WiiPlayer2)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
- [wtayl0r](https://github.com/wtayl0r)
|
||||
- [Wuerfelbecher](https://github.com/Wuerfelbecher)
|
||||
- [Wunax](https://github.com/Wunax)
|
||||
- [WWWesten](https://github.com/WWWesten)
|
||||
- [WX9yMOXWId](https://github.com/WX9yMOXWId)
|
||||
- [xosdy](https://github.com/xosdy)
|
||||
- [XVicarious](https://github.com/XVicarious)
|
||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -21,6 +21,13 @@ RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --
|
|||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
||||
FROM debian:buster-slim
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
@ -31,9 +38,16 @@ COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
|||
# mesa-va-drivers: needed for VAAPI
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl ca-certificates \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
libfontconfig1 \
|
||||
libgomp1 \
|
||||
libva-drm2 \
|
||||
mesa-va-drivers \
|
||||
openssl \
|
||||
ca-certificates \
|
||||
vainfo \
|
||||
i965-va-driver \
|
||||
&& apt-get clean autoclean -y\
|
||||
&& apt-get autoremove -y\
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
|
|
|
@ -27,10 +27,35 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
|
|||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM arm32v7/debian:buster-slim
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
libssl-dev ca-certificates \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
|
||||
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
|
||||
curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
|
||||
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
|
||||
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
|
||||
apt-get update && \
|
||||
apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
jellyfin-ffmpeg \
|
||||
libssl-dev \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
libomxil-bellagio0 \
|
||||
libomxil-bellagio-bin \
|
||||
libraspberrypi0 \
|
||||
vainfo \
|
||||
libva2 \
|
||||
&& apt-get remove curl gnupg -y \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
|
@ -44,4 +69,4 @@ VOLUME /cache /config /media
|
|||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/bin/ffmpeg"]
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
|
||||
|
|
|
@ -26,10 +26,25 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
|
|||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM arm64v8/debian:buster-slim
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
libssl-dev ca-certificates \
|
||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
ffmpeg \
|
||||
libssl-dev \
|
||||
ca-certificates \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
libomxil-bellagio0 \
|
||||
libomxil-bellagio-bin \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
|
|
|
@ -15,7 +15,11 @@ namespace Emby.Dlna.ConnectionManager
|
|||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
|
||||
public ConnectionManager(
|
||||
IDlnaManager dlna,
|
||||
IServerConfigurationManager config,
|
||||
ILogger<ConnectionManager> logger,
|
||||
IHttpClient httpClient)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
ILogger logger,
|
||||
ILogger<ContentDirectory> logger,
|
||||
IHttpClient httpClient,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Globalization;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.PlayTo;
|
||||
|
@ -26,7 +26,7 @@ using MediaBrowser.Model.System;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Rssdp;
|
||||
using Rssdp.Infrastructure;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Dlna.Main
|
||||
{
|
||||
|
@ -58,7 +58,9 @@ namespace Emby.Dlna.Main
|
|||
private ISsdpCommunicationsServer _communicationsServer;
|
||||
|
||||
internal IContentDirectory ContentDirectory { get; private set; }
|
||||
|
||||
internal IConnectionManager ConnectionManager { get; private set; }
|
||||
|
||||
internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
||||
|
||||
public static DlnaEntryPoint Current;
|
||||
|
@ -106,7 +108,7 @@ namespace Emby.Dlna.Main
|
|||
libraryManager,
|
||||
config,
|
||||
userManager,
|
||||
_logger,
|
||||
loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(),
|
||||
httpClient,
|
||||
localizationManager,
|
||||
mediaSourceManager,
|
||||
|
@ -114,9 +116,16 @@ namespace Emby.Dlna.Main
|
|||
mediaEncoder,
|
||||
tvSeriesManager);
|
||||
|
||||
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
|
||||
ConnectionManager = new ConnectionManager.ConnectionManager(
|
||||
dlnaManager,
|
||||
config,
|
||||
loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(),
|
||||
httpClient);
|
||||
|
||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
|
||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(
|
||||
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(),
|
||||
httpClient,
|
||||
config);
|
||||
Current = this;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
|
||||
public MediaReceiverRegistrar(
|
||||
ILogger<MediaReceiverRegistrar> logger,
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager config)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_config = config;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace Emby.Dlna.Service
|
|||
protected IHttpClient HttpClient;
|
||||
protected ILogger Logger;
|
||||
|
||||
protected BaseService(ILogger logger, IHttpClient httpClient)
|
||||
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
|
||||
{
|
||||
Logger = logger;
|
||||
HttpClient = httpClient;
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1402
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1649
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -16,7 +22,7 @@ namespace Emby.Notifications.Api
|
|||
public class GetNotifications : IReturn<NotificationResult>
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool? IsRead { get; set; }
|
||||
|
@ -30,32 +36,34 @@ namespace Emby.Notifications.Api
|
|||
|
||||
public class Notification
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public bool IsRead { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string Url { get; set; }
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public NotificationLevel Level { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationResult
|
||||
{
|
||||
public Notification[] Notifications { get; set; }
|
||||
public IReadOnlyList<Notification> Notifications { get; set; } = Array.Empty<Notification>();
|
||||
|
||||
public int TotalRecordCount { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationsSummary
|
||||
{
|
||||
public int UnreadCount { get; set; }
|
||||
|
||||
public NotificationLevel MaxUnreadNotificationLevel { get; set; }
|
||||
}
|
||||
|
||||
|
@ -63,7 +71,7 @@ namespace Emby.Notifications.Api
|
|||
public class GetNotificationsSummary : IReturn<NotificationsSummary>
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Route("/Notifications/Types", "GET", Summary = "Gets notification types")]
|
||||
|
@ -80,16 +88,16 @@ namespace Emby.Notifications.Api
|
|||
public class AddAdminNotification : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Description { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string ImageUrl { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
[ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; set; }
|
||||
|
||||
[ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public NotificationLevel Level { get; set; }
|
||||
|
@ -99,20 +107,20 @@ namespace Emby.Notifications.Api
|
|||
public class MarkRead : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||
public string Ids { get; set; }
|
||||
public string Ids { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")]
|
||||
public class MarkUnread : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||
public string Ids { get; set; }
|
||||
public string Ids { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Authenticated]
|
||||
|
@ -127,32 +135,29 @@ namespace Emby.Notifications.Api
|
|||
_userManager = userManager;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotificationTypes request)
|
||||
{
|
||||
return _notificationManager.GetNotificationTypes();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotificationServices request)
|
||||
{
|
||||
return _notificationManager.GetNotificationServices().ToList();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotificationsSummary request)
|
||||
{
|
||||
return new NotificationsSummary
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public Task Post(AddAdminNotification request)
|
||||
{
|
||||
// This endpoint really just exists as post of a real with sickbeard
|
||||
return AddNotification(request);
|
||||
}
|
||||
|
||||
private Task AddNotification(AddAdminNotification request)
|
||||
{
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
|
@ -166,14 +171,17 @@ namespace Emby.Notifications.Api
|
|||
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public void Post(MarkRead request)
|
||||
{
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public void Post(MarkUnread request)
|
||||
{
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotifications request)
|
||||
{
|
||||
return new NotificationResult();
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -16,4 +18,16 @@
|
|||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
|
@ -13,7 +16,7 @@ namespace Emby.Notifications
|
|||
new ConfigurationStore
|
||||
{
|
||||
Key = "notifications",
|
||||
ConfigurationType = typeof (NotificationOptions)
|
||||
ConfigurationType = typeof(NotificationOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,70 +21,85 @@ using Microsoft.Extensions.Logging;
|
|||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates notifications for various system events
|
||||
/// Creates notifications for various system events.
|
||||
/// </summary>
|
||||
public class Notifications : IServerEntryPoint
|
||||
public class NotificationEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly INotificationManager _notificationManager;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IActivityManager _activityManager;
|
||||
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
|
||||
private Timer? _libraryUpdateTimer;
|
||||
|
||||
private string[] _coreNotificationTypes;
|
||||
|
||||
public Notifications(
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="activityManager">The activity manager.</param>
|
||||
/// <param name="localization">The localization manager.</param>
|
||||
/// <param name="notificationManager">The notification manager.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public NotificationEntryPoint(
|
||||
ILogger<NotificationEntryPoint> logger,
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
ILogger logger,
|
||||
INotificationManager notificationManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerApplicationHost appHost,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_activityManager = activityManager;
|
||||
_localization = localization;
|
||||
_notificationManager = notificationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_localization = localization;
|
||||
_activityManager = activityManager;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += _libraryManager_ItemAdded;
|
||||
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated += _activityManager_EntryCreated;
|
||||
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged += OnAppHostHasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated += OnActivityManagerEntryCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
|
||||
private async void OnAppHostHasPendingRestartChanged(object sender, EventArgs e)
|
||||
{
|
||||
var type = NotificationType.ServerRestartRequired.ToString();
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type,
|
||||
Name = string.Format(_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"), _appHost.Name)
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"),
|
||||
_appHost.Name)
|
||||
};
|
||||
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
private async void OnActivityManagerEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
{
|
||||
var entry = e.Argument;
|
||||
|
||||
|
@ -117,7 +132,7 @@ namespace Emby.Notifications
|
|||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
private async void OnAppHostHasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_appHost.HasUpdateAvailable)
|
||||
{
|
||||
|
@ -136,8 +151,7 @@ namespace Emby.Notifications
|
|||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
|
||||
private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
|
@ -146,14 +160,17 @@ namespace Emby.Notifications
|
|||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
if (_libraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000,
|
||||
Timeout.Infinite);
|
||||
_libraryUpdateTimer = new Timer(
|
||||
LibraryUpdateTimerCallback,
|
||||
null,
|
||||
5000,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
LibraryUpdateTimer.Change(5000, Timeout.Infinite);
|
||||
_libraryUpdateTimer.Change(5000, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_itemsAdded.Add(e.Item);
|
||||
|
@ -188,7 +205,8 @@ namespace Emby.Notifications
|
|||
{
|
||||
items = _itemsAdded.ToList();
|
||||
_itemsAdded.Clear();
|
||||
DisposeLibraryUpdateTimer();
|
||||
_libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback
|
||||
_libraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
items = items.Take(10).ToList();
|
||||
|
@ -198,7 +216,10 @@ namespace Emby.Notifications
|
|||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = NotificationType.NewLibraryContent.ToString(),
|
||||
Name = string.Format(_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"), GetItemName(item)),
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"),
|
||||
GetItemName(item)),
|
||||
Description = item.Overview
|
||||
};
|
||||
|
||||
|
@ -206,6 +227,11 @@ namespace Emby.Notifications
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human readable name for the item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>A human readable name for the item.</returns>
|
||||
public static string GetItemName(BaseItem item)
|
||||
{
|
||||
var name = item.Name;
|
||||
|
@ -219,6 +245,7 @@ namespace Emby.Notifications
|
|||
episode.IndexNumber.Value,
|
||||
name);
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
name = string.Format(
|
||||
|
@ -229,7 +256,6 @@ namespace Emby.Notifications
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (item is IHasSeries hasSeries)
|
||||
{
|
||||
name = hasSeries.SeriesName + " - " + name;
|
||||
|
@ -257,7 +283,7 @@ namespace Emby.Notifications
|
|||
return name;
|
||||
}
|
||||
|
||||
private async Task SendNotification(NotificationRequest notification, BaseItem relatedItem)
|
||||
private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -269,23 +295,37 @@ namespace Emby.Notifications
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeLibraryUpdateTimer();
|
||||
|
||||
_libraryManager.ItemAdded -= _libraryManager_ItemAdded;
|
||||
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated -= _activityManager_EntryCreated;
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeLibraryUpdateTimer()
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (LibraryUpdateTimer != null)
|
||||
if (_disposed)
|
||||
{
|
||||
LibraryUpdateTimer.Dispose();
|
||||
LibraryUpdateTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_libraryUpdateTimer?.Dispose();
|
||||
}
|
||||
|
||||
_libraryUpdateTimer = null;
|
||||
|
||||
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged -= OnAppHostHasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated -= OnActivityManagerEntryCreated;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,20 +16,32 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// NotificationManager class.
|
||||
/// </summary>
|
||||
public class NotificationManager : INotificationManager
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
private INotificationService[] _services;
|
||||
private INotificationTypeFactory[] _typeFactories;
|
||||
private INotificationService[] _services = Array.Empty<INotificationService>();
|
||||
private INotificationTypeFactory[] _typeFactories = Array.Empty<INotificationTypeFactory>();
|
||||
|
||||
public NotificationManager(ILoggerFactory loggerFactory, IUserManager userManager, IServerConfigurationManager config)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
public NotificationManager(
|
||||
ILogger<NotificationManager> logger,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_config = config;
|
||||
_logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
}
|
||||
|
||||
private NotificationOptions GetConfiguration()
|
||||
|
@ -37,12 +49,14 @@ namespace Emby.Notifications
|
|||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendNotification(request, null, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendNotification(NotificationRequest request, BaseItem relatedItem, CancellationToken cancellationToken)
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken)
|
||||
{
|
||||
var notificationType = request.NotificationType;
|
||||
|
||||
|
@ -64,7 +78,8 @@ namespace Emby.Notifications
|
|||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private Task SendNotification(NotificationRequest request,
|
||||
private Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
IEnumerable<User> users,
|
||||
string title,
|
||||
|
@ -79,7 +94,7 @@ namespace Emby.Notifications
|
|||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption options)
|
||||
private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption? options)
|
||||
{
|
||||
if (request.SendToUserMode.HasValue)
|
||||
{
|
||||
|
@ -109,7 +124,8 @@ namespace Emby.Notifications
|
|||
return request.UserIds;
|
||||
}
|
||||
|
||||
private async Task SendNotification(NotificationRequest request,
|
||||
private async Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
string title,
|
||||
string description,
|
||||
|
@ -161,12 +177,14 @@ namespace Emby.Notifications
|
|||
return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
|
||||
{
|
||||
_services = services.ToArray();
|
||||
_typeFactories = notificationTypeFactories.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var list = _typeFactories.Select(i =>
|
||||
|
@ -180,7 +198,6 @@ namespace Emby.Notifications
|
|||
_logger.LogError(ex, "Error in GetNotificationTypes");
|
||||
return new List<NotificationTypeInfo>();
|
||||
}
|
||||
|
||||
}).SelectMany(i => i).ToList();
|
||||
|
||||
var config = GetConfiguration();
|
||||
|
@ -193,13 +210,13 @@ namespace Emby.Notifications
|
|||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NameIdPair> GetNotificationServices()
|
||||
{
|
||||
return _services.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
|
||||
}).OrderBy(i => i.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
|
|
|
@ -28,7 +28,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.Activity
|
||||
{
|
||||
public class ActivityLogEntryPoint : IServerEntryPoint
|
||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IInstallationManager _installationManager;
|
||||
|
@ -38,7 +38,6 @@ namespace Emby.Server.Implementations.Activity
|
|||
private readonly ILocalizationManager _localization;
|
||||
private readonly ISubtitleManager _subManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
||||
/// <summary>
|
||||
|
@ -63,8 +62,7 @@ namespace Emby.Server.Implementations.Activity
|
|||
ILocalizationManager localization,
|
||||
IInstallationManager installationManager,
|
||||
ISubtitleManager subManager,
|
||||
IUserManager userManager,
|
||||
IServerApplicationHost appHost)
|
||||
IUserManager userManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionManager = sessionManager;
|
||||
|
@ -75,7 +73,6 @@ namespace Emby.Server.Implementations.Activity
|
|||
_installationManager = installationManager;
|
||||
_subManager = subManager;
|
||||
_userManager = userManager;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
|
@ -140,7 +137,7 @@ namespace Emby.Server.Implementations.Activity
|
|||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
||||
e.Provider,
|
||||
Notifications.Notifications.GetItemName(e.Item)),
|
||||
Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
||||
Type = "SubtitleDownloadFailure",
|
||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
ShortOverview = e.Exception.Message
|
||||
|
@ -532,6 +529,7 @@ namespace Emby.Server.Implementations.Activity
|
|||
private void CreateLogEntry(ActivityLogEntry entry)
|
||||
=> _activityManager.Create(entry);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_taskManager.TaskCompleted -= OnTaskCompleted;
|
||||
|
|
|
@ -118,7 +118,6 @@ namespace Emby.Server.Implementations
|
|||
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
|
||||
{
|
||||
private SqliteUserRepository _userRepository;
|
||||
|
||||
private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
|
||||
|
||||
/// <summary>
|
||||
|
@ -166,10 +165,9 @@ namespace Emby.Server.Implementations
|
|||
public bool IsShuttingDown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger.
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
protected ILogger Logger { get; set; }
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
private IPlugin[] _plugins;
|
||||
|
||||
|
@ -180,10 +178,9 @@ namespace Emby.Server.Implementations
|
|||
public IReadOnlyList<IPlugin> Plugins => _plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger factory.
|
||||
/// Gets the logger factory.
|
||||
/// </summary>
|
||||
/// <value>The logger factory.</value>
|
||||
public ILoggerFactory LoggerFactory { get; protected set; }
|
||||
protected ILoggerFactory LoggerFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application paths.
|
||||
|
@ -327,8 +324,6 @@ namespace Emby.Server.Implementations
|
|||
|
||||
private IMediaSourceManager MediaSourceManager { get; set; }
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installation manager.
|
||||
/// </summary>
|
||||
|
@ -366,11 +361,8 @@ namespace Emby.Server.Implementations
|
|||
IStartupOptions options,
|
||||
IFileSystem fileSystem,
|
||||
IImageEncoder imageEncoder,
|
||||
INetworkManager networkManager,
|
||||
IConfiguration configuration)
|
||||
INetworkManager networkManager)
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
XmlSerializer = new MyXmlSerializer();
|
||||
|
||||
NetworkManager = networkManager;
|
||||
|
@ -586,7 +578,8 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
}
|
||||
|
||||
public async Task InitAsync(IServiceCollection serviceCollection)
|
||||
/// <inheritdoc/>
|
||||
public async Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig)
|
||||
{
|
||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||
|
@ -619,7 +612,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
DiscoverTypes();
|
||||
|
||||
await RegisterResources(serviceCollection).ConfigureAwait(false);
|
||||
await RegisterResources(serviceCollection, startupConfig).ConfigureAwait(false);
|
||||
|
||||
ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
||||
if (string.IsNullOrEmpty(ContentRoot))
|
||||
|
@ -651,14 +644,14 @@ namespace Emby.Server.Implementations
|
|||
var response = context.Response;
|
||||
var localPath = context.Request.Path.ToString();
|
||||
|
||||
var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
|
||||
var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>());
|
||||
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers resources that classes will depend on
|
||||
/// </summary>
|
||||
protected async Task RegisterResources(IServiceCollection serviceCollection)
|
||||
protected async Task RegisterResources(IServiceCollection serviceCollection, IConfiguration startupConfig)
|
||||
{
|
||||
serviceCollection.AddMemoryCache();
|
||||
|
||||
|
@ -667,13 +660,10 @@ namespace Emby.Server.Implementations
|
|||
|
||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||
|
||||
serviceCollection.AddSingleton<IConfiguration>(_configuration);
|
||||
|
||||
serviceCollection.AddSingleton(JsonSerializer);
|
||||
|
||||
serviceCollection.AddSingleton(LoggerFactory);
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddSingleton(Logger);
|
||||
// TODO: Support for injecting ILogger should be deprecated in favour of ILogger<T> and this removed
|
||||
serviceCollection.AddSingleton<ILogger>(Logger);
|
||||
|
||||
serviceCollection.AddSingleton(FileSystemManager);
|
||||
serviceCollection.AddSingleton<TvDbClientManager>();
|
||||
|
@ -761,7 +751,7 @@ namespace Emby.Server.Implementations
|
|||
ProcessFactory,
|
||||
LocalizationManager,
|
||||
() => SubtitleEncoder,
|
||||
_configuration,
|
||||
startupConfig,
|
||||
StartupOptions.FFmpegPath);
|
||||
serviceCollection.AddSingleton(MediaEncoder);
|
||||
|
||||
|
@ -783,7 +773,7 @@ namespace Emby.Server.Implementations
|
|||
this,
|
||||
LoggerFactory.CreateLogger<HttpListenerHost>(),
|
||||
ServerConfigurationManager,
|
||||
_configuration,
|
||||
startupConfig,
|
||||
NetworkManager,
|
||||
JsonSerializer,
|
||||
XmlSerializer,
|
||||
|
@ -846,7 +836,10 @@ namespace Emby.Server.Implementations
|
|||
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(UserViewManager);
|
||||
|
||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||
NotificationManager = new NotificationManager(
|
||||
LoggerFactory.CreateLogger<NotificationManager>(),
|
||||
UserManager,
|
||||
ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(NotificationManager);
|
||||
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
||||
|
@ -1202,7 +1195,7 @@ namespace Emby.Server.Implementations
|
|||
});
|
||||
}
|
||||
|
||||
protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger);
|
||||
protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(LoggerFactory.CreateLogger<WebSocketSharpListener>());
|
||||
|
||||
private CertificateInfo GetCertificateInfo(bool generateCertificate)
|
||||
{
|
||||
|
|
|
@ -19,7 +19,11 @@ namespace Emby.Server.Implementations.Channels
|
|||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public RefreshChannelsScheduledTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
|
||||
public RefreshChannelsScheduledTask(
|
||||
IChannelManager channelManager,
|
||||
IUserManager userManager,
|
||||
ILogger<RefreshChannelsScheduledTask> logger,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_channelManager = channelManager;
|
||||
_userManager = userManager;
|
||||
|
|
|
@ -347,7 +347,10 @@ namespace Emby.Server.Implementations.Collections
|
|||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
|
||||
public CollectionManagerEntryPoint(
|
||||
ICollectionManager collectionManager,
|
||||
IServerConfigurationManager config,
|
||||
ILogger<CollectionManagerEntryPoint> logger)
|
||||
{
|
||||
_collectionManager = (CollectionManager)collectionManager;
|
||||
_config = config;
|
||||
|
|
|
@ -8,7 +8,6 @@ namespace Emby.Server.Implementations
|
|||
public static Dictionary<string, string> Configuration => new Dictionary<string, string>
|
||||
{
|
||||
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
|
||||
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" }
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Emby.Server.Implementations.Data
|
|||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
|
|
@ -406,7 +406,10 @@ namespace Emby.Server.Implementations.Devices
|
|||
private readonly IServerConfigurationManager _config;
|
||||
private ILogger _logger;
|
||||
|
||||
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
|
||||
public DeviceManagerEntryPoint(
|
||||
IDeviceManager deviceManager,
|
||||
IServerConfigurationManager config,
|
||||
ILogger<DeviceManagerEntryPoint> logger)
|
||||
{
|
||||
_deviceManager = (DeviceManager)deviceManager;
|
||||
_config = config;
|
||||
|
|
|
@ -55,7 +55,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
||||
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager)
|
||||
public LibraryChangedNotifier(
|
||||
ILibraryManager libraryManager,
|
||||
ISessionManager sessionManager,
|
||||
IUserManager userManager,
|
||||
ILogger<LibraryChangedNotifier> logger,
|
||||
IProviderManager providerManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_sessionManager = sessionManager;
|
||||
|
|
|
@ -12,14 +12,18 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
public class RecordingNotifier : IServerEntryPoint
|
||||
public sealed class RecordingNotifier : IServerEntryPoint
|
||||
{
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RecordingNotifier(ISessionManager sessionManager, IUserManager userManager, ILogger logger, ILiveTvManager liveTvManager)
|
||||
public RecordingNotifier(
|
||||
ISessionManager sessionManager,
|
||||
IUserManager userManager,
|
||||
ILogger<RecordingNotifier> logger,
|
||||
ILiveTvManager liveTvManager)
|
||||
{
|
||||
_sessionManager = sessionManager;
|
||||
_userManager = userManager;
|
||||
|
@ -27,32 +31,33 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
_liveTvManager = liveTvManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
|
||||
_liveTvManager.TimerCancelled += OnLiveTvManagerTimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled += OnLiveTvManagerSeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated += OnLiveTvManagerTimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated += OnLiveTvManagerSeriesTimerCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("SeriesTimerCreated", e.Argument);
|
||||
}
|
||||
|
||||
private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("TimerCreated", e.Argument);
|
||||
}
|
||||
|
||||
private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("SeriesTimerCancelled", e.Argument);
|
||||
}
|
||||
|
||||
private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("TimerCancelled", e.Argument);
|
||||
}
|
||||
|
@ -63,11 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
try
|
||||
{
|
||||
await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// TODO Log exception or Investigate and properly fix.
|
||||
await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -75,12 +76,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
|
||||
_liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
|
@ -15,21 +14,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
/// </summary>
|
||||
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The user manager.
|
||||
/// </summary>
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private IFileSystem _fileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
|
||||
/// </summary>
|
||||
public RefreshUsersMetadata(ILogger logger, IUserManager userManager, IFileSystem fileSystem)
|
||||
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
|
|
@ -3,37 +3,28 @@ using Emby.Server.Implementations.Browser;
|
|||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Class StartupWizard.
|
||||
/// </summary>
|
||||
public class StartupWizard : IServerEntryPoint
|
||||
public sealed class StartupWizard : IServerEntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The app host.
|
||||
/// </summary>
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
/// <summary>
|
||||
/// The user manager.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private IServerConfigurationManager _config;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StartupWizard"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config)
|
||||
public StartupWizard(IServerApplicationHost appHost, IServerConfigurationManager config)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ using System.Threading.Tasks;
|
|||
using Emby.Server.Implementations.Udp;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
|
@ -23,9 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
/// <summary>
|
||||
/// The UDP server.
|
||||
|
@ -64,7 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
_cancellationTokenSource.Cancel();
|
||||
_udpServer.Dispose();
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
_udpServer = null;
|
||||
|
||||
|
|
|
@ -12,39 +12,38 @@ using MediaBrowser.Controller.Plugins;
|
|||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
public class UserDataChangeNotifier : IServerEntryPoint
|
||||
public sealed class UserDataChangeNotifier : IServerEntryPoint
|
||||
{
|
||||
private const int UpdateDuration = 500;
|
||||
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private Timer UpdateTimer { get; set; }
|
||||
private const int UpdateDuration = 500;
|
||||
|
||||
private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
|
||||
|
||||
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager)
|
||||
private readonly object _syncLock = new object();
|
||||
private Timer _updateTimer;
|
||||
|
||||
|
||||
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
|
||||
{
|
||||
_userDataManager = userDataManager;
|
||||
_sessionManager = sessionManager;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
|
||||
_userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
|
||||
void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e)
|
||||
{
|
||||
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
|
||||
{
|
||||
|
@ -53,14 +52,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (UpdateTimer == null)
|
||||
if (_updateTimer == null)
|
||||
{
|
||||
UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration,
|
||||
Timeout.Infinite);
|
||||
_updateTimer = new Timer(
|
||||
UpdateTimerCallback,
|
||||
null,
|
||||
UpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTimer.Change(UpdateDuration, Timeout.Infinite);
|
||||
_updateTimer.Change(UpdateDuration, Timeout.Infinite);
|
||||
}
|
||||
|
||||
if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem> keys))
|
||||
|
@ -96,10 +98,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
var task = SendNotifications(changes, CancellationToken.None);
|
||||
|
||||
if (UpdateTimer != null)
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
UpdateTimer.Dispose();
|
||||
UpdateTimer = null;
|
||||
_updateTimer.Dispose();
|
||||
_updateTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,13 +146,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (UpdateTimer != null)
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
UpdateTimer.Dispose();
|
||||
UpdateTimer = null;
|
||||
_updateTimer.Dispose();
|
||||
_updateTimer = null;
|
||||
}
|
||||
|
||||
_userDataManager.UserDataSaved -= _userDataManager_UserDataSaved;
|
||||
_userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||
{
|
||||
_logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
|
||||
url = url.Replace(userInfo + '@', string.Empty);
|
||||
url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(method, url);
|
||||
|
|
|
@ -39,9 +39,9 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
private readonly Func<Type, Func<string, object>> _funcParseFn;
|
||||
private readonly string _defaultRedirectPath;
|
||||
private readonly string _baseUrlPrefix;
|
||||
private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
|
||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private bool _disposed = false;
|
||||
|
||||
public HttpListenerHost(
|
||||
|
@ -71,6 +71,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
|
||||
}
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
public Action<IRequest, HttpResponse, object>[] ResponseFilters { get; set; }
|
||||
|
||||
public static HttpListenerHost Instance { get; protected set; }
|
||||
|
@ -81,8 +83,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
public ServiceController ServiceController { get; private set; }
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
return _appHost.CreateInstance(type);
|
||||
|
@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
private static string NormalizeUrlPath(string path)
|
||||
{
|
||||
if (path.StartsWith("/"))
|
||||
if (path.Length > 0 && path[0] == '/')
|
||||
{
|
||||
// If the path begins with a leading slash, just return it as-is
|
||||
return path;
|
||||
|
@ -130,13 +130,13 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
public Type GetServiceTypeByRequest(Type requestType)
|
||||
{
|
||||
ServiceOperationsMap.TryGetValue(requestType, out var serviceType);
|
||||
_serviceOperationsMap.TryGetValue(requestType, out var serviceType);
|
||||
return serviceType;
|
||||
}
|
||||
|
||||
public void AddServiceInfo(Type serviceType, Type requestType)
|
||||
{
|
||||
ServiceOperationsMap[requestType] = serviceType;
|
||||
_serviceOperationsMap[requestType] = serviceType;
|
||||
}
|
||||
|
||||
private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType)
|
||||
|
@ -198,7 +198,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
else
|
||||
{
|
||||
var inners = agg.InnerExceptions;
|
||||
if (inners != null && inners.Count > 0)
|
||||
if (inners.Count > 0)
|
||||
{
|
||||
return GetActualException(inners[0]);
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return true;
|
||||
}
|
||||
|
||||
host = host ?? string.Empty;
|
||||
host ??= string.Empty;
|
||||
|
||||
if (_networkManager.IsInPrivateAddressSpace(host))
|
||||
{
|
||||
|
@ -432,7 +432,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridable method that can be used to implement a custom hnandler
|
||||
/// Overridable method that can be used to implement a custom handler.
|
||||
/// </summary>
|
||||
public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.IsNullOrEmpty(localPath)
|
||||
|| !localPath.StartsWith(_baseUrlPrefix))
|
||||
|| !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Always redirect back to the default path if the base prefix is invalid or missing
|
||||
_logger.LogDebug("Normalizing a URL at {0}", localPath);
|
||||
|
@ -692,7 +692,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
|
|
|
@ -5,7 +5,9 @@ namespace Emby.Server.Implementations.IO
|
|||
public class ExtendedFileSystemInfo
|
||||
{
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
public bool IsReadOnly { get; set; }
|
||||
|
||||
public bool Exists { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,27 +14,29 @@ namespace Emby.Server.Implementations.IO
|
|||
{
|
||||
public class FileRefresher : IDisposable
|
||||
{
|
||||
private ILogger Logger { get; set; }
|
||||
private ILibraryManager LibraryManager { get; set; }
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private Timer _timer;
|
||||
private readonly object _timerLock = new object();
|
||||
public string Path { get; private set; }
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
|
||||
public event EventHandler<EventArgs> Completed;
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private readonly object _timerLock = new object();
|
||||
private Timer _timer;
|
||||
|
||||
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
logger.LogDebug("New file refresher created for {0}", path);
|
||||
Path = path;
|
||||
|
||||
ConfigurationManager = configurationManager;
|
||||
LibraryManager = libraryManager;
|
||||
Logger = logger;
|
||||
_configurationManager = configurationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
AddPath(path);
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> Completed;
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
private void AddAffectedPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
|
@ -79,11 +81,11 @@ namespace Emby.Server.Implementations.IO
|
|||
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
_timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +94,7 @@ namespace Emby.Server.Implementations.IO
|
|||
{
|
||||
lock (_timerLock)
|
||||
{
|
||||
Logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
|
||||
_logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
|
||||
|
||||
Path = path;
|
||||
AddAffectedPath(path);
|
||||
|
@ -115,7 +117,7 @@ namespace Emby.Server.Implementations.IO
|
|||
paths = _affectedPaths.ToList();
|
||||
}
|
||||
|
||||
Logger.LogDebug("Timer stopped.");
|
||||
_logger.LogDebug("Timer stopped.");
|
||||
|
||||
DisposeTimer();
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
|
@ -126,7 +128,7 @@ namespace Emby.Server.Implementations.IO
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error processing directory changes");
|
||||
_logger.LogError(ex, "Error processing directory changes");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +148,7 @@ namespace Emby.Server.Implementations.IO
|
|||
continue;
|
||||
}
|
||||
|
||||
Logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
|
||||
_logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -157,11 +159,11 @@ namespace Emby.Server.Implementations.IO
|
|||
// For now swallow and log.
|
||||
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
||||
// Should we remove it from it's parent?
|
||||
Logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +179,7 @@ namespace Emby.Server.Implementations.IO
|
|||
|
||||
while (item == null && !string.IsNullOrEmpty(path))
|
||||
{
|
||||
item = LibraryManager.FindByPath(path, null);
|
||||
item = _libraryManager.FindByPath(path, null);
|
||||
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
}
|
||||
|
|
|
@ -943,7 +943,6 @@ namespace Emby.Server.Implementations.Library
|
|||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
Name = name,
|
||||
DtoOptions = options
|
||||
|
||||
}).Cast<MusicArtist>()
|
||||
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
|
||||
.Cast<T>()
|
||||
|
@ -1079,7 +1078,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96));
|
||||
innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
|
||||
|
||||
// Validate the entire media library
|
||||
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
|
||||
|
|
|
@ -27,7 +27,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
|
||||
public MusicArtistResolver(
|
||||
ILogger<MusicArtistResolver> logger,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
|
|
|
@ -5,38 +5,33 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.LocalMetadata.Savers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items.
|
||||
/// </summary>
|
||||
public class PlaylistResolver : FolderResolver<Playlist>
|
||||
{
|
||||
private string[] SupportedCollectionTypes = new string[] {
|
||||
|
||||
private string[] _musicPlaylistCollectionTypes = new string[] {
|
||||
string.Empty,
|
||||
CollectionType.Music
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the specified args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>BoxSet.</returns>
|
||||
/// <inheritdoc/>
|
||||
protected override Playlist Resolve(ItemResolveArgs args)
|
||||
{
|
||||
// It's a boxset if all of the following conditions are met:
|
||||
// Is a Directory
|
||||
// Contains [playlist] in the path
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var filename = Path.GetFileName(args.Path);
|
||||
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// It's a boxset if the path is a directory with [playlist] in it's the name
|
||||
// TODO: Should this use Path.GetDirectoryName() instead?
|
||||
bool isBoxSet = Path.GetFileName(args.Path)
|
||||
?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)
|
||||
?? false;
|
||||
if (isBoxSet)
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
|
@ -44,21 +39,32 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||
Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SupportedCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
|
||||
// It's a directory-based playlist if the directory contains a playlist file
|
||||
var filePaths = Directory.EnumerateFiles(args.Path);
|
||||
if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
return new Playlist
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||
IsInMixedFolder = true
|
||||
};
|
||||
}
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileName(args.Path)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is a music playlist file
|
||||
// It should have the correct collection type and a supported file extension
|
||||
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||
IsInMixedFolder = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="localization">The localization</param>
|
||||
/// <param name="logger">The logger</param>
|
||||
public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger logger)
|
||||
public SeasonResolver(
|
||||
IServerConfigurationManager config,
|
||||
ILibraryManager libraryManager,
|
||||
ILocalizationManager localization,
|
||||
ILogger<SeasonResolver> logger)
|
||||
{
|
||||
_config = config;
|
||||
_libraryManager = libraryManager;
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
|
||||
public SeriesResolver(IFileSystem fileSystem, ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
|
|
|
@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public ArtistsPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<ArtistsPostScanTask> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
|
|
@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public GenresPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<GenresPostScanTask> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
|
|
@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public MusicGenresPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<MusicGenresPostScanTask> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
|
|
@ -26,7 +26,10 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
public StudiosPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<StudiosPostScanTask> logger,
|
||||
IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
|
|
@ -29,7 +29,6 @@ using MediaBrowser.Model.Diagnostics;
|
|||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
@ -80,7 +79,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
IServerApplicationHost appHost,
|
||||
IStreamHelper streamHelper,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
ILogger logger,
|
||||
ILogger<EmbyTV> logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager config,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
|
||||
|
@ -5,11 +8,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
public class EntryPoint : IServerEntryPoint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
return EmbyTV.Current.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -21,7 +24,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
if (info.SeasonNumber.HasValue && info.EpisodeNumber.HasValue)
|
||||
{
|
||||
name += string.Format(" S{0}E{1}", info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture), info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
|
||||
name += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" S{0}E{1}",
|
||||
info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture),
|
||||
info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
|
||||
addHyphen = false;
|
||||
}
|
||||
else if (info.OriginalAirDate.HasValue)
|
||||
|
@ -32,7 +39,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
else
|
||||
{
|
||||
name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd");
|
||||
name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -67,14 +74,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
date = date.ToLocalTime();
|
||||
|
||||
return string.Format("{0}_{1}_{2}_{3}_{4}_{5}",
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}_{1}_{2}_{3}_{4}_{5}",
|
||||
date.Year.ToString("0000", CultureInfo.InvariantCulture),
|
||||
date.Month.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Day.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Hour.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Minute.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Second.ToString("00", CultureInfo.InvariantCulture)
|
||||
);
|
||||
date.Second.ToString("00", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
@ -12,6 +15,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Add(SeriesTimerInfo item)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.Id))
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -30,7 +33,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
|
||||
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
||||
|
||||
public SchedulesDirect(ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IApplicationHost appHost)
|
||||
public SchedulesDirect(
|
||||
ILogger<SchedulesDirect> logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IHttpClient httpClient,
|
||||
IApplicationHost appHost)
|
||||
{
|
||||
_logger = logger;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -31,7 +34,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
public XmlTvListingsProvider(
|
||||
IServerConfigurationManager config,
|
||||
IHttpClient httpClient,
|
||||
ILogger logger,
|
||||
ILogger<XmlTvListingsProvider> logger,
|
||||
IFileSystem fileSystem,
|
||||
IZipClient zipClient)
|
||||
{
|
||||
|
@ -91,12 +94,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
{
|
||||
using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
|
||||
{
|
||||
await gzStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
await gzStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,52 +1,48 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
public class LiveTvMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const char StreamIdDelimeter = '_';
|
||||
private const string StreamIdDelimeterString = "_";
|
||||
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private IApplicationPaths _appPaths;
|
||||
|
||||
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerApplicationHost appHost)
|
||||
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, ILogger<LiveTvMediaSourceProvider> logger, IMediaSourceManager mediaSourceManager, IServerApplicationHost appHost)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appHost = appHost;
|
||||
_logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var baseItem = (BaseItem)item;
|
||||
|
||||
if (baseItem.SourceType == SourceType.LiveTV)
|
||||
if (item.SourceType == SourceType.LiveTV)
|
||||
{
|
||||
var activeRecordingInfo = _liveTvManager.GetActiveRecordingInfo(item.Path);
|
||||
|
||||
if (string.IsNullOrEmpty(baseItem.Path) || activeRecordingInfo != null)
|
||||
if (string.IsNullOrEmpty(item.Path) || activeRecordingInfo != null)
|
||||
{
|
||||
return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
|
||||
}
|
||||
|
@ -55,10 +51,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(Array.Empty<MediaSourceInfo>());
|
||||
}
|
||||
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const char StreamIdDelimeter = '_';
|
||||
private const string StreamIdDelimeterString = "_";
|
||||
|
||||
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<MediaSourceInfo> sources;
|
||||
|
@ -91,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
foreach (var source in list)
|
||||
{
|
||||
source.Type = MediaSourceType.Default;
|
||||
source.BufferMs = source.BufferMs ?? 1500;
|
||||
source.BufferMs ??= 1500;
|
||||
|
||||
if (source.RequiresOpening || forceRequireOpening)
|
||||
{
|
||||
|
@ -100,11 +92,14 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (source.RequiresOpening)
|
||||
{
|
||||
var openKeys = new List<string>();
|
||||
openKeys.Add(item.GetType().Name);
|
||||
openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
openKeys.Add(source.Id ?? string.Empty);
|
||||
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
|
||||
var openKeys = new List<string>
|
||||
{
|
||||
item.GetType().Name,
|
||||
item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
source.Id ?? string.Empty
|
||||
};
|
||||
|
||||
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys);
|
||||
}
|
||||
|
||||
// Dummy this up so that direct play checks can still run
|
||||
|
@ -114,11 +109,12 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("MediaSources: {0}", _jsonSerializer.SerializeToString(list));
|
||||
_logger.LogDebug("MediaSources: {@MediaSources}", list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -36,7 +39,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
|
||||
public HdHomerunHost(
|
||||
IServerConfigurationManager config,
|
||||
ILogger logger,
|
||||
ILogger<HdHomerunHost> logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IHttpClient httpClient,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
|
@ -2,22 +2,22 @@
|
|||
"Albums": "ألبومات",
|
||||
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
|
||||
"Application": "التطبيق",
|
||||
"Artists": "الفنان",
|
||||
"Artists": "الفنانين",
|
||||
"AuthenticationSucceededWithUserName": "{0} سجل الدخول بنجاح",
|
||||
"Books": "كتب",
|
||||
"CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}",
|
||||
"Channels": "القنوات",
|
||||
"ChapterNameValue": "الباب {0}",
|
||||
"ChapterNameValue": "فصل {0}",
|
||||
"Collections": "مجموعات",
|
||||
"DeviceOfflineWithName": "تم قطع اتصال {0}",
|
||||
"DeviceOfflineWithName": "قُطِع الاتصال بـ{0}",
|
||||
"DeviceOnlineWithName": "{0} متصل",
|
||||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||
"Favorites": "التفضيلات",
|
||||
"Favorites": "المفضلة",
|
||||
"Folders": "المجلدات",
|
||||
"Genres": "أنواع الأفلام",
|
||||
"Genres": "الأنواع",
|
||||
"HeaderAlbumArtists": "فناني الألبومات",
|
||||
"HeaderCameraUploads": "تحميلات الكاميرا",
|
||||
"HeaderContinueWatching": "استئناف المشاهدة",
|
||||
"HeaderContinueWatching": "استئناف",
|
||||
"HeaderFavoriteAlbums": "الألبومات المفضلة",
|
||||
"HeaderFavoriteArtists": "الفنانون المفضلون",
|
||||
"HeaderFavoriteEpisodes": "الحلقات المفضلة",
|
||||
|
@ -31,28 +31,28 @@
|
|||
"ItemAddedWithName": "تم إضافة {0} للمكتبة",
|
||||
"ItemRemovedWithName": "تم إزالة {0} من المكتبة",
|
||||
"LabelIpAddressValue": "عنوان الآي بي: {0}",
|
||||
"LabelRunningTimeValue": "وقت التشغيل: {0}",
|
||||
"LabelRunningTimeValue": "المدة: {0}",
|
||||
"Latest": "الأحدث",
|
||||
"MessageApplicationUpdated": "لقد تم تحديث خادم أمبي",
|
||||
"MessageApplicationUpdated": "لقد تم تحديث خادم Jellyfin",
|
||||
"MessageApplicationUpdatedTo": "تم تحديث سيرفر Jellyfin الى {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث إعدادات الخادم في قسم {0}",
|
||||
"MessageServerConfigurationUpdated": "تم تحديث إعدادات الخادم",
|
||||
"MixedContent": "محتوى مخلوط",
|
||||
"MixedContent": "محتوى مختلط",
|
||||
"Movies": "الأفلام",
|
||||
"Music": "الموسيقى",
|
||||
"MusicVideos": "الفيديوهات الموسيقية",
|
||||
"NameInstallFailed": "فشل التثبيت {0}",
|
||||
"NameSeasonNumber": "الموسم {0}",
|
||||
"NameSeasonUnknown": "الموسم غير معروف",
|
||||
"NewVersionIsAvailable": "نسخة حديثة من سيرفر Jellyfin متوفرة للتحميل .",
|
||||
"NewVersionIsAvailable": "نسخة جديدة من سيرفر Jellyfin متوفرة للتحميل.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق",
|
||||
"NotificationOptionApplicationUpdateInstalled": "تم تحديث التطبيق",
|
||||
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
|
||||
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
|
||||
"NotificationOptionCameraImageUploaded": "تم رقع صورة الكاميرا",
|
||||
"NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا",
|
||||
"NotificationOptionInstallationFailed": "فشل في التثبيت",
|
||||
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
|
||||
"NotificationOptionPluginError": "فشل في الملحق",
|
||||
"NotificationOptionNewLibraryContent": "أُضِيفَ محتوى جديد",
|
||||
"NotificationOptionPluginError": "فشل في الـPlugin",
|
||||
"NotificationOptionPluginInstalled": "تم تثبيت الملحق",
|
||||
"NotificationOptionPluginUninstalled": "تمت إزالة الملحق",
|
||||
"NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Kunstnere",
|
||||
"AuthenticationSucceededWithUserName": "{0} bekræftet med succes",
|
||||
"AuthenticationSucceededWithUserName": "{0} succesfuldt autentificeret",
|
||||
"Books": "Bøger",
|
||||
"CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
|
||||
"Channels": "Kanaler",
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"Albums": "Alben",
|
||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||
"Application": "Anwendung",
|
||||
"AppDeviceValues": "Anw: {0}, Gerät: {1}",
|
||||
"Application": "Programm",
|
||||
"Artists": "Interpreten",
|
||||
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
|
||||
"Books": "Bücher",
|
||||
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
|
||||
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
||||
"Channels": "Kanäle",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Sammlungen",
|
||||
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||
"DeviceOfflineWithName": "{0} hat die Verbindung getrennt",
|
||||
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||
"Favorites": "Favoriten",
|
||||
|
@ -17,7 +17,7 @@
|
|||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album-Interpreten",
|
||||
"HeaderCameraUploads": "Kamera-Uploads",
|
||||
"HeaderContinueWatching": "Weiterschauen",
|
||||
"HeaderContinueWatching": "Fortsetzen",
|
||||
"HeaderFavoriteAlbums": "Lieblingsalben",
|
||||
"HeaderFavoriteArtists": "Lieblings-Interpreten",
|
||||
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||
|
|
|
@ -10,5 +10,12 @@
|
|||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
||||
"Application": "Aplicación",
|
||||
"AppDeviceValues": "App: {0}, Dispositivo: {1}"
|
||||
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
|
||||
"HeaderContinueWatching": "Continuar Viendo",
|
||||
"HeaderCameraUploads": "Subidas de Cámara",
|
||||
"HeaderAlbumArtists": "Artistas del Álbum",
|
||||
"Genres": "Géneros",
|
||||
"Folders": "Carpetas",
|
||||
"Favorites": "Favoritos",
|
||||
"FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido de {0}"
|
||||
}
|
||||
|
|
|
@ -17,31 +17,31 @@
|
|||
"LabelIpAddressValue": "IP-osoite: {0}",
|
||||
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
|
||||
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
||||
"Inherit": "Periä",
|
||||
"Inherit": "Periytyä",
|
||||
"HomeVideos": "Kotivideot",
|
||||
"HeaderRecordingGroups": "Äänitysryhmät",
|
||||
"HeaderRecordingGroups": "Nauhoitusryhmät",
|
||||
"HeaderNextUp": "Seuraavaksi",
|
||||
"HeaderFavoriteSongs": "Lempikappaleet",
|
||||
"HeaderFavoriteShows": "Lempisarjat",
|
||||
"HeaderFavoriteEpisodes": "Lempijaksot",
|
||||
"HeaderCameraUploads": "Kamerasta lähetetyt",
|
||||
"HeaderCameraUploads": "Kameralataukset",
|
||||
"HeaderFavoriteArtists": "Lempiartistit",
|
||||
"HeaderFavoriteAlbums": "Lempialbumit",
|
||||
"HeaderContinueWatching": "Jatka katsomista",
|
||||
"HeaderAlbumArtists": "Albumin artistit",
|
||||
"Genres": "Tyylilaji",
|
||||
"HeaderAlbumArtists": "Albumin esittäjä",
|
||||
"Genres": "Tyylilajit",
|
||||
"Folders": "Kansiot",
|
||||
"Favorites": "Suosikit",
|
||||
"FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys kohteesta {0}",
|
||||
"DeviceOnlineWithName": "{0} on yhdistynyt",
|
||||
"FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}",
|
||||
"DeviceOnlineWithName": "{0} on yhdistetty",
|
||||
"DeviceOfflineWithName": "{0} on katkaissut yhteytensä",
|
||||
"Collections": "Kokoelmat",
|
||||
"ChapterNameValue": "Luku: {0}",
|
||||
"Channels": "Kanavat",
|
||||
"CameraImageUploadedFrom": "Uusi kamerakuva on lähetetty kohteesta {0}",
|
||||
"CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
|
||||
"Books": "Kirjat",
|
||||
"AuthenticationSucceededWithUserName": "{0} todennettu onnistuneesti",
|
||||
"Artists": "Artistit",
|
||||
"AuthenticationSucceededWithUserName": "{0} todennus onnistui",
|
||||
"Artists": "Esiintyjät",
|
||||
"Application": "Sovellus",
|
||||
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
|
||||
"Albums": "Albumit",
|
||||
|
@ -76,21 +76,21 @@
|
|||
"Shows": "Ohjelmat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
||||
"ProviderValue": "Palveluntarjoaja: {0}",
|
||||
"Plugin": "Laajennus",
|
||||
"Plugin": "Liitännäinen",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
|
||||
"NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
|
||||
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
||||
"NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
|
||||
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
||||
"NotificationOptionPluginUpdateInstalled": "Laajennuksen päivitys asennettu",
|
||||
"NotificationOptionPluginUninstalled": "Laajennus poistettu",
|
||||
"NotificationOptionPluginInstalled": "Laajennus asennettu",
|
||||
"NotificationOptionPluginError": "Ongelma laajennuksessa",
|
||||
"NotificationOptionNewLibraryContent": "Uusi sisältö lisätty",
|
||||
"NotificationOptionInstallationFailed": "Asennusvirhe",
|
||||
"NotificationOptionCameraImageUploaded": "Kameran kuva lisätty",
|
||||
"NotificationOptionAudioPlaybackStopped": "Äänen toistaminen pysäytetty",
|
||||
"NotificationOptionAudioPlayback": "Äänen toistaminen aloitettu",
|
||||
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
||||
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
||||
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
||||
"NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
|
||||
"NotificationOptionAudioPlayback": "Audion toisto aloitettu",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
|
||||
}
|
||||
|
|
|
@ -2,33 +2,33 @@
|
|||
"Albums": "אלבומים",
|
||||
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
|
||||
"Application": "אפליקציה",
|
||||
"Artists": "אמנים",
|
||||
"AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
|
||||
"Artists": "אומנים",
|
||||
"AuthenticationSucceededWithUserName": "{0} אומת בהצלחה",
|
||||
"Books": "ספרים",
|
||||
"CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
|
||||
"CameraImageUploadedFrom": "תמונת מצלמה חדשה הועלתה מ {0}",
|
||||
"Channels": "ערוצים",
|
||||
"ChapterNameValue": "פרק {0}",
|
||||
"Collections": "קולקציות",
|
||||
"Collections": "אוספים",
|
||||
"DeviceOfflineWithName": "{0} התנתק",
|
||||
"DeviceOnlineWithName": "{0} מחובר",
|
||||
"FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
|
||||
"Favorites": "אהובים",
|
||||
"Favorites": "מועדפים",
|
||||
"Folders": "תיקיות",
|
||||
"Genres": "ז'אנרים",
|
||||
"HeaderAlbumArtists": "אמני האלבום",
|
||||
"HeaderCameraUploads": "העלאות ממצלמה",
|
||||
"HeaderContinueWatching": "המשך לצפות",
|
||||
"HeaderFavoriteAlbums": "אלבומים שאהבתי",
|
||||
"HeaderFavoriteArtists": "אמנים שאהבתי",
|
||||
"HeaderFavoriteEpisodes": "פרקים אהובים",
|
||||
"HeaderFavoriteShows": "תוכניות אהובות",
|
||||
"HeaderFavoriteSongs": "שירים שאהבתי",
|
||||
"HeaderLiveTV": "טלוויזיה בשידור חי",
|
||||
"HeaderFavoriteArtists": "אמנים מועדפים",
|
||||
"HeaderFavoriteEpisodes": "פרקים מועדפים",
|
||||
"HeaderFavoriteShows": "סדרות מועדפות",
|
||||
"HeaderFavoriteSongs": "שירים מועדפים",
|
||||
"HeaderLiveTV": "שידורים חיים",
|
||||
"HeaderNextUp": "הבא",
|
||||
"HeaderRecordingGroups": "קבוצות הקלטה",
|
||||
"HomeVideos": "סרטונים בייתים",
|
||||
"Inherit": "הורש",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemAddedWithName": "{0} הוסף לספרייה",
|
||||
"ItemRemovedWithName": "{0} נמחק מהספרייה",
|
||||
"LabelIpAddressValue": "Ip כתובת: {0}",
|
||||
"LabelRunningTimeValue": "משך צפייה: {0}",
|
||||
|
@ -36,15 +36,15 @@
|
|||
"MessageApplicationUpdated": "שרת הJellyfin עודכן",
|
||||
"MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MessageServerConfigurationUpdated": "תצורת השרת עודכנה",
|
||||
"MixedContent": "תוכן מעורב",
|
||||
"Movies": "סרטים",
|
||||
"Music": "מוזיקה",
|
||||
"MusicVideos": "Music videos",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"MusicVideos": "קליפים",
|
||||
"NameInstallFailed": "התקנת {0} נכשלה",
|
||||
"NameSeasonNumber": "עונה {0}",
|
||||
"NameSeasonUnknown": "עונה לא ידועה",
|
||||
"NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
|
@ -53,10 +53,10 @@
|
|||
"NotificationOptionInstallationFailed": "התקנה נכשלה",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionPluginInstalled": "התוסף הותקן",
|
||||
"NotificationOptionPluginUninstalled": "התוסף הוסר",
|
||||
"NotificationOptionPluginUpdateInstalled": "העדכון לתוסף הותקן",
|
||||
"NotificationOptionServerRestartRequired": "יש לאתחל את השרת",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
|
@ -71,26 +71,26 @@
|
|||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Shows",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
||||
"Shows": "סדרות",
|
||||
"Songs": "שירים",
|
||||
"StartupEmbyServerIsLoading": "שרת Jellyfin בהליכי טעינה. אנא נסה שנית בעוד זמן קצר.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"Sync": "סנכרן",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"TvShows": "סדרות טלוויזיה",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserCreatedWithName": "המשתמש {0} נוצר",
|
||||
"UserDeletedWithName": "המשתמש {0} הוסר",
|
||||
"UserDownloadingItemWithValues": "{0} מוריד את {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} מנגן את {1} על {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} סיים לנגן את {1} על {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "מיוחד- {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"Collections": "Gyűjtemények",
|
||||
"DeviceOfflineWithName": "{0} kijelentkezett",
|
||||
"DeviceOnlineWithName": "{0} belépett",
|
||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet {0}",
|
||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet tőle: {0}",
|
||||
"Favorites": "Kedvencek",
|
||||
"Folders": "Könyvtárak",
|
||||
"Genres": "Műfajok",
|
||||
|
@ -27,7 +27,7 @@
|
|||
"HeaderNextUp": "Következik",
|
||||
"HeaderRecordingGroups": "Felvételi csoportok",
|
||||
"HomeVideos": "Házi videók",
|
||||
"Inherit": "Öröklés",
|
||||
"Inherit": "Örökölt",
|
||||
"ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
|
||||
"ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
|
||||
"LabelIpAddressValue": "IP cím: {0}",
|
||||
|
@ -73,7 +73,7 @@
|
|||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||
"Shows": "Műsorok",
|
||||
"Songs": "Dalok",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
|
||||
"SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
|
||||
|
@ -86,11 +86,11 @@
|
|||
"UserDownloadingItemWithValues": "{0} letölti {1}",
|
||||
"UserLockedOutWithName": "{0} felhasználó zárolva van",
|
||||
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
|
||||
"UserOnlineFromDevice": "{0} online itt: {1}",
|
||||
"UserOnlineFromDevice": "{0} online innen: {1}",
|
||||
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
|
||||
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Verzió: {0}"
|
||||
|
|
|
@ -91,5 +91,6 @@
|
|||
"NotificationOptionVideoPlayback": "Pemutaran video dimulai",
|
||||
"NotificationOptionAudioPlaybackStopped": "Pemutaran audio berhenti",
|
||||
"NotificationOptionAudioPlayback": "Pemutaran audio dimulai",
|
||||
"MixedContent": "Konten campur"
|
||||
"MixedContent": "Konten campur",
|
||||
"PluginUninstalledWithName": "{0} telah dihapus"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"Artists": "Artisti",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticato con successo",
|
||||
"Books": "Libri",
|
||||
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera {0}",
|
||||
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera da {0}",
|
||||
"Channels": "Canali",
|
||||
"ChapterNameValue": "Capitolo {0}",
|
||||
"Collections": "Collezioni",
|
||||
|
@ -18,7 +18,7 @@
|
|||
"HeaderAlbumArtists": "Artisti dell' Album",
|
||||
"HeaderCameraUploads": "Caricamenti Fotocamera",
|
||||
"HeaderContinueWatching": "Continua a guardare",
|
||||
"HeaderFavoriteAlbums": "Album preferiti",
|
||||
"HeaderFavoriteAlbums": "Album Preferiti",
|
||||
"HeaderFavoriteArtists": "Artisti Preferiti",
|
||||
"HeaderFavoriteEpisodes": "Episodi Preferiti",
|
||||
"HeaderFavoriteShows": "Serie TV Preferite",
|
||||
|
|
96
Emby.Server.Implementations/Localization/Core/lv.json
Normal file
96
Emby.Server.Implementations/Localization/Core/lv.json
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts",
|
||||
"NotificationOptionTaskFailed": "Plānota uzdevuma kļūme",
|
||||
"HeaderRecordingGroups": "Ierakstu Grupas",
|
||||
"UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta",
|
||||
"NotificationOptionVideoPlayback": "Video atskaņošana sākta",
|
||||
"NotificationOptionInstallationFailed": "Instalācija neizdevās",
|
||||
"AuthenticationSucceededWithUserName": "{0} veiksmīgi autentificējies",
|
||||
"ValueSpecialEpisodeName": "Speciālais - {0}",
|
||||
"ScheduledTaskStartedWithName": "{0} iesākts",
|
||||
"ScheduledTaskFailedWithName": "{0} neizdevās",
|
||||
"Photos": "Attēli",
|
||||
"NotificationOptionUserLockedOut": "Lietotājs bloķēts",
|
||||
"LabelRunningTimeValue": "Garums: {0}",
|
||||
"Inherit": "Mantot",
|
||||
"AppDeviceValues": "Lietotne:{0}, Ierīce:{1}",
|
||||
"VersionNumber": "Versija {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots tavai multvides bibliotēkai",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}",
|
||||
"UserPasswordChangedWithName": "Parole nomainīta lietotājam {0}",
|
||||
"UserOnlineFromDevice": "{0} ir tiešsaistē no {1}",
|
||||
"UserOfflineFromDevice": "{0} ir atvienojies no {1}",
|
||||
"UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts",
|
||||
"UserDownloadingItemWithValues": "{0} lejupielādē {1}",
|
||||
"UserDeletedWithName": "Lietotājs {0} ir izdzēsts",
|
||||
"UserCreatedWithName": "Lietotājs {0} ir ticis izveidots",
|
||||
"User": "Lietotājs",
|
||||
"TvShows": "TV Raidījumi",
|
||||
"Sync": "Sinhronizācija",
|
||||
"System": "Sistēma",
|
||||
"SubtitlesDownloadedForItem": "Subtitri lejupielādēti priekš {0}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.",
|
||||
"Songs": "Dziesmas",
|
||||
"Shows": "Raidījumi",
|
||||
"PluginUpdatedWithName": "{0} tika atjaunots",
|
||||
"PluginUninstalledWithName": "{0} tika noņemts",
|
||||
"PluginInstalledWithName": "{0} tika uzstādīts",
|
||||
"Plugin": "Paplašinājums",
|
||||
"Playlists": "Atskaņošanas Saraksti",
|
||||
"MixedContent": "Jaukts saturs",
|
||||
"HomeVideos": "Mājas Video",
|
||||
"HeaderNextUp": "Nākamais",
|
||||
"ChapterNameValue": "Nodaļa {0}",
|
||||
"Application": "Lietotne",
|
||||
"NotificationOptionServerRestartRequired": "Vajadzīgs servera restarts",
|
||||
"NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts",
|
||||
"NotificationOptionPluginUninstalled": "Paplašinājums noņemts",
|
||||
"NotificationOptionPluginInstalled": "Paplašinājums uzstādīts",
|
||||
"NotificationOptionPluginError": "Paplašinājuma kļūda",
|
||||
"NotificationOptionNewLibraryContent": "Jauns saturs pievienots",
|
||||
"NotificationOptionCameraImageUploaded": "Kameras attēls augšupielādēts",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio atskaņošana apturēta",
|
||||
"NotificationOptionAudioPlayback": "Audio atskaņošana sākta",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams",
|
||||
"NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.",
|
||||
"NameSeasonUnknown": "Nezināma Sezona",
|
||||
"NameSeasonNumber": "Sezona {0}",
|
||||
"NameInstallFailed": "{0} instalācija neizdevās",
|
||||
"MusicVideos": "Mūzikas video",
|
||||
"Music": "Mūzika",
|
||||
"Movies": "Filmas",
|
||||
"MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} ir tikusi atjaunota",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots",
|
||||
"Latest": "Jaunākais",
|
||||
"LabelIpAddressValue": "IP adrese: {0}",
|
||||
"ItemRemovedWithName": "{0} tika noņemts no bibliotēkas",
|
||||
"ItemAddedWithName": "{0} tika pievienots bibliotēkai",
|
||||
"HeaderLiveTV": "Tiešraides TV",
|
||||
"HeaderContinueWatching": "Turpināt Skatīšanos",
|
||||
"HeaderCameraUploads": "Kameras augšupielādes",
|
||||
"HeaderAlbumArtists": "Albumu Izpildītāji",
|
||||
"Genres": "Žanri",
|
||||
"Folders": "Mapes",
|
||||
"Favorites": "Favorīti",
|
||||
"FailedLoginAttemptWithUserName": "Neizdevies pieslēgšanās mēģinājums no {0}",
|
||||
"DeviceOnlineWithName": "{0} ir pievienojies",
|
||||
"DeviceOfflineWithName": "{0} ir atvienojies",
|
||||
"Collections": "Kolekcijas",
|
||||
"Channels": "Kanāli",
|
||||
"CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
|
||||
"Books": "Grāmatas",
|
||||
"Artists": "Izpildītāji",
|
||||
"Albums": "Albumi",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"HeaderFavoriteSongs": "Dziesmu Favorīti",
|
||||
"HeaderFavoriteShows": "Raidījumu Favorīti",
|
||||
"HeaderFavoriteEpisodes": "Episožu Favorīti",
|
||||
"HeaderFavoriteArtists": "Izpildītāju Favorīti",
|
||||
"HeaderFavoriteAlbums": "Albumu Favorīti"
|
||||
}
|
96
Emby.Server.Implementations/Localization/Core/mk.json
Normal file
96
Emby.Server.Implementations/Localization/Core/mk.json
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"ScheduledTaskFailedWithName": "{0} неуспешно",
|
||||
"ProviderValue": "Провајдер: {0}",
|
||||
"PluginUpdatedWithName": "{0} беше надоградено",
|
||||
"PluginUninstalledWithName": "{0} беше успешно деинсталирано",
|
||||
"PluginInstalledWithName": "{0} беше успешно инсталирано",
|
||||
"Plugin": "Додатоци",
|
||||
"Playlists": "Листи",
|
||||
"Photos": "Слики",
|
||||
"NotificationOptionVideoPlaybackStopped": "Видео стопирано",
|
||||
"NotificationOptionVideoPlayback": "Видео пуштено",
|
||||
"NotificationOptionUserLockedOut": "Корисникот е ослободен",
|
||||
"NotificationOptionTaskFailed": "Закажани задачи неуспешно",
|
||||
"NotificationOptionServerRestartRequired": "Задолжително рестартирање на серверот",
|
||||
"NotificationOptionPluginUpdateInstalled": "Надоградба на Додаток успешна",
|
||||
"NotificationOptionPluginUninstalled": "Додаток успешно деинсталиран",
|
||||
"NotificationOptionPluginInstalled": "Додаток успешно инсталиран",
|
||||
"NotificationOptionPluginError": "Грешка на додаток",
|
||||
"NotificationOptionNewLibraryContent": "Додадена нова содржина",
|
||||
"NotificationOptionInstallationFailed": "Неуспешна Инсталација",
|
||||
"NotificationOptionCameraImageUploaded": "Слика од камера поставена",
|
||||
"NotificationOptionAudioPlaybackStopped": "Аудио стопирано",
|
||||
"NotificationOptionAudioPlayback": "Аудио стартувано",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Надоградбата на Апликацијата е иснталирана",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Возможна надоградба на Апликацијата",
|
||||
"NewVersionIsAvailable": "Нова верзија од Jellyfin е возможна за спуштање.",
|
||||
"NameSeasonUnknown": "Непозната Сезона",
|
||||
"NameSeasonNumber": "Сезона {0}",
|
||||
"NameInstallFailed": "{0} неуспешна инсталација",
|
||||
"MusicVideos": "Музички видеа",
|
||||
"Music": "Музика",
|
||||
"Movies": "Филмови",
|
||||
"MixedContent": "Мешана содржина",
|
||||
"MessageServerConfigurationUpdated": "Серверската конфигурација беше надградена",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Секцијата на конфигурација на сервер {0} беше надоградена",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin беше надограден до {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Серверот беше надограден",
|
||||
"Latest": "Последно",
|
||||
"LabelRunningTimeValue": "Време на работа: {0}",
|
||||
"LabelIpAddressValue": "ИП Адреса: {0}",
|
||||
"ItemRemovedWithName": "{0} е избришано до Библиотеката",
|
||||
"ItemAddedWithName": "{0} беше додадено во Библиотеката",
|
||||
"Inherit": "Следно",
|
||||
"HomeVideos": "Домашни Видеа",
|
||||
"HeaderRecordingGroups": "Групи на снимање",
|
||||
"HeaderNextUp": "Следно",
|
||||
"HeaderLiveTV": "ТВ",
|
||||
"HeaderFavoriteSongs": "Омилени Песни",
|
||||
"HeaderFavoriteShows": "Омилени Серии",
|
||||
"HeaderFavoriteEpisodes": "Омилени Епизоди",
|
||||
"HeaderFavoriteArtists": "Омилени Изведувачи",
|
||||
"HeaderFavoriteAlbums": "Омилени Албуми",
|
||||
"HeaderContinueWatching": "Продолжи со гледање",
|
||||
"HeaderCameraUploads": "Поставувања од камера",
|
||||
"HeaderAlbumArtists": "Изведувачи од Албуми",
|
||||
"Genres": "Жанрови",
|
||||
"Folders": "Папки",
|
||||
"Favorites": "Омилени",
|
||||
"FailedLoginAttemptWithUserName": "Неуспешно поврзување од {0}",
|
||||
"DeviceOnlineWithName": "{0} е приклучен",
|
||||
"DeviceOfflineWithName": "{0} се исклучи",
|
||||
"Collections": "Колекции",
|
||||
"ChapterNameValue": "Дел {0}",
|
||||
"Channels": "Канали",
|
||||
"CameraImageUploadedFrom": "Нова слика од камера беше поставена од {0}",
|
||||
"Books": "Книги",
|
||||
"AuthenticationSucceededWithUserName": "{0} успешно поврзан",
|
||||
"Artists": "Изведувач",
|
||||
"Application": "Апликација",
|
||||
"AppDeviceValues": "Аплиакција: {0}, Уред: {1}",
|
||||
"Albums": "Албуми",
|
||||
"VersionNumber": "Верзија {0}",
|
||||
"ValueSpecialEpisodeName": "Специјално - {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} е додадено во твојата библиотека",
|
||||
"UserStoppedPlayingItemWithValues": "{0} заврши со репродукција {1} во {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} пушти {1} на {2}",
|
||||
"UserPolicyUpdatedWithName": "Полисата на користење беше надоградена за {0}",
|
||||
"UserPasswordChangedWithName": "Лозинката е сменета за корисникот {0}",
|
||||
"UserOnlineFromDevice": "{0} е приклучен од {1}",
|
||||
"UserOfflineFromDevice": "{0} е дисконектиран од {1}",
|
||||
"UserLockedOutWithName": "Корисникот {0} е заклучен",
|
||||
"UserDownloadingItemWithValues": "{0} се спушта {1}",
|
||||
"UserDeletedWithName": "Корисникот {0} е избришан",
|
||||
"UserCreatedWithName": "Корисникот {0} е креиран",
|
||||
"User": "Корисник",
|
||||
"TvShows": "ТВ Серии",
|
||||
"System": "Систем",
|
||||
"Sync": "Синхронизација",
|
||||
"SubtitlesDownloadedForItem": "Спуштање превод за {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Преводот неуспешно се спушти од {0} за {1}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server се пушта. Ве молиме причекајте.",
|
||||
"Songs": "Песни",
|
||||
"Shows": "Серии",
|
||||
"ServerNameNeedsToBeRestarted": "{0} треба да се рестартира",
|
||||
"ScheduledTaskStartedWithName": "{0} започна"
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Albums": "Album-album",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"AppDeviceValues": "Apl: {0}, Peranti: {1}",
|
||||
"Application": "Aplikasi",
|
||||
"Artists": "Artis-artis",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Artists": "Artis",
|
||||
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
|
||||
"Books": "Buku-buku",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Saluran",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelIpAddressValue": "Alamat IP: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionInstallationFailed": "Pemasangan gagal",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
||||
"Application": "Applicatie",
|
||||
"Artists": "Artiesten",
|
||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||
"AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd",
|
||||
"Books": "Boeken",
|
||||
"CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}",
|
||||
"Channels": "Kanalen",
|
||||
"ChapterNameValue": "Hoofdstuk {0}",
|
||||
"Collections": "Verzamelingen",
|
||||
"DeviceOfflineWithName": "{0} heeft de verbinding verbroken",
|
||||
"DeviceOfflineWithName": "Verbinding met {0} is verbroken",
|
||||
"DeviceOnlineWithName": "{0} is verbonden",
|
||||
"FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}",
|
||||
"Favorites": "Favorieten",
|
||||
|
|
|
@ -1 +1,40 @@
|
|||
{}
|
||||
{
|
||||
"MessageServerConfigurationUpdated": "Tenar konfigurasjonen har blitt oppdatert",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Tenar konfigurasjon seksjon {0} har blitt oppdatert",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Tenaren har blitt oppdatert til {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Tenaren har blitt oppdatert",
|
||||
"Latest": "Nyaste",
|
||||
"LabelRunningTimeValue": "Speletid: {0}",
|
||||
"LabelIpAddressValue": "IP adresse: {0}",
|
||||
"ItemRemovedWithName": "{0} vart fjerna frå biblioteket",
|
||||
"ItemAddedWithName": "{0} vart lagt til i biblioteket",
|
||||
"Inherit": "Arv",
|
||||
"HomeVideos": "Heime Videoar",
|
||||
"HeaderRecordingGroups": "Innspelingsgrupper",
|
||||
"HeaderNextUp": "Neste",
|
||||
"HeaderLiveTV": "Direkte TV",
|
||||
"HeaderFavoriteSongs": "Favoritt Songar",
|
||||
"HeaderFavoriteShows": "Favoritt Seriar",
|
||||
"HeaderFavoriteEpisodes": "Favoritt Episodar",
|
||||
"HeaderFavoriteArtists": "Favoritt Artistar",
|
||||
"HeaderFavoriteAlbums": "Favoritt Album",
|
||||
"HeaderContinueWatching": "Fortsett å sjå",
|
||||
"HeaderCameraUploads": "Kamera Opplastingar",
|
||||
"HeaderAlbumArtists": "Album Artist",
|
||||
"Genres": "Sjangrar",
|
||||
"Folders": "Mapper",
|
||||
"Favorites": "Favorittar",
|
||||
"FailedLoginAttemptWithUserName": "Mislukka påloggingsforsøk frå {0}",
|
||||
"DeviceOnlineWithName": "{0} er tilkopla",
|
||||
"DeviceOfflineWithName": "{0} har kopla frå",
|
||||
"Collections": "Samlingar",
|
||||
"ChapterNameValue": "Kapittel {0}",
|
||||
"Channels": "Kanalar",
|
||||
"CameraImageUploadedFrom": "Eit nytt kamera bilete har blitt lasta opp frå {0}",
|
||||
"Books": "Bøker",
|
||||
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
|
||||
"Artists": "Artistar",
|
||||
"Application": "Program",
|
||||
"AppDeviceValues": "App: {0}, Einheit: {1}",
|
||||
"Albums": "Album"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"HeaderLiveTV": "TV ao Vivo",
|
||||
"HeaderLiveTV": "TV em Directo",
|
||||
"Collections": "Colecções",
|
||||
"Books": "Livros",
|
||||
"Artists": "Artistas",
|
||||
|
@ -10,13 +10,13 @@
|
|||
"HeaderFavoriteAlbums": "Álbuns Favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episódios Favoritos",
|
||||
"HeaderFavoriteShows": "Séries Favoritas",
|
||||
"HeaderContinueWatching": "Continuar a Ver",
|
||||
"HeaderContinueWatching": "Continuar a Assistir",
|
||||
"HeaderAlbumArtists": "Artistas do Álbum",
|
||||
"Genres": "Géneros",
|
||||
"Folders": "Pastas",
|
||||
"Folders": "Directórios",
|
||||
"Favorites": "Favoritos",
|
||||
"Channels": "Canais",
|
||||
"UserDownloadingItemWithValues": "{0} está a transferir {1}",
|
||||
"UserDownloadingItemWithValues": "{0} está a ser transferido {1}",
|
||||
"VersionNumber": "Versão {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
|
||||
"UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
|
||||
|
@ -24,12 +24,12 @@
|
|||
"UserPolicyUpdatedWithName": "A política do utilizador {0} foi alterada",
|
||||
"UserPasswordChangedWithName": "A palavra-passe do utilizador {0} foi alterada",
|
||||
"UserOnlineFromDevice": "{0} ligou-se a partir de {1}",
|
||||
"UserOfflineFromDevice": "{0} desligou-se a partir de {1}",
|
||||
"UserLockedOutWithName": "Utilizador {0} bloqueado",
|
||||
"UserDeletedWithName": "Utilizador {0} removido",
|
||||
"UserCreatedWithName": "Utilizador {0} criado",
|
||||
"UserOfflineFromDevice": "{0} desconectou-se a partir de {1}",
|
||||
"UserLockedOutWithName": "O utilizador {0} foi bloqueado",
|
||||
"UserDeletedWithName": "O utilizador {0} foi removido",
|
||||
"UserCreatedWithName": "O utilizador {0} foi criado",
|
||||
"User": "Utilizador",
|
||||
"TvShows": "Programas",
|
||||
"TvShows": "Séries",
|
||||
"System": "Sistema",
|
||||
"SubtitlesDownloadedForItem": "Legendas transferidas para {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}",
|
||||
|
@ -38,22 +38,22 @@
|
|||
"ScheduledTaskStartedWithName": "{0} iniciou",
|
||||
"ScheduledTaskFailedWithName": "{0} falhou",
|
||||
"ProviderValue": "Fornecedor: {0}",
|
||||
"PluginUpdatedWithName": "{0} foi actualizado",
|
||||
"PluginUpdatedWithName": "{0} foi atualizado",
|
||||
"PluginUninstalledWithName": "{0} foi desinstalado",
|
||||
"PluginInstalledWithName": "{0} foi instalado",
|
||||
"Plugin": "Extensão",
|
||||
"Plugin": "Plugin",
|
||||
"NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada",
|
||||
"NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada",
|
||||
"NotificationOptionUserLockedOut": "Utilizador bloqueado",
|
||||
"NotificationOptionTaskFailed": "Falha em tarefa agendada",
|
||||
"NotificationOptionServerRestartRequired": "É necessário reiniciar o servidor",
|
||||
"NotificationOptionPluginUpdateInstalled": "Extensão actualizada",
|
||||
"NotificationOptionPluginUninstalled": "Extensão desinstalada",
|
||||
"NotificationOptionPluginInstalled": "Extensão instalada",
|
||||
"NotificationOptionPluginError": "Falha na extensão",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin actualizado",
|
||||
"NotificationOptionPluginUninstalled": "Plugin desinstalado",
|
||||
"NotificationOptionPluginInstalled": "Plugin instalado",
|
||||
"NotificationOptionPluginError": "Falha no plugin",
|
||||
"NotificationOptionNewLibraryContent": "Novo conteúdo adicionado",
|
||||
"NotificationOptionInstallationFailed": "Falha de instalação",
|
||||
"NotificationOptionCameraImageUploaded": "Imagem da câmara enviada",
|
||||
"NotificationOptionCameraImageUploaded": "Imagem de câmara enviada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reprodução Parada",
|
||||
"NotificationOptionAudioPlayback": "Reprodução Iniciada",
|
||||
"NotificationOptionApplicationUpdateInstalled": "A actualização da aplicação foi instalada",
|
||||
|
@ -66,30 +66,30 @@
|
|||
"Music": "Música",
|
||||
"MixedContent": "Conteúdo Misto",
|
||||
"MessageServerConfigurationUpdated": "A configuração do servidor foi actualizada",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Configurações do servidor na secção {0} foram atualizadas",
|
||||
"MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado para a versão {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "As configurações do servidor na secção {0} foram atualizadas",
|
||||
"MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}",
|
||||
"MessageApplicationUpdated": "O servidor Jellyfin foi actualizado",
|
||||
"Latest": "Mais Recente",
|
||||
"LabelRunningTimeValue": "Duração: {0}",
|
||||
"LabelIpAddressValue": "Endereço IP: {0}",
|
||||
"LabelIpAddressValue": "Endereço de IP: {0}",
|
||||
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
||||
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
|
||||
"Inherit": "Herdar",
|
||||
"HomeVideos": "Vídeos Caseiros",
|
||||
"HeaderRecordingGroups": "Grupos de Gravação",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"ValueSpecialEpisodeName": "Episódio Especial - {0}",
|
||||
"Sync": "Sincronização",
|
||||
"Songs": "Músicas",
|
||||
"Shows": "Séries",
|
||||
"Playlists": "Listas de Reprodução",
|
||||
"Photos": "Fotografias",
|
||||
"Movies": "Filmes",
|
||||
"HeaderCameraUploads": "Envios a partir da câmara",
|
||||
"FailedLoginAttemptWithUserName": "Tentativa de ligação a partir de {0} falhou",
|
||||
"DeviceOnlineWithName": "{0} ligou-se",
|
||||
"DeviceOfflineWithName": "{0} desligou-se",
|
||||
"HeaderCameraUploads": "Carregamentos a partir da câmara",
|
||||
"FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}",
|
||||
"DeviceOnlineWithName": "{0} está connectado",
|
||||
"DeviceOfflineWithName": "{0} desconectou-se",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
"CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}",
|
||||
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||
"Application": "Aplicação",
|
||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"Albums": "Album",
|
||||
"AppDeviceValues": "App: {0}, Enhet: {1}",
|
||||
"Application": "App",
|
||||
"AppDeviceValues": "Applikation: {0}, Enhet: {1}",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Artister",
|
||||
"AuthenticationSucceededWithUserName": "{0} har autentiserats",
|
||||
"Books": "Böcker",
|
||||
|
@ -16,15 +16,15 @@
|
|||
"Folders": "Mappar",
|
||||
"Genres": "Genrer",
|
||||
"HeaderAlbumArtists": "Albumartister",
|
||||
"HeaderCameraUploads": "Kamera Uppladdningar",
|
||||
"HeaderContinueWatching": "Fortsätt kolla på",
|
||||
"HeaderCameraUploads": "Kamerauppladdningar",
|
||||
"HeaderContinueWatching": "Fortsätt kolla",
|
||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
||||
"HeaderFavoriteArtists": "Favoritartister",
|
||||
"HeaderFavoriteEpisodes": "Favoritavsnitt",
|
||||
"HeaderFavoriteShows": "Favoritserier",
|
||||
"HeaderFavoriteSongs": "Favoritlåtar",
|
||||
"HeaderLiveTV": "Live-TV",
|
||||
"HeaderNextUp": "Nästa på tur",
|
||||
"HeaderNextUp": "Nästa",
|
||||
"HeaderRecordingGroups": "Inspelningsgrupper",
|
||||
"HomeVideos": "Hemvideor",
|
||||
"Inherit": "Ärv",
|
||||
|
@ -34,9 +34,9 @@
|
|||
"LabelRunningTimeValue": "Speltid: {0}",
|
||||
"Latest": "Senaste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server har uppdaterats",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server har uppgraderats till {0}",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server har uppdaterats till {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Serverinställningarna {0} har uppdaterats",
|
||||
"MessageServerConfigurationUpdated": "Server konfigurationen har uppdaterats",
|
||||
"MessageServerConfigurationUpdated": "Serverkonfigurationen har uppdaterats",
|
||||
"MixedContent": "Blandat innehåll",
|
||||
"Movies": "Filmer",
|
||||
"Music": "Musik",
|
||||
|
@ -44,11 +44,11 @@
|
|||
"NameInstallFailed": "{0} installationen misslyckades",
|
||||
"NameSeasonNumber": "Säsong {0}",
|
||||
"NameSeasonUnknown": "Okänd säsong",
|
||||
"NewVersionIsAvailable": "En ny version av Jellyfin Server är klar för nedladdning.",
|
||||
"NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig att hämta.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad",
|
||||
"NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats",
|
||||
"NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppad",
|
||||
"NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp",
|
||||
"NotificationOptionInstallationFailed": "Fel vid installation",
|
||||
"NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till",
|
||||
|
@ -60,7 +60,7 @@
|
|||
"NotificationOptionTaskFailed": "Schemalagd aktivitet har misslyckats",
|
||||
"NotificationOptionUserLockedOut": "Användare har låsts ut",
|
||||
"NotificationOptionVideoPlayback": "Videouppspelning har påbörjats",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppad",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppades",
|
||||
"Photos": "Bilder",
|
||||
"Playlists": "Spellistor",
|
||||
"Plugin": "Tillägg",
|
||||
|
@ -69,13 +69,13 @@
|
|||
"PluginUpdatedWithName": "{0} uppdaterades",
|
||||
"ProviderValue": "Källa: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} misslyckades",
|
||||
"ScheduledTaskStartedWithName": "{0} startad",
|
||||
"ScheduledTaskStartedWithName": "{0} startades",
|
||||
"ServerNameNeedsToBeRestarted": "{0} behöver startas om",
|
||||
"Shows": "Serier",
|
||||
"Songs": "Låtar",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin server arbetar. Pröva igen inom kort.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.",
|
||||
"SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades",
|
||||
"SubtitleDownloadFailureFromForItem": "Undertexter misslyckades att ladda ner {0} för {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} för {1}",
|
||||
"SubtitlesDownloadedForItem": "Undertexter har laddats ner till {0}",
|
||||
"Sync": "Synk",
|
||||
"System": "System",
|
||||
|
@ -89,9 +89,9 @@
|
|||
"UserOnlineFromDevice": "{0} är uppkopplad från {1}",
|
||||
"UserPasswordChangedWithName": "Lösenordet för {0} har ändrats",
|
||||
"UserPolicyUpdatedWithName": "Användarpolicyn har uppdaterats för {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} har börjat spela upp {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} har blivit tillagd till ditt mediabibliotek",
|
||||
"UserStartedPlayingItemWithValues": "{0} spelar upp {1} på {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
"Genres": "风格",
|
||||
"HeaderAlbumArtists": "专辑作家",
|
||||
"HeaderCameraUploads": "相机上传",
|
||||
"HeaderContinueWatching": "继续观看",
|
||||
"HeaderContinueWatching": "继续观影",
|
||||
"HeaderFavoriteAlbums": "收藏的专辑",
|
||||
"HeaderFavoriteArtists": "最爱的艺术家",
|
||||
"HeaderFavoriteEpisodes": "最爱的剧集",
|
||||
"HeaderFavoriteShows": "最爱的节目",
|
||||
"HeaderFavoriteSongs": "最爱的歌曲",
|
||||
"HeaderLiveTV": "电视直播",
|
||||
"HeaderNextUp": "下一步",
|
||||
"HeaderNextUp": "接下来",
|
||||
"HeaderRecordingGroups": "录制组",
|
||||
"HomeVideos": "家庭视频",
|
||||
"Inherit": "继承",
|
||||
|
|
|
@ -33,14 +33,14 @@ namespace Emby.Server.Implementations.Playlists
|
|||
ILibraryManager libraryManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryMonitor iLibraryMonitor,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILogger<PlaylistManager> logger,
|
||||
IUserManager userManager,
|
||||
IProviderManager providerManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
_iLibraryMonitor = iLibraryMonitor;
|
||||
_logger = loggerFactory.CreateLogger(nameof(PlaylistManager));
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_providerManager = providerManager;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
|
||||
/// </summary>
|
||||
public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
|
||||
public DeleteCacheFileTask(
|
||||
IApplicationPaths appPaths,
|
||||
ILogger<DeleteCacheFileTask> logger,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
ApplicationPaths = appPaths;
|
||||
_logger = logger;
|
||||
|
|
|
@ -23,7 +23,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
|
||||
/// </summary>
|
||||
public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager)
|
||||
public DeleteTranscodeFileTask(
|
||||
ILogger<DeleteTranscodeFileTask> logger,
|
||||
IFileSystem fileSystem,
|
||||
IConfigurationManager configurationManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
|
||||
private readonly IInstallationManager _installationManager;
|
||||
|
||||
public PluginUpdateTask(ILogger logger, IInstallationManager installationManager)
|
||||
public PluginUpdateTask(ILogger<PluginUpdateTask> logger, IInstallationManager installationManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_installationManager = installationManager;
|
||||
|
|
|
@ -21,15 +21,14 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
private CancellationToken _disposeCancellationToken;
|
||||
|
||||
public WebSocketSharpListener(
|
||||
ILogger logger)
|
||||
public WebSocketSharpListener(ILogger<WebSocketSharpListener> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
_disposeCancellationToken = _disposeCancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
|
||||
|
||||
public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
|
||||
|
||||
public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
public class AlphanumComparator : IComparer<string>
|
||||
{
|
||||
public static int CompareValues(string s1, string s2)
|
||||
{
|
||||
if (s1 == null || s2 == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thisMarker = 0, thisNumericChunk = 0;
|
||||
int thatMarker = 0, thatNumericChunk = 0;
|
||||
|
||||
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
|
||||
{
|
||||
if (thisMarker >= s1.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (thatMarker >= s2.Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char thisCh = s1[thisMarker];
|
||||
char thatCh = s2[thatMarker];
|
||||
|
||||
var thisChunk = new StringBuilder();
|
||||
var thatChunk = new StringBuilder();
|
||||
|
||||
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
|
||||
{
|
||||
thisChunk.Append(thisCh);
|
||||
thisMarker++;
|
||||
|
||||
if (thisMarker < s1.Length)
|
||||
{
|
||||
thisCh = s1[thisMarker];
|
||||
}
|
||||
}
|
||||
|
||||
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || SortHelper.InChunk(thatCh, thatChunk[0])))
|
||||
{
|
||||
thatChunk.Append(thatCh);
|
||||
thatMarker++;
|
||||
|
||||
if (thatMarker < s2.Length)
|
||||
{
|
||||
thatCh = s2[thatMarker];
|
||||
}
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
// If both chunks contain numeric characters, sort them numerically
|
||||
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
|
||||
{
|
||||
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (thisNumericChunk < thatNumericChunk)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
if (thisNumericChunk > thatNumericChunk)
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Compare(string x, string y)
|
||||
{
|
||||
return CompareValues(x, y);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,23 +23,20 @@ namespace Jellyfin.Server
|
|||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||
/// <param name="imageEncoder">The <see cref="IImageEncoder" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||
/// <param name="configuration">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||
public CoreAppHost(
|
||||
ServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
StartupOptions options,
|
||||
IFileSystem fileSystem,
|
||||
IImageEncoder imageEncoder,
|
||||
INetworkManager networkManager,
|
||||
IConfiguration configuration)
|
||||
INetworkManager networkManager)
|
||||
: base(
|
||||
applicationPaths,
|
||||
loggerFactory,
|
||||
options,
|
||||
fileSystem,
|
||||
imageEncoder,
|
||||
networkManager,
|
||||
configuration)
|
||||
networkManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
28
Jellyfin.Server/Migrations/IMigrationRoutine.cs
Normal file
28
Jellyfin.Server/Migrations/IMigrationRoutine.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface that describes a migration routine.
|
||||
/// </summary>
|
||||
internal interface IMigrationRoutine
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique id for this migration. This should never be modified after the migration has been created.
|
||||
/// </summary>
|
||||
public Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name of the migration.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Execute the migration routine.
|
||||
/// </summary>
|
||||
/// <param name="host">Host that hosts current version.</param>
|
||||
/// <param name="logger">Host logger.</param>
|
||||
public void Perform(CoreAppHost host, ILogger logger);
|
||||
}
|
||||
}
|
24
Jellyfin.Server/Migrations/MigrationOptions.cs
Normal file
24
Jellyfin.Server/Migrations/MigrationOptions.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration part that holds all migrations that were applied.
|
||||
/// </summary>
|
||||
public class MigrationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrationOptions"/> class.
|
||||
/// </summary>
|
||||
public MigrationOptions()
|
||||
{
|
||||
Applied = new List<(Guid Id, string Name)>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of applied migration routine names.
|
||||
/// </summary>
|
||||
public List<(Guid Id, string Name)> Applied { get; }
|
||||
}
|
||||
}
|
73
Jellyfin.Server/Migrations/MigrationRunner.cs
Normal file
73
Jellyfin.Server/Migrations/MigrationRunner.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// The class that knows which migrations to apply and how to apply them.
|
||||
/// </summary>
|
||||
public sealed class MigrationRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of known migrations, in order of applicability.
|
||||
/// </summary>
|
||||
internal static readonly IMigrationRoutine[] Migrations =
|
||||
{
|
||||
new Routines.DisableTranscodingThrottling(),
|
||||
new Routines.CreateUserLoggingConfigFile()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Run all needed migrations.
|
||||
/// </summary>
|
||||
/// <param name="host">CoreAppHost that hosts current version.</param>
|
||||
/// <param name="loggerFactory">Factory for making the logger.</param>
|
||||
public static void Run(CoreAppHost host, ILoggerFactory loggerFactory)
|
||||
{
|
||||
var logger = loggerFactory.CreateLogger<MigrationRunner>();
|
||||
var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
|
||||
|
||||
if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
|
||||
{
|
||||
// If startup wizard is not finished, this is a fresh install.
|
||||
// Don't run any migrations, just mark all of them as applied.
|
||||
logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
|
||||
migrationOptions.Applied.AddRange(Migrations.Select(m => (m.Id, m.Name)));
|
||||
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
|
||||
|
||||
for (var i = 0; i < Migrations.Length; i++)
|
||||
{
|
||||
var migrationRoutine = Migrations[i];
|
||||
if (appliedMigrationIds.Contains(migrationRoutine.Id))
|
||||
{
|
||||
logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("Applying migration '{Name}'", migrationRoutine.Name);
|
||||
|
||||
try
|
||||
{
|
||||
migrationRoutine.Perform(host, logger);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Could not apply migration '{Name}'", migrationRoutine.Name);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Mark the migration as completed
|
||||
logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name);
|
||||
migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name));
|
||||
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
|
||||
logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
Jellyfin.Server/Migrations/MigrationsFactory.cs
Normal file
20
Jellyfin.Server/Migrations/MigrationsFactory.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory that can find a persistent file of the migration configuration, which lists all applied migrations.
|
||||
/// </summary>
|
||||
public class MigrationsFactory : IConfigurationFactory
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new MigrationsListStore()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
24
Jellyfin.Server/Migrations/MigrationsListStore.cs
Normal file
24
Jellyfin.Server/Migrations/MigrationsListStore.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A configuration that lists all the migration routines that were applied.
|
||||
/// </summary>
|
||||
public class MigrationsListStore : ConfigurationStore
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the configuration in the storage.
|
||||
/// </summary>
|
||||
public static readonly string StoreKey = "migrations";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrationsListStore"/> class.
|
||||
/// </summary>
|
||||
public MigrationsListStore()
|
||||
{
|
||||
ConfigurationType = typeof(MigrationOptions);
|
||||
Key = StoreKey;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Migration to initialize the user logging configuration file "logging.user.json".
|
||||
/// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json,
|
||||
/// otherwise a blank file will be created.
|
||||
/// </summary>
|
||||
internal class CreateUserLoggingConfigFile : IMigrationRoutine
|
||||
{
|
||||
/// <summary>
|
||||
/// File history for logging.json as existed during this migration creation. The contents for each has been minified.
|
||||
/// </summary>
|
||||
private readonly List<string> _defaultConfigHistory = new List<string>
|
||||
{
|
||||
// 9a6c27947353585391e211aa88b925f81e8cd7b9
|
||||
@"{""Serilog"":{""MinimumLevel"":{""Default"":""Information"",""Override"":{""Microsoft"":""Warning"",""System"":""Warning""}},""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
// 71bdcd730705a714ee208eaad7290b7c68df3885
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
// a44936f97f8afc2817d3491615a7cfe1e31c251c
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}",
|
||||
// 7af3754a11ad5a4284f107997fb5419a010ce6f3
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}",
|
||||
// 60691349a11f541958e0b2247c9abc13cb40c9fb
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}",
|
||||
// 65fe243afbcc4b596cf8726708c1965cd34b5f68
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {ThreadId} {SourceContext}: {Message:lj} {NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {ThreadId} {SourceContext}:{Message} {NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
// 96c9af590494aa8137d5a061aaf1e68feee60b67
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Guid Id => Guid.Parse("{EF103419-8451-40D8-9F34-D1A8E93A1679}");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "CreateLoggingConfigHeirarchy";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform(CoreAppHost host, ILogger logger)
|
||||
{
|
||||
var logDirectory = host.Resolve<IApplicationPaths>().ConfigurationDirectoryPath;
|
||||
var existingConfigPath = Path.Combine(logDirectory, "logging.json");
|
||||
|
||||
// If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json'
|
||||
// NOTE: This config file has 'reloadOnChange: true', so this change will take effect immediately even though it has already been loaded
|
||||
if (File.Exists(existingConfigPath) && ExistingConfigUnmodified(existingConfigPath))
|
||||
{
|
||||
File.Move(existingConfigPath, Path.Combine(logDirectory, "logging.old.json"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the existing logging.json file has not been modified by the user by comparing it to all the
|
||||
/// versions in our git history. Until now, the file has never been migrated after first creation so users
|
||||
/// could have any version from the git history.
|
||||
/// </summary>
|
||||
/// <exception cref="IOException"><paramref name="oldConfigPath"/> does not exist or could not be read.</exception>
|
||||
private bool ExistingConfigUnmodified(string oldConfigPath)
|
||||
{
|
||||
var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath));
|
||||
return _defaultConfigHistory
|
||||
.Select(historicalConfigText => JToken.Parse(historicalConfigText))
|
||||
.Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable transcode throttling for all installations since it is currently broken for certain video formats.
|
||||
/// </summary>
|
||||
internal class DisableTranscodingThrottling : IMigrationRoutine
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Guid Id => Guid.Parse("{4124C2CD-E939-4FFB-9BE9-9B311C413638}");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "DisableTranscodingThrottling";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform(CoreAppHost host, ILogger logger)
|
||||
{
|
||||
// Set EnableThrottling to false since it wasn't used before and may introduce issues
|
||||
var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration<EncodingOptions>("encoding");
|
||||
if (encoding.EnableThrottling)
|
||||
{
|
||||
logger.LogInformation("Disabling transcoding throttling during migration");
|
||||
encoding.EnableThrottling = false;
|
||||
|
||||
host.ServerConfigurationManager.SaveConfiguration("encoding", encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Extensions.Logging;
|
||||
using SQLitePCL;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
@ -37,6 +38,16 @@ namespace Jellyfin.Server
|
|||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of logging configuration file containing application defaults.
|
||||
/// </summary>
|
||||
public static readonly string LoggingConfigFileDefault = "logging.default.json";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the logging configuration file containing the system-specific override settings.
|
||||
/// </summary>
|
||||
public static readonly string LoggingConfigFileSystem = "logging.json";
|
||||
|
||||
private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
|
||||
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
|
||||
private static ILogger _logger = NullLogger.Instance;
|
||||
|
@ -101,10 +112,12 @@ namespace Jellyfin.Server
|
|||
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
|
||||
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
|
||||
|
||||
IConfiguration appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false);
|
||||
|
||||
CreateLogger(appConfig, appPaths);
|
||||
// Create an instance of the application configuration to use for application startup
|
||||
await InitLoggingConfigFile(appPaths).ConfigureAwait(false);
|
||||
IConfiguration startupConfig = CreateAppConfiguration(appPaths);
|
||||
|
||||
// Initialize logging framework
|
||||
InitializeLoggingFramework(startupConfig, appPaths);
|
||||
_logger = _loggerFactory.CreateLogger("Main");
|
||||
|
||||
// Log uncaught exceptions to the logging instead of std error
|
||||
|
@ -169,22 +182,22 @@ namespace Jellyfin.Server
|
|||
options,
|
||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||
GetImageEncoder(appPaths),
|
||||
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()),
|
||||
appConfig);
|
||||
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
|
||||
try
|
||||
{
|
||||
ServiceCollection serviceCollection = new ServiceCollection();
|
||||
await appHost.InitAsync(serviceCollection).ConfigureAwait(false);
|
||||
await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false);
|
||||
|
||||
var host = CreateWebHostBuilder(appHost, serviceCollection).Build();
|
||||
var webHost = CreateWebHostBuilder(appHost, serviceCollection, appPaths).Build();
|
||||
|
||||
// A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection.
|
||||
appHost.ServiceProvider = host.Services;
|
||||
appHost.ServiceProvider = webHost.Services;
|
||||
appHost.FindParts();
|
||||
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
|
||||
|
||||
try
|
||||
{
|
||||
await host.StartAsync().ConfigureAwait(false);
|
||||
await webHost.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -220,7 +233,7 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
|
||||
private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection)
|
||||
private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection, IApplicationPaths appPaths)
|
||||
{
|
||||
return new WebHostBuilder()
|
||||
.UseKestrel(options =>
|
||||
|
@ -260,6 +273,8 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
})
|
||||
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths))
|
||||
.UseSerilog()
|
||||
.UseContentRoot(appHost.ContentRoot)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
|
@ -432,37 +447,51 @@ namespace Jellyfin.Server
|
|||
return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir, webDir);
|
||||
}
|
||||
|
||||
private static async Task<IConfiguration> CreateConfiguration(IApplicationPaths appPaths)
|
||||
/// <summary>
|
||||
/// Initialize the logging configuration file using the bundled resource file as a default if it doesn't exist
|
||||
/// already.
|
||||
/// </summary>
|
||||
private static async Task InitLoggingConfigFile(IApplicationPaths appPaths)
|
||||
{
|
||||
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
|
||||
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
// Do nothing if the config file already exists
|
||||
string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault);
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
// For some reason the csproj name is used instead of the assembly name
|
||||
await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath);
|
||||
if (resource == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Invalid resource path: '{0}'",
|
||||
ResourcePath));
|
||||
}
|
||||
|
||||
await using Stream dst = File.Open(configPath, FileMode.CreateNew);
|
||||
await resource.CopyToAsync(dst).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a stream of the resource contents
|
||||
// NOTE: The .csproj name is used instead of the assembly name in the resource path
|
||||
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
|
||||
await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
|
||||
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
|
||||
|
||||
// Copy the resource contents to the expected file path for the config file
|
||||
await using Stream dst = File.Open(configPath, FileMode.CreateNew);
|
||||
await resource.CopyToAsync(dst).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static IConfiguration CreateAppConfiguration(IApplicationPaths appPaths)
|
||||
{
|
||||
return new ConfigurationBuilder()
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddInMemoryCollection(ConfigurationOptions.Configuration)
|
||||
.AddJsonFile("logging.json", false, true)
|
||||
.AddEnvironmentVariables("JELLYFIN_")
|
||||
.ConfigureAppConfiguration(appPaths)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static void CreateLogger(IConfiguration configuration, IApplicationPaths appPaths)
|
||||
private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths)
|
||||
{
|
||||
return config
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddInMemoryCollection(ConfigurationOptions.Configuration)
|
||||
.AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
|
||||
.AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
|
||||
.AddEnvironmentVariables("JELLYFIN_");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Serilog using configuration and fall back to defaults on failure.
|
||||
/// </summary>
|
||||
private static void InitializeLoggingFramework(IConfiguration configuration, IApplicationPaths appPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
{
|
||||
"Serilog": {
|
||||
"MinimumLevel": "Information",
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace MediaBrowser.Api
|
|||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="mediaSourceManager">The media source manager.</param>
|
||||
public ApiEntryPoint(
|
||||
ILogger logger,
|
||||
ILogger<ApiEntryPoint> logger,
|
||||
ISessionManager sessionManager,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
|
|
|
@ -815,7 +815,7 @@ namespace MediaBrowser.Api.Library
|
|||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
// Kestrel doesn't support non-ASCII characters in headers
|
||||
if (Regex.IsMatch(filename, "[^[:ascii:]]"))
|
||||
if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]"))
|
||||
{
|
||||
// Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
|
||||
headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user