diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-12-05 13:37:55 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-12-05 13:37:55 -0500 |
| commit | c32d8656382a0eacb301692e0084377fc433ae9b (patch) | |
| tree | 121dd382bf71a9b5c96e00771c0ba18a7d28ab87 /Emby.Server.Implementations | |
| parent | e7ffdf3fbdae7d4ec76a0a4e0e37792b079c56e5 (diff) | |
| parent | b3fbdde04305a0406b5322ec6947f8a30ddc12af (diff) | |
Merge pull request #3055 from MediaBrowser/beta
Beta
Diffstat (limited to 'Emby.Server.Implementations')
36 files changed, 729 insertions, 240 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 745d83eb5..26450c06c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -148,6 +148,34 @@ namespace Emby.Server.Implementations } } + public virtual bool CanLaunchWebBrowser + { + get + { + if (!Environment.UserInteractive) + { + return false; + } + + if (StartupOptions.ContainsOption("-service")) + { + return false; + } + + if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + return true; + } + + if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) + { + return true; + } + + return false; + } + } + /// <summary> /// Occurs when [has pending restart changed]. /// </summary> @@ -361,7 +389,7 @@ namespace Emby.Server.Implementations protected IAuthService AuthService { get; private set; } - protected readonly StartupOptions StartupOptions; + public StartupOptions StartupOptions { get; private set; } protected readonly string ReleaseAssetFilename; internal IPowerManagement PowerManagement { get; private set; } @@ -393,6 +421,7 @@ namespace Emby.Server.Implementations ISystemEvents systemEvents, INetworkManager networkManager) { + // hack alert, until common can target .net core BaseExtensions.CryptographyProvider = CryptographyProvider; @@ -423,6 +452,13 @@ namespace Emby.Server.Implementations SetBaseExceptionMessage(); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + + NetworkManager.NetworkChanged += NetworkManager_NetworkChanged; + } + + private void NetworkManager_NetworkChanged(object sender, EventArgs e) + { + _validAddressResults.Clear(); } private Version _version; @@ -1901,9 +1937,9 @@ namespace Emby.Server.Implementations /// Gets the system status. /// </summary> /// <returns>SystemInfo.</returns> - public async Task<SystemInfo> GetSystemInfo() + public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken) { - var localAddress = await GetLocalApiUrl().ConfigureAwait(false); + var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); return new SystemInfo { @@ -1928,6 +1964,7 @@ namespace Emby.Server.Implementations OperatingSystemDisplayName = OperatingSystemDisplayName, CanSelfRestart = CanSelfRestart, CanSelfUpdate = CanSelfUpdate, + CanLaunchWebBrowser = CanLaunchWebBrowser, WanAddress = ConnectManager.WanApiAddress, HasUpdateAvailable = HasUpdateAvailable, SupportsAutoRunAtStartup = SupportsAutoRunAtStartup, @@ -1942,6 +1979,21 @@ namespace Emby.Server.Implementations }; } + public async Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken) + { + var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + + return new PublicSystemInfo + { + Version = ApplicationVersion.ToString(), + Id = SystemId, + OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), + WanAddress = ConnectManager.WanApiAddress, + ServerName = FriendlyName, + LocalAddress = localAddress + }; + } + public bool EnableHttps { get @@ -1955,14 +2007,14 @@ namespace Emby.Server.Implementations get { return Certificate != null || ServerConfigurationManager.Configuration.IsBehindProxy; } } - public async Task<string> GetLocalApiUrl() + public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken) { try { // Return the first matched address, if found, or the first known local address - var address = (await GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !i.Equals(IpAddressInfo.Loopback) && !i.Equals(IpAddressInfo.IPv6Loopback)); + var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false); - if (address != null) + foreach (var address in addresses) { return GetLocalApiUrl(address); } @@ -1994,7 +2046,12 @@ namespace Emby.Server.Implementations HttpPort.ToString(CultureInfo.InvariantCulture)); } - public async Task<List<IpAddressInfo>> GetLocalIpAddresses() + public Task<List<IpAddressInfo>> GetLocalIpAddresses(CancellationToken cancellationToken) + { + return GetLocalIpAddressesInternal(true, 0, cancellationToken); + } + + private async Task<List<IpAddressInfo>> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken) { var addresses = ServerConfigurationManager .Configuration @@ -2006,22 +2063,33 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { addresses.AddRange(NetworkManager.GetLocalIpAddresses()); + } - var list = new List<IpAddressInfo>(); + var resultList = new List<IpAddressInfo>(); - foreach (var address in addresses) + foreach (var address in addresses) + { + if (!allowLoopback) { - var valid = await IsIpAddressValidAsync(address).ConfigureAwait(false); - if (valid) + if (address.Equals(IpAddressInfo.Loopback) || address.Equals(IpAddressInfo.IPv6Loopback)) { - list.Add(address); + continue; } } - addresses = list; + var valid = await IsIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false); + if (valid) + { + resultList.Add(address); + + if (limit > 0 && resultList.Count >= limit) + { + return resultList; + } + } } - return addresses; + return resultList; } private IpAddressInfo NormalizeConfiguredLocalAddress(string address) @@ -2042,8 +2110,7 @@ namespace Emby.Server.Implementations } private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase); - private DateTime _lastAddressCacheClear; - private async Task<bool> IsIpAddressValidAsync(IpAddressInfo address) + private async Task<bool> IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken) { if (address.Equals(IpAddressInfo.Loopback) || address.Equals(IpAddressInfo.IPv6Loopback)) @@ -2054,12 +2121,6 @@ namespace Emby.Server.Implementations var apiUrl = GetLocalApiUrl(address); apiUrl += "/system/ping"; - if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 15) - { - _lastAddressCacheClear = DateTime.UtcNow; - _validAddressResults.Clear(); - } - bool cachedResult; if (_validAddressResults.TryGetValue(apiUrl, out cachedResult)) { @@ -2075,7 +2136,9 @@ namespace Emby.Server.Implementations LogErrors = false, LogRequest = false, TimeoutMs = 30000, - BufferContent = false + BufferContent = false, + + CancellationToken = cancellationToken }, "POST").ConfigureAwait(false)) { @@ -2085,14 +2148,19 @@ namespace Emby.Server.Implementations var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); - //Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid); + Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid); return valid; } } } + catch (OperationCanceledException) + { + Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled"); + throw; + } catch { - //Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false); + Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false); _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false); return false; @@ -2317,12 +2385,11 @@ namespace Emby.Server.Implementations } } - public void LaunchUrl(string url) + public virtual void LaunchUrl(string url) { - if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows && - EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX) + if (!CanLaunchWebBrowser) { - throw new NotImplementedException(); + throw new NotSupportedException(); } var process = ProcessFactory.Create(new ProcessOptions diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs index d7d37bb61..32938e151 100644 --- a/Emby.Server.Implementations/Archiving/ZipClient.cs +++ b/Emby.Server.Implementations/Archiving/ZipClient.cs @@ -89,6 +89,24 @@ namespace Emby.Server.Implementations.Archiving } } + public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName) + { + using (var reader = GZipReader.Open(source)) + { + if (reader.MoveToNextEntry()) + { + var entry = reader.Entry; + + var filename = entry.Key; + if (string.IsNullOrWhiteSpace(filename)) + { + filename = defaultFileName; + } + reader.WriteEntryToFile(Path.Combine(targetPath, filename)); + } + } + } + /// <summary> /// Extracts all from7z. /// </summary> diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 05cde91e2..71497f6bf 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Browser { appHost.LaunchUrl(url); } - catch (NotImplementedException) + catch (NotSupportedException) { } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index eb0f5150f..830d6447e 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -4286,7 +4286,7 @@ namespace Emby.Server.Implementations.Data if (query.MinParentalRating.HasValue) { - whereClauses.Add("InheritedParentalRatingValue<=@MinParentalRating"); + whereClauses.Add("InheritedParentalRatingValue>=@MinParentalRating"); if (statement != null) { statement.TryBind("@MinParentalRating", query.MinParentalRating.Value); @@ -5264,7 +5264,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type ItemIds = query.ItemIds, TopParentIds = query.TopParentIds, ParentId = query.ParentId, - IsPlayed = query.IsPlayed + IsPlayed = query.IsPlayed, + IsAiring = query.IsAiring, + IsMovie = query.IsMovie, + IsSports = query.IsSports, + IsKids = query.IsKids, + IsNews = query.IsNews, + IsSeries = query.IsSeries }; var innerWhereClauses = GetWhereClauses(innerQuery, null); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 7ccf54d20..cef37910e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -708,6 +708,8 @@ <EmbeddedResource Include="Localization\Core\zh-CN.json" /> <EmbeddedResource Include="Localization\Core\zh-HK.json" /> <EmbeddedResource Include="Localization\Core\en-US.json" /> + <EmbeddedResource Include="Localization\Core\el.json" /> + <EmbeddedResource Include="Localization\Core\gsw.json" /> <None Include="packages.config" /> <None Include="TextEncoding\NLangDetect\Profiles\afr" /> <None Include="TextEncoding\NLangDetect\Profiles\ara" /> diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 2cef46839..903bb0ff4 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -13,6 +13,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Threading; using Mono.Nat; using MediaBrowser.Model.Extensions; +using System.Threading; namespace Emby.Server.Implementations.EntryPoints { @@ -158,7 +159,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false); + var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false); Uri uri; if (Uri.TryCreate(localAddressString, UriKind.Absolute, out uri)) diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 299da0744..0e771cbec 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -260,7 +260,7 @@ namespace Emby.Server.Implementations.EntryPoints LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); } - var parent = e.Item.GetParent() as Folder; + var parent = e.Parent as Folder; if (parent != null) { _foldersRemovedFrom.Add(parent); @@ -363,10 +363,16 @@ namespace Emby.Server.Implementations.EntryPoints /// <param name="foldersRemovedFrom">The folders removed from.</param> /// <param name="userId">The user id.</param> /// <returns>LibraryUpdateInfo.</returns> - private LibraryUpdateInfo GetLibraryUpdateInfo(IEnumerable<BaseItem> itemsAdded, IEnumerable<BaseItem> itemsUpdated, IEnumerable<BaseItem> itemsRemoved, IEnumerable<Folder> foldersAddedTo, IEnumerable<Folder> foldersRemovedFrom, Guid userId) + private LibraryUpdateInfo GetLibraryUpdateInfo(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, Guid userId) { var user = _userManager.GetUserById(userId); + var newAndRemoved = new List<BaseItem>(); + newAndRemoved.AddRange(foldersAddedTo); + newAndRemoved.AddRange(foldersRemovedFrom); + + var allUserRootChildren = _libraryManager.GetUserRootFolder().GetChildren(user, true).OfType<Folder>().ToList(); + return new LibraryUpdateInfo { ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), @@ -377,7 +383,9 @@ namespace Emby.Server.Implementations.EntryPoints FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), - FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray() + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + + CollectionFolders = GetTopParentIds(newAndRemoved, user, allUserRootChildren).ToArray() }; } @@ -396,6 +404,28 @@ namespace Emby.Server.Implementations.EntryPoints return item.SourceType == SourceType.Library; } + private IEnumerable<string> GetTopParentIds(List<BaseItem> items, User user, List<Folder> allUserRootChildren) + { + var list = new List<string>(); + + foreach (var item in items) + { + // If the physical root changed, return the user root + if (item is AggregateFolder) + { + continue; + } + + var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren); + foreach (var folder in allUserRootChildren) + { + list.Add(folder.Id.ToString("N")); + } + } + + return list.Distinct(StringComparer.Ordinal); + } + /// <summary> /// Translates the physical item to user library. /// </summary> diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 614c04fd2..103b4b321 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -3,6 +3,7 @@ using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; +using MediaBrowser.Controller.Configuration; namespace Emby.Server.Implementations.EntryPoints { @@ -20,15 +21,13 @@ namespace Emby.Server.Implementations.EntryPoints /// </summary> private readonly ILogger _logger; - /// <summary> - /// Initializes a new instance of the <see cref="StartupWizard" /> class. - /// </summary> - /// <param name="appHost">The app host.</param> - /// <param name="logger">The logger.</param> - public StartupWizard(IServerApplicationHost appHost, ILogger logger) + private IServerConfigurationManager _config; + + public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config) { _appHost = appHost; _logger = logger; + _config = config; } /// <summary> @@ -36,18 +35,24 @@ namespace Emby.Server.Implementations.EntryPoints /// </summary> public void Run() { + if (!_appHost.CanLaunchWebBrowser) + { + return; + } + if (_appHost.IsFirstRun) { - LaunchStartupWizard(); + BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost); } - } + else if (_config.Configuration.IsStartupWizardCompleted && _config.Configuration.AutoRunWebApp) + { + var options = ((ApplicationHost)_appHost).StartupOptions; - /// <summary> - /// Launches the startup wizard. - /// </summary> - private void LaunchStartupWizard() - { - BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost); + if (!options.ContainsOption("-noautorunwebapp")) + { + BrowserLauncher.OpenDashboardPage("index.html", _appHost); + } + } } /// <summary> diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index c99b601c9..a2abb2a5c 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) { - if (e.Item.GetParent() is AggregateFolder) + if (e.Parent is AggregateFolder) { StopWatchingPath(e.Item.Path); } @@ -250,7 +250,7 @@ namespace Emby.Server.Implementations.IO /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) { - if (e.Item.GetParent() is AggregateFolder) + if (e.Parent is AggregateFolder) { StartWatching(e.Item); } @@ -320,7 +320,8 @@ namespace Emby.Server.Implementations.IO IncludeSubdirectories = true }; - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows || + _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) { newWatcher.InternalBufferSize = 32767; } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 125d9e980..c8e4031a9 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -473,6 +473,11 @@ namespace Emby.Server.Implementations.IO public void SetHidden(string path, bool isHidden) { + if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + { + return; + } + if (_sharpCifsFileSystem.IsEnabledForPath(path)) { _sharpCifsFileSystem.SetHidden(path, isHidden); @@ -498,6 +503,11 @@ namespace Emby.Server.Implementations.IO public void SetReadOnly(string path, bool isReadOnly) { + if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + { + return; + } + if (_sharpCifsFileSystem.IsEnabledForPath(path)) { _sharpCifsFileSystem.SetReadOnly(path, isReadOnly); @@ -523,6 +533,11 @@ namespace Emby.Server.Implementations.IO public void SetAttributes(string path, bool isHidden, bool isReadOnly) { + if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + { + return; + } + if (_sharpCifsFileSystem.IsEnabledForPath(path)) { _sharpCifsFileSystem.SetAttributes(path, isHidden, isReadOnly); diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 961f9886c..5c3e1dab1 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -141,17 +141,6 @@ namespace Emby.Server.Implementations.Library return true; } } - - // Ignore samples - var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase) - .Replace("-", " ", StringComparison.OrdinalIgnoreCase) - .Replace("_", " ", StringComparison.OrdinalIgnoreCase) - .Replace("!", " ", StringComparison.OrdinalIgnoreCase); - - if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1) - { - return true; - } } return false; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f71e2714a..2934a5147 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -443,7 +443,7 @@ namespace Emby.Server.Implementations.Library BaseItem removed; _libraryItemsCache.TryRemove(item.Id, out removed); - ReportItemRemoved(item); + ReportItemRemoved(item, parent); } private IEnumerable<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) @@ -1804,7 +1804,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void CreateItem(BaseItem item, CancellationToken cancellationToken) { - CreateItems(new[] { item }, cancellationToken); + CreateItems(new[] { item }, item.GetParent(), cancellationToken); } /// <summary> @@ -1813,7 +1813,7 @@ namespace Emby.Server.Implementations.Library /// <param name="items">The items.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public void CreateItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) + public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken) { var list = items.ToList(); @@ -1830,7 +1830,11 @@ namespace Emby.Server.Implementations.Library { try { - ItemAdded(this, new ItemChangeEventArgs { Item = item }); + ItemAdded(this, new ItemChangeEventArgs + { + Item = item, + Parent = parent ?? item.GetParent() + }); } catch (Exception ex) { @@ -1878,6 +1882,7 @@ namespace Emby.Server.Implementations.Library ItemUpdated(this, new ItemChangeEventArgs { Item = item, + Parent = item.GetParent(), UpdateReason = updateReason }); } @@ -1892,13 +1897,17 @@ namespace Emby.Server.Implementations.Library /// Reports the item removed. /// </summary> /// <param name="item">The item.</param> - public void ReportItemRemoved(BaseItem item) + public void ReportItemRemoved(BaseItem item, BaseItem parent) { if (ItemRemoved != null) { try { - ItemRemoved(this, new ItemChangeEventArgs { Item = item }); + ItemRemoved(this, new ItemChangeEventArgs + { + Item = item, + Parent = parent + }); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 667616414..d74235ec7 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -126,10 +126,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { leftOver.Add(child); } - else if (IsIgnored(child.Name)) - { - - } else { files.Add(child); @@ -298,22 +294,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return item; } - private bool IsIgnored(string filename) - { - // Ignore samples - var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase) - .Replace("-", " ", StringComparison.OrdinalIgnoreCase) - .Replace("_", " ", StringComparison.OrdinalIgnoreCase) - .Replace("!", " ", StringComparison.OrdinalIgnoreCase); - - if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1) - { - return true; - } - - return false; - } - /// <summary> /// Sets the initial item values. /// </summary> diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index a0ff29482..3bad69b56 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Globalization; using Emby.Naming.Common; using Emby.Naming.TV; +using MediaBrowser.Model.Logging; namespace Emby.Server.Implementations.Library.Resolvers.TV { @@ -21,16 +22,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV private readonly ILibraryManager _libraryManager; private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private readonly ILocalizationManager _localization; + private readonly ILogger _logger; /// <summary> /// Initializes a new instance of the <see cref="SeasonResolver"/> class. /// </summary> /// <param name="config">The config.</param> - public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization) + public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger logger) { _config = config; _libraryManager = libraryManager; _localization = localization; + _logger = logger; } /// <summary> @@ -45,20 +48,40 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var series = ((Series)args.Parent); + var path = args.Path; + var season = new Season { - IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber, + IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(path, true, true).SeasonNumber, SeriesId = series.Id, SeriesName = series.Name }; if (season.IndexNumber.HasValue) { + var resolver = new Emby.Naming.TV.EpisodeResolver(namingOptions); + + var episodeInfo = resolver.Resolve(path, true); + + if (episodeInfo != null) + { + if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue) + { + _logger.Info("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}", + path, + episodeInfo.SeasonNumber.Value, + episodeInfo.EpisodeNumber.Value); + + return null; + } + } + var seasonNumber = season.IndexNumber.Value; season.Name = seasonNumber == 0 ? args.LibraryOptions.SeasonZeroDisplayName : string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage); + } return season; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index df21c1409..8021399bd 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -191,7 +191,8 @@ namespace Emby.Server.Implementations.Library { ItemFields.AirTime, ItemFields.DateCreated, - ItemFields.ChannelInfo + ItemFields.ChannelInfo, + ItemFields.ParentId } } }; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 0f48ff46b..71c953b2c 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Library return builder.ToString(); } - public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint) + public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint, bool isUserSession) { if (string.IsNullOrWhiteSpace(username)) { @@ -288,8 +288,11 @@ namespace Emby.Server.Implementations.Library // Update LastActivityDate and LastLoginDate, then save if (success) { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } UpdateInvalidLoginAttemptCount(user, 0); } else @@ -812,7 +815,7 @@ namespace Emby.Server.Implementations.Library var text = new StringBuilder(); - var localAddress = _appHost.GetLocalApiUrl().Result ?? string.Empty; + var localAddress = _appHost.GetLocalApiUrl(CancellationToken.None).Result ?? string.Empty; text.AppendLine("Use your web browser to visit:"); text.AppendLine(string.Empty); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 9992c71ec..35d2d3c0a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1512,6 +1512,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } + DeleteFileIfEmpty(recordPath); + TriggerRefresh(recordPath); _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false); @@ -1542,6 +1544,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } + private void DeleteFileIfEmpty(string path) + { + var file = _fileSystem.GetFileInfo(path); + + if (file.Exists && file.Length == 0) + { + try + { + _fileSystem.DeleteFile(path); + } + catch (Exception ex) + { + _logger.ErrorException("Error deleting 0-byte failed recording file {0}", ex, path); + } + } + } + private void TriggerRefresh(string path) { _logger.Info("Triggering refresh on {0}", path); @@ -1897,7 +1916,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV imageSaveFilenameWithoutExtension = "logo"; break; case ImageType.Thumb: - imageSaveFilenameWithoutExtension = "landscape"; + if (program.IsSeries) + { + imageSaveFilenameWithoutExtension = Path.GetFileNameWithoutExtension(recordingPath) + "-thumb"; + } + else + { + imageSaveFilenameWithoutExtension = "landscape"; + } + break; case ImageType.Backdrop: imageSaveFilenameWithoutExtension = "fanart"; @@ -1921,9 +1948,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program) { - var image = program.GetImageInfo(ImageType.Primary, 0); + var image = program.IsSeries ? + (program.GetImageInfo(ImageType.Thumb, 0) ?? program.GetImageInfo(ImageType.Primary, 0)) : + (program.GetImageInfo(ImageType.Primary, 0) ?? program.GetImageInfo(ImageType.Thumb, 0)); - if (image != null && program.IsMovie) + if (image != null) { try { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 95ec1dee0..7c251e303 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -105,31 +105,64 @@ namespace Emby.Server.Implementations.LiveTv.Listings if (string.Equals(ext, ".gz", StringComparison.OrdinalIgnoreCase)) { - using (var stream = _fileSystem.OpenRead(file)) + try { - var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); - _fileSystem.CreateDirectory(tempFolder); - - try - { - _zipClient.ExtractAllFromGz(stream, tempFolder, true); - } - catch - { - // If the extraction fails just return the original file, it could be a gz - return file; - } + var tempFolder = ExtractGz(file); + return FindXmlFile(tempFolder); + } + catch (Exception ex) + { + //_logger.ErrorException("Error extracting from gz file {0}", ex, file); + } - return _fileSystem.GetFiles(tempFolder, true) - .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase)) - .Select(i => i.FullName) - .FirstOrDefault(); + try + { + var tempFolder = ExtractFirstFileFromGz(file); + return FindXmlFile(tempFolder); + } + catch (Exception ex) + { + //_logger.ErrorException("Error extracting from zip file {0}", ex, file); } } return file; } + private string ExtractFirstFileFromGz(string file) + { + using (var stream = _fileSystem.OpenRead(file)) + { + var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); + _fileSystem.CreateDirectory(tempFolder); + + _zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml"); + + return tempFolder; + } + } + + private string ExtractGz(string file) + { + using (var stream = _fileSystem.OpenRead(file)) + { + var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); + _fileSystem.CreateDirectory(tempFolder); + + _zipClient.ExtractAllFromGz(stream, tempFolder, true); + + return tempFolder; + } + } + + private string FindXmlFile(string directory) + { + return _fileSystem.GetFiles(directory, true) + .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase)) + .Select(i => i.FullName) + .FirstOrDefault(); + } + public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(channelId)) @@ -149,6 +182,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings _logger.Debug("Getting xmltv programs for channel {0}", channelId); var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); + _logger.Debug("Opening XmlTvReader for {0}", path); var reader = new XmlTvReader(path, GetLanguage(info)); var results = reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken); @@ -251,6 +285,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { // In theory this should never be called because there is always only one lineup var path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false); + _logger.Debug("Opening XmlTvReader for {0}", path); var reader = new XmlTvReader(path, GetLanguage(info)); var results = reader.GetChannels(); @@ -262,6 +297,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { // In theory this should never be called because there is always only one lineup var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); + _logger.Debug("Opening XmlTvReader for {0}", path); var reader = new XmlTvReader(path, GetLanguage(info)); var results = reader.GetChannels(); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 7e72d1b1a..211e0de4b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -125,7 +125,7 @@ namespace Emby.Server.Implementations.LiveTv public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders) { _services = services.ToArray(); - _tunerHosts.AddRange(tunerHosts); + _tunerHosts.AddRange(tunerHosts.Where(i => i.IsSupported)); _listingProviders.AddRange(listingProviders); foreach (var service in _services) @@ -947,6 +947,7 @@ namespace Emby.Server.Implementations.LiveTv IsKids = query.IsKids, IsNews = query.IsNews, Genres = query.Genres, + GenreIds = query.GenreIds, StartIndex = query.StartIndex, Limit = query.Limit, OrderBy = query.OrderBy, @@ -1020,7 +1021,8 @@ namespace Emby.Server.Implementations.LiveTv EnableTotalRecordCount = query.EnableTotalRecordCount, OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { topFolder.Id.ToString("N") }, - DtoOptions = options + DtoOptions = options, + GenreIds = query.GenreIds }; if (query.Limit.HasValue) @@ -1421,7 +1423,7 @@ namespace Emby.Server.Implementations.LiveTv if (newPrograms.Count > 0) { - _libraryManager.CreateItems(newPrograms, cancellationToken); + _libraryManager.CreateItems(newPrograms, null, cancellationToken); } // TODO: Do this in bulk diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index ed8b3074b..29b7c41ef 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv } var list = sources.ToList(); - var serverUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); + var serverUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); foreach (var source in list) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index e0fd32aee..45e96c36d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -39,6 +39,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts FileSystem = fileSystem; } + public virtual bool IsSupported + { + get + { + return true; + } + } + protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken); public abstract string Type { get; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index c96d1f359..04c5303f1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -17,6 +17,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; +using System.IO; namespace Emby.Server.Implementations.LiveTv.TunerHosts { @@ -75,6 +76,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } + private string[] _disallowedSharedStreamExtensions = new string[] + { + ".mkv", + ".mp4", + ".m3u8", + ".mpd" + }; + protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { var tunerCount = info.TunerCount; @@ -95,7 +104,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) { - return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment); + var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty; + + if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment); + } } return new LiveStream(mediaSource, info, _environment, FileSystem, Logger, Config.ApplicationPaths); @@ -152,6 +166,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts isRemote = !_networkManager.IsInLocalNetwork(uri.Host); } + var supportsDirectPlay = !info.EnableStreamLooping && info.TunerCount == 0; + var mediaSource = new MediaSourceInfo { Path = path, @@ -183,7 +199,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IsInfiniteStream = true, IsRemote = isRemote, - IgnoreDts = true + IgnoreDts = true, + SupportsDirectPlay = supportsDirectPlay }; mediaSource.InferTotalBitrate(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index cc2cb3e5e..af7491e86 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -71,7 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } else if (contentType.IndexOf("mp4", StringComparison.OrdinalIgnoreCase) != -1 || contentType.IndexOf("dash", StringComparison.OrdinalIgnoreCase) != -1 || - contentType.IndexOf("mpegURL", StringComparison.OrdinalIgnoreCase) != -1) + contentType.IndexOf("mpegURL", StringComparison.OrdinalIgnoreCase) != -1 || + contentType.IndexOf("text/", StringComparison.OrdinalIgnoreCase) != -1) { requiresRemux = true; } @@ -88,6 +89,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts SetTempFilePath(extension); var taskCompletionSource = new TaskCompletionSource<bool>(); + + var now = DateTime.UtcNow; + StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; @@ -97,11 +101,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; - if (OpenedMediaSource.SupportsProbing) - { - await Task.Delay(3000).ConfigureAwait(false); - } - //OpenedMediaSource.Path = TempFilePath; //OpenedMediaSource.Protocol = MediaProtocol.File; @@ -111,6 +110,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); + + if (OpenedMediaSource.SupportsProbing) + { + var elapsed = (DateTime.UtcNow - now).TotalMilliseconds; + + var delay = Convert.ToInt32(3000 - elapsed); + + if (delay > 0) + { + Logger.Info("Delaying shared stream by {0}ms to allow the buffer to build.", delay); + + await Task.Delay(delay).ConfigureAwait(false); + } + } } protected override void CloseInternal() diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index f21a0b295..7c55540e5 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -1,74 +1,74 @@ { "Latest": "Darreres", "ValueSpecialEpisodeName": "Especial - {0}", - "Inherit": "Inherit", - "Books": "Books", - "Music": "Music", - "Games": "Games", - "Photos": "Photos", - "MixedContent": "Mixed content", - "MusicVideos": "Music videos", - "HomeVideos": "Home videos", - "Playlists": "Playlists", - "HeaderRecordingGroups": "Recording Groups", + "Inherit": "Heretat", + "Books": "Llibres", + "Music": "M\u00fasica", + "Games": "Jocs", + "Photos": "Fotos", + "MixedContent": "Contingut mesclat", + "MusicVideos": "V\u00eddeos musicals", + "HomeVideos": "V\u00eddeos dom\u00e8stics", + "Playlists": "Llistes de reproducci\u00f3", + "HeaderRecordingGroups": "Grups d'Enregistrament", "HeaderContinueWatching": "Continua Veient", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteSongs": "Favorite Songs", + "HeaderFavoriteArtists": "Artistes Preferits", + "HeaderFavoriteSongs": "Can\u00e7ons Preferides", "HeaderAlbumArtists": "Album Artists", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteEpisodes": "Favorite Episodes", + "HeaderFavoriteAlbums": "\u00c0lbums Preferits", + "HeaderFavoriteEpisodes": "Episodis Preferits", "HeaderFavoriteShows": "Programes Preferits", "HeaderNextUp": "A continuaci\u00f3", - "Favorites": "Favorites", - "Collections": "Collections", - "Channels": "Channels", - "Movies": "Movies", - "Albums": "Albums", - "Artists": "Artists", - "Folders": "Folders", - "Songs": "Songs", - "TvShows": "TV Shows", - "Shows": "Shows", + "Favorites": "Preferits", + "Collections": "Col\u00b7leccions", + "Channels": "Canals", + "Movies": "Pel\u00b7l\u00edcules", + "Albums": "\u00c0lbums", + "Artists": "Artistes", + "Folders": "Directoris", + "Songs": "Can\u00e7ons", + "TvShows": "Espectacles de TV", + "Shows": "Espectacles", "Genres": "G\u00e8neres", - "NameSeasonNumber": "Season {0}", - "AppDeviceValues": "App: {0}, Device: {1}", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "HeaderLiveTV": "Live TV", - "ChapterNameValue": "Chapter {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "LabelRunningTimeValue": "Running time: {0}", - "ScheduledTaskStartedWithName": "{0} started", - "VersionNumber": "Version {0}", - "PluginInstalledWithName": "{0} was installed", - "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly.", - "PluginUpdatedWithName": "{0} was updated", - "PluginUninstalledWithName": "{0} was uninstalled", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "DeviceOnlineWithName": "{0} is connected", - "UserOnlineFromDevice": "{0} is online from {1}", - "ProviderValue": "Provider: {0}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "UserCreatedWithName": "User {0} has been created", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserDeletedWithName": "User {0} has been deleted", - "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageApplicationUpdated": "Emby Server has been updated", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "DeviceOfflineWithName": "{0} has disconnected", - "UserStartedPlayingItemWithValues": "{0} has started playing {1}", - "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", + "NameSeasonNumber": "Temporada {0}", + "AppDeviceValues": "App: {0}, Dispositiu: {1}", + "UserDownloadingItemWithValues": "{0} est\u00e0 descarregant {1}", + "HeaderLiveTV": "TV en Directe", + "ChapterNameValue": "Episodi {0}", + "ScheduledTaskFailedWithName": "{0} ha fallat", + "LabelRunningTimeValue": "Temps en marxa: {0}", + "ScheduledTaskStartedWithName": "{0} iniciat", + "VersionNumber": "Versi\u00f3 {0}", + "PluginInstalledWithName": "{0} ha estat instal\u00b7lat", + "StartupEmbyServerIsLoading": "El Servidor d'Emby està carregant. Si et plau, prova de nou en breus.", + "PluginUpdatedWithName": "{0} ha estat actualitzat", + "PluginUninstalledWithName": "{0} ha estat desinstal\u00b7lat", + "ItemAddedWithName": "{0} afegit a la biblioteca", + "ItemRemovedWithName": "{0} eliminat de la biblioteca", + "LabelIpAddressValue": "Adre\u00e7a IP: {0}", + "DeviceOnlineWithName": "{0} est\u00e0 connectat", + "UserOnlineFromDevice": "{0} est\u00e0 connectat des de {1}", + "ProviderValue": "Prove\u00efdor: {0}", + "SubtitlesDownloadedForItem": "Subt\u00edtols descarregats per a {0}", + "UserCreatedWithName": "S'ha creat l'usuari {0}", + "UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}", + "UserDeletedWithName": "L'usuari {0} ha estat eliminat", + "UserConfigurationUpdatedWithName": "La configuraci\u00f3 d'usuari ha estat actualitzada per a {0}", + "MessageServerConfigurationUpdated": "S'ha actualitzat la configuraci\u00f3 del servidor", + "MessageNamedServerConfigurationUpdatedWithValue": "La secci\u00f3 de configuraci\u00f3 {0} ha estat actualitzada", + "MessageApplicationUpdated": "El Servidor d'Emby ha estat actualitzat", + "FailedLoginAttemptWithUserName": "Intent de connexi\u00f3 fallit des de {0}", + "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", + "UserOfflineFromDevice": "{0} s'ha desconnectat de {1}", + "DeviceOfflineWithName": "{0} s'ha desconnectat", + "UserStartedPlayingItemWithValues": "{0} ha comen\u00e7at a reproduir {1}", + "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}", + "NotificationOptionPluginError": "Un component ha fallat", + "NotificationOptionApplicationUpdateAvailable": "Actualitzaci\u00f3 d'aplicaci\u00f3 disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualitzaci\u00f3 d'aplicaci\u00f3 instal\u00b7lada", + "NotificationOptionPluginUpdateInstalled": "Actualitzaci\u00f3 de complement instal\u00b7lada", + "NotificationOptionPluginInstalled": "Complement instal\u00b7lat", + "NotificationOptionPluginUninstalled": "Complement desinstal\u00b7lat", "NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionGamePlayback": "Game playback started", diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 7bab689eb..bcfadb61c 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -7,7 +7,7 @@ "Games": "Spiele", "Photos": "Fotos", "MixedContent": "Gemischte Inhalte", - "MusicVideos": "Musik-Videos", + "MusicVideos": "Musikvideos", "HomeVideos": "Heimvideos", "Playlists": "Wiedergabelisten", "HeaderRecordingGroups": "Aufnahme-Gruppen", @@ -27,7 +27,7 @@ "Artists": "Interpreten", "Folders": "Verzeichnisse", "Songs": "Songs", - "TvShows": "TV Shows", + "TvShows": "TV Sendungen", "Shows": "Serien", "Genres": "Genres", "NameSeasonNumber": "Staffel {0}", diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json new file mode 100644 index 000000000..ab229e111 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -0,0 +1,91 @@ +{ + "Latest": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1", + "ValueSpecialEpisodeName": "\u0395\u03b9\u03b4\u03b9\u03ba\u03ac - {0} ", + "Inherit": "Inherit", + "Books": "\u0392\u03b9\u03b2\u03bb\u03af\u03b1", + "Music": "\u039c\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae", + "Games": "\u03a0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1", + "Photos": "\u03a6\u03c9\u03c4\u03bf\u03b3\u03c1\u03b1\u03c6\u03af\u03b5\u03c2", + "MixedContent": "\u0391\u03bd\u03ac\u03bc\u03b5\u03b9\u03ba\u03c4\u03bf \u03a0\u03b5\u03c1\u03b9\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf", + "MusicVideos": "\u039c\u03bf\u03c5\u03c3\u03b9\u03ba\u03ac \u03b2\u03af\u03bd\u03c4\u03b5\u03bf", + "HomeVideos": "\u03a0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ac \u0392\u03af\u03bd\u03c4\u03b5\u03bf", + "Playlists": "\u039b\u03af\u03c3\u03c4\u03b5\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2", + "HeaderRecordingGroups": "\u0393\u03ba\u03c1\u03bf\u03c5\u03c0 \u0395\u03b3\u03b3\u03c1\u03b1\u03c6\u03ce\u03bd", + "HeaderContinueWatching": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7", + "HeaderFavoriteArtists": "\u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03bf\u03b9 \u039a\u03b1\u03bb\u03bb\u03b9\u03c4\u03ad\u03c7\u03bd\u03b5\u03c2", + "HeaderFavoriteSongs": "\u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03b1 \u03a4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1", + "HeaderAlbumArtists": "\u0386\u03bb\u03bc\u03c0\u03bf\u03c5\u03bc \u039a\u03b1\u03bb\u03bb\u03b9\u03c4\u03b5\u03c7\u03bd\u03ce\u03bd", + "HeaderFavoriteAlbums": "\u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03b1 \u0386\u03bb\u03bc\u03c0\u03bf\u03c5\u03bc", + "HeaderFavoriteEpisodes": "\u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03b1 \u03b5\u03c0\u03b5\u03b9\u03c3\u03cc\u03b4\u03b9\u03b1", + "HeaderFavoriteShows": "\u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03b5\u03c2 \u03a3\u03b5\u03b9\u03c1\u03ad\u03c2", + "HeaderNextUp": "\u0395\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf", + "Favorites": "\u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03b1", + "Collections": "\u03a3\u03c5\u03bb\u03bb\u03bf\u03b3\u03ad\u03c2", + "Channels": "\u039a\u03b1\u03bd\u03ac\u03bb\u03b9\u03b1", + "Movies": "\u03a4\u03b1\u03b9\u03bd\u03af\u03b5\u03c2", + "Albums": "\u0386\u03bb\u03bc\u03c0\u03bf\u03c5\u03bc", + "Artists": "\u039a\u03b1\u03bb\u03bb\u03b9\u03c4\u03ad\u03c7\u03bd\u03b5\u03c2", + "Folders": "\u03a6\u03ac\u03ba\u03b5\u03bb\u03bf\u03b9", + "Songs": "\u03a4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1", + "TvShows": "\u03a4\u03b7\u03bb\u03b5\u03bf\u03c0\u03c4\u03b9\u03ba\u03ac \u03c0\u03c1\u03bf\u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03b1", + "Shows": "\u03a3\u03b5\u03b9\u03c1\u03ad\u03c2", + "Genres": "\u0395\u03af\u03b4\u03b7", + "NameSeasonNumber": "\u039a\u03cd\u03ba\u03bb\u03bf\u03c2 {0}", + "AppDeviceValues": "\u0395\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae: {0}, \u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae: {1}", + "UserDownloadingItemWithValues": "{0} \u03ba\u03b1\u03c4\u03b5\u03b2\u03ac\u03b6\u03b5\u03b9 {1}", + "HeaderLiveTV": "\u0396\u03c9\u03bd\u03c4\u03b1\u03bd\u03ae \u03a4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7", + "ChapterNameValue": "\u039a\u03b5\u03c6\u03ac\u03bb\u03b1\u03b9\u03bf {0}", + "ScheduledTaskFailedWithName": "{0} \u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1", + "LabelRunningTimeValue": "\u0394\u03b9\u03ac\u03c1\u03ba\u03b5\u03b9\u03b1: {0}", + "ScheduledTaskStartedWithName": "{0} \u03ad\u03bd\u03b1\u03c1\u03be\u03b7", + "VersionNumber": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 {0}", + "PluginInstalledWithName": "{0} \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5", + "StartupEmbyServerIsLoading": "\u039f \u03a3\u03ad\u03c1\u03b2\u03b5\u03c1 \u03c6\u03bf\u03c1\u03c4\u03ce\u03bd\u03b5\u03b9. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03c3\u03b5 \u03bb\u03af\u03b3\u03bf", + "PluginUpdatedWithName": "{0} \u03ad\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "PluginUninstalledWithName": "{0} \u03ad\u03c7\u03b5\u03b9 \u03b1\u03c0\u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03b1\u03b8\u03b5\u03af", + "ItemAddedWithName": "{0} \u03c0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03b7 \u03b2\u03b9\u03b2\u03bb\u03b9\u03bf\u03b8\u03ae\u03ba\u03b7", + "ItemRemovedWithName": "{0} \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c6\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b2\u03b9\u03b2\u03bb\u03b9\u03bf\u03b8\u03ae\u03ba\u03b7", + "LabelIpAddressValue": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP: {0}", + "DeviceOnlineWithName": "{0} \u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", + "UserOnlineFromDevice": "{0} \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b1\u03c0\u03bf {1}", + "ProviderValue": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2: {0}", + "SubtitlesDownloadedForItem": "\u03a5\u03c0\u03cc\u03c4\u03b9\u03c4\u03bb\u03bf\u03b9 \u03bb\u03ae\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc {0}", + "UserCreatedWithName": "\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03b8\u03b7\u03ba\u03b5 \u03bf \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 {0}", + "UserPasswordChangedWithName": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 {0} \u03b1\u03bb\u03bb\u03ac\u03c7\u03b8\u03b7\u03ba\u03b5", + "UserDeletedWithName": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 {0} \u03b4\u03b9\u03b5\u03b3\u03c1\u03ac\u03c6\u03b5\u03b9", + "UserConfigurationUpdatedWithName": "\u039f\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 {0} \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03b9", + "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MessageNamedServerConfigurationUpdatedWithValue": "\u039f\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03bc\u03ad\u03b1 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae {0} \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03b9", + "MessageApplicationUpdated": "\u039f \u03a3\u03ad\u03c1\u03b2\u03b5\u03c1 \u03ad\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "FailedLoginAttemptWithUserName": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b1\u03c0\u03cc {0}", + "AuthenticationSucceededWithUserName": "{0} \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03b5\u03af\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", + "UserOfflineFromDevice": "{0} \u03b1\u03c0\u03bf\u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc {1}", + "DeviceOfflineWithName": "{0} \u03b1\u03c0\u03bf\u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", + "UserStartedPlayingItemWithValues": "{0} \u03be\u03b5\u03ba\u03af\u03bd\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03af\u03b6\u03b5\u03b9 {1}", + "UserStoppedPlayingItemWithValues": "{0} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03af\u03b6\u03b5\u03b9 {1}", + "NotificationOptionPluginError": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c4\u03bf\u03c5", + "NotificationOptionApplicationUpdateAvailable": "\u03a5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7", + "NotificationOptionApplicationUpdateInstalled": "\u0397 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", + "NotificationOptionPluginUpdateInstalled": "\u0397 \u03b1\u03bd\u03b1\u03b2\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 plugin \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", + "NotificationOptionPluginInstalled": "\u03a4\u03bf plugin \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5", + "NotificationOptionPluginUninstalled": "\u03a4\u03bf plugin \u03b1\u03c0\u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5", + "NotificationOptionVideoPlayback": "\u03a4\u03bf \u03b2\u03af\u03bd\u03c4\u03b5\u03bf \u03c0\u03c1\u03bf\u03b2\u03ac\u03bb\u03bb\u03b5\u03c4\u03b1\u03b9", + "NotificationOptionAudioPlayback": "\u0397 \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae \u03c0\u03b1\u03af\u03b6\u03b5\u03b9", + "NotificationOptionGamePlayback": "\u03a4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03be\u03b5\u03ba\u03af\u03bd\u03b7\u03c3\u03b5", + "NotificationOptionVideoPlaybackStopped": "\u03a4\u03bf \u03b2\u03af\u03bd\u03c4\u03b5\u03bf \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5", + "NotificationOptionAudioPlaybackStopped": "\u0397 \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5", + "NotificationOptionGamePlaybackStopped": "\u03a4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5", + "NotificationOptionTaskFailed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7\u03c2 \u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1\u03c2", + "NotificationOptionInstallationFailed": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", + "NotificationOptionNewLibraryContent": "\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03bd\u03ad\u03bf \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf", + "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionUserLockedOut": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", + "NotificationOptionServerRestartRequired": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae", + "UserLockedOutWithName": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 {0} \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", + "SubtitleDownloadFailureForItem": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 \u03c5\u03c0\u03bf\u03c4\u03af\u03c4\u03bb\u03c9\u03bd \u03b1\u03c0\u03cc {0}", + "Sync": "\u03a3\u03c5\u03b3\u03c7\u03c1\u03bf\u03bd\u03b9\u03c3\u03bc\u03cc\u03c2", + "User": "\u03a7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2", + "System": "\u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1", + "Application": "\u0395\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae", + "Plugin": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf" +}
\ No newline at end of file diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 2cc1dd759..8bfaffec8 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -83,7 +83,7 @@ "NotificationOptionServerRestartRequired": "Se necesita reiniciar el Servidor", "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", "SubtitleDownloadFailureForItem": "Fall\u00f3 la descarga de subt\u00edtulos para {0}", - "Sync": "Sinc.", + "Sync": "Sincronizar", "User": "Usuario", "System": "Sistema", "Application": "Aplicaci\u00f3n", diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json new file mode 100644 index 000000000..5c7ff5d6d --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -0,0 +1,91 @@ +{ + "Latest": "Letschte", + "ValueSpecialEpisodeName": "Spezial - {0}", + "Inherit": "Hinzuef\u00fcege", + "Books": "B\u00fcecher", + "Music": "Musig", + "Games": "Spiel", + "Photos": "Fotis", + "MixedContent": "Gmischte Inhalt", + "MusicVideos": "Musigfilm", + "HomeVideos": "Heimfilmli", + "Playlists": "Abspielliste", + "HeaderRecordingGroups": "Ufnahmegruppe", + "HeaderContinueWatching": "Wiiterluege", + "HeaderFavoriteArtists": "Besti Interpret", + "HeaderFavoriteSongs": "Besti Lieder", + "HeaderAlbumArtists": "Albuminterprete", + "HeaderFavoriteAlbums": "Favorite Albums", + "HeaderFavoriteEpisodes": "Favorite Episodes", + "HeaderFavoriteShows": "Favorite Shows", + "HeaderNextUp": "Next Up", + "Favorites": "Favorites", + "Collections": "Collections", + "Channels": "Channels", + "Movies": "Movies", + "Albums": "Albums", + "Artists": "Artists", + "Folders": "Folders", + "Songs": "Songs", + "TvShows": "TV Shows", + "Shows": "Shows", + "Genres": "Genres", + "NameSeasonNumber": "Season {0}", + "AppDeviceValues": "App: {0}, Device: {1}", + "UserDownloadingItemWithValues": "{0} is downloading {1}", + "HeaderLiveTV": "Live TV", + "ChapterNameValue": "Chapter {0}", + "ScheduledTaskFailedWithName": "{0} failed", + "LabelRunningTimeValue": "Running time: {0}", + "ScheduledTaskStartedWithName": "{0} started", + "VersionNumber": "Version {0}", + "PluginInstalledWithName": "{0} was installed", + "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly.", + "PluginUpdatedWithName": "{0} was updated", + "PluginUninstalledWithName": "{0} was uninstalled", + "ItemAddedWithName": "{0} was added to the library", + "ItemRemovedWithName": "{0} was removed from the library", + "LabelIpAddressValue": "Ip address: {0}", + "DeviceOnlineWithName": "{0} is connected", + "UserOnlineFromDevice": "{0} is online from {1}", + "ProviderValue": "Provider: {0}", + "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", + "UserCreatedWithName": "User {0} has been created", + "UserPasswordChangedWithName": "Password has been changed for user {0}", + "UserDeletedWithName": "User {0} has been deleted", + "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", + "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageApplicationUpdated": "Emby Server has been updated", + "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "UserOfflineFromDevice": "{0} has disconnected from {1}", + "DeviceOfflineWithName": "{0} has disconnected", + "UserStartedPlayingItemWithValues": "{0} has started playing {1}", + "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", + "NotificationOptionPluginError": "Plugin failure", + "NotificationOptionApplicationUpdateAvailable": "Application update available", + "NotificationOptionApplicationUpdateInstalled": "Application update installed", + "NotificationOptionPluginUpdateInstalled": "Plugin update installed", + "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionPluginUninstalled": "Plugin uninstalled", + "NotificationOptionVideoPlayback": "Video playback started", + "NotificationOptionAudioPlayback": "Audio playback started", + "NotificationOptionGamePlayback": "Game playback started", + "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", + "NotificationOptionGamePlaybackStopped": "Game playback stopped", + "NotificationOptionTaskFailed": "Scheduled task failure", + "NotificationOptionInstallationFailed": "Installation failure", + "NotificationOptionNewLibraryContent": "New content added", + "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionUserLockedOut": "User locked out", + "NotificationOptionServerRestartRequired": "Server restart required", + "UserLockedOutWithName": "User {0} has been locked out", + "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", + "Sync": "Sync", + "User": "User", + "System": "System", + "Application": "Application", + "Plugin": "Plugin" +}
\ No newline at end of file diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index fa4eac1c4..e0a375170 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -27,7 +27,7 @@ "Artists": "Artistas", "Folders": "Pastas", "Songs": "M\u00fasicas", - "TvShows": "TV Shows", + "TvShows": "S\u00e9ries de TV", "Shows": "S\u00e9ries", "Genres": "G\u00eaneros", "NameSeasonNumber": "Temporada {0}", diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 6e0e55bef..d790f4ab8 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -76,6 +76,21 @@ namespace Emby.Server.Implementations.MediaEncoder return false; } + if (video.VideoType == VideoType.Iso) + { + return false; + } + + if (video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd) + { + return false; + } + + if (video.IsShortcut) + { + return false; + } + if (!video.IsCompleteMedia) { return false; @@ -118,16 +133,6 @@ namespace Emby.Server.Implementations.MediaEncoder { if (extractImages) { - if (video.VideoType == VideoType.Iso) - { - continue; - } - - if (video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd) - { - continue; - } - try { // Add some time for the first chapter to make sure we don't end up with a black image diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index fbdb5c128..60da8a012 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -17,11 +17,54 @@ namespace Emby.Server.Implementations.Networking public class NetworkManager : INetworkManager { protected ILogger Logger { get; private set; } - private DateTime _lastRefresh; + + public event EventHandler NetworkChanged; public NetworkManager(ILogger logger) { Logger = logger; + + try + { + NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged; + } + catch (Exception ex) + { + Logger.ErrorException("Error binding to NetworkAddressChanged event", ex); + } + + try + { + NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged; + } + catch (Exception ex) + { + Logger.ErrorException("Error binding to NetworkChange_NetworkAvailabilityChanged event", ex); + } + } + + private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) + { + Logger.Debug("NetworkAvailabilityChanged"); + OnNetworkChanged(); + } + + private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e) + { + Logger.Debug("NetworkAddressChanged"); + OnNetworkChanged(); + } + + private void OnNetworkChanged() + { + lock (_localIpAddressSyncLock) + { + _localIpAddresses = null; + } + if (NetworkChanged != null) + { + NetworkChanged(this, EventArgs.Empty); + } } private List<IpAddressInfo> _localIpAddresses; @@ -29,34 +72,28 @@ namespace Emby.Server.Implementations.Networking public List<IpAddressInfo> GetLocalIpAddresses() { - const int cacheMinutes = 10; - lock (_localIpAddressSyncLock) { - var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes; - - if (_localIpAddresses == null || forceRefresh) + if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal().Select(ToIpAddressInfo).ToList(); + var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToList(); _localIpAddresses = addresses; - _lastRefresh = DateTime.UtcNow; return addresses; } + return _localIpAddresses; } - - return _localIpAddresses; } - private IEnumerable<IPAddress> GetLocalIpAddressesInternal() + private async Task<List<IPAddress>> GetLocalIpAddressesInternal() { var list = GetIPsDefault() .ToList(); if (list.Count == 0) { - list.AddRange(GetLocalIpAddressesFallback().Result); + list.AddRange(await GetLocalIpAddressesFallback().ConfigureAwait(false)); } var listClone = list.ToList(); @@ -65,7 +102,8 @@ namespace Emby.Server.Implementations.Networking .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) .ThenBy(i => listClone.IndexOf(i)) .Where(FilterIpAddress) - .DistinctBy(i => i.ToString()); + .DistinctBy(i => i.ToString()) + .ToList(); } private bool FilterIpAddress(IPAddress address) diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index e1c1bbe2b..6725cd7af 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -117,6 +117,10 @@ namespace Emby.Server.Implementations.Session { dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture); } + if (command.StartIndex.HasValue) + { + dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture); + } if (!string.IsNullOrWhiteSpace(command.MediaSourceId)) { dict["MediaSourceId"] = command.MediaSourceId; @@ -147,7 +151,7 @@ namespace Emby.Server.Implementations.Session return SendMessage("LibraryChanged", info, cancellationToken); } - public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { return SendMessage("RestartRequired", cancellationToken); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 30f6e6521..6b70f2cda 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1182,13 +1182,11 @@ namespace Emby.Server.Implementations.Session { var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var info = await _appHost.GetSystemInfo().ConfigureAwait(false); - var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendRestartRequiredNotification(info, cancellationToken).ConfigureAwait(false); + await session.SessionController.SendRestartRequiredNotification(cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -1423,7 +1421,7 @@ namespace Emby.Server.Implementations.Session if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint, true).ConfigureAwait(false); if (result == null) { diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index ee9ee8969..b13eb6116 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -145,12 +145,12 @@ namespace Emby.Server.Implementations.Session /// <param name="info">The information.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { - return SendMessagesInternal(new WebSocketMessage<SystemInfo> + return SendMessagesInternal(new WebSocketMessage<string> { MessageType = "RestartRequired", - Data = info + Data = string.Empty }, cancellationToken); } diff --git a/Emby.Server.Implementations/Social/SharingManager.cs b/Emby.Server.Implementations/Social/SharingManager.cs index 57cf93948..23ce7492a 100644 --- a/Emby.Server.Implementations/Social/SharingManager.cs +++ b/Emby.Server.Implementations/Social/SharingManager.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Social; using System; +using System.Threading; using System.Threading.Tasks; namespace Emby.Server.Implementations.Social @@ -42,7 +43,7 @@ namespace Emby.Server.Implementations.Social throw new ResourceNotFoundException(); } - var externalUrl = (await _appHost.GetSystemInfo().ConfigureAwait(false)).WanAddress; + var externalUrl = (await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false)).WanAddress; if (string.IsNullOrWhiteSpace(externalUrl)) { @@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.Social { var info = _repository.GetShareInfo(id); - AddShareInfo(info, _appHost.GetSystemInfo().Result.WanAddress); + AddShareInfo(info, _appHost.GetPublicSystemInfo(CancellationToken.None).Result.WanAddress); return info; } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 8dc1fae4b..28de80da1 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Udp private bool _isDisposed; - private readonly List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, Task>>> _responders = new List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, Task>>>(); + private readonly List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>> _responders = new List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>>(); private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _json; @@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Udp AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); } - private void AddMessageResponder(string message, bool isSubstring, Func<string, IpEndPointInfo, Encoding, Task> responder) + private void AddMessageResponder(string message, bool isSubstring, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task> responder) { - _responders.Add(new Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, Task>>(message, isSubstring, responder)); + _responders.Add(new Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>(message, isSubstring, responder)); } /// <summary> @@ -67,9 +67,15 @@ namespace Emby.Server.Implementations.Udp if (responder != null) { + var cancellationToken = CancellationToken.None; + try { - await responder.Item2.Item3(responder.Item1, message.RemoteEndPoint, encoding).ConfigureAwait(false); + await responder.Item2.Item3(responder.Item1, message.RemoteEndPoint, encoding, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } catch (Exception ex) { @@ -78,7 +84,7 @@ namespace Emby.Server.Implementations.Udp } } - private Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, Task>>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) + private Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) { var text = encoding.GetString(buffer, 0, bytesReceived); var responder = _responders.FirstOrDefault(i => @@ -94,14 +100,14 @@ namespace Emby.Server.Implementations.Udp { return null; } - return new Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, Task>>>(text, responder); + return new Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>>(text, responder); } - private async Task RespondToV2Message(string messageText, IpEndPointInfo endpoint, Encoding encoding) + private async Task RespondToV2Message(string messageText, IpEndPointInfo endpoint, Encoding encoding, CancellationToken cancellationToken) { var parts = messageText.Split('|'); - var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); + var localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(localUrl)) { @@ -112,7 +118,7 @@ namespace Emby.Server.Implementations.Udp Name = _appHost.FriendlyName }; - await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false); + await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint, cancellationToken).ConfigureAwait(false); if (parts.Length > 1) { @@ -248,7 +254,7 @@ namespace Emby.Server.Implementations.Udp } } - public async Task SendAsync(byte[] bytes, IpEndPointInfo remoteEndPoint) + public async Task SendAsync(byte[] bytes, IpEndPointInfo remoteEndPoint, CancellationToken cancellationToken) { if (_isDisposed) { @@ -267,7 +273,7 @@ namespace Emby.Server.Implementations.Udp try { - await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false); + await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, cancellationToken).ConfigureAwait(false); _logger.Info("Udp message sent to {0}", remoteEndPoint); } |
