Merge branch 'master' into usings

This commit is contained in:
Bond-009 2020-04-01 22:42:43 +02:00 committed by GitHub
commit f31efce52d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 538 additions and 317 deletions

View File

@ -1,6 +1,5 @@
#pragma warning disable CS1591
#pragma warning disable SA1402
#pragma warning disable SA1600
#pragma warning disable SA1649
using System;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;

View File

@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
namespace Emby.Server.Implementations.AppBase
{
/// <summary>
/// Provides a base class to hold common application paths used by both the Ui and Server.
/// Provides a base class to hold common application paths used by both the UI and Server.
/// This can be subclassed to add application-specific paths.
/// </summary>
public abstract class BaseApplicationPaths : IApplicationPaths
@ -37,10 +37,7 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The program data path.</value>
public string ProgramDataPath { get; }
/// <summary>
/// Gets the path to the web UI resources folder.
/// </summary>
/// <value>The web UI resources path.</value>
/// <inheritdoc/>
public string WebPath { get; }
/// <summary>

View File

@ -235,11 +235,6 @@ namespace Emby.Server.Implementations
/// </summary>
public int HttpsPort { get; private set; }
/// <summary>
/// Gets the content root for the webhost.
/// </summary>
public string ContentRoot { get; private set; }
/// <summary>
/// Gets the server configuration manager.
/// </summary>
@ -612,13 +607,7 @@ namespace Emby.Server.Implementations
DiscoverTypes();
await RegisterResources(serviceCollection, startupConfig).ConfigureAwait(false);
ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
if (string.IsNullOrEmpty(ContentRoot))
{
ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
}
await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false);
}
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
@ -649,9 +638,9 @@ namespace Emby.Server.Implementations
}
/// <summary>
/// Registers resources that classes will depend on
/// Registers services/resources with the service collection that will be available via DI.
/// </summary>
protected async Task RegisterResources(IServiceCollection serviceCollection, IConfiguration startupConfig)
protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig)
{
serviceCollection.AddMemoryCache();
@ -769,20 +758,8 @@ namespace Emby.Server.Implementations
CertificateInfo = GetCertificateInfo(true);
Certificate = GetCertificate(CertificateInfo);
HttpServer = new HttpListenerHost(
this,
LoggerFactory.CreateLogger<HttpListenerHost>(),
ServerConfigurationManager,
startupConfig,
NetworkManager,
JsonSerializer,
XmlSerializer,
CreateHttpListener())
{
GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading")
};
serviceCollection.AddSingleton(HttpServer);
serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
serviceCollection.AddSingleton(ImageProcessor);
@ -844,10 +821,15 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
ChapterManager = new ChapterManager(ItemRepository);
serviceCollection.AddSingleton(ChapterManager);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
EncodingManager = new MediaEncoder.EncodingManager(
LoggerFactory.CreateLogger<MediaEncoder.EncodingManager>(),
FileSystemManager,
MediaEncoder,
ChapterManager,
LibraryManager);
serviceCollection.AddSingleton(EncodingManager);
var activityLogRepo = GetActivityLogRepository();
@ -890,6 +872,14 @@ namespace Emby.Server.Implementations
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
}
/// <summary>
/// Create services registered with the service container that need to be initialized at application startup.
/// </summary>
public void InitializeServices()
{
HttpServer = Resolve<IHttpServer>();
}
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
{
// Distinct these to prevent users from reporting problems that aren't actually problems
@ -1167,7 +1157,7 @@ namespace Emby.Server.Implementations
{
exportedTypes = ass.GetExportedTypes();
}
catch (TypeLoadException ex)
catch (FileNotFoundException ex)
{
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
continue;
@ -1207,8 +1197,6 @@ namespace Emby.Server.Implementations
});
}
protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(LoggerFactory.CreateLogger<WebSocketSharpListener>());
private CertificateInfo GetCertificateInfo(bool generateCertificate)
{
// Custom cert

View File

@ -1,51 +1,48 @@
using System;
using MediaBrowser.Controller;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Browser
{
/// <summary>
/// Class BrowserLauncher.
/// Assists in opening application URLs in an external browser.
/// </summary>
public static class BrowserLauncher
{
/// <summary>
/// Opens the dashboard page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="appHost">The app host.</param>
private static void OpenDashboardPage(string page, IServerApplicationHost appHost)
{
var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page;
OpenUrl(appHost, url);
}
/// <summary>
/// Opens the web client.
/// Opens the home page of the web client.
/// </summary>
/// <param name="appHost">The app host.</param>
public static void OpenWebApp(IServerApplicationHost appHost)
{
OpenDashboardPage("index.html", appHost);
TryOpenUrl(appHost, "/web/index.html");
}
/// <summary>
/// Opens the URL.
/// Opens the swagger API page.
/// </summary>
/// <param name="appHost">The application host instance.</param>
/// <param name="appHost">The app host.</param>
public static void OpenSwaggerPage(IServerApplicationHost appHost)
{
TryOpenUrl(appHost, "/swagger/index.html");
}
/// <summary>
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
/// </summary>
/// <param name="appHost">The application host.</param>
/// <param name="url">The URL.</param>
private static void OpenUrl(IServerApplicationHost appHost, string url)
private static void TryOpenUrl(IServerApplicationHost appHost, string url)
{
try
{
appHost.LaunchUrl(url);
string baseUrl = appHost.GetLocalApiUrl("localhost");
appHost.LaunchUrl(baseUrl + url);
}
catch (NotSupportedException)
{
}
catch (Exception)
catch (Exception ex)
{
var logger = appHost.Resolve<ILogger>();
logger?.LogError(ex, "Failed to open browser window with URL {URL}", url);
}
}
}

View File

@ -1,13 +1,22 @@
using System.Collections.Generic;
using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Providers.Music;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations
{
/// <summary>
/// Static class containing the default configuration options for the web server.
/// </summary>
public static class ConfigurationOptions
{
public static Dictionary<string, string> Configuration => new Dictionary<string, string>
/// <summary>
/// Gets a new copy of the default configuration options.
/// </summary>
public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string>
{
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }

View File

@ -2006,7 +2006,7 @@ namespace Emby.Server.Implementations.Data
/// <summary>
/// Saves the chapters.
/// </summary>
public void SaveChapters(Guid id, List<ChapterInfo> chapters)
public void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters)
{
CheckDisposed();
@ -2035,22 +2035,24 @@ namespace Emby.Server.Implementations.Data
}
}
private void InsertChapters(byte[] idBlob, List<ChapterInfo> chapters, IDatabaseConnection db)
private void InsertChapters(byte[] idBlob, IReadOnlyList<ChapterInfo> chapters, IDatabaseConnection db)
{
var startIndex = 0;
var limit = 100;
var chapterIndex = 0;
const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ";
var insertText = new StringBuilder(StartInsertText, 256);
while (startIndex < chapters.Count)
{
var insertText = new StringBuilder("insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ");
var endIndex = Math.Min(chapters.Count, startIndex + limit);
for (var i = startIndex; i < endIndex; i++)
{
insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
}
insertText.Length -= 1; // Remove last ,
using (var statement = PrepareStatement(db, insertText.ToString()))
@ -2077,6 +2079,7 @@ namespace Emby.Server.Implementations.Data
}
startIndex += limit;
insertText.Length = StartInsertText.Length;
}
}

View File

@ -2,7 +2,9 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.Browser;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.Configuration;
namespace Emby.Server.Implementations.EntryPoints
{
@ -11,10 +13,8 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
public sealed class StartupWizard : IServerEntryPoint
{
/// <summary>
/// The app host.
/// </summary>
private readonly IServerApplicationHost _appHost;
private readonly IConfiguration _appConfig;
private readonly IServerConfigurationManager _config;
/// <summary>
@ -22,9 +22,10 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="appHost">The application host.</param>
/// <param name="config">The configuration manager.</param>
public StartupWizard(IServerApplicationHost appHost, IServerConfigurationManager config)
public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config)
{
_appHost = appHost;
_appConfig = appConfig;
_config = config;
}
@ -36,7 +37,11 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
if (!_config.Configuration.IsStartupWizardCompleted)
if (!_appConfig.HostWebClient())
{
BrowserLauncher.OpenSwaggerPage(_appHost);
}
else if (!_config.Configuration.IsStartupWizardCompleted)
{
BrowserLauncher.OpenWebApp(_appHost);
}

View File

@ -17,6 +17,7 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
@ -29,6 +30,12 @@ namespace Emby.Server.Implementations.HttpServer
{
public class HttpListenerHost : IHttpServer, IDisposable
{
/// <summary>
/// The key for a setting that specifies the default redirect path
/// to use for requests where the URL base prefix is invalid or missing.
/// </summary>
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
@ -52,12 +59,13 @@ namespace Emby.Server.Implementations.HttpServer
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer,
IHttpListener socketListener)
IHttpListener socketListener,
ILocalizationManager localizationManager)
{
_appHost = applicationHost;
_logger = logger;
_config = config;
_defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_defaultRedirectPath = configuration[DefaultRedirectKey];
_baseUrlPrefix = _config.Configuration.BaseUrl;
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
@ -69,6 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
Instance = this;
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
}
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.IO;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Threading;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Globalization;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using MediaBrowser.Controller.LiveTv;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Globalization;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Buffers;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@ -1,8 +1,8 @@
{
"Albums": "Албуми",
"AppDeviceValues": "Програма: {0}, устройство: {1}",
"AppDeviceValues": "Програма: {0}, Устройство: {1}",
"Application": "Програма",
"Artists": "Изпълнители",
"Artists": "Артисти",
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
"Books": "Книги",
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
"ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
"ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}"
"VersionNumber": "Версия {0}",
"TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.",
"TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи",
"TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.",
"TaskRefreshChannels": "Обновяване на Канали",
"TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.",
"TaskCleanTranscode": "Изчиства директорията за прекодиране",
"TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.",
"TaskUpdatePlugins": "Актуализира добавките",
"TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",
"TaskRefreshPeople": "Обновяване на участниците",
"TaskCleanLogsDescription": "Изтрива лог файлове по-стари от {0} дни.",
"TaskCleanLogs": "Изчисти директорията с логове",
"TaskRefreshLibraryDescription": "Сканира Вашата библиотека с медия за нови файлове и обновява мета-данните.",
"TaskRefreshLibrary": "Сканиране на библиотеката с медия",
"TaskRefreshChapterImagesDescription": "Създава иконки за видеа, които имат епизоди.",
"TaskRefreshChapterImages": "Извличане на изображения за епизода",
"TaskCleanCacheDescription": "Изтриване на ненужните от системата файлове.",
"TaskCleanCache": "Изчистване на Кеш-директорията",
"TasksChannelsCategory": "Интернет Канали",
"TasksApplicationCategory": "Приложение",
"TasksLibraryCategory": "Библиотека",
"TasksMaintenanceCategory": "Поддръжка"
}

View File

@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
"VersionNumber": "Version {0}"
"VersionNumber": "Version {0}",
"TasksChannelsCategory": "Chaines en ligne",
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
"TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant",
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
"TaskRefreshChannels": "Rafraîchit les chaines",
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
"TaskCleanTranscode": "Nettoie les dossier des transcodages",
"TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.",
"TaskUpdatePlugins": "Mettre à jour les plugins",
"TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.",
"TaskRefreshPeople": "Rafraîchit les acteurs",
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
"TaskCleanLogs": "Nettoie le répertoire des journaux",
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
"TaskRefreshLibrary": "Scanne toute les Bibliothèques",
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
"TaskRefreshChapterImages": "Extrait les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
"TaskCleanCache": "Vider le répertoire cache",
"TasksApplicationCategory": "Application",
"TasksLibraryCategory": "Bibliothèque",
"TasksMaintenanceCategory": "Maintenance"
}

View File

@ -5,7 +5,7 @@
"Artists": "Artisti",
"AuthenticationSucceededWithUserName": "{0} autenticato con successo",
"Books": "Libri",
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera da {0}",
"CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera dal device {0}",
"Channels": "Canali",
"ChapterNameValue": "Capitolo {0}",
"Collections": "Collezioni",
@ -15,7 +15,7 @@
"Favorites": "Preferiti",
"Folders": "Cartelle",
"Genres": "Generi",
"HeaderAlbumArtists": "Artisti dell' Album",
"HeaderAlbumArtists": "Artisti degli Album",
"HeaderCameraUploads": "Caricamenti Fotocamera",
"HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti",
@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
"ValueSpecialEpisodeName": "Speciale - {0}",
"VersionNumber": "Versione {0}"
"VersionNumber": "Versione {0}",
"TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali Internet.",
"TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.",
"TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti",
"TaskRefreshChannels": "Aggiorna i canali",
"TaskCleanTranscodeDescription": "Cancella i file di transcode più vecchi di un giorno.",
"TaskCleanTranscode": "Svuota la cartella del transcoding",
"TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.",
"TaskUpdatePlugins": "Aggiorna i Plugin",
"TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.",
"TaskRefreshPeople": "Aggiorna persone",
"TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.",
"TaskCleanLogs": "Pulisci la cartella dei log",
"TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.",
"TaskRefreshLibrary": "Analizza la libreria dei contenuti multimediali",
"TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.",
"TaskRefreshChapterImages": "Estrai immagini capitolo",
"TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.",
"TaskCleanCache": "Pulisci la directory della cache",
"TasksChannelsCategory": "Canali su Internet",
"TasksApplicationCategory": "Applicazione",
"TasksLibraryCategory": "Libreria",
"TasksMaintenanceCategory": "Manutenzione"
}

View File

@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1} em {2}",
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca de mídia",
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versão {0}"
"VersionNumber": "Versão {0}",
"TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas faltando baseado na configuração de metadados.",
"TaskDownloadMissingSubtitles": "Baixar legendas que estão faltando",
"TaskRefreshChannelsDescription": "Atualizar informação de canais da internet .",
"TaskRefreshChannels": "Atualizar Canais",
"TaskCleanTranscodeDescription": "Deletar arquivos de transcodificação com mais de um dia de criação.",
"TaskCleanTranscode": "Limpar pasta de transcodificação",
"TaskUpdatePluginsDescription": "Baixa e instala atualizações para plugins que estão configurados para atualizar automaticamente.",
"TaskUpdatePlugins": "Atualizar Plugins",
"TaskRefreshPeopleDescription": "Atualiza metadados para atores e diretores na sua biblioteca de mídia.",
"TaskRefreshPeople": "Atualizar pessoas",
"TaskCleanLogsDescription": "Deletar arquivos temporários com mais de {0} dias.",
"TaskCleanLogs": "Limpar pasta de logs",
"TaskRefreshLibraryDescription": "Escaneie a sua biblioteca de mídia para arquivos novos e atualize os metadados.",
"TaskRefreshLibrary": "Escanear a Biblioteca de Mídia",
"TaskRefreshChapterImagesDescription": "Criar miniaturas para vídeos que tem capítulos.",
"TaskRefreshChapterImages": "Extrair imagens dos capítulos",
"TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.",
"TaskCleanCache": "Limpar Arquivos Temporários",
"TasksChannelsCategory": "Canais da Internet",
"TasksApplicationCategory": "Aplicativo",
"TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Manutenção"
}

View File

@ -26,14 +26,20 @@ namespace Emby.Server.Implementations.MediaEncoder
private readonly IChapterManager _chapterManager;
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The first chapter ticks.
/// </summary>
private static readonly long _firstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
public EncodingManager(
ILogger<EncodingManager> logger,
IFileSystem fileSystem,
ILoggerFactory loggerFactory,
IMediaEncoder encoder,
IChapterManager chapterManager, ILibraryManager libraryManager)
IChapterManager chapterManager,
ILibraryManager libraryManager)
{
_logger = logger;
_fileSystem = fileSystem;
_logger = loggerFactory.CreateLogger(nameof(EncodingManager));
_encoder = encoder;
_chapterManager = chapterManager;
_libraryManager = libraryManager;
@ -97,12 +103,7 @@ namespace Emby.Server.Implementations.MediaEncoder
return video.DefaultVideoStreamIndex.HasValue;
}
/// <summary>
/// The first chapter ticks
/// </summary>
private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{
if (!IsEligibleForChapterImageExtraction(video))
{
@ -135,7 +136,7 @@ namespace Emby.Server.Implementations.MediaEncoder
try
{
// Add some time for the first chapter to make sure we don't end up with a black image
var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(_firstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
var protocol = MediaProtocol.File;
@ -152,9 +153,9 @@ namespace Emby.Server.Implementations.MediaEncoder
{
_fileSystem.DeleteFile(tempFile);
}
catch
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting temporary chapter image encoding file {Path}", tempFile);
}
chapter.ImagePath = path;
@ -184,7 +185,7 @@ namespace Emby.Server.Implementations.MediaEncoder
if (saveChapters && changesMade)
{
_chapterManager.SaveChapters(video.Id.ToString(), chapters);
_chapterManager.SaveChapters(video.Id, chapters);
}
DeleteDeadImages(currentImages, chapters);
@ -199,22 +200,21 @@ namespace Emby.Server.Implementations.MediaEncoder
return Path.Combine(GetChapterImagesPath(video), filename);
}
private static List<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
private static IReadOnlyList<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
{
var path = GetChapterImagesPath(video);
if (!Directory.Exists(path))
{
return new List<string>();
return Array.Empty<string>();
}
try
{
return directoryService.GetFilePaths(path)
.ToList();
return directoryService.GetFilePaths(path);
}
catch (IOException)
{
return new List<string>();
return Array.Empty<string>();
}
}
@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.MediaEncoder
foreach (var image in deadImages)
{
_logger.LogDebug("Deleting dead chapter image {path}", image);
_logger.LogDebug("Deleting dead chapter image {Path}", image);
try
{
@ -235,7 +235,7 @@ namespace Emby.Server.Implementations.MediaEncoder
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting {path}.", image);
_logger.LogError(ex, "Error deleting {Path}.", image);
}
}
}

View File

@ -12,11 +12,14 @@ using System.Threading.Tasks;
using CommandLine;
using Emby.Drawing;
using Emby.Server.Implementations;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Networking;
using Jellyfin.Drawing.Skia;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.WebDashboard.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -109,9 +112,10 @@ namespace Jellyfin.Server
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
// Create an instance of the application configuration to use for application startup
await InitLoggingConfigFile(appPaths).ConfigureAwait(false);
IConfiguration startupConfig = CreateAppConfiguration(appPaths);
// Create an instance of the application configuration to use for application startup
IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
// Initialize logging framework
InitializeLoggingFramework(startupConfig, appPaths);
@ -180,15 +184,31 @@ namespace Jellyfin.Server
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
GetImageEncoder(appPaths),
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
try
{
// If hosting the web client, validate the client content path
if (startupConfig.HostWebClient())
{
string webContentPath = DashboardService.GetDashboardUIPath(startupConfig, appHost.ServerConfigurationManager);
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
{
throw new InvalidOperationException(
"The server is expected to host the web client, but the provided content directory is either " +
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
"server, you may set the '--nowebclient' command line flag, or set" +
$"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
}
}
ServiceCollection serviceCollection = new ServiceCollection();
await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false);
var webHost = CreateWebHostBuilder(appHost, serviceCollection, appPaths).Build();
var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
// A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection.
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
appHost.ServiceProvider = webHost.Services;
appHost.InitializeServices();
appHost.FindParts();
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
@ -230,7 +250,12 @@ namespace Jellyfin.Server
}
}
private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection, IApplicationPaths appPaths)
private static IWebHostBuilder CreateWebHostBuilder(
ApplicationHost appHost,
IServiceCollection serviceCollection,
StartupOptions commandLineOpts,
IConfiguration startupConfig,
IApplicationPaths appPaths)
{
return new WebHostBuilder()
.UseKestrel(options =>
@ -270,9 +295,8 @@ namespace Jellyfin.Server
}
}
})
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths))
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
.UseSerilog()
.UseContentRoot(appHost.ContentRoot)
.ConfigureServices(services =>
{
// Merge the external ServiceCollection into ASP.NET DI
@ -395,9 +419,8 @@ namespace Jellyfin.Server
// webDir
// IF --webdir
// ELSE IF $JELLYFIN_WEB_DIR
// ELSE use <bindir>/jellyfin-web
// ELSE <bindir>/jellyfin-web
var webDir = options.WebDir;
if (string.IsNullOrEmpty(webDir))
{
webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR");
@ -468,21 +491,33 @@ namespace Jellyfin.Server
await resource.CopyToAsync(dst).ConfigureAwait(false);
}
private static IConfiguration CreateAppConfiguration(IApplicationPaths appPaths)
private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
{
return new ConfigurationBuilder()
.ConfigureAppConfiguration(appPaths)
.ConfigureAppConfiguration(commandLineOpts, appPaths)
.Build();
}
private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths)
private static IConfigurationBuilder ConfigureAppConfiguration(
this IConfigurationBuilder config,
StartupOptions commandLineOpts,
IApplicationPaths appPaths,
IConfiguration? startupConfig = null)
{
// Use the swagger API page as the default redirect path if not hosting the web client
var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
if (startupConfig != null && !startupConfig.HostWebClient())
{
inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html";
}
return config
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddInMemoryCollection(ConfigurationOptions.Configuration)
.AddInMemoryCollection(inMemoryDefaultConfig)
.AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
.AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
.AddEnvironmentVariables("JELLYFIN_");
.AddEnvironmentVariables("JELLYFIN_")
.AddInMemoryCollection(commandLineOpts.ConvertToConfig());
}
/// <summary>

View File

@ -0,0 +1,11 @@
{
"profiles": {
"Jellyfin.Server": {
"commandName": "Project"
},
"Jellyfin.Server (nowebclient)": {
"commandName": "Project",
"commandLineArgs": "--nowebclient"
}
}
}

View File

@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.Globalization;
using CommandLine;
using Emby.Server.Implementations;
using MediaBrowser.Controller.Extensions;
namespace Jellyfin.Server
{
@ -15,6 +18,12 @@ namespace Jellyfin.Server
[Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")]
public string? DataDir { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the server should not host the web client.
/// </summary>
[Option("nowebclient", Required = false, HelpText = "Indicates that the web server should not host the web client.")]
public bool NoWebClient { get; set; }
/// <summary>
/// Gets or sets the path to the web directory.
/// </summary>
@ -66,5 +75,21 @@ namespace Jellyfin.Server
/// <inheritdoc />
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
public string? RestartArgs { get; set; }
/// <summary>
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
/// </summary>
/// <returns>The configuration dictionary.</returns>
public Dictionary<string, string> ConvertToConfig()
{
var config = new Dictionary<string, string>();
if (NoWebClient)
{
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
}
return config;
}
}
}

View File

@ -1,3 +1,5 @@
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Common.Configuration
{
/// <summary>
@ -12,9 +14,12 @@ namespace MediaBrowser.Common.Configuration
string ProgramDataPath { get; }
/// <summary>
/// Gets the path to the web UI resources folder
/// Gets the path to the web UI resources folder.
/// </summary>
/// <value>The web UI resources path.</value>
/// <remarks>
/// This value is not relevant if the server is configured to not host any static web content. Additionally,
/// the value for <see cref="ServerConfiguration.DashboardSourcePath"/> takes precedence over this one.
/// </remarks>
string WebPath { get; }
/// <summary>

View File

@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Chapters
{
/// <summary>
/// Interface IChapterManager
/// Interface IChapterManager.
/// </summary>
public interface IChapterManager
{
/// <summary>
/// Saves the chapters.
/// </summary>
void SaveChapters(string itemId, List<ChapterInfo> chapters);
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
}
}

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@ -28,7 +30,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Folder : BaseItem
{
public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
/// <summary>
@ -620,7 +621,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
}).TotalRecordCount;
}
@ -1713,7 +1713,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
});
double unplayedCount = unplayedQueryResult.TotalRecordCount;

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Marker interface
/// Marker interface.
/// </summary>
public interface IItemByName
{

View File

@ -1,3 +1,4 @@
using System;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.Extensions
@ -7,6 +8,11 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
public static class ConfigurationExtensions
{
/// <summary>
/// The key for a setting that indicates whether the application should host web client content.
/// </summary>
public const string HostWebClientKey = "hostwebclient";
/// <summary>
/// The key for the FFmpeg probe size option.
/// </summary>
@ -22,6 +28,15 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
/// <summary>
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configuration">The configuration to retrieve the value from.</param>
/// <returns>The parsed config value.</returns>
/// <exception cref="FormatException">The config value is not a valid bool string. See <see cref="bool.Parse(string)"/>.</exception>
public static bool HostWebClient(this IConfiguration configuration)
=> configuration.GetValue<bool>(HostWebClientKey);
/// <summary>
/// Gets the FFmpeg probe size from the <see cref="IConfiguration" />.
/// </summary>

View File

@ -82,6 +82,11 @@ namespace MediaBrowser.Controller
/// <returns>The local API URL.</returns>
string GetLocalApiUrl(IPAddress address);
/// <summary>
/// Open a URL in an external browser window.
/// </summary>
/// <param name="url">The URL to open.</param>
/// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception>
void LaunchUrl(string url);
void EnableLoopback(string appName);

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic;
using System.Threading;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -12,6 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Refreshes the chapter images.
/// </summary>
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
}
}

View File

@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Persistence
/// <summary>
/// Saves the chapters.
/// </summary>
void SaveChapters(Guid id, List<ChapterInfo> chapters);
void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters);
/// <summary>
/// Gets the media streams.

View File

@ -66,12 +66,10 @@ namespace MediaBrowser.Controller.Providers
return file;
}
public List<string> GetFilePaths(string path)
{
return GetFilePaths(path, false);
}
public IReadOnlyList<string> GetFilePaths(string path)
=> GetFilePaths(path, false);
public List<string> GetFilePaths(string path, bool clearCache)
public IReadOnlyList<string> GetFilePaths(string path, bool clearCache)
{
if (clearCache || !_filePathCache.TryGetValue(path, out List<string> result))
{

View File

@ -11,8 +11,8 @@ namespace MediaBrowser.Controller.Providers
FileSystemMetadata GetFile(string path);
List<string> GetFilePaths(string path);
IReadOnlyList<string> GetFilePaths(string path);
List<string> GetFilePaths(string path, bool clearCache);
IReadOnlyList<string> GetFilePaths(string path, bool clearCache);
}
}

View File

@ -429,7 +429,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
return new ProbeResultNormalizer(_logger, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
}
}

View File

@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@ -19,13 +18,11 @@ namespace MediaBrowser.MediaEncoding.Probing
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization)
public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
{
_logger = logger;
_fileSystem = fileSystem;
_localization = localization;
}
@ -40,7 +37,7 @@ namespace MediaBrowser.MediaEncoding.Probing
FFProbeHelpers.NormalizeFFProbeResult(data);
SetSize(data, info);
var internalStreams = data.Streams ?? new MediaStreamInfo[] { };
var internalStreams = data.Streams ?? Array.Empty<MediaStreamInfo>();
info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format))
.Where(i => i != null)

View File

@ -148,9 +148,9 @@ namespace MediaBrowser.Model.Configuration
public bool EnableDashboardResponseCaching { get; set; }
/// <summary>
/// Allows the dashboard to be served from a custom path.
/// Gets or sets a custom path to serve the dashboard from.
/// </summary>
/// <value>The dashboard source path.</value>
/// <value>The dashboard source path, or null if the default path should be used.</value>
public string DashboardSourcePath { get; set; }
/// <summary>

View File

@ -1,36 +1,26 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Chapters
{
public class ChapterManager : IChapterManager
{
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly IItemRepository _itemRepo;
public ChapterManager(
ILibraryManager libraryManager,
ILoggerFactory loggerFactory,
IServerConfigurationManager config,
IItemRepository itemRepo)
public ChapterManager(IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger(nameof(ChapterManager));
_config = config;
_itemRepo = itemRepo;
}
public void SaveChapters(string itemId, List<ChapterInfo> chapters)
/// <inheritdoc />
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
{
_itemRepo.SaveChapters(new Guid(itemId), chapters);
_itemRepo.SaveChapters(itemId, chapters);
}
}
}

View File

@ -1,5 +1,9 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@ -897,7 +901,10 @@ namespace MediaBrowser.Providers.Manager
return new ExternalUrl
{
Name = i.Name,
Url = string.Format(i.UrlFormatString, value)
Url = string.Format(
CultureInfo.InvariantCulture,
i.UrlFormatString,
value)
};
}).Where(i => i != null).Concat(item.GetRelatedUrls());
@ -911,11 +918,10 @@ namespace MediaBrowser.Providers.Manager
Name = i.Name,
Key = i.Key,
UrlFormatString = i.UrlFormatString
});
}
private Dictionary<Guid, double> _activeRefreshes = new Dictionary<Guid, double>();
private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
public Dictionary<Guid, Guid> GetRefreshQueue()
{
@ -927,66 +933,54 @@ namespace MediaBrowser.Providers.Manager
{
dict[item.Item1] = item.Item1;
}
return dict;
}
}
public void OnRefreshStart(BaseItem item)
{
//_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
var id = item.Id;
lock (_activeRefreshes)
{
_activeRefreshes[id] = 0;
}
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
_activeRefreshes[item.Id] = 0;
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
public void OnRefreshComplete(BaseItem item)
{
//_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
lock (_activeRefreshes)
{
_activeRefreshes.Remove(item.Id);
}
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
_activeRefreshes.Remove(item.Id, out _);
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
public double? GetRefreshProgress(Guid id)
{
lock (_activeRefreshes)
if (_activeRefreshes.TryGetValue(id, out double value))
{
if (_activeRefreshes.TryGetValue(id, out double value))
{
return value;
}
return null;
return value;
}
return null;
}
public void OnRefreshProgress(BaseItem item, double progress)
{
//_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N", CultureInfo.InvariantCulture), progress);
var id = item.Id;
_logger.LogInformation("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress);
lock (_activeRefreshes)
{
if (_activeRefreshes.ContainsKey(id))
{
_activeRefreshes[id] = progress;
// TODO: Need to hunt down the conditions for this happening
_activeRefreshes.AddOrUpdate(
id,
(_) => throw new Exception(
string.Format(
CultureInfo.InvariantCulture,
"Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running",
item.GetType().Name,
item.Id.ToString("N", CultureInfo.InvariantCulture))),
(_, __) => progress);
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
}
else
{
// TODO: Need to hunt down the conditions for this happening
//throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture)));
}
}
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
}
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
@ -1040,10 +1034,9 @@ namespace MediaBrowser.Providers.Manager
// Try to throttle this a little bit.
await Task.Delay(100).ConfigureAwait(false);
var artist = item as MusicArtist;
var task = artist == null
? RefreshItem(item, refreshItem.Item2, cancellationToken)
: RefreshArtist(artist, refreshItem.Item2, cancellationToken);
var task = item is MusicArtist artist
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
: RefreshItem(item, refreshItem.Item2, cancellationToken);
await task.ConfigureAwait(false);
}
@ -1125,8 +1118,7 @@ namespace MediaBrowser.Providers.Manager
}
}
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options,
CancellationToken cancellationToken)
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return RefreshItem(item, options, cancellationToken);
}

View File

@ -24,6 +24,18 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- 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>
<ItemGroup>
<None Remove="Plugins\AudioDb\Configuration\config.html" />
<EmbeddedResource Include="Plugins\AudioDb\Configuration\config.html" />

View File

@ -194,7 +194,19 @@ namespace MediaBrowser.Providers.MediaInfo
FetchShortcutInfo(item);
}
var prober = new FFProbeVideoInfo(_logger, _mediaSourceManager, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager, _libraryManager);
var prober = new FFProbeVideoInfo(
_logger,
_mediaSourceManager,
_mediaEncoder,
_itemRepo,
_blurayExaminer,
_localization,
_encodingManager,
_fileSystem,
_config,
_subtitleManager,
_chapterManager,
_libraryManager);
return prober.ProbeVideo(item, options, cancellationToken);
}

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@ -25,7 +27,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
@ -33,13 +34,10 @@ namespace MediaBrowser.Providers.MediaInfo
public class FFProbeVideoInfo
{
private readonly ILogger _logger;
private readonly IIsoManager _isoManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
private readonly IBlurayExaminer _blurayExaminer;
private readonly ILocalizationManager _localization;
private readonly IApplicationPaths _appPaths;
private readonly IJsonSerializer _json;
private readonly IEncodingManager _encodingManager;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config;
@ -48,16 +46,27 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager;
public FFProbeVideoInfo(ILogger logger, IMediaSourceManager mediaSourceManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager)
private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
public FFProbeVideoInfo(
ILogger logger,
IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder,
IItemRepository itemRepo,
IBlurayExaminer blurayExaminer,
ILocalizationManager localization,
IEncodingManager encodingManager,
IFileSystem fileSystem,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
IChapterManager chapterManager,
ILibraryManager libraryManager)
{
_logger = logger;
_isoManager = isoManager;
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo;
_blurayExaminer = blurayExaminer;
_localization = localization;
_appPaths = appPaths;
_json = json;
_encodingManager = encodingManager;
_fileSystem = fileSystem;
_config = config;
@ -159,7 +168,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
List<MediaStream> mediaStreams;
IReadOnlyList<MediaAttachment> mediaAttachments;
List<ChapterInfo> chapters;
ChapterInfo[] chapters;
if (mediaInfo != null)
{
@ -177,6 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
video.RunTimeTicks = mediaInfo.RunTimeTicks;
}
video.Size = mediaInfo.Size;
if (video.VideoType == VideoType.VideoFile)
@ -189,19 +199,20 @@ namespace MediaBrowser.Providers.MediaInfo
{
video.Container = null;
}
video.Container = mediaInfo.Container;
chapters = mediaInfo.Chapters == null ? new List<ChapterInfo>() : mediaInfo.Chapters.ToList();
chapters = mediaInfo.Chapters == null ? Array.Empty<ChapterInfo>() : mediaInfo.Chapters;
if (blurayInfo != null)
{
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
}
}
else
{
mediaStreams = new List<MediaStream>();
mediaAttachments = Array.Empty<MediaAttachment>();
chapters = new List<ChapterInfo>();
chapters = Array.Empty<ChapterInfo>();
}
await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
@ -231,9 +242,9 @@ namespace MediaBrowser.Providers.MediaInfo
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
{
if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
if (chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
{
AddDummyChapters(video, chapters);
chapters = CreateDummyChapters(video);
}
NormalizeChapterNames(chapters);
@ -246,28 +257,29 @@ namespace MediaBrowser.Providers.MediaInfo
await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
_chapterManager.SaveChapters(video.Id.ToString(), chapters);
_chapterManager.SaveChapters(video.Id, chapters);
}
}
private void NormalizeChapterNames(List<ChapterInfo> chapters)
private void NormalizeChapterNames(ChapterInfo[] chapters)
{
var index = 1;
foreach (var chapter in chapters)
for (int i = 0; i < chapters.Length; i++)
{
string name = chapters[i].Name;
// Check if the name is empty and/or if the name is a time
// Some ripping programs do that.
if (string.IsNullOrWhiteSpace(chapter.Name) ||
TimeSpan.TryParse(chapter.Name, out var time))
if (string.IsNullOrWhiteSpace(name) ||
TimeSpan.TryParse(name, out _))
{
chapter.Name = string.Format(_localization.GetLocalizedString("ChapterNameValue"), index.ToString(CultureInfo.InvariantCulture));
chapters[i].Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ChapterNameValue"),
(i + 1).ToString(CultureInfo.InvariantCulture));
}
index++;
}
}
private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
{
var video = (Video)item;
@ -301,13 +313,15 @@ namespace MediaBrowser.Providers.MediaInfo
if (blurayInfo.Chapters != null)
{
chapters.Clear();
chapters.AddRange(blurayInfo.Chapters.Select(c => new ChapterInfo
double[] brChapter = blurayInfo.Chapters;
chapters = new ChapterInfo[brChapter.Length];
for (int i = 0; i < brChapter.Length; i++)
{
StartPositionTicks = TimeSpan.FromSeconds(c).Ticks
}));
chapters[i] = new ChapterInfo
{
StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
};
}
}
videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
@ -495,17 +509,17 @@ namespace MediaBrowser.Providers.MediaInfo
var libraryOptions = _libraryManager.GetLibraryOptions(video);
string[] subtitleDownloadLanguages;
bool SkipIfEmbeddedSubtitlesPresent;
bool SkipIfAudioTrackMatches;
bool RequirePerfectMatch;
bool skipIfEmbeddedSubtitlesPresent;
bool skipIfAudioTrackMatches;
bool requirePerfectMatch;
bool enabled;
if (libraryOptions.SubtitleDownloadLanguages == null)
{
subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
SkipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
SkipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
RequirePerfectMatch = subtitleOptions.RequirePerfectMatch;
skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
video is Episode) ||
(subtitleOptions.DownloadMovieSubtitles &&
@ -514,9 +528,9 @@ namespace MediaBrowser.Providers.MediaInfo
else
{
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
enabled = true;
}
@ -526,9 +540,9 @@ namespace MediaBrowser.Providers.MediaInfo
_subtitleManager)
.DownloadSubtitles(video,
currentStreams.Concat(externalSubtitleStreams).ToList(),
SkipIfEmbeddedSubtitlesPresent,
SkipIfAudioTrackMatches,
RequirePerfectMatch,
skipIfEmbeddedSubtitlesPresent,
skipIfAudioTrackMatches,
requirePerfectMatch,
subtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
@ -547,43 +561,45 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
/// The dummy chapter duration
/// </summary>
private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
/// <summary>
/// Adds the dummy chapters.
/// Creates dummy chapters.
/// </summary>
/// <param name="video">The video.</param>
/// <param name="chapters">The chapters.</param>
private void AddDummyChapters(Video video, List<ChapterInfo> chapters)
/// <return>An array of dummy chapters.</returns>
private ChapterInfo[] CreateDummyChapters(Video video)
{
var runtime = video.RunTimeTicks ?? 0;
if (runtime < 0)
{
throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime));
throw new ArgumentException(
string.Format(
CultureInfo.InvariantCulture,
"{0} has invalid runtime of {1}",
video.Name,
runtime));
}
if (runtime < _dummyChapterDuration)
{
return;
return Array.Empty<ChapterInfo>();
}
long currentChapterTicks = 0;
var index = 1;
// Limit to 100 chapters just in case there's some incorrect metadata here
while (currentChapterTicks < runtime && index < 100)
int chapterCount = (int)Math.Min(runtime / _dummyChapterDuration, 100);
var chapters = new ChapterInfo[chapterCount];
long currentChapterTicks = 0;
for (int i = 0; i < chapterCount; i++)
{
chapters.Add(new ChapterInfo
chapters[i] = new ChapterInfo
{
StartPositionTicks = currentChapterTicks
});
};
index++;
currentChapterTicks += _dummyChapterDuration;
}
return chapters;
}
private string[] FetchFromDvdLib(Video item)

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@ -36,7 +37,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger _logger;
private readonly ILocalizationManager _localization;
private readonly ILibraryManager _libraryManager;
private readonly IApplicationHost _appHost;
@ -48,7 +48,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILogger<TmdbMovieProvider> logger,
ILocalizationManager localization,
ILibraryManager libraryManager,
IApplicationHost appHost)
{
@ -57,7 +56,6 @@ namespace MediaBrowser.Providers.Tmdb.Movies
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
_localization = localization;
_libraryManager = libraryManager;
_appHost = appHost;
Current = this;
@ -409,15 +407,15 @@ namespace MediaBrowser.Providers.Tmdb.Movies
private static long _lastRequestTicks;
// The limit is 40 requests per 10 seconds
private static int requestIntervalMs = 300;
private const int RequestIntervalMs = 300;
/// <summary>
/// Gets the movie db response.
/// </summary>
internal async Task<HttpResponseInfo> GetMovieDbResponse(HttpRequestOptions options)
{
var delayTicks = (requestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks);
var delayMs = Math.Min(delayTicks / 10000, requestIntervalMs);
var delayTicks = (RequestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks);
var delayMs = Math.Min(delayTicks / 10000, RequestIntervalMs);
if (delayMs > 0)
{
@ -430,11 +428,13 @@ namespace MediaBrowser.Providers.Tmdb.Movies
options.BufferContent = true;
options.UserAgent = _appHost.ApplicationUserAgent;
return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
}
/// <inheritdoc />
public int Order => 1;
/// <inheritdoc />
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClient.GetResponse(new HttpRequestOptions

View File

@ -12,12 +12,14 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.WebDashboard.Api
@ -102,6 +104,7 @@ namespace MediaBrowser.WebDashboard.Api
/// <value>The HTTP result factory.</value>
private readonly IHttpResultFactory _resultFactory;
private readonly IServerApplicationHost _appHost;
private readonly IConfiguration _appConfig;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IFileSystem _fileSystem;
private readonly IResourceFileManager _resourceFileManager;
@ -111,6 +114,7 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appHost">The application host.</param>
/// <param name="appConfig">The application configuration.</param>
/// <param name="resourceFileManager">The resource file manager.</param>
/// <param name="serverConfigurationManager">The server configuration manager.</param>
/// <param name="fileSystem">The file system.</param>
@ -118,6 +122,7 @@ namespace MediaBrowser.WebDashboard.Api
public DashboardService(
ILogger<DashboardService> logger,
IServerApplicationHost appHost,
IConfiguration appConfig,
IResourceFileManager resourceFileManager,
IServerConfigurationManager serverConfigurationManager,
IFileSystem fileSystem,
@ -125,6 +130,7 @@ namespace MediaBrowser.WebDashboard.Api
{
_logger = logger;
_appHost = appHost;
_appConfig = appConfig;
_resourceFileManager = resourceFileManager;
_serverConfigurationManager = serverConfigurationManager;
_fileSystem = fileSystem;
@ -138,20 +144,30 @@ namespace MediaBrowser.WebDashboard.Api
public IRequest Request { get; set; }
/// <summary>
/// Gets the path for the web interface.
/// Gets the path of the directory containing the static web interface content, or null if the server is not
/// hosting the web client.
/// </summary>
/// <value>The path for the web interface.</value>
public string DashboardUIPath
{
get
{
if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath))
{
return _serverConfigurationManager.Configuration.DashboardSourcePath;
}
public string DashboardUIPath => GetDashboardUIPath(_appConfig, _serverConfigurationManager);
return _serverConfigurationManager.ApplicationPaths.WebPath;
/// <summary>
/// Gets the path of the directory containing the static web interface content.
/// </summary>
/// <param name="appConfig">The app configuration.</param>
/// <param name="serverConfigManager">The server configuration manager.</param>
/// <returns>The directory path, or null if the server is not hosting the web client.</returns>
public static string GetDashboardUIPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
{
if (!appConfig.HostWebClient())
{
return null;
}
if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
{
return serverConfigManager.Configuration.DashboardSourcePath;
}
return serverConfigManager.ApplicationPaths.WebPath;
}
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
@ -209,7 +225,7 @@ namespace MediaBrowser.WebDashboard.Api
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream));
}
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null));
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null));
}
throw new ResourceNotFoundException();
@ -307,6 +323,11 @@ namespace MediaBrowser.WebDashboard.Api
/// <returns>System.Object.</returns>
public async Task<object> Get(GetDashboardResource request)
{
if (!_appConfig.HostWebClient() || DashboardUIPath == null)
{
throw new ResourceNotFoundException();
}
var path = request.ResourceName;
var contentType = MimeTypes.GetMimeType(path);
@ -378,6 +399,11 @@ namespace MediaBrowser.WebDashboard.Api
public async Task<object> Get(GetDashboardPackage request)
{
if (!_appConfig.HostWebClient() || DashboardUIPath == null)
{
throw new ResourceNotFoundException();
}
var mode = request.Mode;
var inputPath = string.IsNullOrWhiteSpace(mode) ?

View File

@ -31,7 +31,8 @@ namespace MediaBrowser.WebDashboard.Api
if (resourceStream != null && IsCoreHtml(virtualPath))
{
resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase);
resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
}
return resourceStream;
@ -47,16 +48,25 @@ namespace MediaBrowser.WebDashboard.Api
return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase);
}
// Modifies the HTML by adding common meta tags, css and js.
public async Task<Stream> ModifyHtml(
string path,
/// <summary>
/// Modifies the source HTML stream by adding common meta tags, css and js.
/// </summary>
/// <param name="isMainIndexPage">True if the stream contains content for the main index page.</param>
/// <param name="sourceStream">The stream whose content should be modified.</param>
/// <param name="mode">The client mode ('cordova', 'android', etc).</param>
/// <param name="appVersion">The application version.</param>
/// <param name="localizationCulture">The localization culture.</param>
/// <returns>
/// A task that represents the async operation to read and modify the input stream.
/// The task result contains a stream containing the modified HTML content.
/// </returns>
public static async Task<Stream> ModifyHtml(
bool isMainIndexPage,
Stream sourceStream,
string mode,
string appVersion,
string localizationCulture)
{
var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase);
string html;
using (var reader = new StreamReader(sourceStream, Encoding.UTF8))
{

View File

@ -12,7 +12,7 @@
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />

View File

@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />

View File

@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />

View File

@ -14,7 +14,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />

View File

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />