diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations')
76 files changed, 1269 insertions, 843 deletions
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index 41592865c..bb7e142b6 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -657,7 +657,7 @@ namespace MediaBrowser.Server.Implementations.Channels _logger.ErrorException("Error getting all media from {0}", ex, i.Name); } } - return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult { }); + return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult()); }); var results = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -811,7 +811,7 @@ namespace MediaBrowser.Server.Implementations.Channels _logger.ErrorException("Error getting all media from {0}", ex, i.Name); } } - return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult { }); + return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult()); }); var results = await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs index cb95bfd14..6cd9e9620 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Server.Implementations.Collections public class CollectionsDynamicFolder : IVirtualFolderCreator { private readonly IApplicationPaths _appPaths; - private IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; public CollectionsDynamicFolder(IApplicationPaths appPaths, IFileSystem fileSystem) { diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs index 24750de94..45cb2f57d 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs @@ -65,12 +65,11 @@ namespace MediaBrowser.Server.Implementations.Connect if (!string.IsNullOrWhiteSpace(address)) { - try - { - address = new Uri(address).Host; - } - catch + Uri newUri; + + if (Uri.TryCreate(address, UriKind.Absolute, out newUri)) { + address = newUri.Host; } } @@ -151,7 +150,7 @@ namespace MediaBrowser.Server.Implementations.Connect { DiscoveredWanIpAddress = address; - UpdateConnectInfo(); + var task = UpdateConnectInfo(); } private async Task UpdateConnectInfo() diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 616625bc9..be68162ca 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Drawing; @@ -98,16 +97,15 @@ namespace MediaBrowser.Server.Implementations.Dto throw new ArgumentNullException("options"); } - var syncJobItems = GetSyncedItemProgress(options); - var syncDictionary = GetSyncedItemProgressDictionary(syncJobItems); + var syncDictionary = GetSyncedItemProgress(options); var list = new List<BaseItemDto>(); - var programTuples = new List<Tuple<BaseItem, BaseItemDto>> { }; - var channelTuples = new List<Tuple<BaseItemDto, LiveTvChannel>> { }; + var programTuples = new List<Tuple<BaseItem, BaseItemDto>>(); + var channelTuples = new List<Tuple<BaseItemDto, LiveTvChannel>>(); foreach (var item in items) { - var dto = await GetBaseItemDtoInternal(item, options, syncDictionary, user, owner).ConfigureAwait(false); + var dto = await GetBaseItemDtoInternal(item, options, user, owner).ConfigureAwait(false); var tvChannel = item as LiveTvChannel; if (tvChannel != null) @@ -134,7 +132,7 @@ namespace MediaBrowser.Server.Implementations.Dto } } - FillSyncInfo(dto, item, syncJobItems, options, user); + FillSyncInfo(dto, item, options, user, syncDictionary); list.Add(dto); } @@ -152,23 +150,11 @@ namespace MediaBrowser.Server.Implementations.Dto return list; } - private Dictionary<string, SyncedItemProgress> GetSyncedItemProgressDictionary(IEnumerable<SyncedItemProgress> items) - { - var dict = new Dictionary<string, SyncedItemProgress>(); - - foreach (var item in items) - { - dict[item.ItemId] = item; - } - - return dict; - } - public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { - var syncProgress = GetSyncedItemProgress(options); + var syncDictionary = GetSyncedItemProgress(options); - var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user, owner).Result; + var dto = GetBaseItemDtoInternal(item, options, user, owner).Result; var tvChannel = item as LiveTvChannel; if (tvChannel != null) { @@ -191,11 +177,11 @@ namespace MediaBrowser.Server.Implementations.Dto SetItemByNameInfo(item, dto, GetTaggedItems(byName, user), user); } - FillSyncInfo(dto, item, options, user, syncProgress); + FillSyncInfo(dto, item, options, user, syncDictionary); return dto; } - FillSyncInfo(dto, item, options, user, syncProgress); + FillSyncInfo(dto, item, options, user, syncDictionary); return dto; } @@ -211,23 +197,24 @@ namespace MediaBrowser.Server.Implementations.Dto return items; } - private SyncedItemProgress[] GetSyncedItemProgress(DtoOptions options) + public Dictionary<string, SyncedItemProgress> GetSyncedItemProgress(DtoOptions options) { - if (!options.Fields.Contains(ItemFields.SyncInfo)) + if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && + !options.Fields.Contains(ItemFields.SyncInfo)) { - return new SyncedItemProgress[] { }; + return new Dictionary<string, SyncedItemProgress>(); } var deviceId = options.DeviceId; if (string.IsNullOrWhiteSpace(deviceId)) { - return new SyncedItemProgress[] { }; + return new Dictionary<string, SyncedItemProgress>(); } var caps = _deviceManager().GetCapabilities(deviceId); if (caps == null || !caps.SupportsSync) { - return new SyncedItemProgress[] { }; + return new Dictionary<string, SyncedItemProgress>(); } return _syncManager.GetSyncedItemProgresses(new SyncJobItemQuery @@ -241,12 +228,13 @@ namespace MediaBrowser.Server.Implementations.Dto SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced } - }).Items; + }); } public void FillSyncInfo(IEnumerable<Tuple<BaseItem, BaseItemDto>> tuples, DtoOptions options, User user) { - if (options.Fields.Contains(ItemFields.SyncInfo)) + if (options.Fields.Contains(ItemFields.BasicSyncInfo) || + options.Fields.Contains(ItemFields.SyncInfo)) { var syncProgress = GetSyncedItemProgress(options); @@ -254,62 +242,45 @@ namespace MediaBrowser.Server.Implementations.Dto { var item = tuple.Item1; - FillSyncInfo(tuple.Item2, item, syncProgress, options, user); + FillSyncInfo(tuple.Item2, item, options, user, syncProgress); } } } - private void FillSyncInfo(IHasSyncInfo dto, BaseItem item, DtoOptions options, User user, SyncedItemProgress[] syncProgress) + private void FillSyncInfo(IHasSyncInfo dto, BaseItem item, DtoOptions options, User user, Dictionary<string, SyncedItemProgress> syncProgress) { - if (options.Fields.Contains(ItemFields.SyncInfo)) - { - var userCanSync = user != null && user.Policy.EnableSync; - dto.SupportsSync = userCanSync && _syncManager.SupportsSync(item); - } + var hasFullSyncInfo = options.Fields.Contains(ItemFields.SyncInfo); - if (dto.SupportsSync ?? false) + if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && + !hasFullSyncInfo) { - dto.HasSyncJob = syncProgress.Any(i => i.Status != SyncJobItemStatus.Synced && string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)); - dto.IsSynced = syncProgress.Any(i => i.Status == SyncJobItemStatus.Synced && string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)); - - if (dto.IsSynced.Value) - { - dto.SyncStatus = SyncJobItemStatus.Synced; - } - - else if (dto.HasSyncJob.Value) - { - dto.SyncStatus = syncProgress.Where(i => string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)).Select(i => i.Status).Max(); - } - } - } - - private void FillSyncInfo(IHasSyncInfo dto, BaseItem item, SyncedItemProgress[] syncProgress, DtoOptions options, User user) - { - if (options.Fields.Contains(ItemFields.SyncInfo)) - { - var userCanSync = user != null && user.Policy.EnableSync; - dto.SupportsSync = userCanSync && _syncManager.SupportsSync(item); + return; } if (dto.SupportsSync ?? false) { - dto.HasSyncJob = syncProgress.Any(i => i.Status != SyncJobItemStatus.Synced && string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)); - dto.IsSynced = syncProgress.Any(i => i.Status == SyncJobItemStatus.Synced && string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)); - - if (dto.IsSynced.Value) + SyncedItemProgress syncStatus; + if (syncProgress.TryGetValue(dto.Id, out syncStatus)) { - dto.SyncStatus = SyncJobItemStatus.Synced; - } + if (syncStatus.Status == SyncJobItemStatus.Synced) + { + dto.SyncPercent = 100; + } + else + { + dto.SyncPercent = syncStatus.Progress; + } - else if (dto.HasSyncJob.Value) - { - dto.SyncStatus = syncProgress.Where(i => string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)).Select(i => i.Status).Max(); + if (hasFullSyncInfo) + { + dto.HasSyncJob = true; + dto.SyncStatus = syncStatus.Status; + } } } } - private async Task<BaseItemDto> GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Dictionary<string, SyncedItemProgress> syncProgress, User user = null, BaseItem owner = null) + private async Task<BaseItemDto> GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var fields = options.Fields; @@ -358,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (user != null) { - await AttachUserSpecificInfo(dto, item, user, fields, syncProgress).ConfigureAwait(false); + await AttachUserSpecificInfo(dto, item, user, options).ConfigureAwait(false); } var hasMediaSources = item as IHasMediaSources; @@ -421,11 +392,9 @@ namespace MediaBrowser.Server.Implementations.Dto return dto; } - public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null) + public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null) { - var syncProgress = GetSyncedItemProgress(options); - - var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user).Result; + var dto = GetBaseItemDtoInternal(item, options, user).Result; if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts)) { @@ -439,12 +408,19 @@ namespace MediaBrowser.Server.Implementations.Dto private void SetItemByNameInfo(BaseItem item, BaseItemDto dto, List<BaseItem> taggedItems, User user = null) { - if (item is MusicArtist || item is MusicGenre) + if (item is MusicArtist) { dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.SongCount = taggedItems.Count(i => i is Audio); } + else if (item is MusicGenre) + { + dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } else if (item is GameGenre) { dto.GameCount = taggedItems.Count(i => i is Game); @@ -453,6 +429,7 @@ namespace MediaBrowser.Server.Implementations.Dto { // This populates them all and covers Genre, Person, Studio, Year + dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); dto.EpisodeCount = taggedItems.Count(i => i is Episode); dto.GameCount = taggedItems.Count(i => i is Game); @@ -469,34 +446,20 @@ namespace MediaBrowser.Server.Implementations.Dto /// <summary> /// Attaches the user specific info. /// </summary> - /// <param name="dto">The dto.</param> - /// <param name="item">The item.</param> - /// <param name="user">The user.</param> - /// <param name="fields">The fields.</param> - /// <param name="syncProgress">The synchronize progress.</param> - private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields, Dictionary<string, SyncedItemProgress> syncProgress) + private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions) { + var fields = dtoOptions.Fields; + if (item.IsFolder) { var folder = (Folder)item; - if (item.SourceType == SourceType.Library && folder.SupportsUserDataFromChildren && fields.Contains(ItemFields.SyncInfo)) - { - // Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice - // TODO: Improve in future - dto.UserData = GetUserItemDataDto(_userDataRepository.GetUserData(user, item)); - - await SetSpecialCounts(folder, user, dto, fields, syncProgress).ConfigureAwait(false); - - dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue && - dto.UserData.PlayedPercentage.Value >= 100; - } - else + if (dtoOptions.EnableUserData) { dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false); } - if (item.SourceType == SourceType.Library) + if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) { dto.ChildCount = GetChildCount(folder, user); } @@ -514,11 +477,23 @@ namespace MediaBrowser.Server.Implementations.Dto else { - dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result; + if (dtoOptions.EnableUserData) + { + dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result; + } } dto.PlayAccess = item.GetPlayAccess(user); + if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo)) + { + var userCanSync = user != null && user.Policy.EnableSync; + if (userCanSync && _syncManager.SupportsSync(item)) + { + dto.SupportsSync = true; + } + } + if (fields.Contains(ItemFields.SeasonUserData)) { var episode = item as Episode; @@ -980,20 +955,23 @@ namespace MediaBrowser.Server.Implementations.Dto dto.Genres = item.Genres; } - dto.ImageTags = new Dictionary<ImageType, string>(); - - // Prevent implicitly captured closure - var currentItem = item; - foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) - .ToList()) + if (options.EnableImages) { - if (options.GetImageLimit(image.Type) > 0) - { - var tag = GetImageCacheTag(item, image); + dto.ImageTags = new Dictionary<ImageType, string>(); - if (tag != null) + // Prevent implicitly captured closure + var currentItem = item; + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) + .ToList()) + { + if (options.GetImageLimit(image.Type) > 0) { - dto.ImageTags[image.Type] = tag; + var tag = GetImageCacheTag(item, image); + + if (tag != null) + { + dto.ImageTags[image.Type] = tag; + } } } } @@ -1001,7 +979,16 @@ namespace MediaBrowser.Server.Implementations.Dto dto.Id = GetDtoId(item); dto.IndexNumber = item.IndexNumber; dto.ParentIndexNumber = item.ParentIndexNumber; - dto.IsFolder = item.IsFolder; + + if (item.IsFolder) + { + dto.IsFolder = true; + } + else if (item is IHasMediaSources) + { + dto.IsFolder = false; + } + dto.MediaType = item.MediaType; dto.LocationType = item.LocationType; if (item.IsHD.HasValue && item.IsHD.Value) @@ -1445,7 +1432,7 @@ namespace MediaBrowser.Server.Implementations.Dto BaseItem parent = null; var isFirst = true; - while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) && + while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) && (parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null) { if (parent == null) @@ -1544,97 +1531,6 @@ namespace MediaBrowser.Server.Implementations.Dto } /// <summary> - /// Since it can be slow to make all of these calculations independently, this method will provide a way to do them all at once - /// </summary> - /// <param name="folder">The folder.</param> - /// <param name="user">The user.</param> - /// <param name="dto">The dto.</param> - /// <param name="fields">The fields.</param> - /// <param name="syncProgress">The synchronize progress.</param> - /// <returns>Task.</returns> - private async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, List<ItemFields> fields, Dictionary<string, SyncedItemProgress> syncProgress) - { - var recursiveItemCount = 0; - var unplayed = 0; - - double totalPercentPlayed = 0; - double totalSyncPercent = 0; - - var children = await folder.GetItems(new InternalItemsQuery - { - IsFolder = false, - Recursive = true, - ExcludeLocationTypes = new[] { LocationType.Virtual }, - User = user - - }).ConfigureAwait(false); - - // Loop through each recursive child - foreach (var child in children.Items) - { - var userdata = _userDataRepository.GetUserData(user, child); - - recursiveItemCount++; - - var isUnplayed = true; - - // Incrememt totalPercentPlayed - if (userdata != null) - { - if (userdata.Played) - { - totalPercentPlayed += 100; - - isUnplayed = false; - } - else if (userdata.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0) - { - double itemPercent = userdata.PlaybackPositionTicks; - itemPercent /= child.RunTimeTicks.Value; - totalPercentPlayed += itemPercent; - } - } - - if (isUnplayed) - { - unplayed++; - } - - double percent = 0; - SyncedItemProgress syncItemProgress; - if (syncProgress.TryGetValue(child.Id.ToString("N"), out syncItemProgress)) - { - switch (syncItemProgress.Status) - { - case SyncJobItemStatus.Synced: - percent = 100; - break; - case SyncJobItemStatus.Converting: - case SyncJobItemStatus.ReadyToTransfer: - case SyncJobItemStatus.Transferring: - percent = 50; - break; - } - } - totalSyncPercent += percent; - } - - dto.RecursiveItemCount = recursiveItemCount; - dto.UserData.UnplayedItemCount = unplayed; - - if (recursiveItemCount > 0) - { - dto.UserData.PlayedPercentage = totalPercentPlayed / recursiveItemCount; - - var pct = totalSyncPercent / recursiveItemCount; - if (pct > 0) - { - dto.SyncPercent = pct; - } - } - } - - /// <summary> /// Attaches the primary image aspect ratio. /// </summary> /// <param name="dto">The dto.</param> @@ -1660,7 +1556,7 @@ namespace MediaBrowser.Server.Implementations.Dto { size = _imageProcessor.GetImageSize(imageInfo); } - catch (Exception ex) + catch { //_logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); return null; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs index 46ddf3dd8..a36583a41 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IInstallationManager _installationManager; //private readonly ILogManager _logManager; - private readonly ILogger _logger; + //private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; private readonly IActivityManager _activityManager; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 50ad3cfbc..280bec65b 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -7,9 +7,7 @@ using Mono.Nat; using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Net; -using System.Text; using MediaBrowser.Common.Threading; namespace MediaBrowser.Server.Implementations.EntryPoints @@ -167,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints CreateRules(device); } - catch (Exception ex) + catch { // I think it could be a good idea to log the exception because // you are using permanent portmapping here (never expire) and that means that next time diff --git a/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs index cc4ef1972..414fda400 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -57,18 +54,15 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private async void SendMessage(string name, TimerEventInfo info) { - var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).ToList(); + var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id.ToString("N")).ToList(); - foreach (var user in users) + try { - try - { - await _sessionManager.SendMessageToUserSessions<TimerEventInfo>(user.Id.ToString("N"), name, info, CancellationToken.None); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending message", ex); - } + await _sessionManager.SendMessageToUserSessions<TimerEventInfo>(users, name, info, CancellationToken.None); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs index b5624625f..3ea8417f8 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -11,6 +11,7 @@ using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Events; using MediaBrowser.Model.Sync; using System; +using System.Collections.Generic; using System.Threading; namespace MediaBrowser.Server.Implementations.EntryPoints @@ -164,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private async void SendMessageToUserSession<T>(User user, string name, T data) { - await _sessionManager.SendMessageToUserSessions(user.Id.ToString("N"), name, data, CancellationToken.None); + await _sessionManager.SendMessageToUserSessions(new List<string> { user.Id.ToString("N") }, name, data, CancellationToken.None); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index b616b7761..2bb010133 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints var changes = _changedItems.ToList(); _changedItems.Clear(); - SendNotifications(changes, CancellationToken.None); + var task = SendNotifications(changes, CancellationToken.None); if (UpdateTimer != null) { diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 2109f8d59..5e01666a9 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -43,13 +43,6 @@ namespace MediaBrowser.Server.Implementations.FileOrganization _providerManager = providerManager; } - public Task<FileOrganizationResult> OrganizeEpisodeFile(string path, CancellationToken cancellationToken) - { - var options = _config.GetAutoOrganizeOptions(); - - return OrganizeEpisodeFile(path, options, false, cancellationToken); - } - public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken) { _logger.Info("Sorting file {0}", path); @@ -63,6 +56,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization FileSize = new FileInfo(path).Length }; + try + { if (_libraryMonitor.IsPathLocked(path)) { result.Status = FileSortingStatus.Failure; @@ -148,6 +143,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + result.Status = FileSortingStatus.Failure; + result.StatusMessage = ex.Message; + } return result; } @@ -156,6 +157,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { var result = _organizationService.GetResult(request.ResultId); + try + { Series series = null; if (request.NewSeriesProviderIds.Count > 0) @@ -207,6 +210,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization cancellationToken).ConfigureAwait(false); await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + result.Status = FileSortingStatus.Failure; + result.StatusMessage = ex.Message; + } return result; } @@ -263,16 +272,27 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var originalExtractedSeriesString = result.ExtractedName; + bool isNew = string.IsNullOrWhiteSpace(result.Id); + + if (isNew) + { + await _organizationService.SaveResult(result, cancellationToken); + } + + if (!_organizationService.AddToInProgressList(result, isNew)) + { + throw new Exception("File is currently processed otherwise. Please try again later."); + } + + try + { // Proceed to sort the file var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(newPath)) { var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - return; + throw new Exception(msg); } _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); @@ -347,6 +367,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } } } + } + catch (Exception ex) + { + result.Status = FileSortingStatus.Failure; + result.StatusMessage = ex.Message; + _logger.Warn(ex.Message); + return; + } + finally + { + _organizationService.RemoveFromInprogressList(result); + } if (rememberCorrection) { @@ -505,7 +537,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } catch (Exception ex) { - var errorMsg = string.Format("Failed to move file from {0} to {1}", result.OriginalPath, result.TargetPath); + var errorMsg = string.Format("Failed to move file from {0} to {1}: {2}", result.OriginalPath, result.TargetPath, ex.Message); result.Status = FileSortingStatus.Failure; result.StatusMessage = errorMsg; @@ -616,7 +648,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber); _logger.Warn(msg); - return null; + throw new Exception(msg); } var episodeName = episode.Name; @@ -715,6 +747,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var pattern = endingEpisodeNumber.HasValue ? options.MultiEpisodeNamePattern : options.EpisodeNamePattern; + if (string.IsNullOrWhiteSpace(pattern)) + { + throw new Exception("GetEpisodeFileName: Configured episode name pattern is empty!"); + } + var result = pattern.Replace("%sn", seriesName) .Replace("%s.n", seriesName.Replace(" ", ".")) .Replace("%s_n", seriesName.Replace(" ", "_")) @@ -759,8 +796,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization // There may be cases where reducing the title length may still not be sufficient to // stay below maxLength var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength); - _logger.Warn(msg); - return string.Empty; + throw new Exception(msg); } return result; diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs new file mode 100644 index 000000000..5c3814f66 --- /dev/null +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs @@ -0,0 +1,80 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.FileOrganization; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.FileOrganization; +using MediaBrowser.Model.Logging; +using System; +using System.Threading; + +namespace MediaBrowser.Server.Implementations.FileOrganization +{ + /// <summary> + /// Class SessionInfoWebSocketListener + /// </summary> + class FileOrganizationNotifier : IServerEntryPoint + { + private readonly IFileOrganizationService _organizationService; + private readonly ISessionManager _sessionManager; + private readonly ITaskManager _taskManager; + + public FileOrganizationNotifier(ILogger logger, IFileOrganizationService organizationService, ISessionManager sessionManager, ITaskManager taskManager) + { + _organizationService = organizationService; + _sessionManager = sessionManager; + _taskManager = taskManager; + } + + public void Run() + { + _organizationService.ItemAdded += _organizationService_ItemAdded; + _organizationService.ItemRemoved += _organizationService_ItemRemoved; + _organizationService.ItemUpdated += _organizationService_ItemUpdated; + _organizationService.LogReset += _organizationService_LogReset; + + //_taskManager.TaskCompleted += _taskManager_TaskCompleted; + } + + private void _organizationService_LogReset(object sender, EventArgs e) + { + _sessionManager.SendMessageToAdminSessions("AutoOrganize_LogReset", (FileOrganizationResult)null, CancellationToken.None); + } + + private void _organizationService_ItemUpdated(object sender, GenericEventArgs<FileOrganizationResult> e) + { + _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemUpdated", e.Argument, CancellationToken.None); + } + + private void _organizationService_ItemRemoved(object sender, GenericEventArgs<FileOrganizationResult> e) + { + _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemRemoved", e.Argument, CancellationToken.None); + } + + private void _organizationService_ItemAdded(object sender, GenericEventArgs<FileOrganizationResult> e) + { + _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemAdded", e.Argument, CancellationToken.None); + } + + //private void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + //{ + // var taskWithKey = e.Task.ScheduledTask as IHasKey; + // if (taskWithKey != null && taskWithKey.Key == "AutoOrganize") + // { + // _sessionManager.SendMessageToAdminSessions("AutoOrganize_TaskCompleted", (FileOrganizationResult)null, CancellationToken.None); + // } + //} + + public void Dispose() + { + _organizationService.ItemAdded -= _organizationService_ItemAdded; + _organizationService.ItemRemoved -= _organizationService_ItemRemoved; + _organizationService.ItemUpdated -= _organizationService_ItemUpdated; + _organizationService.LogReset -= _organizationService_LogReset; + + //_taskManager.TaskCompleted -= _taskManager_TaskCompleted; + } + + + } +} diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index 60d515e12..a42eba6ca 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -3,16 +3,21 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using System; +using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Events; +using MediaBrowser.Common.Events; namespace MediaBrowser.Server.Implementations.FileOrganization { @@ -26,6 +31,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IProviderManager _providerManager; + private readonly ConcurrentDictionary<string, bool> _inProgressItemIds = new ConcurrentDictionary<string, bool>(); + + public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded; + public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated; + public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved; + public event EventHandler LogReset; public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager) { @@ -58,12 +69,26 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public QueryResult<FileOrganizationResult> GetResults(FileOrganizationResultQuery query) { - return _repo.GetResults(query); + var results = _repo.GetResults(query); + + foreach (var result in results.Items) + { + result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id); + } + + return results; } public FileOrganizationResult GetResult(string id) { - return _repo.GetResult(id); + var result = _repo.GetResult(id); + + if (result != null) + { + result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id); + } + + return result; } public FileOrganizationResult GetResultBySourcePath(string path) @@ -78,11 +103,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization return GetResult(id); } - public Task DeleteOriginalFile(string resultId) + public async Task DeleteOriginalFile(string resultId) { var result = _repo.GetResult(resultId); _logger.Info("Requested to delete {0}", result.OriginalPath); + + if (!AddToInProgressList(result, false)) + { + throw new Exception("Path is currently processed otherwise. Please try again later."); + } + try { _fileSystem.DeleteFile(result.OriginalPath); @@ -91,8 +122,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath); } + finally + { + RemoveFromInprogressList(result); + } + + await _repo.Delete(resultId); - return _repo.Delete(resultId); + EventHelper.FireEventIfNotNull(ItemRemoved, this, new GenericEventArgs<FileOrganizationResult>(result), _logger); } private AutoOrganizeOptions GetAutoOrganizeOptions() @@ -112,13 +149,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None) + var organizeResult = await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None) .ConfigureAwait(false); + + if (organizeResult.Status != FileSortingStatus.Success) + { + throw new Exception(result.StatusMessage); + } } - public Task ClearLog() + public async Task ClearLog() { - return _repo.DeleteAll(); + await _repo.DeleteAll(); + EventHelper.FireEventIfNotNull(LogReset, this, EventArgs.Empty, _logger); } public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) @@ -126,7 +169,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false); + var result = await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false); + + if (result.Status != FileSortingStatus.Success) + { + throw new Exception(result.StatusMessage); + } } public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query) @@ -179,5 +227,55 @@ namespace MediaBrowser.Server.Implementations.FileOrganization _config.SaveAutoOrganizeOptions(options); } } + + /// <summary> + /// Attempts to add a an item to the list of currently processed items. + /// </summary> + /// <param name="result">The result item.</param> + /// <param name="isNewItem">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param> + /// <returns>True if the item was added, False if the item is already contained in the list.</returns> + public bool AddToInProgressList(FileOrganizationResult result, bool isNewItem) + { + if (string.IsNullOrWhiteSpace(result.Id)) + { + result.Id = result.OriginalPath.GetMD5().ToString("N"); + } + + if (!_inProgressItemIds.TryAdd(result.Id, false)) + { + return false; + } + + result.IsInProgress = true; + + if (isNewItem) + { + EventHelper.FireEventIfNotNull(ItemAdded, this, new GenericEventArgs<FileOrganizationResult>(result), _logger); + } + else + { + EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs<FileOrganizationResult>(result), _logger); + } + + return true; + } + + /// <summary> + /// Removes an item from the list of currently processed items. + /// </summary> + /// <param name="result">The result item.</param> + /// <returns>True if the item was removed, False if the item was not contained in the list.</returns> + public bool RemoveFromInprogressList(FileOrganizationResult result) + { + bool itemValue; + var retval = _inProgressItemIds.TryRemove(result.Id, out itemValue); + + result.IsInProgress = false; + + EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs<FileOrganizationResult>(result), _logger); + + return retval; + } + } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriter.cs index 5aa01c706..e44b0c6af 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriter.cs @@ -4,38 +4,41 @@ using System.IO; using System.Threading.Tasks; using ServiceStack; using ServiceStack.Web; +using MediaBrowser.Controller.Net; namespace MediaBrowser.Server.Implementations.HttpServer { - public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions + public class AsyncStreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions { /// <summary> /// Gets or sets the source stream. /// </summary> /// <value>The source stream.</value> - private Func<Stream, Task> Writer { get; set; } - - /// <summary> - /// Gets the options. - /// </summary> - /// <value>The options.</value> - public IDictionary<string, string> Options { get; private set; } + private IAsyncStreamSource _source; public Action OnComplete { get; set; } public Action OnError { get; set; } /// <summary> - /// Initializes a new instance of the <see cref="StreamWriter" /> class. + /// Initializes a new instance of the <see cref="AsyncStreamWriter" /> class. /// </summary> - public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers) + public AsyncStreamWriter(IAsyncStreamSource source) { - Writer = writer; + _source = source; + } - if (headers == null) + public IDictionary<string, string> Options + { + get { - headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + var hasOptions = _source as IHasOptions; + if (hasOptions != null) + { + return hasOptions.Options; + } + + return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } - Options = headers; } /// <summary> @@ -44,13 +47,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - var task = Writer(responseStream); + var task = _source.WriteToAsync(responseStream); Task.WaitAll(task); } public async Task WriteToAsync(Stream responseStream) { - await Writer(responseStream).ConfigureAwait(false); + await _source.WriteToAsync(responseStream).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 17e4793cb..51a53fe21 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -67,6 +67,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer public override void Configure(Container container) { HostConfig.Instance.DefaultRedirectPath = DefaultRedirectPath; + HostConfig.Instance.LogUnobservedTaskExceptions = false; HostConfig.Instance.MapExceptionToStatusCode = new Dictionary<Type, int> { @@ -80,7 +81,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer {typeof (ApplicationException), 500} }; - HostConfig.Instance.DebugMode = true; + HostConfig.Instance.GlobalResponseHeaders = new Dictionary<string, string>(); + HostConfig.Instance.DebugMode = false; HostConfig.Instance.LogFactory = LogManager.LogFactory; @@ -250,7 +252,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.Close(); } - catch (Exception errorEx) + catch { //_logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx); } @@ -329,6 +331,46 @@ namespace MediaBrowser.Server.Implementations.HttpServer return url; } + private string NormalizeConfiguredLocalAddress(string address) + { + var index = address.Trim('/').IndexOf('/'); + + if (index != -1) + { + address = address.Substring(index + 1); + } + + return address.Trim('/'); + } + + private bool ValidateHost(Uri url) + { + var hosts = _config + .Configuration + .LocalNetworkAddresses + .Select(NormalizeConfiguredLocalAddress) + .ToList(); + + if (hosts.Count == 0) + { + return true; + } + + var host = url.Host ?? string.Empty; + + _logger.Debug("Validating host {0}", host); + + if (_networkManager.IsInPrivateAddressSpace(host)) + { + hosts.Add("localhost"); + hosts.Add("127.0.0.1"); + + return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1); + } + + return true; + } + /// <summary> /// Overridable method that can be used to implement a custom hnandler /// </summary> @@ -348,6 +390,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer return ; } + if (!ValidateHost(url)) + { + httpRes.StatusCode = 400; + httpRes.ContentType = "text/plain"; + httpRes.Write("Invalid host"); + + httpRes.Close(); + return; + } + var operationName = httpReq.OperationName; var localPath = url.LocalPath; @@ -376,8 +428,24 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) || - localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1 || - localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1) + localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1) + { + httpRes.StatusCode = 200; + httpRes.ContentType = "text/html"; + var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) + .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); + + if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) + { + httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>"); + + httpRes.Close(); + return; + } + } + + if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 && + localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1) { httpRes.StatusCode = 200; httpRes.ContentType = "text/html"; diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index c0a2a5eb3..c55e98388 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -275,7 +275,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <returns>System.Object.</returns> private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) { - responseHeaders["ETag"] = cacheKeyString; + responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString); if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration)) { @@ -534,7 +534,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) { AddAgeHeader(responseHeaders, lastDateModified); - responseHeaders["LastModified"] = lastDateModified.Value.ToString("r"); + responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r"); } if (cacheDuration.HasValue) @@ -704,9 +704,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw error; } - public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null) + public object GetAsyncStreamWriter(IAsyncStreamSource streamSource) { - return new AsyncStreamWriterFunc(streamWriter, responseHeaders); + return new AsyncStreamWriter(streamSource); } } }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 7ac92408b..488c630fe 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -28,8 +28,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer public Action OnComplete { get; set; } private readonly ILogger _logger; - // 256k - private const int BufferSize = 262144; + private const int BufferSize = 81920; /// <summary> /// The _options @@ -192,7 +191,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - catch (IOException ex) + catch (IOException) { throw; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index f5906f6b7..ae408f8d6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -75,8 +75,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { } - // 256k - private const int BufferSize = 262144; + private const int BufferSize = 81920; /// <summary> /// Writes to. diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index f48beacb5..2f4605c5c 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -61,6 +61,11 @@ namespace MediaBrowser.Server.Implementations.IO public void RestartTimer() { + if (_disposed) + { + return; + } + lock (_timerLock) { if (_timer == null) @@ -254,6 +259,11 @@ namespace MediaBrowser.Server.Implementations.IO // File may have been deleted return false; } + catch (UnauthorizedAccessException) + { + Logger.Debug("No write permission for: {0}.", path); + return false; + } catch (IOException) { //the file is unavailable because it is: @@ -281,8 +291,10 @@ namespace MediaBrowser.Server.Implementations.IO } } + private bool _disposed; public void Dispose() { + _disposed = true; DisposeTimer(); } } diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 99cb80cb2..7ed4dc71e 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -5,14 +5,12 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.ScheduledTasks; using Microsoft.Win32; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller; @@ -40,7 +38,6 @@ namespace MediaBrowser.Server.Implementations.IO /// </summary> private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string> { - "thumbs.db", "small.jpg", "albumart.jpg", @@ -49,6 +46,16 @@ namespace MediaBrowser.Server.Implementations.IO "TempSBE" }; + private readonly IReadOnlyList<string> _alwaysIgnoreExtensions = new List<string> + { + // thumbs.db + ".db", + + // bts sync files + ".bts", + ".sync" + }; + /// <summary> /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// </summary> @@ -165,27 +172,29 @@ namespace MediaBrowser.Server.Implementations.IO } } - public void Start() + private bool IsLibraryMonitorEnabaled(BaseItem item) { - if (EnableLibraryMonitor) + var options = LibraryManager.GetLibraryOptions(item); + + if (options != null && options.SchemaVersion >= 1) { - StartInternal(); + return options.EnableRealtimeMonitor; } + + return EnableLibraryMonitor; } - /// <summary> - /// Starts this instance. - /// </summary> - private void StartInternal() + public void Start() { LibraryManager.ItemAdded += LibraryManager_ItemAdded; LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; - var pathsToWatch = new List<string> { LibraryManager.RootFolder.Path }; + var pathsToWatch = new List<string> { }; var paths = LibraryManager .RootFolder .Children + .Where(IsLibraryMonitorEnabaled) .OfType<Folder>() .SelectMany(f => f.PhysicalLocations) .Distinct(StringComparer.OrdinalIgnoreCase) @@ -206,6 +215,14 @@ namespace MediaBrowser.Server.Implementations.IO } } + private void StartWatching(BaseItem item) + { + if (IsLibraryMonitorEnabaled(item)) + { + StartWatchingPath(item.Path); + } + } + /// <summary> /// Handles the ItemRemoved event of the LibraryManager control. /// </summary> @@ -228,7 +245,7 @@ namespace MediaBrowser.Server.Implementations.IO { if (e.Item.GetParent() is AggregateFolder) { - StartWatchingPath(e.Item.Path); + StartWatching(e.Item); } } @@ -375,14 +392,6 @@ namespace MediaBrowser.Server.Implementations.IO Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); DisposeWatcher(dw); - - if (ConfigurationManager.Configuration.EnableLibraryMonitor == AutoOnOff.Auto) - { - Logger.Info("Disabling realtime monitor to prevent future instability"); - - ConfigurationManager.Configuration.EnableLibraryMonitor = AutoOnOff.Disabled; - Stop(); - } } /// <summary> @@ -413,7 +422,9 @@ namespace MediaBrowser.Server.Implementations.IO var filename = Path.GetFileName(path); - var monitorPath = !(!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)); + var monitorPath = !string.IsNullOrEmpty(filename) && + !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && + !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase); // Ignore certain files var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 712ea4ef3..cc3a7e41f 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -364,7 +364,7 @@ namespace MediaBrowser.Server.Implementations.Library if (item.IsFolder) { - if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel)) + if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel) && !(item is AggregateFolder)) { if (item.SourceType != SourceType.Library) { @@ -556,7 +556,12 @@ namespace MediaBrowser.Server.Implementations.Library return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); } - private BaseItem ResolvePath(FileSystemMetadata fileInfo, IDirectoryService directoryService, IItemResolver[] resolvers, Folder parent = null, string collectionType = null) + private BaseItem ResolvePath(FileSystemMetadata fileInfo, + IDirectoryService directoryService, + IItemResolver[] resolvers, + Folder parent = null, + string collectionType = null, + LibraryOptions libraryOptions = null) { if (fileInfo == null) { @@ -575,7 +580,8 @@ namespace MediaBrowser.Server.Implementations.Library Parent = parent, Path = fullPath, FileInfo = fileInfo, - CollectionType = collectionType + CollectionType = collectionType, + LibraryOptions = libraryOptions }; // Return null if ignore rules deem that we should do so @@ -653,12 +659,17 @@ namespace MediaBrowser.Server.Implementations.Library return !args.ContainsFileSystemEntryByName(".ignore"); } - public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, string collectionType) + public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType) { - return ResolvePaths(files, directoryService, parent, collectionType, EntityResolvers); + return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); } - public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, string collectionType, IItemResolver[] resolvers) + public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, + IDirectoryService directoryService, + Folder parent, + LibraryOptions libraryOptions, + string collectionType, + IItemResolver[] resolvers) { var fileList = files.Where(i => !IgnoreFile(i, parent)).ToList(); @@ -679,22 +690,27 @@ namespace MediaBrowser.Server.Implementations.Library { ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); } - items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers)); + items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); return items; } } } - return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers); + return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions); } - private IEnumerable<BaseItem> ResolveFileList(IEnumerable<FileSystemMetadata> fileList, IDirectoryService directoryService, Folder parent, string collectionType, IItemResolver[] resolvers) + private IEnumerable<BaseItem> ResolveFileList(IEnumerable<FileSystemMetadata> fileList, + IDirectoryService directoryService, + Folder parent, + string collectionType, + IItemResolver[] resolvers, + LibraryOptions libraryOptions) { return fileList.Select(f => { try { - return ResolvePath(f, directoryService, resolvers, parent, collectionType); + return ResolvePath(f, directoryService, resolvers, parent, collectionType, libraryOptions); } catch (Exception ex) { @@ -813,7 +829,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Person}.</returns> public Person GetPerson(string name) { - return GetItemByName<Person>(ConfigurationManager.ApplicationPaths.PeoplePath, name); + return CreateItemByName<Person>(Person.GetPath(name), name); } /// <summary> @@ -823,7 +839,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Studio}.</returns> public Studio GetStudio(string name) { - return GetItemByName<Studio>(ConfigurationManager.ApplicationPaths.StudioPath, name); + return CreateItemByName<Studio>(Studio.GetPath(name), name); } /// <summary> @@ -833,7 +849,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public Genre GetGenre(string name) { - return GetItemByName<Genre>(ConfigurationManager.ApplicationPaths.GenrePath, name); + return CreateItemByName<Genre>(Genre.GetPath(name), name); } /// <summary> @@ -843,7 +859,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{MusicGenre}.</returns> public MusicGenre GetMusicGenre(string name) { - return GetItemByName<MusicGenre>(ConfigurationManager.ApplicationPaths.MusicGenrePath, name); + return CreateItemByName<MusicGenre>(MusicGenre.GetPath(name), name); } /// <summary> @@ -853,15 +869,10 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{GameGenre}.</returns> public GameGenre GetGameGenre(string name) { - return GetItemByName<GameGenre>(ConfigurationManager.ApplicationPaths.GameGenrePath, name); + return CreateItemByName<GameGenre>(GameGenre.GetPath(name), name); } /// <summary> - /// The us culture - /// </summary> - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// <summary> /// Gets a Year /// </summary> /// <param name="value">The value.</param> @@ -874,19 +885,9 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentOutOfRangeException("Years less than or equal to 0 are invalid."); } - return GetItemByName<Year>(ConfigurationManager.ApplicationPaths.YearPath, value.ToString(UsCulture)); - } + var name = value.ToString(CultureInfo.InvariantCulture); - /// <summary> - /// Gets the artists path. - /// </summary> - /// <value>The artists path.</value> - public string ArtistsPath - { - get - { - return Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, "artists"); - } + return CreateItemByName<Year>(Year.GetPath(name), name); } /// <summary> @@ -896,48 +897,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public MusicArtist GetArtist(string name) { - return GetItemByName<MusicArtist>(ArtistsPath, name); - } - - private T GetItemByName<T>(string path, string name) - where T : BaseItem, new() - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException("name"); - } - - // Trim the period at the end because windows will have a hard time with that - var validFilename = _fileSystem.GetValidFilename(name) - .Trim() - .TrimEnd('.'); - - string subFolderPrefix = null; - - var type = typeof(T); - - if (type == typeof(Person)) - { - var subFolderIndex = 0; - - while (!char.IsLetterOrDigit(validFilename[subFolderIndex])) - { - subFolderIndex++; - } - - subFolderPrefix = validFilename.Substring(subFolderIndex, 1); - } - - var fullPath = string.IsNullOrEmpty(subFolderPrefix) ? - Path.Combine(path, validFilename) : - Path.Combine(path, subFolderPrefix, validFilename); - - return CreateItemByName<T>(fullPath, name); + return CreateItemByName<MusicArtist>(MusicArtist.GetPath(name), name); } private T CreateItemByName<T>(string path, string name) @@ -1207,7 +1167,7 @@ namespace MediaBrowser.Server.Implementations.Library .Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders)); } - private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> collectionFolders) + private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> allCollectionFolders) { var info = new VirtualFolderInfo { @@ -1221,7 +1181,7 @@ namespace MediaBrowser.Server.Implementations.Library CollectionType = GetCollectionType(dir) }; - var libraryFolder = collectionFolders.FirstOrDefault(i => string.Equals(i.Path, dir, StringComparison.OrdinalIgnoreCase)); + var libraryFolder = allCollectionFolders.FirstOrDefault(i => string.Equals(i.Path, dir, StringComparison.OrdinalIgnoreCase)); if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary)) { @@ -1233,6 +1193,12 @@ namespace MediaBrowser.Server.Implementations.Library info.ItemId = libraryFolder.Id.ToString("N"); } + var collectionFolder = libraryFolder as CollectionFolder; + if (collectionFolder != null) + { + info.LibraryOptions = collectionFolder.GetLibraryOptions(); + } + return info; } @@ -1385,6 +1351,17 @@ namespace MediaBrowser.Server.Implementations.Library return ItemRepository.GetMusicGenres(query); } + public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) + { + if (query.User != null) + { + AddUserToQuery(query, query.User); + } + + SetTopParentOrAncestorIds(query); + return ItemRepository.GetAllArtists(query); + } + public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) { if (query.User != null) @@ -1488,7 +1465,12 @@ namespace MediaBrowser.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user) { - if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) + if (query.AncestorIds.Length == 0 && + !query.ParentId.HasValue && + query.ChannelIds.Length == 0 && + query.TopParentIds.Length == 0 && + string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) + && query.ItemIds.Length == 0) { var userViews = _userviewManager().GetUserViews(new UserViewQuery { @@ -1880,6 +1862,15 @@ namespace MediaBrowser.Server.Implementations.Library .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase)); } + public LibraryOptions GetLibraryOptions(BaseItem item) + { + var collectionFolder = GetCollectionFolders(item) + .OfType<CollectionFolder>() + .FirstOrDefault(); + + return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); + } + public string GetContentType(BaseItem item) { string configuredContentType = GetConfiguredContentType(item, false); @@ -2231,18 +2222,28 @@ namespace MediaBrowser.Server.Implementations.Library return item; } - public bool IsVideoFile(string path) + public bool IsVideoFile(string path, LibraryOptions libraryOptions) { - var resolver = new VideoResolver(GetNamingOptions(), new PatternsLogger()); + var resolver = new VideoResolver(GetNamingOptions(libraryOptions), new PatternsLogger()); return resolver.IsVideoFile(path); } - public bool IsAudioFile(string path) + public bool IsVideoFile(string path) { - var parser = new AudioFileParser(GetNamingOptions()); + return IsVideoFile(path, new LibraryOptions()); + } + + public bool IsAudioFile(string path, LibraryOptions libraryOptions) + { + var parser = new AudioFileParser(GetNamingOptions(libraryOptions)); return parser.IsAudioFile(path); } + public bool IsAudioFile(string path) + { + return IsAudioFile(path, new LibraryOptions()); + } + public int? GetSeasonNumberFromPath(string path) { return new SeasonPathParser(GetNamingOptions(), new RegexProvider()).Parse(path, true, true).SeasonNumber; @@ -2369,19 +2370,24 @@ namespace MediaBrowser.Server.Implementations.Library public NamingOptions GetNamingOptions() { + return GetNamingOptions(new LibraryOptions()); + } + + public NamingOptions GetNamingOptions(LibraryOptions libraryOptions) + { var options = new ExtendedNamingOptions(); // These cause apps to have problems options.AudioFileExtensions.Remove(".m3u"); options.AudioFileExtensions.Remove(".wpl"); - if (!ConfigurationManager.Configuration.EnableAudioArchiveFiles) + if (!libraryOptions.EnableArchiveMediaFiles) { options.AudioFileExtensions.Remove(".rar"); options.AudioFileExtensions.Remove(".zip"); } - if (!ConfigurationManager.Configuration.EnableVideoArchiveFiles) + if (!libraryOptions.EnableArchiveMediaFiles) { options.VideoFileExtensions.Remove(".rar"); options.VideoFileExtensions.Remove(".zip"); @@ -2432,7 +2438,7 @@ namespace MediaBrowser.Server.Implementations.Library new GenericVideoResolver<Trailer>(this) }; - return ResolvePaths(files, directoryService, null, null, resolvers) + return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers) .OfType<Trailer>() .Select(video => { @@ -2476,7 +2482,7 @@ namespace MediaBrowser.Server.Implementations.Library files.AddRange(currentVideo.Extras.Where(i => !string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path))); } - return ResolvePaths(files, directoryService, null, null) + return ResolvePaths(files, directoryService, null, new LibraryOptions(), null) .OfType<Video>() .Select(video => { @@ -2605,13 +2611,6 @@ namespace MediaBrowser.Server.Implementations.Library return ItemRepository.GetPeopleNames(query); } - public List<PersonInfo> GetAllPeople() - { - return GetPeople(new InternalPeopleQuery()) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .ToList(); - } - public Task UpdatePeople(BaseItem item, List<PersonInfo> people) { if (!item.SupportsPeople) @@ -2661,7 +2660,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new InvalidOperationException(); } - public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary) + public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) { @@ -2704,6 +2703,8 @@ namespace MediaBrowser.Server.Implementations.Library } } + CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); + if (mediaPaths != null) { foreach (var path in mediaPaths) diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 4f3fe1bf3..4a533ff93 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -278,8 +278,7 @@ namespace MediaBrowser.Server.Implementations.Library } var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? new List<string> { } - : new List<string> { user.Configuration.SubtitleLanguagePreference }; + ? new List<string>() : new List<string> { user.Configuration.SubtitleLanguagePreference }; var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null @@ -446,8 +445,31 @@ namespace MediaBrowser.Server.Implementations.Library } } + private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId, CancellationToken cancellationToken) + { + _logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name); + + try + { + await provider.CloseMediaSource(streamId, cancellationToken).ConfigureAwait(false); + } + catch (NotImplementedException) + { + + } + catch (Exception ex) + { + _logger.ErrorException("Error closing live stream {0}", ex, streamId); + } + } + public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -459,7 +481,7 @@ namespace MediaBrowser.Server.Implementations.Library { var tuple = GetProvider(id); - await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index b4cda39cd..039a17100 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -37,14 +37,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio if (!args.IsDirectory) { - if (_libraryManager.IsAudioFile(args.Path)) + var libraryOptions = args.GetLibraryOptions(); + + if (_libraryManager.IsAudioFile(args.Path, libraryOptions)) { var collectionType = args.GetCollectionType(); var isMixed = string.IsNullOrWhiteSpace(collectionType); // For conflicting extensions, give priority to videos - if (isMixed && _libraryManager.IsVideoFile(args.Path)) + if (isMixed && _libraryManager.IsVideoFile(args.Path, libraryOptions)) { return null; } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 9f8293cb5..1a8295800 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.IO; using CommonIO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { @@ -72,12 +74,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// <summary> /// Determine if the supplied file data points to a music album /// </summary> - /// <param name="path">The path.</param> - /// <param name="directoryService">The directory service.</param> - /// <returns><c>true</c> if [is music album] [the specified data]; otherwise, <c>false</c>.</returns> - public bool IsMusicAlbum(string path, IDirectoryService directoryService) + public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions) { - return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager); + return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager); } /// <summary> @@ -91,7 +90,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio if (args.IsDirectory) { //if (args.Parent is MusicArtist) return true; //saves us from testing children twice - if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager)) return true; + if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager)) return true; } return false; @@ -100,18 +99,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// <summary> /// Determine if the supplied list contains what we should consider music /// </summary> - /// <param name="list">The list.</param> - /// <param name="allowSubfolders">if set to <c>true</c> [allow subfolders].</param> - /// <param name="directoryService">The directory service.</param> - /// <param name="logger">The logger.</param> - /// <param name="fileSystem">The file system.</param> - /// <param name="libraryManager">The library manager.</param> - /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns> private bool ContainsMusic(IEnumerable<FileSystemMetadata> list, bool allowSubfolders, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem, + LibraryOptions libraryOptions, ILibraryManager libraryManager) { var discSubfolderCount = 0; @@ -124,11 +117,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio if (allowSubfolders) { var path = fileSystemInfo.FullName; - var isMultiDisc = IsMultiDiscFolder(path); + var isMultiDisc = IsMultiDiscFolder(path, libraryOptions); if (isMultiDisc) { - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager); if (hasMusic) { @@ -138,7 +131,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio } else { - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager); if (hasMusic) { @@ -151,7 +144,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var fullName = fileSystemInfo.FullName; - if (libraryManager.IsAudioFile(fullName)) + if (libraryManager.IsAudioFile(fullName, libraryOptions)) { return true; } @@ -165,9 +158,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio return discSubfolderCount > 0; } - private bool IsMultiDiscFolder(string path) + private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions) { - var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); + var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(libraryOptions); var parser = new AlbumParser(namingOptions, new PatternsLogger()); var result = parser.ParseMultiPart(path); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index e3c991e7e..e819af06f 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; + return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null; } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 703a33856..d0042a990 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers return null; } - if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub) + if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub) { var path = args.Path; diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 37d1e163f..ee9533d2a 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -48,6 +48,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies string collectionType, IDirectoryService directoryService) { + if (parent != null && parent.Path != null && parent.Path.IndexOf("disney", StringComparison.OrdinalIgnoreCase) != -1) + { + var b = true; + var a = b; + } + var result = ResolveMultipleInternal(parent, files, collectionType, directoryService); if (result != null) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 9dd30edde..3f9475480 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -6,6 +6,8 @@ using System; using System.IO; using System.Linq; using CommonIO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Server.Implementations.Library.Resolvers { @@ -32,15 +34,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.GetCollectionType(); + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || - string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) + (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { var filename = Path.GetFileNameWithoutExtension(args.Path); // Make sure the image doesn't belong to a video file - if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename))) + if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename))) { return null; } @@ -56,9 +59,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers return null; } - private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename) + private bool IsOwnedByMedia(LibraryOptions libraryOptions, FileSystemMetadata file, string imageFilename) { - if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase)) + if (_libraryManager.IsVideoFile(file.FullName, libraryOptions) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase)) { return true; } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 144f788a7..7bb66ed89 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -50,7 +50,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers { return new CollectionFolder { - CollectionType = GetCollectionType(args) + CollectionType = GetCollectionType(args), + PhysicalLocationsList = args.PhysicalLocations.ToList() }; } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 7b8832c59..6edc4a009 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using System.Linq; @@ -57,10 +56,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV if (series != null) { episode.SeriesId = series.Id; + episode.SeriesName = series.Name; + episode.SeriesSortName = series.SortName; } if (season != null) { episode.SeasonId = season.Id; + episode.SeasonName = season.Name; } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index eeac1345e..fc4929748 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -43,9 +43,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV var season = new Season { IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber, - SeriesId = series.Id + SeriesId = series.Id, + SeriesSortName = series.SortName, + SeriesName = series.Name }; - + if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0) { season.Name = _config.Configuration.SeasonZeroDisplayName; diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 45ba2ddbb..aefb29f1a 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -12,6 +12,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using CommonIO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { @@ -83,7 +85,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { return null; } - if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false)) + if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false)) { return new Series { @@ -104,6 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, + LibraryOptions libraryOptions, bool isTvContentType) { foreach (var child in fileSystemChildren) @@ -134,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV else { string fullName = child.FullName; - if (libraryManager.IsVideoFile(fullName)) + if (libraryManager.IsVideoFile(fullName, libraryOptions)) { if (isTvContentType) { diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index cf6f070d0..a6d6b5cb8 100644 --- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs @@ -166,12 +166,12 @@ namespace MediaBrowser.Server.Implementations.Library ExcludeItemTypes = excludeItemTypes.ToArray(), IncludeItemTypes = includeItemTypes.ToArray(), Limit = query.Limit, - IncludeItemsByName = true - + IncludeItemsByName = true, + IsVirtualItem = false }); // Add search hints based on item name - hints.AddRange(mediaItems.Where(IncludeInSearch).Select(item => + hints.AddRange(mediaItems.Select(item => { var index = GetIndex(item.Name, searchTerm, terms); @@ -187,20 +187,6 @@ namespace MediaBrowser.Server.Implementations.Library return Task.FromResult(returnValue); } - private bool IncludeInSearch(BaseItem item) - { - var episode = item as Episode; - - if (episode != null) - { - if (episode.IsMissingEpisode) - { - return false; - } - } - return true; - } - /// <summary> /// Gets the index. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs index 715f3c522..307cf4cd2 100644 --- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs @@ -10,7 +10,6 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index 319e715c3..5fffa3d1f 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -283,7 +283,7 @@ namespace MediaBrowser.Server.Implementations.Library ExcludeItemTypes = excludeItemTypes, ExcludeLocationTypes = new[] { LocationType.Virtual }, Limit = limit * 5, - ExcludeSourceTypes = parentIds.Length == 0 ? new[] { SourceType.Channel, SourceType.LiveTV } : new SourceType[] { }, + SourceTypes = parentIds.Length == 0 ? new[] { SourceType.Library } : new SourceType[] { }, IsPlayed = request.IsPlayed }, parentIds); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs index 079867ddd..91b035a35 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -16,15 +17,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger) + public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -35,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new ArtistsValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 2b68f98ca..3dcdbeae9 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -23,16 +25,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// The _logger /// </summary> private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public ArtistsValidator(ILibraryManager libraryManager, ILogger logger) + public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -43,36 +47,38 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var allSongs = _libraryManager.RootFolder - .GetRecursiveChildren(i => !i.IsFolder && i is IHasArtist) - .Cast<IHasArtist>() - .ToList(); - - var allArtists = _libraryManager.GetArtists(allSongs).ToList(); + var names = _itemRepo.GetAllArtistNames(); var numComplete = 0; - var numArtists = allArtists.Count; + var count = names.Count; - foreach (var artistItem in allArtists) + foreach (var name in names) { - cancellationToken.ThrowIfCancellationRequested(); - try { - await artistItem.RefreshMetadata(cancellationToken).ConfigureAwait(false); + var item = _libraryManager.GetArtist(name); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Don't clutter the log + break; } - catch (IOException ex) + catch (Exception ex) { - _logger.ErrorException("Error validating Artist {0}", ex, artistItem.Name); + _logger.ErrorException("Error refreshing {0}", ex, name); } - // Update progress numComplete++; double percent = numComplete; - percent /= numArtists; + percent /= count; + percent *= 100; - progress.Report(100 * percent); + progress.Report(percent); } + + progress.Report(100); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs index 8be2e436f..f3891180e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -16,16 +17,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="GameGenresPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public GameGenresPostScanTask(ILibraryManager libraryManager, ILogger logger) + public GameGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -36,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new GameGenresValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new GameGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index 826154fac..b06c0b3b9 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -20,11 +20,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// The _logger /// </summary> private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; - public GameGenresValidator(ILibraryManager libraryManager, ILogger logger) + public GameGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -35,25 +37,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var items = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Game) - .SelectMany(i => i.Genres) - .DistinctNames() - .ToList(); + var names = _itemRepo.GetGameGenreNames(); var numComplete = 0; - var count = items.Count; + var count = names.Count; - var validIds = new List<Guid>(); - - foreach (var name in items) + foreach (var name in names) { try { - var itemByName = _libraryManager.GetGameGenre(name); - - validIds.Add(itemByName.Id); + var item = _libraryManager.GetGameGenre(name); - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -73,28 +68,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(percent); } - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(GameGenre).Name } - }); - - var invalidIds = allIds - .Except(validIds) - .ToList(); - - foreach (var id in invalidIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var item = _libraryManager.GetItemById(id); - - await _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = false - - }).ConfigureAwait(false); - } - progress.Report(100); } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs index a1c34676c..ed2429769 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -2,6 +2,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; namespace MediaBrowser.Server.Implementations.Library.Validators @@ -13,16 +14,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger) + public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -33,7 +36,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new GenresValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index 11d4c9f16..f35bb5136 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; @@ -7,6 +6,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -16,16 +16,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// The _library manager /// </summary> private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepo; /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public GenresValidator(ILibraryManager libraryManager, ILogger logger) + public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -36,25 +38,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var items = _libraryManager.RootFolder.GetRecursiveChildren(i => !(i is IHasMusicGenres) && !(i is Game)) - .SelectMany(i => i.Genres) - .DistinctNames() - .ToList(); + var names = _itemRepo.GetGenreNames(); var numComplete = 0; - var count = items.Count; + var count = names.Count; - var validIds = new List<Guid>(); - - foreach (var name in items) + foreach (var name in names) { try { - var itemByName = _libraryManager.GetGenre(name); + var item = _libraryManager.GetGenre(name); - validIds.Add(itemByName.Id); - - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -74,28 +69,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(percent); } - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Genre).Name } - }); - - var invalidIds = allIds - .Except(validIds) - .ToList(); - - foreach (var id in invalidIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var item = _libraryManager.GetItemById(id); - - await _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = false - - }).ConfigureAwait(false); - } - progress.Report(100); } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index dbcab0832..777532ff8 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -16,16 +17,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger) + public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -36,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new MusicGenresValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 0c8c56f5a..2be99f106 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -21,11 +21,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// The _logger /// </summary> private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; - public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger) + public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -36,25 +38,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var items = _libraryManager.RootFolder.GetRecursiveChildren(i => i is IHasMusicGenres) - .SelectMany(i => i.Genres) - .DistinctNames() - .ToList(); + var names = _itemRepo.GetMusicGenreNames(); var numComplete = 0; - var count = items.Count; + var count = names.Count; - var validIds = new List<Guid>(); - - foreach (var name in items) + foreach (var name in names) { try { - var itemByName = _libraryManager.GetMusicGenre(name); - - validIds.Add(itemByName.Id); + var item = _libraryManager.GetMusicGenre(name); - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -74,28 +69,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(percent); } - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(MusicGenre).Name } - }); - - var invalidIds = allIds - .Except(validIds) - .ToList(); - - foreach (var id in invalidIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var item = _libraryManager.GetItemById(id); - - await _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = false - - }).ConfigureAwait(false); - } - progress.Report(100); } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs index 5c43f2e13..d90b9615b 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var peopleOptions = _config.Configuration.PeopleMetadataOptions; - var people = _libraryManager.GetAllPeople(); + var people = _libraryManager.GetPeople(new InternalPeopleQuery()); var dict = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); @@ -112,8 +112,11 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } var numComplete = 0; - var validIds = new List<Guid>(); - + + _logger.Debug("Will refresh {0} people", dict.Count); + + var numPeople = dict.Count; + foreach (var person in dict) { cancellationToken.ThrowIfCancellationRequested(); @@ -122,8 +125,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person.Key); - validIds.Add(item.Id); - var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview); var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 30; @@ -138,7 +139,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var options = new MetadataRefreshOptions(_fileSystem) { MetadataRefreshMode = person.Value ? defaultMetadataRefreshMode : MetadataRefreshMode.ValidationOnly, - ImageRefreshMode = person.Value ? imageRefreshMode : ImageRefreshMode.ValidationOnly + ImageRefreshMode = person.Value ? imageRefreshMode : ImageRefreshMode.ValidationOnly, + ForceSave = performFullRefresh }; await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); @@ -155,36 +157,11 @@ namespace MediaBrowser.Server.Implementations.Library.Validators // Update progress numComplete++; double percent = numComplete; - percent /= people.Count; + percent /= numPeople; progress.Report(100 * percent); } - var allIds = _libraryManager.GetItemIds(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Person).Name } - }); - - var invalidIds = allIds - .Except(validIds) - .ToList(); - - foreach (var id in invalidIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var item = _libraryManager.GetItemById(id); - - if (item != null) - { - await _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = false - - }).ConfigureAwait(false); - } - } - progress.Report(100); _logger.Info("People validation complete"); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index 0ff609da1..77c6d5146 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -17,15 +18,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; /// <summary> /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger) + public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -36,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return new StudiosValidator(_libraryManager, _logger).Run(progress, cancellationToken); + return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index c1803b5e4..a19b8158a 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -1,11 +1,11 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; namespace MediaBrowser.Server.Implementations.Library.Validators { @@ -16,15 +16,17 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepo; /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public StudiosValidator(ILibraryManager libraryManager, ILogger logger) + public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; + _itemRepo = itemRepo; } /// <summary> @@ -35,25 +37,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var items = _libraryManager.RootFolder.GetRecursiveChildren(i => true) - .SelectMany(i => i.Studios) - .DistinctNames() - .ToList(); + var names = _itemRepo.GetStudioNames(); var numComplete = 0; - var count = items.Count; + var count = names.Count; - var validIds = new List<Guid>(); - - foreach (var name in items) + foreach (var name in names) { try { - var itemByName = _libraryManager.GetStudio(name); - - validIds.Add(itemByName.Id); + var item = _libraryManager.GetStudio(name); - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs index 7f52a4506..164b14223 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs @@ -1,7 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 8f56554f1..6acb0783e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -12,7 +12,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; -using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -26,11 +25,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Power; +using MediaBrowser.Model.Configuration; using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV @@ -58,8 +56,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public static EmbyTV Current; - public event EventHandler DataSourceChanged; - public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged; + public event EventHandler DataSourceChanged { add { } remove { } } + public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged { add { } remove { } } private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings = new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase); @@ -146,7 +144,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, pathsToCreate.ToArray(), true); + _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, pathsToCreate.ToArray(), new LibraryOptions(), true); } catch (Exception ex) { @@ -1141,7 +1139,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - var result = await organize.OrganizeEpisodeFile(path, CancellationToken.None).ConfigureAwait(false); + var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 5e428e6f0..fc3a507d1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -191,7 +191,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var maxBitrate = 25000000; videoArgs = string.Format( - "-codec:v:0 libx264 -force_key_frames expr:gte(t,n_forced*5) {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", + "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", GetOutputSizeParam(), maxBitrate.ToString(CultureInfo.InvariantCulture)); } @@ -354,4 +354,4 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 79b26468e..7fe271bea 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV catch (FileNotFoundException) { } - catch (DirectoryNotFoundException ex) + catch (DirectoryNotFoundException) { } catch (IOException ex) diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 1e82e3fce..d1d8df2e8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -11,7 +11,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.XmlTv.Classes; -using Emby.XmlTv.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 64af35a9a..88017aa59 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -133,7 +133,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv void service_DataSourceChanged(object sender, EventArgs e) { - _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + if (!_isDisposed) + { + _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + } } public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken) @@ -948,6 +951,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var queryResult = _libraryManager.QueryItems(internalQuery); + RemoveFields(options); + var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> @@ -1028,6 +1033,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var user = _userManager.GetUserById(query.UserId); + RemoveFields(options); + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); var result = new QueryResult<BaseItemDto> @@ -1187,6 +1194,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv // Load these now which will prefetch metadata var dtoOptions = new DtoOptions(); dtoOptions.Fields.Remove(ItemFields.SyncInfo); + dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo); await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false); progress.Report(100); @@ -1237,7 +1245,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var programs = new List<Guid>(); var channels = new List<Guid>(); - var guideDays = GetGuideDays(list.Count); + var guideDays = GetGuideDays(); _logger.Info("Refreshing guide with {0} days of guide data", guideDays); @@ -1325,7 +1333,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } private const int MaxGuideDays = 14; - private double GetGuideDays(int channelCount) + private double GetGuideDays() { var config = GetConfiguration(); @@ -1334,13 +1342,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays)); } - var programsPerDay = channelCount * 48; - - const int maxPrograms = 24000; - - var days = Math.Round((double)maxPrograms / programsPerDay); - - return Math.Max(3, Math.Min(days, MaxGuideDays)); + return 7; } private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken) @@ -1664,6 +1666,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false); + RemoveFields(options); + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); return new QueryResult<BaseItemDto> @@ -1924,7 +1928,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channelIds = tuples.Select(i => i.Item2.Id.ToString("N")).Distinct().ToArray(); - var programs = _libraryManager.GetItemList(new InternalItemsQuery(user) + var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, ChannelIds = channelIds, @@ -1934,7 +1938,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv SortBy = new[] { "StartDate" }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") } - }).ToList(); + }).ToList() : new List<BaseItem>(); + + RemoveFields(options); foreach (var tuple in tuples) { @@ -1946,14 +1952,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.ChannelType = channel.ChannelType; dto.ServiceName = GetService(channel).Name; - dto.MediaSources = channel.GetMediaSources(true).ToList(); + if (options.Fields.Contains(ItemFields.MediaSources)) + { + dto.MediaSources = channel.GetMediaSources(true).ToList(); + } var channelIdString = channel.Id.ToString("N"); - var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString)); - - if (currentProgram != null) + if (options.AddCurrentProgram) { - dto.CurrentProgram = _dtoService.GetBaseItemDto(currentProgram, options, user); + var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString)); + + if (currentProgram != null) + { + dto.CurrentProgram = _dtoService.GetBaseItemDto(currentProgram, options, user); + } } } } @@ -2308,6 +2320,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } private readonly object _disposeLock = new object(); + private bool _isDisposed = false; /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> @@ -2316,6 +2329,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv { if (dispose) { + _isDisposed = true; + lock (_disposeLock) { foreach (var stream in _openStreams.Values.ToList()) @@ -2446,13 +2461,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user); } + private void RemoveFields(DtoOptions options) + { + options.Fields.Remove(ItemFields.CanDelete); + options.Fields.Remove(ItemFields.CanDownload); + options.Fields.Remove(ItemFields.DisplayPreferencesId); + options.Fields.Remove(ItemFields.Etag); + } + public async Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken) { var name = _localization.GetLocalizedString("ViewTypeLiveTV"); return await _libraryManager.GetNamedView(name, CollectionType.LiveTv, name, cancellationToken).ConfigureAwait(false); } - public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info) + public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) { info = (TunerHostInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(TunerHostInfo)); @@ -2485,7 +2508,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv _config.SaveConfiguration("livetv", config); - _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + if (dataSourceChanged) + { + _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + } return info; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 69b6fb5a9..fd4775938 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -431,7 +431,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false)); } } - catch (Exception ex) + catch { } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 2a974b545..5c508aacd 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -136,12 +136,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts RequiresOpening = false, RequiresClosing = false, - ReadAtNativeFramerate = true + ReadAtNativeFramerate = false }; return new List<MediaSourceInfo> { mediaSource }; } - return new List<MediaSourceInfo> { }; + return new List<MediaSourceInfo>(); } protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs index 71b3f8a18..0f8682b7c 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs @@ -649,7 +649,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp #region Public Events - public event PropertyChangedEventHandler PropertyChanged; + ////public event PropertyChangedEventHandler PropertyChanged; #endregion diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index d0a55966f..9d5dba282 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -1,6 +1,4 @@ using System; -using System.Globalization; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -113,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp M3UUrl = info.M3UUrl, IsEnabled = true - }).ConfigureAwait(false); + }, true).ConfigureAwait(false); } else { @@ -122,7 +120,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp existing.M3UUrl = info.M3UUrl; existing.FriendlyName = info.FriendlyName; existing.Tuners = info.Tuners; - await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false); + await _liveTvManager.SaveTunerHost(existing, false).ConfigureAwait(false); } } catch (OperationCanceledException) diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs index 1e571c84f..b1e349a86 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs @@ -110,7 +110,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp return new List<MediaSourceInfo> { mediaSource }; } - return new List<MediaSourceInfo> { }; + return new List<MediaSourceInfo>(); } protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 0f91d5285..6879c3f40 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -46,7 +46,7 @@ <HintPath>..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll</HintPath> </Reference> <Reference Include="Emby.XmlTv, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\Emby.XmlTv.1.0.0.55\lib\net45\Emby.XmlTv.dll</HintPath> + <HintPath>..\packages\Emby.XmlTv.1.0.0.56\lib\net45\Emby.XmlTv.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="INIFileParser, Version=2.3.0.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL"> @@ -56,8 +56,8 @@ <Reference Include="Interfaces.IO"> <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath> </Reference> - <Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath> + <Reference Include="MediaBrowser.Naming, Version=1.0.6059.24054, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\MediaBrowser.Naming.1.0.0.55\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="MoreLinq"> @@ -73,8 +73,8 @@ <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="SocketHttpListener, Version=1.0.6046.26351, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath> + <Reference Include="SocketHttpListener, Version=1.0.6063.4624, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\SocketHttpListener.1.0.0.39\lib\net45\SocketHttpListener.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="System" /> @@ -149,6 +149,7 @@ <Compile Include="EntryPoints\UsageReporter.cs" /> <Compile Include="FileOrganization\EpisodeFileOrganizer.cs" /> <Compile Include="FileOrganization\Extensions.cs" /> + <Compile Include="FileOrganization\FileOrganizationNotifier.cs" /> <Compile Include="FileOrganization\FileOrganizationService.cs" /> <Compile Include="FileOrganization\NameUtils.cs" /> <Compile Include="FileOrganization\TvFolderOrganizer.cs" /> @@ -156,7 +157,7 @@ <Compile Include="EntryPoints\ServerEventNotifier.cs" /> <Compile Include="EntryPoints\UserDataChangeNotifier.cs" /> <Compile Include="FileOrganization\OrganizerScheduledTask.cs" /> - <Compile Include="HttpServer\AsyncStreamWriterFunc.cs" /> + <Compile Include="HttpServer\AsyncStreamWriter.cs" /> <Compile Include="HttpServer\IHttpListener.cs" /> <Compile Include="HttpServer\Security\AuthorizationContext.cs" /> <Compile Include="HttpServer\ContainerAdapter.cs" /> @@ -271,6 +272,7 @@ <Compile Include="Sorting\StartDateComparer.cs" /> <Compile Include="Sync\SyncHelper.cs" /> <Compile Include="Sync\SyncJobOptions.cs" /> + <Compile Include="Sync\SyncNotificationEntryPoint.cs" /> <Compile Include="UserViews\CollectionFolderImageProvider.cs" /> <Compile Include="UserViews\DynamicImageProvider.cs" /> <Compile Include="News\NewsEntryPoint.cs" /> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 7f709d084..46ba7d2e7 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder { if (extractImages) { - if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay) + if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd) { continue; } diff --git a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index be8c6d48d..f30ba3e54 100644 --- a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.Notifications public event EventHandler<NotificationUpdateEventArgs> NotificationAdded; public event EventHandler<NotificationReadEventArgs> NotificationsMarkedRead; - public event EventHandler<NotificationUpdateEventArgs> NotificationUpdated; + ////public event EventHandler<NotificationUpdateEventArgs> NotificationUpdated; public async Task Initialize() { diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index bf2afb5ac..c1394ee1c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -313,8 +313,7 @@ namespace MediaBrowser.Server.Implementations.Persistence if (Folder.IsPathOffline(path)) { - libraryItem.IsOffline = true; - await libraryItem.UpdateToRepository(ItemUpdateType.None, cancellationToken).ConfigureAwait(false); + await libraryItem.UpdateIsOffline(true).ConfigureAwait(false); continue; } diff --git a/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs index 61ce6e351..a16d23700 100644 --- a/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs +++ b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs @@ -4,7 +4,6 @@ using MediaBrowser.Model.Serialization; using System; using System.Data; using System.IO; -using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Persistence { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs index d5b582da5..c273d4945 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Generic; using System.Data; using System.Data.SQLite; -using System.Linq; -using System.Text; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; namespace MediaBrowser.Server.Implementations.Persistence diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 63dd29e0d..5ece3dd82 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -92,10 +92,9 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _deleteImagesCommand; private IDbCommand _saveImagesCommand; - private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedTagsCommand; - public const int LatestSchemaVersion = 108; + public const int LatestSchemaVersion = 109; /// <summary> /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. @@ -211,7 +210,6 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "TypedBaseItems", "ProductionYear", "INT"); _connection.AddColumn(Logger, "TypedBaseItems", "ParentId", "GUID"); _connection.AddColumn(Logger, "TypedBaseItems", "Genres", "Text"); - _connection.AddColumn(Logger, "TypedBaseItems", "ParentalRatingValue", "INT"); _connection.AddColumn(Logger, "TypedBaseItems", "SchemaVersion", "INT"); _connection.AddColumn(Logger, "TypedBaseItems", "SortName", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "RunTimeTicks", "BIGINT"); @@ -412,7 +410,9 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeasonName", "SeasonId", "SeriesId", - "SeriesSortName" + "SeriesSortName", + "PresentationUniqueKey", + "InheritedParentalRatingValue" }; private readonly string[] _mediaStreamSaveColumns = @@ -487,7 +487,6 @@ namespace MediaBrowser.Server.Implementations.Persistence "ProductionYear", "ParentId", "Genres", - "ParentalRatingValue", "InheritedParentalRatingValue", "SchemaVersion", "SortName", @@ -612,11 +611,6 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveStreamCommand.Parameters.Add(_saveStreamCommand, "@" + col); } - _updateInheritedRatingCommand = _connection.CreateCommand(); - _updateInheritedRatingCommand.CommandText = "Update TypedBaseItems set InheritedParentalRatingValue=@InheritedParentalRatingValue where Guid=@Guid"; - _updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@Guid"); - _updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@InheritedParentalRatingValue"); - _updateInheritedTagsCommand = _connection.CreateCommand(); _updateInheritedTagsCommand.CommandText = "Update TypedBaseItems set InheritedTags=@InheritedTags where Guid=@Guid"; _updateInheritedTagsCommand.Parameters.Add(_updateInheritedTagsCommand, "@Guid"); @@ -794,7 +788,6 @@ namespace MediaBrowser.Server.Implementations.Persistence } _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Genres.ToArray()); - _saveItemCommand.GetParameter(index++).Value = item.GetParentalRatingValue() ?? 0; _saveItemCommand.GetParameter(index++).Value = item.GetInheritedParentalRatingValue() ?? 0; _saveItemCommand.GetParameter(index++).Value = LatestSchemaVersion; @@ -915,10 +908,10 @@ namespace MediaBrowser.Server.Implementations.Persistence } else { - _saveItemCommand.GetParameter(index++).Value = item.Name.RemoveDiacritics(); + _saveItemCommand.GetParameter(index++).Value = GetCleanValue(item.Name); } - _saveItemCommand.GetParameter(index++).Value = item.PresentationUniqueKey; + _saveItemCommand.GetParameter(index++).Value = item.GetPresentationUniqueKey(); _saveItemCommand.GetParameter(index++).Value = item.SlugName; _saveItemCommand.GetParameter(index++).Value = item.OriginalTitle; @@ -944,7 +937,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveItemCommand.GetParameter(index++).Value = item.Album; - _saveItemCommand.GetParameter(index++).Value = item.IsVirtualItem || (!item.IsFolder && item.LocationType == LocationType.Virtual); + _saveItemCommand.GetParameter(index++).Value = item.IsVirtualItem; var hasSeries = item as IHasSeries; if (hasSeries != null) @@ -1454,6 +1447,18 @@ namespace MediaBrowser.Server.Implementations.Persistence } index++; + if (!reader.IsDBNull(index)) + { + item.PresentationUniqueKey = reader.GetString(index); + } + index++; + + if (!reader.IsDBNull(index)) + { + item.InheritedParentalRatingValue = reader.GetInt32(index); + } + index++; + return item; } @@ -2145,7 +2150,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { if (query.User != null) { - query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random }; + query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random }; } else { @@ -2230,7 +2235,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase)) { - return new Tuple<string, bool>("ParentalRatingValue", false); + return new Tuple<string, bool>("InheritedParentalRatingValue", false); } if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase)) { @@ -2763,13 +2768,13 @@ namespace MediaBrowser.Server.Implementations.Persistence if (!string.IsNullOrWhiteSpace(query.Name)) { whereClauses.Add("CleanName=@Name"); - cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = GetCleanValue(query.Name); } if (!string.IsNullOrWhiteSpace(query.NameContains)) { whereClauses.Add("CleanName like @NameContains"); - cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains.RemoveDiacritics() + "%"; + cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + GetCleanValue(query.NameContains) + "%"; } if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) { @@ -2877,7 +2882,7 @@ namespace MediaBrowser.Server.Implementations.Persistence foreach (var artist in query.ArtistNames) { clauses.Add("@ArtistName" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type <= 1)"); - cmd.Parameters.Add(cmd, "@ArtistName" + index, DbType.String).Value = artist.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@ArtistName" + index, DbType.String).Value = GetCleanValue(artist); index++; } var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; @@ -2894,7 +2899,7 @@ namespace MediaBrowser.Server.Implementations.Persistence if (artistItem != null) { clauses.Add("@ExcludeArtistName" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type <= 1)"); - cmd.Parameters.Add(cmd, "@ExcludeArtistName" + index, DbType.String).Value = artistItem.Name.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@ExcludeArtistName" + index, DbType.String).Value = GetCleanValue(artistItem.Name); index++; } } @@ -2915,7 +2920,7 @@ namespace MediaBrowser.Server.Implementations.Persistence foreach (var item in query.Genres) { clauses.Add("@Genre" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=2)"); - cmd.Parameters.Add(cmd, "@Genre" + index, DbType.String).Value = item.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@Genre" + index, DbType.String).Value = GetCleanValue(item); index++; } var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; @@ -2929,7 +2934,7 @@ namespace MediaBrowser.Server.Implementations.Persistence foreach (var item in query.Tags) { clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)"); - cmd.Parameters.Add(cmd, "@Tag" + index, DbType.String).Value = item.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@Tag" + index, DbType.String).Value = GetCleanValue(item); index++; } var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; @@ -2949,7 +2954,7 @@ namespace MediaBrowser.Server.Implementations.Persistence foreach (var item in query.Studios) { clauses.Add("@Studio" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=3)"); - cmd.Parameters.Add(cmd, "@Studio" + index, DbType.String).Value = item.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@Studio" + index, DbType.String).Value = GetCleanValue(item); index++; } var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; @@ -2963,7 +2968,7 @@ namespace MediaBrowser.Server.Implementations.Persistence foreach (var item in query.Keywords) { clauses.Add("@Keyword" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=5)"); - cmd.Parameters.Add(cmd, "@Keyword" + index, DbType.String).Value = item.RemoveDiacritics(); + cmd.Parameters.Add(cmd, "@Keyword" + index, DbType.String).Value = GetCleanValue(item); index++; } var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; @@ -3088,6 +3093,17 @@ namespace MediaBrowser.Server.Implementations.Persistence whereClauses.Add("LocationType<>'Virtual'"); } } + if (query.IsSpecialSeason.HasValue) + { + if (query.IsSpecialSeason.Value) + { + whereClauses.Add("IndexNumber = 0"); + } + else + { + whereClauses.Add("IndexNumber <> 0"); + } + } if (query.IsUnaired.HasValue) { if (query.IsUnaired.Value) @@ -3134,17 +3150,17 @@ namespace MediaBrowser.Server.Implementations.Persistence } if (query.ItemIds.Length > 0) { - var excludeIds = new List<string>(); + var includeIds = new List<string>(); var index = 0; foreach (var id in query.ItemIds) { - excludeIds.Add("Guid = @IncludeId" + index); + includeIds.Add("Guid = @IncludeId" + index); cmd.Parameters.Add(cmd, "@IncludeId" + index, DbType.Guid).Value = new Guid(id); index++; } - whereClauses.Add(string.Join(" OR ", excludeIds.ToArray())); + whereClauses.Add(string.Join(" OR ", includeIds.ToArray())); } if (query.ExcludeItemIds.Length > 0) { @@ -3298,6 +3314,16 @@ namespace MediaBrowser.Server.Implementations.Persistence return whereClauses; } + private string GetCleanValue(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return value; + } + + return value.RemoveDiacritics().ToLower(); + } + private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) { if (!query.GroupByPresentationUniqueKey) @@ -3377,7 +3403,6 @@ namespace MediaBrowser.Server.Implementations.Persistence public async Task UpdateInheritedValues(CancellationToken cancellationToken) { - await UpdateInheritedParentalRating(cancellationToken).ConfigureAwait(false); await UpdateInheritedTags(cancellationToken).ConfigureAwait(false); } @@ -3457,82 +3482,6 @@ namespace MediaBrowser.Server.Implementations.Persistence } } - private async Task UpdateInheritedParentalRating(CancellationToken cancellationToken) - { - var newValues = new List<Tuple<Guid, int>>(); - - using (var cmd = _connection.CreateCommand()) - { - cmd.CommandText = "select Guid,InheritedParentalRatingValue,(select Max(ParentalRatingValue, (select COALESCE(MAX(ParentalRatingValue),0) from TypedBaseItems where guid in (Select AncestorId from AncestorIds where ItemId=Outer.guid)))) as NewInheritedParentalRatingValue from typedbaseitems as Outer where InheritedParentalRatingValue <> NewInheritedParentalRatingValue"; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) - { - while (reader.Read()) - { - var id = reader.GetGuid(0); - var newValue = reader.GetInt32(2); - - newValues.Add(new Tuple<Guid, int>(id, newValue)); - } - } - } - - Logger.Debug("UpdateInheritedParentalRatings - {0} rows", newValues.Count); - if (newValues.Count == 0) - { - return; - } - - await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - IDbTransaction transaction = null; - - try - { - transaction = _connection.BeginTransaction(); - - foreach (var item in newValues) - { - _updateInheritedRatingCommand.GetParameter(0).Value = item.Item1; - _updateInheritedRatingCommand.GetParameter(1).Value = item.Item2; - - _updateInheritedRatingCommand.Transaction = transaction; - _updateInheritedRatingCommand.ExecuteNonQuery(); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Error running query:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - WriteLock.Release(); - } - } - private static Dictionary<string, string[]> GetTypeMapDictionary() { var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase); @@ -3776,6 +3725,11 @@ namespace MediaBrowser.Server.Implementations.Persistence whereClauses.Add("Name like @NameContains"); cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains + "%"; } + if (query.SourceTypes.Length == 1) + { + whereClauses.Add("(select sourcetype from typedbaseitems where guid=ItemId) = @SourceTypes"); + cmd.Parameters.Add(cmd, "@SourceTypes", DbType.String).Value = query.SourceTypes[0].ToString(); + } return whereClauses; } @@ -3812,37 +3766,119 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query) + { + return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); + } + public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query) { - return GetItemValues(query, 0, typeof(MusicArtist).FullName); + return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); } public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query) { - return GetItemValues(query, 1, typeof(MusicArtist).FullName); + return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); } public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query) { - return GetItemValues(query, 3, typeof(Studio).FullName); + return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); } public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query) { - return GetItemValues(query, 2, typeof(Genre).FullName); + return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } public QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query) { - return GetItemValues(query, 2, typeof(GameGenre).FullName); + return GetItemValues(query, new[] { 2 }, typeof(GameGenre).FullName); } public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query) { - return GetItemValues(query, 2, typeof(MusicGenre).FullName); + return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); + } + + public List<string> GetStudioNames() + { + return GetItemValueNames(new[] { 3 }, new List<string>(), new List<string>()); + } + + public List<string> GetAllArtistNames() + { + return GetItemValueNames(new[] { 0, 1 }, new List<string>(), new List<string>()); + } + + public List<string> GetMusicGenreNames() + { + return GetItemValueNames(new[] { 2 }, new List<string> { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }, new List<string>()); + } + + public List<string> GetGameGenreNames() + { + return GetItemValueNames(new[] { 2 }, new List<string> { "Game" }, new List<string>()); + } + + public List<string> GetGenreNames() + { + return GetItemValueNames(new[] { 2 }, new List<string>(), new List<string> { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist", "Game", "GameSystem" }); + } + + private List<string> GetItemValueNames(int[] itemValueTypes, List<string> withItemTypes, List<string> excludeItemTypes) + { + CheckDisposed(); + + withItemTypes = withItemTypes.SelectMany(MapIncludeItemTypes).ToList(); + excludeItemTypes = excludeItemTypes.SelectMany(MapIncludeItemTypes).ToList(); + + var now = DateTime.UtcNow; + + var typeClause = itemValueTypes.Length == 1 ? + ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : + ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")"); + + var list = new List<string>(); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = "Select Value From ItemValues where " + typeClause; + + if (withItemTypes.Count > 0) + { + var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'").ToArray()); + cmd.CommandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))"; + } + if (excludeItemTypes.Count > 0) + { + var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'").ToArray()); + cmd.CommandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))"; + } + + cmd.CommandText += " Group By CleanValue"; + + var commandBehavior = CommandBehavior.SequentialAccess | CommandBehavior.SingleResult; + + using (var reader = cmd.ExecuteReader(commandBehavior)) + { + LogQueryTime("GetItemValueNames", cmd, now); + + while (reader.Read()) + { + if (!reader.IsDBNull(0)) + { + list.Add(reader.GetString(0)); + } + } + } + + } + + return list; } - private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int itemValueType, string returnType) + private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType) { if (query == null) { @@ -3858,6 +3894,10 @@ namespace MediaBrowser.Server.Implementations.Persistence var now = DateTime.UtcNow; + var typeClause = itemValueTypes.Length == 1 ? + ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : + ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")"); + using (var cmd = _connection.CreateCommand()) { var itemCountColumns = new List<Tuple<string, string>>(); @@ -3882,7 +3922,7 @@ namespace MediaBrowser.Server.Implementations.Persistence }; var whereClauses = GetWhereClauses(typeSubQuery, cmd, "itemTypes"); - whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND Type=@ItemValueType)"); + whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")"); var typeWhereText = whereClauses.Count == 0 ? string.Empty : @@ -3924,12 +3964,12 @@ namespace MediaBrowser.Server.Implementations.Persistence if (typesToCount.Count == 0) { - whereText += " And CleanName In (Select CleanValue from ItemValues where Type=@ItemValueType AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))"; + whereText += " And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))"; } else { //whereText += " And itemTypes not null"; - whereText += " And CleanName In (Select CleanValue from ItemValues where Type=@ItemValueType AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))"; + whereText += " And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))"; } var outerQuery = new InternalItemsQuery(query.User) @@ -3944,7 +3984,8 @@ namespace MediaBrowser.Server.Implementations.Persistence AlbumArtistStartsWithOrGreater = query.AlbumArtistStartsWithOrGreater, Tags = query.Tags, OfficialRatings = query.OfficialRatings, - Genres = query.GenreIds, + GenreIds = query.GenreIds, + Genres = query.Genres, Years = query.Years }; @@ -3959,7 +4000,6 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += " group by PresentationUniqueKey"; cmd.Parameters.Add(cmd, "@SelectType", DbType.String).Value = returnType; - cmd.Parameters.Add(cmd, "@ItemValueType", DbType.Int32).Value = itemValueType; if (EnableJoinUserData(query)) { @@ -4098,6 +4138,10 @@ namespace MediaBrowser.Server.Implementations.Persistence { counts.AlbumCount = value; } + else if (string.Equals(typeName, typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase)) + { + counts.ArtistCount = value; + } else if (string.Equals(typeName, typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase)) { counts.SongCount = value; @@ -4163,6 +4207,12 @@ namespace MediaBrowser.Server.Implementations.Persistence var index = 0; foreach (var image in images) { + if (string.IsNullOrWhiteSpace(image.Path)) + { + // Invalid + continue; + } + _saveImagesCommand.GetParameter(0).Value = itemId; _saveImagesCommand.GetParameter(1).Value = image.Type; _saveImagesCommand.GetParameter(2).Value = image.Path; @@ -4255,7 +4305,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } else { - _saveItemValuesCommand.GetParameter(3).Value = pair.Item2.RemoveDiacritics(); + _saveItemValuesCommand.GetParameter(3).Value = GetCleanValue(pair.Item2); } _saveItemValuesCommand.Transaction = transaction; diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 20324215b..ff0e4a0e0 100644 --- a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.Playlists protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) { - return GetRecursiveChildren(i => i is Playlist); + return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>(); } public override bool IsHidden diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index 8a04f29a2..237d49fda 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -88,6 +88,14 @@ namespace MediaBrowser.Server.Implementations } } + public string ArtistsPath + { + get + { + return Path.Combine(ItemsByNamePath, "artists"); + } + } + /// <summary> /// Gets the path to the Genre directory /// </summary> diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 84aab5e1f..b21fcddd4 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1001,7 +1001,8 @@ namespace MediaBrowser.Server.Implementations.Session var series = episode.Series; if (series != null) { - var episodes = series.GetEpisodes(user, false, false) + var episodes = series.GetEpisodes(user) + .Where(i => !i.IsVirtualItem) .SkipWhile(i => i.Id != episode.Id) .ToList(); @@ -1868,10 +1869,17 @@ namespace MediaBrowser.Server.Implementations.Session return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null); } - public Task SendMessageToUserSessions<T>(string userId, string name, T data, + public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken) + { + var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList(); + + return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); + } + + public Task SendMessageToUserSessions<T>(List<string> userIds, string name, T data, CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && i.ContainsUser(userId)).ToList(); + var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && userIds.Any(i.ContainsUser)).ToList(); var tasks = sessions.Select(session => Task.Run(async () => { diff --git a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index e8c78b8e7..5080edffd 100644 --- a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; using System; -using System.Linq; namespace MediaBrowser.Server.Implementations.Sorting { diff --git a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs index 03485012f..408ec717e 100644 --- a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs @@ -85,6 +85,11 @@ namespace MediaBrowser.Server.Implementations.Sync { Name = "Low", Id = "low" + }, + new SyncQualityOption + { + Name = "Custom", + Id = "custom" } }; } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index e120d3a4d..d5dfd3856 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -951,8 +951,7 @@ namespace MediaBrowser.Server.Implementations.Sync : new[] { user.Configuration.AudioLanguagePreference }; var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? new List<string> { } - : new List<string> { user.Configuration.SubtitleLanguagePreference }; + ? new List<string>() : new List<string> { user.Configuration.SubtitleLanguagePreference }; foreach (var source in mediaSources) { diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 38edc3024..ffba60af8 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.Server.Implementations.Sync UserId = request.UserId, UnwatchedOnly = request.UnwatchedOnly, ItemLimit = request.ItemLimit, - RequestedItemIds = request.ItemIds ?? new List<string> { }, + RequestedItemIds = request.ItemIds ?? new List<string>(), DateCreated = DateTime.UtcNow, DateLastModified = DateTime.UtcNow, SyncNewContent = request.SyncNewContent, @@ -646,6 +646,7 @@ namespace MediaBrowser.Server.Implementations.Sync dtoOptions.Fields.Remove(ItemFields.SeriesGenres); dtoOptions.Fields.Remove(ItemFields.Settings); dtoOptions.Fields.Remove(ItemFields.SyncInfo); + dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo); syncedItem.Item = _dtoService().GetBaseItemDto(libraryItem, dtoOptions); @@ -1123,7 +1124,7 @@ namespace MediaBrowser.Server.Implementations.Sync await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); } - public QueryResult<SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query) + public Dictionary<string, SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query) { return _repo.GetSyncedItemProgresses(query); } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncNotificationEntryPoint.cs b/MediaBrowser.Server.Implementations/Sync/SyncNotificationEntryPoint.cs new file mode 100644 index 000000000..7017b422e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/SyncNotificationEntryPoint.cs @@ -0,0 +1,48 @@ +using System.Threading; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Sync; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class SyncNotificationEntryPoint : IServerEntryPoint + { + private readonly ISessionManager _sessionManager; + private readonly ISyncManager _syncManager; + + public SyncNotificationEntryPoint(ISyncManager syncManager, ISessionManager sessionManager) + { + _syncManager = syncManager; + _sessionManager = sessionManager; + } + + public void Run() + { + _syncManager.SyncJobItemUpdated += _syncManager_SyncJobItemUpdated; + } + + private async void _syncManager_SyncJobItemUpdated(object sender, GenericEventArgs<SyncJobItem> e) + { + var item = e.Argument; + + if (item.Status == SyncJobItemStatus.ReadyToTransfer) + { + try + { + await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemReady", item, CancellationToken.None).ConfigureAwait(false); + } + catch + { + + } + } + } + + public void Dispose() + { + _syncManager.SyncJobItemUpdated -= _syncManager_SyncJobItemUpdated; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index a1ed66a99..64ed00ded 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -29,6 +29,17 @@ namespace MediaBrowser.Server.Implementations.Sync DbFilePath = Path.Combine(appPaths.DataPath, "sync14.db"); } + private class SyncSummary + { + public Dictionary<string, int> Items { get; set; } + + public SyncSummary() + { + Items = new Dictionary<string, int>(); + } + } + + public async Task Initialize() { using (var connection = await CreateConnection().ConfigureAwait(false)) @@ -36,12 +47,18 @@ namespace MediaBrowser.Server.Implementations.Sync string[] queries = { "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", - "create index if not exists idx_SyncJobs on SyncJobs(Id)", - "create index if not exists idx_SyncJobs1 on SyncJobs(TargetId)", "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)", - "create index if not exists idx_SyncJobItems1 on SyncJobItems(Id)", - "create index if not exists idx_SyncJobItems2 on SyncJobItems(TargetId)", + + "drop index if exists idx_SyncJobItems2", + "drop index if exists idx_SyncJobItems3", + "drop index if exists idx_SyncJobs1", + "drop index if exists idx_SyncJobs", + "drop index if exists idx_SyncJobItems1", + "create index if not exists idx_SyncJobItems4 on SyncJobItems(TargetId,ItemId,Status,Progress,DateCreated)", + "create index if not exists idx_SyncJobItems5 on SyncJobItems(TargetId,Status,ItemId,Progress)", + + "create index if not exists idx_SyncJobs2 on SyncJobs(TargetId,Status,ItemIds,Progress)", "pragma shrink_memory" }; @@ -400,6 +417,20 @@ namespace MediaBrowser.Server.Implementations.Sync whereClauses.Add("TargetId=@TargetId"); cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId; } + if (!string.IsNullOrWhiteSpace(query.ExcludeTargetIds)) + { + var excludeIds = (query.ExcludeTargetIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (excludeIds.Length == 1) + { + whereClauses.Add("TargetId<>@ExcludeTargetId"); + cmd.Parameters.Add(cmd, "@ExcludeTargetId", DbType.String).Value = excludeIds[0]; + } + else if (excludeIds.Length > 1) + { + whereClauses.Add("TargetId<>@ExcludeTargetId"); + cmd.Parameters.Add(cmd, "@ExcludeTargetId", DbType.String).Value = excludeIds[0]; + } + } if (!string.IsNullOrWhiteSpace(query.UserId)) { whereClauses.Add("UserId=@UserId"); @@ -583,9 +614,155 @@ namespace MediaBrowser.Server.Implementations.Sync } } - public QueryResult<SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query) + public Dictionary<string, SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query) + { + var result = new Dictionary<string, SyncedItemProgress>(); + + var now = DateTime.UtcNow; + + using (var connection = CreateConnection(true).Result) + { + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "select ItemId,Status,Progress from SyncJobItems"; + + var whereClauses = new List<string>(); + + if (!string.IsNullOrWhiteSpace(query.TargetId)) + { + whereClauses.Add("TargetId=@TargetId"); + cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId; + } + + if (query.Statuses.Length > 0) + { + var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); + + whereClauses.Add(string.Format("Status in ({0})", statuses)); + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += ";" + cmd.CommandText + .Replace("select ItemId,Status,Progress from SyncJobItems", "select ItemIds,Status,Progress from SyncJobs") + .Replace("'Synced'", "'Completed','CompletedWithError'"); + + //Logger.Debug(cmd.CommandText); + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + LogQueryTime("GetSyncedItemProgresses", cmd, now); + + while (reader.Read()) + { + AddStatusResult(reader, result, false); + } + + if (reader.NextResult()) + { + while (reader.Read()) + { + AddStatusResult(reader, result, true); + } + } + } + } + } + + return result; + } + + private void LogQueryTime(string methodName, IDbCommand cmd, DateTime startDate) + { + var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; + + var slowThreshold = 1000; + +#if DEBUG + slowThreshold = 50; +#endif + + if (elapsed >= slowThreshold) + { + Logger.Debug("{2} query time (slow): {0}ms. Query: {1}", + Convert.ToInt32(elapsed), + cmd.CommandText, + methodName); + } + else + { + //Logger.Debug("{2} query time: {0}ms. Query: {1}", + // Convert.ToInt32(elapsed), + // cmd.CommandText, + // methodName); + } + } + + private void AddStatusResult(IDataReader reader, Dictionary<string, SyncedItemProgress> result, bool multipleIds) { - return GetJobItemReader(query, "select ItemId,Status from SyncJobItems", GetSyncedItemProgress); + if (reader.IsDBNull(0)) + { + return; + } + + var itemIds = new List<string>(); + + var ids = reader.GetString(0); + + if (multipleIds) + { + itemIds = ids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + } + else + { + itemIds.Add(ids); + } + + if (!reader.IsDBNull(1)) + { + SyncJobItemStatus status; + var statusString = reader.GetString(1); + if (string.Equals(statusString, "Completed", StringComparison.OrdinalIgnoreCase) || + string.Equals(statusString, "CompletedWithError", StringComparison.OrdinalIgnoreCase)) + { + status = SyncJobItemStatus.Synced; + } + else + { + status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), statusString, true); + } + + if (status == SyncJobItemStatus.Synced) + { + foreach (var itemId in itemIds) + { + result[itemId] = new SyncedItemProgress + { + Status = SyncJobItemStatus.Synced + }; + } + } + else + { + double progress = reader.IsDBNull(2) ? 0.0 : reader.GetDouble(2); + + foreach (var itemId in itemIds) + { + SyncedItemProgress currentStatus; + if (!result.TryGetValue(itemId, out currentStatus) || (currentStatus.Status != SyncJobItemStatus.Synced && progress >= currentStatus.Progress)) + { + result[itemId] = new SyncedItemProgress + { + Status = status, + Progress = progress + }; + } + } + } + } } public QueryResult<SyncJobItem> GetJobItems(SyncJobItemQuery query) @@ -795,19 +972,5 @@ namespace MediaBrowser.Server.Implementations.Sync return info; } - - private SyncedItemProgress GetSyncedItemProgress(IDataReader reader) - { - var item = new SyncedItemProgress(); - - item.ItemId = reader.GetString(0); - - if (!reader.IsDBNull(1)) - { - item.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader.GetString(1), true); - } - - return item; - } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index efd37fa00..a2b5851ac 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -144,6 +144,10 @@ namespace MediaBrowser.Server.Implementations.Sync { mediaSource.Id = item.Id; mediaSource.SupportsTranscoding = false; + if (mediaSource.Protocol == Model.MediaInfo.MediaProtocol.File) + { + mediaSource.ETag = item.Id; + } } public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs index ddc1de9cd..03e8a9178 100644 --- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs +++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.Server.Implementations.TV { return series.Id.ToString("N"); } - return series.PresentationUniqueKey; + return series.GetPresentationUniqueKey(); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index ea4da19b2..3c75c8a48 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Server.Implementations.UserViews User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder" } + ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" }, }).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 9ae0a126a..746dc7f62 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
- <package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
+ <package id="Emby.XmlTv" version="1.0.0.56" targetFramework="net45" />
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
- <package id="MediaBrowser.Naming" version="1.0.0.53" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
- <package id="SocketHttpListener" version="1.0.0.35" targetFramework="net45" />
+ <package id="SocketHttpListener" version="1.0.0.39" targetFramework="net45" />
</packages>
\ No newline at end of file |
