diff options
| author | Andrew Rabert <6550543+nvllsvm@users.noreply.github.com> | 2019-01-24 23:03:54 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-01-24 23:03:54 -0500 |
| commit | b4fdfb562dada77ff6d9c75bbc9e5e5b3ee977a2 (patch) | |
| tree | cafeb8316e0120f052eb094e44a02e94639fc223 /Emby.Server.Implementations | |
| parent | 15806de2aad07a4b4c799a0ee56d770dd5248543 (diff) | |
| parent | 48ad18d12baeeb42ec0ec7df9473330dcbc76754 (diff) | |
Merge pull request #700 from jellyfin/dev
Dev sync
Diffstat (limited to 'Emby.Server.Implementations')
16 files changed, 167 insertions, 484 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index a8e8f815a0..aef72c6972 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -92,18 +93,18 @@ namespace Emby.Server.Implementations.Activity _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; } - void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) + async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name), Type = NotificationType.CameraImageUploaded.ToString() }); } - void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e) + async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name), Type = NotificationType.UserLockedOut.ToString(), @@ -111,9 +112,9 @@ namespace Emby.Server.Implementations.Activity }); } - void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) + async void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)), Type = "SubtitleDownloadFailure", @@ -122,7 +123,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + async void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) { var item = e.MediaInfo; @@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Activity var user = e.Users.First(); - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), Type = GetPlaybackStoppedNotificationType(item.MediaType), @@ -153,7 +154,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) { var item = e.MediaInfo; @@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.Activity var user = e.Users.First(); - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), Type = GetPlaybackNotificationType(item.MediaType), @@ -237,7 +238,7 @@ namespace Emby.Server.Implementations.Activity return null; } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + async void _sessionManager_SessionEnded(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; @@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Activity name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName); } - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = name, Type = "SessionEnded", @@ -263,11 +264,11 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e) + async void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e) { var user = e.Argument.User; - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name), Type = "AuthenticationSucceeded", @@ -276,9 +277,9 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) + async void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username), Type = "AuthenticationFailed", @@ -287,9 +288,9 @@ namespace Emby.Server.Implementations.Activity }); } - void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e) + async void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr), Type = NotificationType.ApplicationUpdateInstalled.ToString(), @@ -297,27 +298,27 @@ namespace Emby.Server.Implementations.Activity }); } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + async void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key), Type = "NamedConfigurationUpdated" }); } - void _config_ConfigurationUpdated(object sender, EventArgs e) + async void _config_ConfigurationUpdated(object sender, EventArgs e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"), Type = "ServerConfigurationUpdated" }); } - void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e) + async void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name), Type = "UserPolicyUpdated", @@ -325,18 +326,18 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserDeleted(object sender, GenericEventArgs<User> e) + async void _userManager_UserDeleted(object sender, GenericEventArgs<User> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name), Type = "UserDeleted" }); } - void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e) + async void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), Type = "UserPasswordChanged", @@ -344,9 +345,9 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserCreated(object sender, GenericEventArgs<User> e) + async void _userManager_UserCreated(object sender, GenericEventArgs<User> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), Type = "UserCreated", @@ -354,9 +355,9 @@ namespace Emby.Server.Implementations.Activity }); } - void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e) + async void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)), Type = "SubtitlesDownloaded", @@ -365,7 +366,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + async void _sessionManager_SessionStarted(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; @@ -382,7 +383,7 @@ namespace Emby.Server.Implementations.Activity name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName); } - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = name, Type = "SessionStarted", @@ -391,9 +392,9 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e) + async void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), Type = NotificationType.PluginUpdateInstalled.ToString(), @@ -402,18 +403,18 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e) + async void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), Type = NotificationType.PluginUninstalled.ToString() }); } - void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e) + async void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e) { - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), Type = NotificationType.PluginInstalled.ToString(), @@ -421,11 +422,11 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) + async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) { var installationInfo = e.InstallationInfo; - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name), Type = NotificationType.InstallationFailed.ToString(), @@ -434,7 +435,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) { var result = e.Result; var task = e.Task; @@ -461,7 +462,7 @@ namespace Emby.Server.Implementations.Activity vals.Add(e.Result.LongErrorMessage); } - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), Type = NotificationType.TaskFailed.ToString(), @@ -472,11 +473,11 @@ namespace Emby.Server.Implementations.Activity } } - private void CreateLogEntry(ActivityLogEntry entry) + private async Task CreateLogEntry(ActivityLogEntry entry) { try { - _activityManager.Create(entry); + await _activityManager.CreateAsync(entry); } catch { diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index 6febcc2f7b..8fcacb0024 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Activity @@ -26,20 +27,38 @@ namespace Emby.Server.Implementations.Activity _userManager = userManager; } - public void Create(ActivityLogEntry entry) + public async Task CreateAsync(ActivityLogEntry entry) { entry.Date = DateTime.UtcNow; - _repo.Create(entry); + await _repo.CreateAsync(entry); EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry)); } - public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) + public IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) { - var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit); + var result = _repo.GetActivityLogEntries(); - foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty))) + if (minDate.HasValue) + { + result = result.Where(x => x.Date >= minDate.Value); + } + if (hasUserId.HasValue) + { + result = result.Where(x => x.UserId != null && x.UserId != Guid.Empty); + } + if (startIndex.HasValue) + { + result = result.Where(x => x.Id >= startIndex.Value); + } + if (limit.HasValue) + { + result = result.Take(limit.Value); + } + + // Add images for each user + foreach (var item in result) { var user = _userManager.GetUserById(item.UserId); @@ -50,12 +69,7 @@ namespace Emby.Server.Implementations.Activity } } - return result; - } - - public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) - { - return GetActivityLogEntries(minDate, null, startIndex, limit); + return result.AsEnumerable(); } } } diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index aeed8b6f1a..af0b20d921 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -1,310 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; -using Emby.Server.Implementations.Data; -using MediaBrowser.Controller; +using System.Threading.Tasks; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; +using Microsoft.EntityFrameworkCore; namespace Emby.Server.Implementations.Activity { - public class ActivityRepository : BaseSqliteRepository, IActivityRepository + public class ActivityRepository : DbContext, IActivityRepository { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - protected IFileSystem FileSystem { get; private set; } + protected string _dataDirPath; - public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem) - : base(loggerFactory.CreateLogger(nameof(ActivityRepository))) - { - DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); - FileSystem = fileSystem; - } + public DbSet<ActivityLogEntry> ActivityLogs { get; set; } - public void Initialize() + public ActivityRepository(string dataDirPath) { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading database file. Will reset and retry."); - - FileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } + _dataDirPath = dataDirPath; } - private void InitializeInternal() + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - connection.RunQueries(new[] - { - "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)", - "drop index if exists idx_ActivityLogEntries" - }); - - TryMigrate(connection); - } - } + // Ensure the dir exists + Directory.CreateDirectory(_dataDirPath); - private void TryMigrate(ManagedConnection connection) - { - try - { - if (TableExists(connection, "ActivityLogEntries")) - { - connection.RunQueries(new[] - { - "INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries", - "drop table if exists ActivityLogEntries" - }); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating activity log database"); - } + optionsBuilder.UseSqlite($"Filename={Path.Combine(_dataDirPath, "activitylog.sqlite.db")}"); } - private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog"; - - public void Create(ActivityLogEntry entry) + public async Task CreateAsync(ActivityLogEntry entry) { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - using (WriteLock.Write()) - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)")) - { - statement.TryBind("@Name", entry.Name); - - statement.TryBind("@Overview", entry.Overview); - statement.TryBind("@ShortOverview", entry.ShortOverview); - statement.TryBind("@Type", entry.Type); - statement.TryBind("@ItemId", entry.ItemId); - - if (entry.UserId.Equals(Guid.Empty)) - { - statement.TryBindNull("@UserId"); - } - else - { - statement.TryBind("@UserId", entry.UserId.ToString("N")); - } - - statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); - statement.TryBind("@LogSeverity", entry.Severity.ToString()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - public void Update(ActivityLogEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - using (WriteLock.Write()) - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id")) - { - statement.TryBind("@Id", entry.Id); - - statement.TryBind("@Name", entry.Name); - statement.TryBind("@Overview", entry.Overview); - statement.TryBind("@ShortOverview", entry.ShortOverview); - statement.TryBind("@Type", entry.Type); - statement.TryBind("@ItemId", entry.ItemId); - - if (entry.UserId.Equals(Guid.Empty)) - { - statement.TryBindNull("@UserId"); - } - else - { - statement.TryBind("@UserId", entry.UserId.ToString("N")); - } - - statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); - statement.TryBind("@LogSeverity", entry.Severity.ToString()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) - { - using (WriteLock.Read()) - using (var connection = CreateConnection(true)) - { - var commandText = BaseActivitySelectText; - var whereClauses = new List<string>(); - - if (minDate.HasValue) - { - whereClauses.Add("DateCreated>=@DateCreated"); - } - if (hasUserId.HasValue) - { - if (hasUserId.Value) - { - whereClauses.Add("UserId not null"); - } - else - { - whereClauses.Add("UserId is null"); - } - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - if (startIndex.HasValue && startIndex.Value > 0) - { - var pagingWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})", - pagingWhereText, - startIndex.Value.ToString(_usCulture))); - } - - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - commandText += whereText; - - commandText += " ORDER BY DateCreated DESC"; - - if (limit.HasValue) - { - commandText += " LIMIT " + limit.Value.ToString(_usCulture); - } - - var statementTexts = new List<string>(); - statementTexts.Add(commandText); - statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging); - - return connection.RunInTransaction(db => - { - var list = new List<ActivityLogEntry>(); - var result = new QueryResult<ActivityLogEntry>(); - - var statements = PrepareAllSafe(db, statementTexts).ToList(); - - using (var statement = statements[0]) - { - if (minDate.HasValue) - { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetEntry(row)); - } - } - - using (var statement = statements[1]) - { - if (minDate.HasValue) - { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } - - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } - - result.Items = list.ToArray(); - return result; - - }, ReadTransactionMode); - } + await ActivityLogs.AddAsync(entry); + await SaveChangesAsync(); } - private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader) - { - var index = 0; - - var info = new ActivityLogEntry - { - Id = reader[index].ToInt64() - }; - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Name = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Overview = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.ShortOverview = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Type = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.ItemId = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.UserId = new Guid(reader[index].ToString()); - } - - index++; - info.Date = reader[index].ReadDateTime(); - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true); - } - - return info; - } + public IQueryable<ActivityLogEntry> GetActivityLogEntries() + => ActivityLogs; } } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c8c4da0f76..f5a4f15817 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -709,7 +709,7 @@ namespace Emby.Server.Implementations } } - public void Init() + public async Task InitAsync() { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -739,7 +739,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - RegisterResources(); + await RegisterResourcesAsync(); FindParts(); } @@ -754,7 +754,7 @@ namespace Emby.Server.Implementations /// <summary> /// Registers resources that classes will depend on /// </summary> - protected void RegisterResources() + protected async Task RegisterResourcesAsync() { RegisterSingleInstance(ConfigurationManager); RegisterSingleInstance<IApplicationHost>(this); @@ -931,7 +931,7 @@ namespace Emby.Server.Implementations EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); RegisterSingleInstance(EncodingManager); - var activityLogRepo = GetActivityLogRepository(); + var activityLogRepo = await GetActivityLogRepositoryAsync(); RegisterSingleInstance(activityLogRepo); RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); @@ -1146,11 +1146,11 @@ namespace Emby.Server.Implementations return repo; } - private IActivityRepository GetActivityLogRepository() + private async Task<IActivityRepository> GetActivityLogRepositoryAsync() { - var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager); + var repo = new ActivityRepository(ServerConfigurationManager.ApplicationPaths.DataPath); - repo.Initialize(); + await repo.Database.EnsureCreatedAsync(); return repo; } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 660547c85c..a306b01699 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -681,22 +681,17 @@ namespace Emby.Server.Implementations.Channels // Find the corresponding channel provider plugin var channelProvider = GetChannelProvider(channel); - var user = query.User; - - ChannelItemSortField? sortField = null; - var sortDescending = false; - - var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel; + var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId); var itemsResult = await GetChannelItems(channelProvider, - user, + query.User, parentItem is Channel ? null : parentItem.ExternalId, - sortField, - sortDescending, + null, + false, cancellationToken) .ConfigureAwait(false); - if (query.ParentId.Equals(Guid.Empty)) + if (query.ParentId == Guid.Empty) { query.Parent = channel; } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 3aa617b021..f2a5b2e386 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,6 +22,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" /> <PackageReference Include="sharpcompress" Version="0.22.0" /> <PackageReference Include="SimpleInjector" Version="4.4.2" /> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c32c91670a..7aedba9b31 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer private long RangeStart { get; set; } private long RangeEnd { get; set; } private long RangeLength { get; set; } - private long TotalContentLength { get; set; } + public long TotalContentLength { get; set; } public Action OnComplete { get; set; } public Action OnError { get; set; } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 8b60d61d41..0ad4d8406b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -5,6 +5,7 @@ using System.IO; using System.IO.Compression; using System.Net; using System.Runtime.Serialization; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Xml; @@ -423,13 +424,11 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) { - responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString); - bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; if (!noCache) { - if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration)) + if (IsNotModified(requestContext, cacheKey)) { AddAgeHeader(responseHeaders, lastDateModified); AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration); @@ -442,7 +441,7 @@ namespace Emby.Server.Implementations.HttpServer } } - AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration); + AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration); return null; } @@ -532,10 +531,11 @@ namespace Emby.Server.Implementations.HttpServer public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options) { - var cacheKey = options.CacheKey; options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - var contentType = options.ContentType; + var contentType = options.ContentType; + var etag = requestContext.Headers.Get("If-None-Match"); + var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty; if (!cacheKey.Equals(Guid.Empty)) { var key = cacheKey.ToString("N"); @@ -554,8 +554,6 @@ namespace Emby.Server.Implementations.HttpServer var factoryFn = options.ContentFactory; var responseHeaders = options.ResponseHeaders; - //var requestedCompressionType = GetCompressionType(requestContext); - var rangeHeader = requestContext.Headers.Get("Range"); if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) @@ -568,10 +566,21 @@ namespace Emby.Server.Implementations.HttpServer }; AddResponseHeaders(hasHeaders, options.ResponseHeaders); + // Generate an ETag based on identifying information - TODO read contents from filesystem instead? + var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}"; + var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId)); + hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N"); + return hasHeaders; } var stream = await factoryFn().ConfigureAwait(false); + // Generate an etag based on stream content + var streamHash = MD5.Create().ComputeHash(stream); + var newEtag = new Guid(streamHash).ToString("N"); + + // reset position so the response can re-use it -- TODO is this ok? + stream.Position = 0; var totalContentLength = options.ContentLength; if (!totalContentLength.HasValue) @@ -594,6 +603,7 @@ namespace Emby.Server.Implementations.HttpServer }; AddResponseHeaders(hasHeaders, options.ResponseHeaders); + hasHeaders.Headers["ETag"] = newEtag; return hasHeaders; } else @@ -618,6 +628,7 @@ namespace Emby.Server.Implementations.HttpServer }; AddResponseHeaders(hasHeaders, options.ResponseHeaders); + hasHeaders.Headers["ETag"] = newEtag; return hasHeaders; } } @@ -630,16 +641,8 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Adds the caching responseHeaders. /// </summary> - private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration) { - // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant - // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching - if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) - { - AddAgeHeader(responseHeaders, lastDateModified); - responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r"); - } - if (cacheDuration.HasValue) { responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds); @@ -692,28 +695,15 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="lastDateModified">The last date modified.</param> /// <param name="cacheDuration">Duration of the cache.</param> /// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns> - private bool IsNotModified(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + private bool IsNotModified(IRequest requestContext, Guid cacheKey) { - //var isNotModified = true; - - var ifModifiedSinceHeader = requestContext.Headers.Get("If-Modified-Since"); - - if (!string.IsNullOrEmpty(ifModifiedSinceHeader) - && DateTime.TryParse(ifModifiedSinceHeader, out DateTime ifModifiedSince) - && IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified)) - { - return true; - } - var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match"); bool hasCacheKey = !cacheKey.Equals(Guid.Empty); // Validate If-None-Match - if ((hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader))) + if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader)) { - ifNoneMatchHeader = (ifNoneMatchHeader ?? string.Empty).Trim('\"'); - if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch) && cacheKey.Equals(ifNoneMatch)) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 0adc5553bc..1ed838893e 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -469,7 +469,7 @@ namespace Emby.Server.Implementations.Library } // TODO: Don't hardcode this - var isAudio = false; + const bool isAudio = false; try { @@ -480,9 +480,11 @@ namespace Emby.Server.Implementations.Library else { // hack - these two values were taken from LiveTVMediaSourceProvider - var cacheKey = request.OpenToken; + string cacheKey = request.OpenToken; - await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false); + await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths) + .AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken) + .ConfigureAwait(false); } } catch (Exception ex) @@ -491,6 +493,7 @@ namespace Emby.Server.Implementations.Library AddMediaInfo(mediaSource, isAudio); } + // TODO: @bond Fix var json = _jsonSerializer.SerializeToString(mediaSource); _logger.LogInformation("Live stream opened: " + json); var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json); diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index baa665fce8..e39192d286 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -86,12 +86,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio .Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) .ToList(); - if (isBooksCollectionType) - { - return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); - } - - return null; + return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); } if (LibraryManager.IsAudioFile(args.Path, libraryOptions)) @@ -145,36 +140,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName) where T : MediaBrowser.Controller.Entities.Audio.Audio, new() { - var multiDiscFolders = new List<FileSystemMetadata>(); - - var libraryOptions = args.GetLibraryOptions(); - var filesFromOtherItems = new List<FileSystemMetadata>(); - // TODO: Allow GetMultiDiscMovie in here - var supportsMultiVersion = false; + const bool supportsMultiVersion = false; var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ?? new MultiItemResolverResult(); if (result.Items.Count == 1) { - var videoPath = result.Items[0].Path; - // If we were supporting this we'd be checking filesFromOtherItems - var hasOtherItems = false; - - if (!hasOtherItems) - { - var item = (T)result.Items[0]; - item.IsInMixedFolder = false; - item.Name = Path.GetFileName(item.ContainingFolderPath); - return item; - } - } - - if (result.Items.Count == 0 && multiDiscFolders.Count > 0) - { - //return GetMultiDiscAudio<T>(multiDiscFolders, directoryService); + var item = (T)result.Items[0]; + item.IsInMixedFolder = false; + item.Name = Path.GetFileName(item.ContainingFolderPath); + return item; } return null; @@ -194,11 +172,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { leftOver.Add(child); } - else if (IsIgnored(child.Name)) - { - - } - else + else if (!IsIgnored(child.Name)) { files.Add(child); } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index e48213dbb1..472a3f105c 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -410,7 +410,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } // TODO: Allow GetMultiDiscMovie in here - var supportsMultiVersion = true; + const bool supportsMultiVersion = true; var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ?? new MultiItemResolverResult(); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 8ef2276898..5c95534ec1 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -153,16 +153,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(); var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions); - bool? isNamed = null; - bool? isOptimistic = null; - if (!isTvContentType) - { - isNamed = true; - isOptimistic = false; - } - - var episodeInfo = episodeResolver.Resolve(fullName, false, isNamed, isOptimistic, fillExtendedInfo: false); + var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false); if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue) { return true; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index d5bc3d332e..6139659b70 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -322,17 +322,14 @@ namespace Emby.Server.Implementations.Library throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); } - if (user != null) + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) { - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - throw new SecurityException("Forbidden."); - } + throw new SecurityException("Forbidden."); + } - if (!user.IsParentalScheduleAllowed()) - { - throw new SecurityException("User is not allowed access at this time."); - } + if (!user.IsParentalScheduleAllowed()) + { + throw new SecurityException("User is not allowed access at this time."); } // Update LastActivityDate and LastLoginDate, then save @@ -463,26 +460,26 @@ namespace Emby.Server.Implementations.Library { user.Policy.InvalidLoginAttemptCount = newValue; - var maxCount = user.Policy.IsAdministrator ? - 3 : - 5; + var maxCount = user.Policy.IsAdministrator ? 3 : 5; + // TODO: Fix + /* var fireLockout = false; if (newValue >= maxCount) { - //_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); - //user.Policy.IsDisabled = true; + _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); + user.Policy.IsDisabled = true; - //fireLockout = true; - } + fireLockout = true; + }*/ UpdateUserPolicy(user, user.Policy, false); - if (fireLockout) + /* if (fireLockout) { UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user)); - } + }*/ } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 64e5affd70..9e11494c99 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1133,8 +1133,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV IgnoreIndex = true }; - var isAudio = false; - await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false); + await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths) + .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false); return new List<MediaSourceInfo> { @@ -1149,12 +1149,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public Task RecordLiveStream(string id, CancellationToken cancellationToken) { - return Task.FromResult(0); + return Task.CompletedTask; } public Task ResetTuner(string id, CancellationToken cancellationToken) { - return Task.FromResult(0); + return Task.CompletedTask; } async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index c09ee93482..c11a85027d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -175,12 +175,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } var videoStream = mediaSource.VideoStream; - string videoDecoder = null; - - if (!string.IsNullOrEmpty(videoDecoder)) - { - inputModifier += " " + videoDecoder; - } if (mediaSource.ReadAtNativeFramerate) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index e8e4bc7238..77b09a83de 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -354,10 +354,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int? height = null; bool isInterlaced = true; string videoCodec = null; - string audioCodec = null; int? videoBitrate = null; - int? audioBitrate = null; var isHd = channelInfo.IsHD ?? true; @@ -427,20 +425,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - if (channelInfo != null) + if (string.IsNullOrWhiteSpace(videoCodec)) { - if (string.IsNullOrWhiteSpace(videoCodec)) - { - videoCodec = channelInfo.VideoCodec; - } - audioCodec = channelInfo.AudioCodec; + videoCodec = channelInfo.VideoCodec; + } + string audioCodec = channelInfo.AudioCodec; - if (!videoBitrate.HasValue) - { - videoBitrate = isHd ? 15000000 : 2000000; - } - audioBitrate = isHd ? 448000 : 192000; + if (!videoBitrate.HasValue) + { + videoBitrate = isHd ? 15000000 : 2000000; } + int? audioBitrate = isHd ? 448000 : 192000; // normalize if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase)) |
