From dc7a59c73a1e163f122780bdd8a5ca6cad96eb06 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 23 Jul 2017 18:29:53 -0400 Subject: ensure custom css is loaded last --- .../Data/SqliteItemRepository.cs | 40 ---------------------- 1 file changed, 40 deletions(-) (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 52abbae3e..e4c05bce2 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -251,7 +251,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Tagline", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "Keywords", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Images", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ProductionLocations", "Text", existingColumnNames); @@ -454,7 +453,6 @@ namespace Emby.Server.Implementations.Data "InheritedTags", "ExternalSeriesId", "Tagline", - "Keywords", "ProviderIds", "Images", "ProductionLocations", @@ -578,7 +576,6 @@ namespace Emby.Server.Implementations.Data "SeriesId", "ExternalSeriesId", "Tagline", - "Keywords", "ProviderIds", "Images", "ProductionLocations", @@ -1011,15 +1008,6 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); saveItemStatement.TryBind("@Tagline", item.Tagline); - if (item.Keywords.Count > 0) - { - saveItemStatement.TryBind("@Keywords", string.Join("|", item.Keywords.ToArray())); - } - else - { - saveItemStatement.TryBindNull("@Keywords"); - } - saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); saveItemStatement.TryBind("@Images", SerializeImages(item)); @@ -1874,15 +1862,6 @@ namespace Emby.Server.Implementations.Data index++; } - if (HasField(query, ItemFields.Keywords)) - { - if (!reader.IsDBNull(index)) - { - item.Keywords = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - } - index++; - } - if (!reader.IsDBNull(index)) { DeserializeProviderIds(reader.GetString(index), item); @@ -2287,7 +2266,6 @@ namespace Emby.Server.Implementations.Data switch (name) { case ItemFields.HomePageUrl: - case ItemFields.Keywords: case ItemFields.DisplayMediaType: case ItemFields.CustomRating: case ItemFields.ProductionLocations: @@ -4221,23 +4199,6 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(clause); } - if (query.Keywords.Length > 0) - { - var clauses = new List(); - var index = 0; - foreach (var item in query.Keywords) - { - clauses.Add("@Keyword" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=5)"); - if (statement != null) - { - statement.TryBind("@Keyword" + index, GetCleanValue(item)); - } - index++; - } - var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; - whereClauses.Add(clause); - } - if (query.OfficialRatings.Length > 0) { var clauses = new List(); @@ -5435,7 +5396,6 @@ namespace Emby.Server.Implementations.Data list.AddRange(item.Genres.Select(i => new Tuple(2, i))); list.AddRange(item.Studios.Select(i => new Tuple(3, i))); list.AddRange(item.Tags.Select(i => new Tuple(4, i))); - list.AddRange(item.Keywords.Select(i => new Tuple(5, i))); return list; } -- cgit v1.2.3 From a46840534f943100e22fd869c5c30d4b5516dc1d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 25 Jul 2017 14:32:03 -0400 Subject: 3.2.26.3 --- Emby.Common.Implementations/Net/UdpSocket.cs | 4 - Emby.Drawing/ImageProcessor.cs | 12 +++ .../Data/SqliteItemRepository.cs | 117 +++++++++++++++++---- .../Library/ResolverHelper.cs | 20 ++-- MediaBrowser.Controller/Library/ItemResolveArgs.cs | 15 --- .../MediaEncoding/EncodingHelper.cs | 4 + SharedVersion.cs | 2 +- 7 files changed, 123 insertions(+), 51 deletions(-) (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index df1099d3d..542d16d24 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -37,8 +37,6 @@ namespace Emby.Common.Implementations.Net private TaskCompletionSource _currentReceiveTaskCompletionSource; private TaskCompletionSource _currentSendTaskCompletionSource; - private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); - public UdpSocket(Socket socket, int localPort, IPAddress ip) { if (socket == null) throw new ArgumentNullException("socket"); @@ -234,8 +232,6 @@ namespace Emby.Common.Implementations.Net if (socket != null) socket.Dispose(); - _sendLock.Dispose(); - var tcs = _currentReceiveTaskCompletionSource; if (tcs != null) { diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index be4591223..88ead3a5f 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -171,6 +171,13 @@ namespace Emby.Drawing return _imageEncoder.SupportedOutputFormats; } + private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp" }; + private bool SupportsTransparency(string path) + { + return TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); + ; + } + public async Task> ProcessImage(ImageProcessingOptions options) { if (options == null) @@ -260,6 +267,11 @@ namespace Emby.Drawing item = _libraryManager().GetItemById(options.ItemId); } + if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) + { + options.CropWhiteSpace = false; + } + var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, tmpPath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index e4c05bce2..528acd069 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -233,7 +233,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "InheritedParentalRatingValue", "INT", existingColumnNames); AddColumn(db, "TypedBaseItems", "UnratedType", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "TopParentId", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsItemByName", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "TrailerTypes", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "CriticRating", "Float", existingColumnNames); AddColumn(db, "TypedBaseItems", "InheritedTags", "Text", existingColumnNames); @@ -558,7 +557,6 @@ namespace Emby.Server.Implementations.Data "IsFolder", "UnratedType", "TopParentId", - "IsItemByName", "TrailerTypes", "CriticRating", "InheritedTags", @@ -897,15 +895,6 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@TopParentId"); } - var isByName = false; - var byName = item as IItemByName; - if (byName != null) - { - var dualAccess = item as IHasDualAccess; - isByName = dualAccess == null || dualAccess.IsAccessedByName; - } - saveItemStatement.TryBind("@IsItemByName", isByName); - var trailer = item as Trailer; if (trailer != null && trailer.TrailerTypes.Count > 0) { @@ -1656,7 +1645,11 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { - item.Audio = (ProgramAudio)Enum.Parse(typeof(ProgramAudio), reader.GetString(index), true); + ProgramAudio audio; + if (Enum.TryParse(reader.GetString(index), true, out audio)) + { + item.Audio = audio; + } } index++; @@ -1687,7 +1680,17 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.LockedFields = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (MetadataFields)Enum.Parse(typeof(MetadataFields), i, true)).ToList(); + item.LockedFields = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select( + i => + { + MetadataFields parsedValue; + + if (Enum.TryParse(i, true, out parsedValue)) + { + return parsedValue; + } + return (MetadataFields?)null; + }).Where(i => i.HasValue).Select(i => i.Value).ToList(); } index++; } @@ -1717,7 +1720,18 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList(); + trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select( + i => + { + TrailerType parsedValue; + + if (Enum.TryParse(i, true, out parsedValue)) + { + return parsedValue; + } + return (TrailerType?)null; + + }).Where(i => i.HasValue).Select(i => i.Value).ToList(); } } index++; @@ -1912,7 +1926,11 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { - item.ExtraType = (ExtraType)Enum.Parse(typeof(ExtraType), reader.GetString(index), true); + ExtraType extraType; + if (Enum.TryParse(reader.GetString(index), true, out extraType)) + { + item.ExtraType = extraType; + } } index++; @@ -4444,21 +4462,27 @@ namespace Emby.Server.Implementations.Data } } - //var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0; - var enableItemsByName = query.IncludeItemsByName ?? false; + + var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); + var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; var queryTopParentIds = query.TopParentIds.Where(IsValidId).ToArray(); if (queryTopParentIds.Length == 1) { - if (enableItemsByName) + if (enableItemsByName && includedItemByNameTypes.Count == 1) { - whereClauses.Add("(TopParentId=@TopParentId or IsItemByName=@IsItemByName)"); + whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)"); if (statement != null) { - statement.TryBind("@IsItemByName", true); + statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); } } + else if (enableItemsByName && includedItemByNameTypes.Count > 1) + { + var itemByNameTypeVal = string.Join(",", includedItemByNameTypes.Select(i => "'" + i + "'").ToArray()); + whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))"); + } else { whereClauses.Add("(TopParentId=@TopParentId)"); @@ -4472,14 +4496,19 @@ namespace Emby.Server.Implementations.Data { var val = string.Join(",", queryTopParentIds.Select(i => "'" + i + "'").ToArray()); - if (enableItemsByName) + if (enableItemsByName && includedItemByNameTypes.Count == 1) { - whereClauses.Add("(IsItemByName=@IsItemByName or TopParentId in (" + val + "))"); + whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))"); if (statement != null) { - statement.TryBind("@IsItemByName", true); + statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); } } + else if (enableItemsByName && includedItemByNameTypes.Count > 1) + { + var itemByNameTypeVal = string.Join(",", includedItemByNameTypes.Select(i => "'" + i + "'").ToArray()); + whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))"); + } else { whereClauses.Add("(TopParentId in (" + val + "))"); @@ -4559,6 +4588,48 @@ namespace Emby.Server.Implementations.Data return whereClauses; } + private List GetItemByNameTypesInQuery(InternalItemsQuery query) + { + var list = new List(); + + if (IsTypeInQuery(typeof(Person).Name, query)) + { + list.Add(typeof(Person).Name); + } + if (IsTypeInQuery(typeof(Genre).Name, query)) + { + list.Add(typeof(Genre).Name); + } + if (IsTypeInQuery(typeof(MusicGenre).Name, query)) + { + list.Add(typeof(MusicGenre).Name); + } + if (IsTypeInQuery(typeof(GameGenre).Name, query)) + { + list.Add(typeof(GameGenre).Name); + } + if (IsTypeInQuery(typeof(MusicArtist).Name, query)) + { + list.Add(typeof(MusicArtist).Name); + } + if (IsTypeInQuery(typeof(Studio).Name, query)) + { + list.Add(typeof(Studio).Name); + } + + return list; + } + + private bool IsTypeInQuery(string type, InternalItemsQuery query) + { + if (query.ExcludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase); + } + private string GetCleanValue(string value) { if (string.IsNullOrWhiteSpace(value)) diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 24dc1104a..d0096de0c 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library var fileInfo = directoryService.GetFile(item.Path); SetDateCreated(item, fileSystem, fileInfo); - EnsureName(item, fileInfo); + EnsureName(item, item.Path, fileInfo); } /// @@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.Library item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); // Make sure the item has a name - EnsureName(item, args.FileInfo); + EnsureName(item, item.Path, args.FileInfo); item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || item.GetParents().Any(i => i.IsLocked); @@ -85,14 +85,14 @@ namespace Emby.Server.Implementations.Library /// /// Ensures the name. /// - /// The item. - /// The file information. - private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo) + private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo) { // If the subclass didn't supply a name, add it here - if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path)) + if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath)) { - item.Name = GetDisplayName(fileInfo.Name, fileInfo.IsDirectory); + var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name; + + item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory); } } @@ -170,7 +170,11 @@ namespace Emby.Server.Implementations.Library if (config.UseFileCreationTimeForDateAdded) { - item.DateCreated = fileSystem.GetCreationTimeUtc(info); + // directoryService.getFile may return null + if (info != null) + { + item.DateCreated = fileSystem.GetCreationTimeUtc(info); + } } else { diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 3ddda81cd..76b6d8768 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -237,21 +237,6 @@ namespace MediaBrowser.Controller.Library return null; } - /// - /// Determines whether [contains meta file by name] [the specified name]. - /// - /// The name. - /// true if [contains meta file by name] [the specified name]; otherwise, false. - public bool ContainsMetaFileByName(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(); - } - - return GetFileSystemEntryByName(name) != null; - } - /// /// Determines whether [contains file system entry by name] [the specified name]. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 44bdafc5b..42f0dda16 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -205,6 +205,10 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase)) + { + return null; + } // Seeing reported failures here, not sure yet if this is related to specfying input format if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase)) diff --git a/SharedVersion.cs b/SharedVersion.cs index f4d096f5a..73ef4d4f5 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.26.2")] +[assembly: AssemblyVersion("3.2.26.3")] -- cgit v1.2.3 From d270b10db67eeaa16a35920d5d86b1975dfffd16 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 27 Jul 2017 01:18:39 -0400 Subject: move auto-organize to plugin --- Emby.Server.Core/ApplicationHost.cs | 22 - .../Data/SqliteFileOrganizationRepository.cs | 284 ------- .../Emby.Server.Implementations.csproj | 8 - .../FileOrganization/EpisodeFileOrganizer.cs | 813 --------------------- .../FileOrganization/Extensions.cs | 33 - .../FileOrganization/FileOrganizationNotifier.cs | 80 -- .../FileOrganization/FileOrganizationService.cs | 283 ------- .../FileOrganization/NameUtils.cs | 81 -- .../FileOrganization/OrganizerScheduledTask.cs | 101 --- .../FileOrganization/TvFolderOrganizer.cs | 236 ------ .../LiveTv/EmbyTV/EmbyTV.cs | 6 +- .../Library/FileOrganizationService.cs | 213 ------ MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - .../FileOrganization/IFileOrganizationService.cs | 107 --- .../MediaBrowser.Controller.csproj | 2 - .../Persistence/IFileOrganizationRepository.cs | 45 -- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 33 +- .../FileOrganization/AutoOrganizeOptions.cs | 24 - .../EpisodeFileOrganizationRequest.cs | 26 - .../FileOrganization/FileOrganizationResult.cs | 109 --- .../FileOrganizationResultQuery.cs | 18 - .../FileOrganization/FileOrganizerType.cs | 9 - .../FileOrganization/FileSortingStatus.cs | 9 - .../FileOrganization/SmartMatchInfo.cs | 16 - .../FileOrganization/TvFileOrganizationOptions.cs | 40 - MediaBrowser.Model/MediaBrowser.Model.csproj | 8 - MediaBrowser.WebDashboard/Api/DashboardService.cs | 15 + Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 29 files changed, 20 insertions(+), 2608 deletions(-) delete mode 100644 Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/Extensions.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/NameUtils.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs delete mode 100644 Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs delete mode 100644 MediaBrowser.Api/Library/FileOrganizationService.cs delete mode 100644 MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs delete mode 100644 MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs delete mode 100644 MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs delete mode 100644 MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileOrganizerType.cs delete mode 100644 MediaBrowser.Model/FileOrganization/FileSortingStatus.cs delete mode 100644 MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs delete mode 100644 MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 584706e4f..67c936437 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -17,7 +17,6 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -94,7 +93,6 @@ using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.IO; -using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.Library; @@ -216,7 +214,6 @@ namespace Emby.Server.Core internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } internal IItemRepository ItemRepository { get; set; } private INotificationsRepository NotificationsRepository { get; set; } - private IFileOrganizationRepository FileOrganizationRepository { get; set; } private INotificationManager NotificationManager { get; set; } private ISubtitleManager SubtitleManager { get; set; } @@ -583,9 +580,6 @@ namespace Emby.Server.Core ItemRepository = itemRepo; RegisterSingleInstance(ItemRepository); - FileOrganizationRepository = GetFileOrganizationRepository(); - RegisterSingleInstance(FileOrganizationRepository); - AuthenticationRepository = GetAuthenticationRepository(); RegisterSingleInstance(AuthenticationRepository); @@ -644,9 +638,6 @@ namespace Emby.Server.Core var newsService = new Emby.Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, LogManager.GetLogger("FileOrganizationService"), LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager, ProviderManager); - RegisterSingleInstance(fileOrganizationService); - progress.Report(15); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); @@ -932,19 +923,6 @@ namespace Emby.Server.Core return repo; } - /// - /// Gets the file organization repository. - /// - /// Task{IUserRepository}. - private IFileOrganizationRepository GetFileOrganizationRepository() - { - var repo = new SqliteFileOrganizationRepository(LogManager.GetLogger("SqliteFileOrganizationRepository"), ServerConfigurationManager.ApplicationPaths); - - repo.Initialize(); - - return repo; - } - private IAuthenticationRepository GetAuthenticationRepository() { var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths); diff --git a/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs b/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs deleted file mode 100644 index a254962c9..000000000 --- a/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - public class SqliteFileOrganizationRepository : BaseSqliteRepository, IFileOrganizationRepository, IDisposable - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SqliteFileOrganizationRepository(ILogger logger, IServerApplicationPaths appPaths) : base(logger) - { - DbFilePath = Path.Combine(appPaths.DataPath, "fileorganization.db"); - } - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - - "create table if not exists FileOrganizerResults (ResultId GUID PRIMARY KEY, OriginalPath TEXT, TargetPath TEXT, FileLength INT, OrganizationDate datetime, Status TEXT, OrganizationType TEXT, StatusMessage TEXT, ExtractedName TEXT, ExtractedYear int null, ExtractedSeasonNumber int null, ExtractedEpisodeNumber int null, ExtractedEndingEpisodeNumber, DuplicatePaths TEXT int null)", - "create index if not exists idx_FileOrganizerResults on FileOrganizerResults(ResultId)" - }; - - connection.RunQueries(queries); - } - } - - public async Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken) - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - var commandText = "replace into FileOrganizerResults (ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths) values (@ResultId, @OriginalPath, @TargetPath, @FileLength, @OrganizationDate, @Status, @OrganizationType, @StatusMessage, @ExtractedName, @ExtractedYear, @ExtractedSeasonNumber, @ExtractedEpisodeNumber, @ExtractedEndingEpisodeNumber, @DuplicatePaths)"; - - using (var statement = db.PrepareStatement(commandText)) - { - statement.TryBind("@ResultId", result.Id.ToGuidBlob()); - statement.TryBind("@OriginalPath", result.OriginalPath); - - statement.TryBind("@TargetPath", result.TargetPath); - statement.TryBind("@FileLength", result.FileSize); - statement.TryBind("@OrganizationDate", result.Date.ToDateTimeParamValue()); - statement.TryBind("@Status", result.Status.ToString()); - statement.TryBind("@OrganizationType", result.Type.ToString()); - statement.TryBind("@StatusMessage", result.StatusMessage); - statement.TryBind("@ExtractedName", result.ExtractedName); - statement.TryBind("@ExtractedYear", result.ExtractedYear); - statement.TryBind("@ExtractedSeasonNumber", result.ExtractedSeasonNumber); - statement.TryBind("@ExtractedEpisodeNumber", result.ExtractedEpisodeNumber); - statement.TryBind("@ExtractedEndingEpisodeNumber", result.ExtractedEndingEpisodeNumber); - statement.TryBind("@DuplicatePaths", string.Join("|", result.DuplicatePaths.ToArray())); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public async Task Delete(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from FileOrganizerResults where ResultId = @ResultId")) - { - statement.TryBind("@ResultId", id.ToGuidBlob()); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public async Task DeleteAll() - { - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - var commandText = "delete from FileOrganizerResults"; - - db.Execute(commandText); - }, TransactionMode); - } - } - } - - public QueryResult GetResults(FileOrganizationResultQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = "SELECT ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults"; - - if (query.StartIndex.HasValue && query.StartIndex.Value > 0) - { - commandText += string.Format(" WHERE ResultId NOT IN (SELECT ResultId FROM FileOrganizerResults ORDER BY OrganizationDate desc LIMIT {0})", - query.StartIndex.Value.ToString(_usCulture)); - } - - commandText += " ORDER BY OrganizationDate desc"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - var list = new List(); - - using (var statement = connection.PrepareStatement(commandText)) - { - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetResult(row)); - } - } - - int count; - using (var statement = connection.PrepareStatement("select count (ResultId) from FileOrganizerResults")) - { - count = statement.ExecuteQuery().SelectScalarInt().First(); - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public FileOrganizationResult GetResult(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults where ResultId=@ResultId")) - { - statement.TryBind("@ResultId", id.ToGuidBlob()); - - foreach (var row in statement.ExecuteQuery()) - { - return GetResult(row); - } - } - - return null; - } - } - } - - public FileOrganizationResult GetResult(IReadOnlyList reader) - { - var index = 0; - - var result = new FileOrganizationResult - { - Id = reader[0].ReadGuidFromBlob().ToString("N") - }; - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.OriginalPath = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.TargetPath = reader[index].ToString(); - } - - index++; - result.FileSize = reader[index].ToInt64(); - - index++; - result.Date = reader[index].ReadDateTime(); - - index++; - result.Status = (FileSortingStatus)Enum.Parse(typeof(FileSortingStatus), reader[index].ToString(), true); - - index++; - result.Type = (FileOrganizerType)Enum.Parse(typeof(FileOrganizerType), reader[index].ToString(), true); - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.StatusMessage = reader[index].ToString(); - } - - result.OriginalFileName = Path.GetFileName(result.OriginalPath); - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedName = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedYear = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedSeasonNumber = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedEpisodeNumber = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.ExtractedEndingEpisodeNumber = reader[index].ToInt(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - result.DuplicatePaths = reader[index].ToString().Split('|').Where(i => !string.IsNullOrEmpty(i)).ToList(); - } - - return result; - } - } -} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 1b0cbb936..3d6e02816 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,7 +54,6 @@ - @@ -79,13 +78,6 @@ - - - - - - - diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs deleted file mode 100644 index cf9fdbb16..000000000 --- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ /dev/null @@ -1,813 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Library; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Naming.TV; -using EpisodeInfo = MediaBrowser.Controller.Providers.EpisodeInfo; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class EpisodeFileOrganizer - { - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IFileOrganizationService _organizationService; - private readonly IServerConfigurationManager _config; - private readonly IProviderManager _providerManager; - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager) - { - _organizationService = organizationService; - _config = config; - _fileSystem = fileSystem; - _logger = logger; - _libraryManager = libraryManager; - _libraryMonitor = libraryMonitor; - _providerManager = providerManager; - } - - public async Task OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken) - { - _logger.Info("Sorting file {0}", path); - - var result = new FileOrganizationResult - { - Date = DateTime.UtcNow, - OriginalPath = path, - OriginalFileName = Path.GetFileName(path), - Type = FileOrganizerType.Episode, - FileSize = _fileSystem.GetFileInfo(path).Length - }; - - try - { - if (_libraryMonitor.IsPathLocked(path)) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = "Path is locked by other processes. Please try again later."; - return result; - } - - var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); - var resolver = new EpisodeResolver(namingOptions, new NullLogger()); - - var episodeInfo = resolver.Resolve(path, false) ?? - new MediaBrowser.Naming.TV.EpisodeInfo(); - - var seriesName = episodeInfo.SeriesName; - - if (!string.IsNullOrEmpty(seriesName)) - { - var seasonNumber = episodeInfo.SeasonNumber; - - result.ExtractedSeasonNumber = seasonNumber; - - // Passing in true will include a few extra regex's - var episodeNumber = episodeInfo.EpisodeNumber; - - result.ExtractedEpisodeNumber = episodeNumber; - - var premiereDate = episodeInfo.IsByDate ? - new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) : - (DateTime?)null; - - if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue)) - { - if (episodeInfo.IsByDate) - { - _logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value); - } - else - { - _logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber); - } - - var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber; - - result.ExtractedEndingEpisodeNumber = endingEpisodeNumber; - - await OrganizeEpisode(path, - seriesName, - seasonNumber, - episodeNumber, - endingEpisodeNumber, - premiereDate, - options, - overwriteExisting, - false, - result, - cancellationToken).ConfigureAwait(false); - } - else - { - var msg = string.Format("Unable to determine episode number from {0}", path); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - } - } - else - { - var msg = string.Format("Unable to determine series name from {0}", path); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - } - - var previousResult = _organizationService.GetResultBySourcePath(path); - - if (previousResult != null) - { - // Don't keep saving the same result over and over if nothing has changed - if (previousResult.Status == result.Status && previousResult.StatusMessage == result.StatusMessage && result.Status != FileSortingStatus.Success) - { - return previousResult; - } - } - - await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = ex.Message; - } - - return result; - } - - public async Task OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken) - { - var result = _organizationService.GetResult(request.ResultId); - - try - { - Series series = null; - - if (request.NewSeriesProviderIds.Count > 0) - { - // We're having a new series here - SeriesInfo seriesRequest = new SeriesInfo(); - seriesRequest.ProviderIds = request.NewSeriesProviderIds; - - var refreshOptions = new MetadataRefreshOptions(_fileSystem); - series = new Series(); - series.Id = Guid.NewGuid(); - series.Name = request.NewSeriesName; - - int year; - if (int.TryParse(request.NewSeriesYear, out year)) - { - series.ProductionYear = year; - } - - var seriesFolderName = series.Name; - if (series.ProductionYear.HasValue) - { - seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear); - } - - seriesFolderName = _fileSystem.GetValidFilename(seriesFolderName); - - series.Path = Path.Combine(request.TargetFolder, seriesFolderName); - - series.ProviderIds = request.NewSeriesProviderIds; - - await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - } - - if (series == null) - { - // Existing Series - series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId)); - } - - await OrganizeEpisode(result.OriginalPath, - series, - request.SeasonNumber, - request.EpisodeNumber, - request.EndingEpisodeNumber, - null, - options, - true, - request.RememberCorrection, - result, - cancellationToken).ConfigureAwait(false); - - await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = ex.Message; - } - - return result; - } - - private Task OrganizeEpisode(string sourcePath, - string seriesName, - int? seasonNumber, - int? episodeNumber, - int? endingEpiosdeNumber, - DateTime? premiereDate, - AutoOrganizeOptions options, - bool overwriteExisting, - bool rememberCorrection, - FileOrganizationResult result, - CancellationToken cancellationToken) - { - var series = GetMatchingSeries(seriesName, result, options); - - if (series == null) - { - var msg = string.Format("Unable to find series in library matching name {0}", seriesName); - result.Status = FileSortingStatus.Failure; - result.StatusMessage = msg; - _logger.Warn(msg); - return Task.FromResult(true); - } - - return OrganizeEpisode(sourcePath, - series, - seasonNumber, - episodeNumber, - endingEpiosdeNumber, - premiereDate, - options, - overwriteExisting, - rememberCorrection, - result, - cancellationToken); - } - - private async Task OrganizeEpisode(string sourcePath, - Series series, - int? seasonNumber, - int? episodeNumber, - int? endingEpiosdeNumber, - DateTime? premiereDate, - AutoOrganizeOptions options, - bool overwriteExisting, - bool rememberCorrection, - FileOrganizationResult result, - CancellationToken cancellationToken) - { - _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); - - 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); - throw new Exception(msg); - } - - _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); - result.TargetPath = newPath; - - var fileExists = _fileSystem.FileExists(result.TargetPath); - var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); - - if (!overwriteExisting) - { - if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) - { - var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath); - _logger.Info(msg); - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = msg; - return; - } - - if (fileExists) - { - var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath); - _logger.Info(msg); - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = msg; - result.TargetPath = newPath; - return; - } - - if (otherDuplicatePaths.Count > 0) - { - var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths)); - _logger.Info(msg); - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = msg; - result.DuplicatePaths = otherDuplicatePaths; - return; - } - } - - PerformFileSorting(options.TvOptions, result); - - if (overwriteExisting) - { - var hasRenamedFiles = false; - - foreach (var path in otherDuplicatePaths) - { - _logger.Debug("Removing duplicate episode {0}", path); - - _libraryMonitor.ReportFileSystemChangeBeginning(path); - - var renameRelatedFiles = !hasRenamedFiles && - string.Equals(_fileSystem.GetDirectoryName(path), _fileSystem.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); - - if (renameRelatedFiles) - { - hasRenamedFiles = true; - } - - try - { - DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error removing duplicate episode", ex, path); - } - finally - { - _libraryMonitor.ReportFileSystemChangeComplete(path, true); - } - } - } - } - catch (Exception ex) - { - result.Status = FileSortingStatus.Failure; - result.StatusMessage = ex.Message; - _logger.Warn(ex.Message); - return; - } - finally - { - _organizationService.RemoveFromInprogressList(result); - } - - if (rememberCorrection) - { - SaveSmartMatchString(originalExtractedSeriesString, series, options); - } - } - - private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options) - { - if (string.IsNullOrEmpty(matchString) || matchString.Length < 3) - { - return; - } - - SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, series.Name, StringComparison.OrdinalIgnoreCase)); - - if (info == null) - { - info = new SmartMatchInfo(); - info.ItemName = series.Name; - info.OrganizerType = FileOrganizerType.Episode; - info.DisplayName = series.Name; - var list = options.SmartMatchInfos.ToList(); - list.Add(info); - options.SmartMatchInfos = list.ToArray(); - } - - if (!info.MatchStrings.Contains(matchString, StringComparer.OrdinalIgnoreCase)) - { - var list = info.MatchStrings.ToList(); - list.Add(matchString); - info.MatchStrings = list.ToArray(); - _config.SaveAutoOrganizeOptions(options); - } - } - - private void DeleteLibraryFile(string path, bool renameRelatedFiles, string targetPath) - { - _fileSystem.DeleteFile(path); - - if (!renameRelatedFiles) - { - return; - } - - // Now find other files - var originalFilenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - var directory = _fileSystem.GetDirectoryName(path); - - if (!string.IsNullOrWhiteSpace(originalFilenameWithoutExtension) && !string.IsNullOrWhiteSpace(directory)) - { - // Get all related files, e.g. metadata, images, etc - var files = _fileSystem.GetFilePaths(directory) - .Where(i => (Path.GetFileNameWithoutExtension(i) ?? string.Empty).StartsWith(originalFilenameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - var targetFilenameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath); - - foreach (var file in files) - { - directory = _fileSystem.GetDirectoryName(file); - var filename = Path.GetFileName(file); - - filename = filename.Replace(originalFilenameWithoutExtension, targetFilenameWithoutExtension, - StringComparison.OrdinalIgnoreCase); - - var destination = Path.Combine(directory, filename); - - _fileSystem.MoveFile(file, destination); - } - } - } - - private List GetOtherDuplicatePaths(string targetPath, - Series series, - int? seasonNumber, - int? episodeNumber, - int? endingEpisodeNumber) - { - // TODO: Support date-naming? - if (!seasonNumber.HasValue || !episodeNumber.HasValue) - { - return new List(); - } - - var episodePaths = series.GetRecursiveChildren(i => i is Episode) - .OfType() - .Where(i => - { - var locationType = i.LocationType; - - // Must be file system based and match exactly - if (locationType != LocationType.Remote && - locationType != LocationType.Virtual && - i.ParentIndexNumber.HasValue && - i.ParentIndexNumber.Value == seasonNumber && - i.IndexNumber.HasValue && - i.IndexNumber.Value == episodeNumber) - { - - if (endingEpisodeNumber.HasValue || i.IndexNumberEnd.HasValue) - { - return endingEpisodeNumber.HasValue && i.IndexNumberEnd.HasValue && - endingEpisodeNumber.Value == i.IndexNumberEnd.Value; - } - - return true; - } - - return false; - }) - .Select(i => i.Path) - .ToList(); - - var folder = _fileSystem.GetDirectoryName(targetPath); - var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath); - - try - { - var filesOfOtherExtensions = _fileSystem.GetFilePaths(folder) - .Where(i => _libraryManager.IsVideoFile(i) && string.Equals(_fileSystem.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); - - episodePaths.AddRange(filesOfOtherExtensions); - } - catch (IOException) - { - // No big deal. Maybe the season folder doesn't already exist. - } - - return episodePaths.Where(i => !string.Equals(i, targetPath, StringComparison.OrdinalIgnoreCase)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - - private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) - { - // We should probably handle this earlier so that we never even make it this far - if (string.Equals(result.OriginalPath, result.TargetPath, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath); - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(result.TargetPath)); - - var targetAlreadyExists = _fileSystem.FileExists(result.TargetPath); - - try - { - if (targetAlreadyExists || options.CopyOriginalFile) - { - _fileSystem.CopyFile(result.OriginalPath, result.TargetPath, true); - } - else - { - _fileSystem.MoveFile(result.OriginalPath, result.TargetPath); - } - - result.Status = FileSortingStatus.Success; - result.StatusMessage = string.Empty; - } - catch (Exception ex) - { - 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; - _logger.ErrorException(errorMsg, ex); - - return; - } - finally - { - _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); - } - - if (targetAlreadyExists && !options.CopyOriginalFile) - { - try - { - _fileSystem.DeleteFile(result.OriginalPath); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath); - } - } - } - - private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options) - { - var parsedName = _libraryManager.ParseName(seriesName); - - var yearInName = parsedName.Year; - var nameWithoutYear = parsedName.Name; - - result.ExtractedName = nameWithoutYear; - result.ExtractedYear = yearInName; - - var series = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true, - DtoOptions = new DtoOptions(true) - }) - .Cast() - .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i)) - .Where(i => i.Item2 > 0) - .OrderByDescending(i => i.Item2) - .Select(i => i.Item1) - .FirstOrDefault(); - - if (series == null) - { - SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(e => e.MatchStrings.Contains(nameWithoutYear, StringComparer.OrdinalIgnoreCase)); - - if (info != null) - { - series = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true, - Name = info.ItemName, - DtoOptions = new DtoOptions(true) - - }).Cast().FirstOrDefault(); - } - } - - return series; - } - - /// - /// Gets the new path. - /// - /// The source path. - /// The series. - /// The season number. - /// The episode number. - /// The ending episode number. - /// The premiere date. - /// The options. - /// The cancellation token. - /// System.String. - private async Task GetNewPath(string sourcePath, - Series series, - int? seasonNumber, - int? episodeNumber, - int? endingEpisodeNumber, - DateTime? premiereDate, - TvFileOrganizationOptions options, - CancellationToken cancellationToken) - { - var episodeInfo = new EpisodeInfo - { - IndexNumber = episodeNumber, - IndexNumberEnd = endingEpisodeNumber, - MetadataCountryCode = series.GetPreferredMetadataCountryCode(), - MetadataLanguage = series.GetPreferredMetadataLanguage(), - ParentIndexNumber = seasonNumber, - SeriesProviderIds = series.ProviderIds, - PremiereDate = premiereDate - }; - - var searchResults = await _providerManager.GetRemoteSearchResults(new RemoteSearchQuery - { - SearchInfo = episodeInfo - - }, cancellationToken).ConfigureAwait(false); - - var episode = searchResults.FirstOrDefault(); - - if (episode == null) - { - var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber); - _logger.Warn(msg); - throw new Exception(msg); - } - - var episodeName = episode.Name; - - //if (string.IsNullOrWhiteSpace(episodeName)) - //{ - // var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber); - // _logger.Warn(msg); - // return null; - //} - - seasonNumber = seasonNumber ?? episode.ParentIndexNumber; - episodeNumber = episodeNumber ?? episode.IndexNumber; - - var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options); - - var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options); - - if (string.IsNullOrEmpty(episodeFileName)) - { - // cause failure - return string.Empty; - } - - newPath = Path.Combine(newPath, episodeFileName); - - return newPath; - } - - /// - /// Gets the season folder path. - /// - /// The series. - /// The season number. - /// The options. - /// System.String. - private string GetSeasonFolderPath(Series series, int seasonNumber, TvFileOrganizationOptions options) - { - // If there's already a season folder, use that - var season = series - .GetRecursiveChildren(i => i is Season && i.LocationType == LocationType.FileSystem && i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber) - .FirstOrDefault(); - - if (season != null) - { - return season.Path; - } - - var path = series.Path; - - if (series.ContainsEpisodesWithoutSeasonFolders) - { - return path; - } - - if (seasonNumber == 0) - { - return Path.Combine(path, _fileSystem.GetValidFilename(options.SeasonZeroFolderName)); - } - - var seasonFolderName = options.SeasonFolderPattern - .Replace("%s", seasonNumber.ToString(_usCulture)) - .Replace("%0s", seasonNumber.ToString("00", _usCulture)) - .Replace("%00s", seasonNumber.ToString("000", _usCulture)); - - return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); - } - - private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) - { - seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); - - if (string.IsNullOrWhiteSpace(episodeTitle)) - { - episodeTitle = string.Empty; - } - else - { - episodeTitle = _fileSystem.GetValidFilename(episodeTitle).Trim(); - } - - var sourceExtension = (Path.GetExtension(sourcePath) ?? string.Empty).TrimStart('.'); - - 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(" ", "_")) - .Replace("%s", seasonNumber.ToString(_usCulture)) - .Replace("%0s", seasonNumber.ToString("00", _usCulture)) - .Replace("%00s", seasonNumber.ToString("000", _usCulture)) - .Replace("%ext", sourceExtension) - .Replace("%en", "%#1") - .Replace("%e.n", "%#2") - .Replace("%e_n", "%#3"); - - if (endingEpisodeNumber.HasValue) - { - result = result.Replace("%ed", endingEpisodeNumber.Value.ToString(_usCulture)) - .Replace("%0ed", endingEpisodeNumber.Value.ToString("00", _usCulture)) - .Replace("%00ed", endingEpisodeNumber.Value.ToString("000", _usCulture)); - } - - result = result.Replace("%e", episodeNumber.ToString(_usCulture)) - .Replace("%0e", episodeNumber.ToString("00", _usCulture)) - .Replace("%00e", episodeNumber.ToString("000", _usCulture)); - - if (result.Contains("%#")) - { - result = result.Replace("%#1", episodeTitle) - .Replace("%#2", episodeTitle.Replace(" ", ".")) - .Replace("%#3", episodeTitle.Replace(" ", "_")); - } - - // Finally, call GetValidFilename again in case user customized the episode expression with any invalid filename characters - return _fileSystem.GetValidFilename(result).Trim(); - } - - private bool IsSameEpisode(string sourcePath, string newPath) - { - try - { - var sourceFileInfo = _fileSystem.GetFileInfo(sourcePath); - var destinationFileInfo = _fileSystem.GetFileInfo(newPath); - - if (sourceFileInfo.Length == destinationFileInfo.Length) - { - return true; - } - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) - { - return false; - } - - return false; - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/Extensions.cs b/Emby.Server.Implementations/FileOrganization/Extensions.cs deleted file mode 100644 index 506bc0327..000000000 --- a/Emby.Server.Implementations/FileOrganization/Extensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.FileOrganization; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.FileOrganization -{ - public static class ConfigurationExtension - { - public static AutoOrganizeOptions GetAutoOrganizeOptions(this IConfigurationManager manager) - { - return manager.GetConfiguration("autoorganize"); - } - public static void SaveAutoOrganizeOptions(this IConfigurationManager manager, AutoOrganizeOptions options) - { - manager.SaveConfiguration("autoorganize", options); - } - } - - public class AutoOrganizeOptionsFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - Key = "autoorganize", - ConfigurationType = typeof (AutoOrganizeOptions) - } - }; - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs b/Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs deleted file mode 100644 index 2a0176547..000000000 --- a/Emby.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs +++ /dev/null @@ -1,80 +0,0 @@ -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; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.FileOrganization -{ - /// - /// Class SessionInfoWebSocketListener - /// - 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 e) - { - _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemUpdated", e.Argument, CancellationToken.None); - } - - private void _organizationService_ItemRemoved(object sender, GenericEventArgs e) - { - _sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemRemoved", e.Argument, CancellationToken.None); - } - - private void _organizationService_ItemAdded(object sender, GenericEventArgs 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/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs b/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs deleted file mode 100644 index d95bd8734..000000000 --- a/Emby.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ /dev/null @@ -1,283 +0,0 @@ -using MediaBrowser.Common.Extensions; -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 MediaBrowser.Model.IO; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; -using MediaBrowser.Common.Events; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class FileOrganizationService : IFileOrganizationService - { - private readonly ITaskManager _taskManager; - private readonly IFileOrganizationRepository _repo; - private readonly ILogger _logger; - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IProviderManager _providerManager; - private readonly ConcurrentDictionary _inProgressItemIds = new ConcurrentDictionary(); - - public event EventHandler> ItemAdded; - public event EventHandler> ItemUpdated; - public event EventHandler> ItemRemoved; - public event EventHandler LogReset; - - public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager) - { - _taskManager = taskManager; - _repo = repo; - _logger = logger; - _libraryMonitor = libraryMonitor; - _libraryManager = libraryManager; - _config = config; - _fileSystem = fileSystem; - _providerManager = providerManager; - } - - public void BeginProcessNewFiles() - { - _taskManager.CancelIfRunningAndQueue(); - } - - public Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken) - { - if (result == null || string.IsNullOrEmpty(result.OriginalPath)) - { - throw new ArgumentNullException("result"); - } - - result.Id = result.OriginalPath.GetMD5().ToString("N"); - - return _repo.SaveResult(result, cancellationToken); - } - - public QueryResult GetResults(FileOrganizationResultQuery 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) - { - var result = _repo.GetResult(id); - - if (result != null) - { - result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id); - } - - return result; - } - - public FileOrganizationResult GetResultBySourcePath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - var id = path.GetMD5().ToString("N"); - - return GetResult(id); - } - - 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); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath); - } - finally - { - RemoveFromInprogressList(result); - } - - await _repo.Delete(resultId); - - EventHelper.FireEventIfNotNull(ItemRemoved, this, new GenericEventArgs(result), _logger); - } - - private AutoOrganizeOptions GetAutoOrganizeOptions() - { - return _config.GetAutoOrganizeOptions(); - } - - public async Task PerformOrganization(string resultId) - { - var result = _repo.GetResult(resultId); - - if (string.IsNullOrEmpty(result.TargetPath)) - { - throw new ArgumentException("No target path available."); - } - - var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _libraryMonitor, _providerManager); - - var organizeResult = await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None) - .ConfigureAwait(false); - - if (organizeResult.Status != FileSortingStatus.Success) - { - throw new Exception(result.StatusMessage); - } - } - - public async Task ClearLog() - { - await _repo.DeleteAll(); - EventHelper.FireEventIfNotNull(LogReset, this, EventArgs.Empty, _logger); - } - - public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) - { - var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _libraryMonitor, _providerManager); - - var result = await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false); - - if (result.Status != FileSortingStatus.Success) - { - throw new Exception(result.StatusMessage); - } - } - - public QueryResult GetSmartMatchInfos(FileOrganizationResultQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - var options = GetAutoOrganizeOptions(); - - var items = options.SmartMatchInfos.Skip(query.StartIndex ?? 0).Take(query.Limit ?? Int32.MaxValue).ToArray(); - - return new QueryResult() - { - Items = items, - TotalRecordCount = options.SmartMatchInfos.Length - }; - } - - public void DeleteSmartMatchEntry(string itemName, string matchString) - { - if (string.IsNullOrEmpty(itemName)) - { - throw new ArgumentNullException("itemName"); - } - - if (string.IsNullOrEmpty(matchString)) - { - throw new ArgumentNullException("matchString"); - } - - var options = GetAutoOrganizeOptions(); - - SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, itemName)); - - if (info != null && info.MatchStrings.Contains(matchString)) - { - var list = info.MatchStrings.ToList(); - list.Remove(matchString); - info.MatchStrings = list.ToArray(); - - if (info.MatchStrings.Length == 0) - { - var infos = options.SmartMatchInfos.ToList(); - infos.Remove(info); - options.SmartMatchInfos = infos.ToArray(); - } - - _config.SaveAutoOrganizeOptions(options); - } - } - - /// - /// Attempts to add a an item to the list of currently processed items. - /// - /// The result item. - /// Passing true will notify the client to reload all items, otherwise only a single item will be refreshed. - /// True if the item was added, False if the item is already contained in the list. - 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(result), _logger); - } - else - { - EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs(result), _logger); - } - - return true; - } - - /// - /// Removes an item from the list of currently processed items. - /// - /// The result item. - /// True if the item was removed, False if the item was not contained in the list. - public bool RemoveFromInprogressList(FileOrganizationResult result) - { - bool itemValue; - var retval = _inProgressItemIds.TryRemove(result.Id, out itemValue); - - result.IsInProgress = false; - - EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs(result), _logger); - - return retval; - } - - } -} diff --git a/Emby.Server.Implementations/FileOrganization/NameUtils.cs b/Emby.Server.Implementations/FileOrganization/NameUtils.cs deleted file mode 100644 index eb22ca4ea..000000000 --- a/Emby.Server.Implementations/FileOrganization/NameUtils.cs +++ /dev/null @@ -1,81 +0,0 @@ -using MediaBrowser.Model.Extensions; -using MediaBrowser.Controller.Entities; -using System; -using System.Globalization; -using MediaBrowser.Controller.Extensions; - -namespace Emby.Server.Implementations.FileOrganization -{ - public static class NameUtils - { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - internal static Tuple GetMatchScore(string sortedName, int? year, T series) - where T : BaseItem - { - var score = 0; - - var seriesNameWithoutYear = series.Name; - if (series.ProductionYear.HasValue) - { - seriesNameWithoutYear = seriesNameWithoutYear.Replace(series.ProductionYear.Value.ToString(UsCulture), String.Empty); - } - - if (IsNameMatch(sortedName, seriesNameWithoutYear)) - { - score++; - - if (year.HasValue && series.ProductionYear.HasValue) - { - if (year.Value == series.ProductionYear.Value) - { - score++; - } - else - { - // Regardless of name, return a 0 score if the years don't match - return new Tuple(series, 0); - } - } - } - - return new Tuple(series, score); - } - - - private static bool IsNameMatch(string name1, string name2) - { - name1 = GetComparableName(name1); - name2 = GetComparableName(name2); - - return String.Equals(name1, name2, StringComparison.OrdinalIgnoreCase); - } - - private static string GetComparableName(string name) - { - name = name.RemoveDiacritics(); - - name = " " + name + " "; - - name = name.Replace(".", " ") - .Replace("_", " ") - .Replace(" and ", " ") - .Replace(".and.", " ") - .Replace("&", " ") - .Replace("!", " ") - .Replace("(", " ") - .Replace(")", " ") - .Replace(":", " ") - .Replace(",", " ") - .Replace("-", " ") - .Replace("'", " ") - .Replace("[", " ") - .Replace("]", " ") - .Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(" the ", String.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(" ", String.Empty); - - return name.Trim(); - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs deleted file mode 100644 index b71a3975f..000000000 --- a/Emby.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ /dev/null @@ -1,101 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask - { - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IServerConfigurationManager _config; - private readonly IFileOrganizationService _organizationService; - private readonly IProviderManager _providerManager; - - public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService, IProviderManager providerManager) - { - _libraryMonitor = libraryMonitor; - _libraryManager = libraryManager; - _logger = logger; - _fileSystem = fileSystem; - _config = config; - _organizationService = organizationService; - _providerManager = providerManager; - } - - public string Name - { - get { return "Organize new media files"; } - } - - public string Description - { - get { return "Processes new files available in the configured watch folder."; } - } - - public string Category - { - get { return "Library"; } - } - - private AutoOrganizeOptions GetAutoOrganizeOptions() - { - return _config.GetAutoOrganizeOptions(); - } - - public async Task Execute(CancellationToken cancellationToken, IProgress progress) - { - if (GetAutoOrganizeOptions().TvOptions.IsEnabled) - { - await new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config, _providerManager) - .Organize(GetAutoOrganizeOptions(), cancellationToken, progress).ConfigureAwait(false); - } - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromMinutes(5).Ticks} - }; - } - - public bool IsHidden - { - get { return !GetAutoOrganizeOptions().TvOptions.IsEnabled; } - } - - public bool IsEnabled - { - get { return GetAutoOrganizeOptions().TvOptions.IsEnabled; } - } - - public bool IsLogged - { - get { return false; } - } - - public string Key - { - get { return "AutoOrganize"; } - } - } -} diff --git a/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs deleted file mode 100644 index 0dbd6f837..000000000 --- a/Emby.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ /dev/null @@ -1,236 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.FileOrganization -{ - public class TvFolderOrganizer - { - private readonly ILibraryMonitor _libraryMonitor; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IFileOrganizationService _organizationService; - private readonly IServerConfigurationManager _config; - private readonly IProviderManager _providerManager; - - public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config, IProviderManager providerManager) - { - _libraryManager = libraryManager; - _logger = logger; - _fileSystem = fileSystem; - _libraryMonitor = libraryMonitor; - _organizationService = organizationService; - _config = config; - _providerManager = providerManager; - } - - private bool EnableOrganization(FileSystemMetadata fileInfo, TvFileOrganizationOptions options) - { - var minFileBytes = options.MinFileSizeMb * 1024 * 1024; - - try - { - return _libraryManager.IsVideoFile(fileInfo.FullName) && fileInfo.Length >= minFileBytes; - } - catch (Exception ex) - { - _logger.ErrorException("Error organizing file {0}", ex, fileInfo.Name); - } - - return false; - } - - private bool IsValidWatchLocation(string path, List libraryFolderPaths) - { - if (IsPathAlreadyInMediaLibrary(path, libraryFolderPaths)) - { - _logger.Info("Folder {0} is not eligible for auto-organize because it is also part of an Emby library", path); - return false; - } - - return true; - } - - private bool IsPathAlreadyInMediaLibrary(string path, List libraryFolderPaths) - { - return libraryFolderPaths.Any(i => string.Equals(i, path, StringComparison.Ordinal) || _fileSystem.ContainsSubPath(i, path)); - } - - public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress progress) - { - var libraryFolderPaths = _libraryManager.GetVirtualFolders().SelectMany(i => i.Locations).ToList(); - - var watchLocations = options.TvOptions.WatchLocations - .Where(i => IsValidWatchLocation(i, libraryFolderPaths)) - .ToList(); - - var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize) - .OrderBy(_fileSystem.GetCreationTimeUtc) - .Where(i => EnableOrganization(i, options.TvOptions)) - .ToList(); - - var processedFolders = new HashSet(); - - progress.Report(10); - - if (eligibleFiles.Count > 0) - { - var numComplete = 0; - - foreach (var file in eligibleFiles) - { - cancellationToken.ThrowIfCancellationRequested(); - - var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, - _libraryMonitor, _providerManager); - - try - { - var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.TvOptions.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false); - - if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase)) - { - processedFolders.Add(file.DirectoryName); - } - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - _logger.ErrorException("Error organizing episode {0}", ex, file.FullName); - } - - numComplete++; - double percent = numComplete; - percent /= eligibleFiles.Count; - - progress.Report(10 + 89 * percent); - } - } - - cancellationToken.ThrowIfCancellationRequested(); - progress.Report(99); - - foreach (var path in processedFolders) - { - var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete - .Select(i => i.Trim().TrimStart('.')) - .Where(i => !string.IsNullOrEmpty(i)) - .Select(i => "." + i) - .ToList(); - - if (deleteExtensions.Count > 0) - { - DeleteLeftOverFiles(path, deleteExtensions); - } - - if (options.TvOptions.DeleteEmptyFolders) - { - if (!IsWatchFolder(path, watchLocations)) - { - DeleteEmptyFolders(path); - } - } - } - - progress.Report(100); - } - - /// - /// Gets the files to organize. - /// - /// The path. - /// IEnumerable{FileInfo}. - private List GetFilesToOrganize(string path) - { - try - { - return _fileSystem.GetFiles(path, true) - .ToList(); - } - catch (IOException ex) - { - _logger.ErrorException("Error getting files from {0}", ex, path); - - return new List(); - } - } - - /// - /// Deletes the left over files. - /// - /// The path. - /// The extensions. - private void DeleteLeftOverFiles(string path, IEnumerable extensions) - { - var eligibleFiles = _fileSystem.GetFilePaths(path, extensions.ToArray(), false, true) - .ToList(); - - foreach (var file in eligibleFiles) - { - try - { - _fileSystem.DeleteFile(file); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting file {0}", ex, file); - } - } - } - - /// - /// Deletes the empty folders. - /// - /// The path. - private void DeleteEmptyFolders(string path) - { - try - { - foreach (var d in _fileSystem.GetDirectoryPaths(path)) - { - DeleteEmptyFolders(d); - } - - var entries = _fileSystem.GetFileSystemEntryPaths(path); - - if (!entries.Any()) - { - try - { - _logger.Debug("Deleting empty directory {0}", path); - _fileSystem.DeleteDirectory(path, false); - } - catch (UnauthorizedAccessException) { } - catch (IOException) { } - } - } - catch (UnauthorizedAccessException) { } - } - - /// - /// Determines if a given folder path is contained in a folder list - /// - /// The folder path to check. - /// A list of folders. - private bool IsWatchFolder(string path, IEnumerable watchLocations) - { - return watchLocations.Contains(path, StringComparer.OrdinalIgnoreCase); - } - } -} \ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b55e4412b..9ac599846 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Common.Security; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -36,7 +35,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; using MediaBrowser.Model.Extensions; @@ -61,7 +59,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly IProviderManager _providerManager; - private readonly IFileOrganizationService _organizationService; private readonly IMediaEncoder _mediaEncoder; private readonly IProcessFactory _processFactory; private readonly ISystemEvents _systemEvents; @@ -74,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly ConcurrentDictionary _activeRecordings = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, ITimerFactory timerFactory, IProcessFactory processFactory, ISystemEvents systemEvents) + public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory, IProcessFactory processFactory, ISystemEvents systemEvents) { Current = this; @@ -86,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _libraryManager = libraryManager; _libraryMonitor = libraryMonitor; _providerManager = providerManager; - _organizationService = organizationService; _mediaEncoder = mediaEncoder; _processFactory = processFactory; _systemEvents = systemEvents; diff --git a/MediaBrowser.Api/Library/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs deleted file mode 100644 index ea610ac5c..000000000 --- a/MediaBrowser.Api/Library/FileOrganizationService.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.FileOrganization; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Querying; -using System.Threading.Tasks; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Library -{ - [Route("/Library/FileOrganization", "GET", Summary = "Gets file organization results")] - public class GetFileOrganizationActivity : IReturn> - { - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - } - - [Route("/Library/FileOrganizations", "DELETE", Summary = "Clears the activity log")] - public class ClearOrganizationLog : IReturnVoid - { - } - - [Route("/Library/FileOrganizations/{Id}/File", "DELETE", Summary = "Deletes the original file of a organizer result")] - public class DeleteOriginalFile : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Library/FileOrganizations/{Id}/Organize", "POST", Summary = "Performs an organization")] - public class PerformOrganization : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Library/FileOrganizations/{Id}/Episode/Organize", "POST", Summary = "Performs organization of a tv episode")] - public class OrganizeEpisode - { - [ApiMember(Name = "Id", Description = "Result Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "SeriesId", Description = "Series Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SeriesId { get; set; } - - [ApiMember(Name = "SeasonNumber", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")] - public int SeasonNumber { get; set; } - - [ApiMember(Name = "EpisodeNumber", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")] - public int EpisodeNumber { get; set; } - - [ApiMember(Name = "EndingEpisodeNumber", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? EndingEpisodeNumber { get; set; } - - [ApiMember(Name = "RememberCorrection", Description = "Whether or not to apply the same correction to future episodes of the same series.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool RememberCorrection { get; set; } - - [ApiMember(Name = "NewSeriesProviderIds", Description = "A list of provider IDs identifying a new series.", IsRequired = false, DataType = "Dictionary", ParameterType = "query", Verb = "POST")] - public Dictionary NewSeriesProviderIds { get; set; } - - [ApiMember(Name = "NewSeriesName", Description = "Name of a series to add.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string NewSeriesName { get; set; } - - [ApiMember(Name = "NewSeriesYear", Description = "Year of a series to add.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string NewSeriesYear { get; set; } - - [ApiMember(Name = "TargetFolder", Description = "Target Folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string TargetFolder { get; set; } - } - - [Route("/Library/FileOrganizations/SmartMatches", "GET", Summary = "Gets smart match entries")] - public class GetSmartMatchInfos : IReturn> - { - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - } - - [Route("/Library/FileOrganizations/SmartMatches/Delete", "POST", Summary = "Deletes a smart match entry")] - public class DeleteSmartMatchEntry - { - [ApiMember(Name = "Entries", Description = "SmartMatch Entry", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public List Entries { get; set; } - } - - [Authenticated(Roles = "Admin")] - public class FileOrganizationService : BaseApiService - { - private readonly IFileOrganizationService _iFileOrganizationService; - - private readonly IJsonSerializer _jsonSerializer; - - public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer) - { - _iFileOrganizationService = iFileOrganizationService; - _jsonSerializer = jsonSerializer; - } - - public object Get(GetFileOrganizationActivity request) - { - var result = _iFileOrganizationService.GetResults(new FileOrganizationResultQuery - { - Limit = request.Limit, - StartIndex = request.StartIndex - }); - - return ToOptimizedSerializedResultUsingCache(result); - } - - public void Delete(DeleteOriginalFile request) - { - var task = _iFileOrganizationService.DeleteOriginalFile(request.Id); - - Task.WaitAll(task); - } - - public void Delete(ClearOrganizationLog request) - { - var task = _iFileOrganizationService.ClearLog(); - - Task.WaitAll(task); - } - - public void Post(PerformOrganization request) - { - // Don't await this - var task = _iFileOrganizationService.PerformOrganization(request.Id); - - // Async processing (close dialog early instead of waiting until the file has been copied) - // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display - task.Wait(2000); - } - - public void Post(OrganizeEpisode request) - { - var dicNewProviderIds = new Dictionary(); - - if (request.NewSeriesProviderIds != null) - { - dicNewProviderIds = request.NewSeriesProviderIds; - } - - // Don't await this - var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest - { - EndingEpisodeNumber = request.EndingEpisodeNumber, - EpisodeNumber = request.EpisodeNumber, - RememberCorrection = request.RememberCorrection, - ResultId = request.Id, - SeasonNumber = request.SeasonNumber, - SeriesId = request.SeriesId, - NewSeriesName = request.NewSeriesName, - NewSeriesYear = request.NewSeriesYear, - NewSeriesProviderIds = dicNewProviderIds, - TargetFolder = request.TargetFolder - }); - - // Async processing (close dialog early instead of waiting until the file has been copied) - // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display - task.Wait(2000); - } - - public object Get(GetSmartMatchInfos request) - { - var result = _iFileOrganizationService.GetSmartMatchInfos(new FileOrganizationResultQuery - { - Limit = request.Limit, - StartIndex = request.StartIndex - }); - - return ToOptimizedSerializedResultUsingCache(result); - } - - public void Post(DeleteSmartMatchEntry request) - { - foreach (var entry in request.Entries) - { - _iFileOrganizationService.DeleteSmartMatchEntry(entry.Name, entry.Value); - } - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a798ab5ff..88889e5e7 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -93,7 +93,6 @@ - diff --git a/MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs b/MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs deleted file mode 100644 index 9a5b96a24..000000000 --- a/MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs +++ /dev/null @@ -1,107 +0,0 @@ -using MediaBrowser.Model.Events; -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Querying; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.FileOrganization -{ - public interface IFileOrganizationService - { - event EventHandler> ItemAdded; - event EventHandler> ItemUpdated; - event EventHandler> ItemRemoved; - event EventHandler LogReset; - - /// - /// Processes the new files. - /// - void BeginProcessNewFiles(); - - /// - /// Deletes the original file. - /// - /// The result identifier. - /// Task. - Task DeleteOriginalFile(string resultId); - - /// - /// Clears the log. - /// - /// Task. - Task ClearLog(); - - /// - /// Performs the organization. - /// - /// The result identifier. - /// Task. - Task PerformOrganization(string resultId); - - /// - /// Performs the episode organization. - /// - /// The request. - /// Task. - Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request); - - /// - /// Gets the results. - /// - /// The query. - /// IEnumerable{FileOrganizationResult}. - QueryResult GetResults(FileOrganizationResultQuery query); - - /// - /// Gets the result. - /// - /// The identifier. - /// FileOrganizationResult. - FileOrganizationResult GetResult(string id); - - /// - /// Gets the result by source path. - /// - /// The path. - /// FileOrganizationResult. - FileOrganizationResult GetResultBySourcePath(string path); - - /// - /// Saves the result. - /// - /// The result. - /// The cancellation token. - /// Task. - Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken); - - /// - /// Returns a list of smart match entries - /// - /// The query. - /// IEnumerable{SmartMatchInfo}. - QueryResult GetSmartMatchInfos(FileOrganizationResultQuery query); - - /// - /// Deletes a smart match entry. - /// - /// Item name. - /// The match string to delete. - void DeleteSmartMatchEntry(string ItemName, string matchString); - - /// - /// Attempts to add a an item to the list of currently processed items. - /// - /// The result item. - /// Passing true will notify the client to reload all items, otherwise only a single item will be refreshed. - /// True if the item was added, False if the item is already contained in the list. - bool AddToInProgressList(FileOrganizationResult result, bool fullClientRefresh); - - /// - /// Removes an item from the list of currently processed items. - /// - /// The result item. - /// True if the item was removed, False if the item was not contained in the list. - bool RemoveFromInprogressList(FileOrganizationResult result); - } -} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index e28e1761e..9cba48b74 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -128,7 +128,6 @@ - @@ -205,7 +204,6 @@ - diff --git a/MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs b/MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs deleted file mode 100644 index f71784d82..000000000 --- a/MediaBrowser.Controller/Persistence/IFileOrganizationRepository.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MediaBrowser.Model.FileOrganization; -using MediaBrowser.Model.Querying; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Persistence -{ - public interface IFileOrganizationRepository - { - /// - /// Saves the result. - /// - /// The result. - /// The cancellation token. - /// Task. - Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken); - - /// - /// Deletes the specified identifier. - /// - /// The identifier. - /// Task. - Task Delete(string id); - - /// - /// Gets the result. - /// - /// The identifier. - /// FileOrganizationResult. - FileOrganizationResult GetResult(string id); - - /// - /// Gets the results. - /// - /// The query. - /// IEnumerable{FileOrganizationResult}. - QueryResult GetResults(FileOrganizationResultQuery query); - - /// - /// Deletes all. - /// - /// Task. - Task DeleteAll(); - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index f1bf29d92..f416ea417 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -597,21 +597,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); - - var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - - if (videoStream != null && !videoStream.IsInterlaced) - { - var isInterlaced = DetectInterlaced(mediaInfo, videoStream); - - if (isInterlaced) - { - videoStream.IsInterlaced = true; - } - } - - return mediaInfo; + return new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } catch { @@ -622,23 +608,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private bool DetectInterlaced(MediaSourceInfo video, MediaStream videoStream) - { - // If it's mpeg based, assume true - if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1) - { - var formats = (video.Container ?? string.Empty).Split(',').ToList(); - return formats.Contains("vob", StringComparer.OrdinalIgnoreCase) || - formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) || - formats.Contains("ts", StringComparer.OrdinalIgnoreCase) || - formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) || - formats.Contains("wtv", StringComparer.OrdinalIgnoreCase); - - } - - return false; - } - /// /// The us culture /// diff --git a/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs b/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs deleted file mode 100644 index 830d55bf5..000000000 --- a/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class AutoOrganizeOptions - { - /// - /// Gets or sets the tv options. - /// - /// The tv options. - public TvFileOrganizationOptions TvOptions { get; set; } - - /// - /// Gets or sets a list of smart match entries. - /// - /// The smart match entries. - public SmartMatchInfo[] SmartMatchInfos { get; set; } - - public AutoOrganizeOptions() - { - TvOptions = new TvFileOrganizationOptions(); - SmartMatchInfos = new SmartMatchInfo[]{}; - } - } -} diff --git a/MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs b/MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs deleted file mode 100644 index b20e43e54..000000000 --- a/MediaBrowser.Model/FileOrganization/EpisodeFileOrganizationRequest.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Model.FileOrganization -{ - public class EpisodeFileOrganizationRequest - { - public string ResultId { get; set; } - - public string SeriesId { get; set; } - - public int SeasonNumber { get; set; } - - public int EpisodeNumber { get; set; } - - public int? EndingEpisodeNumber { get; set; } - - public bool RememberCorrection { get; set; } - public string NewSeriesName { get; set; } - - public string NewSeriesYear { get; set; } - - public string TargetFolder { get; set; } - - public Dictionary NewSeriesProviderIds { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs b/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs deleted file mode 100644 index caf99183d..000000000 --- a/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Model.FileOrganization -{ - public class FileOrganizationResult - { - /// - /// Gets or sets the result identifier. - /// - /// The result identifier. - public string Id { get; set; } - - /// - /// Gets or sets the original path. - /// - /// The original path. - public string OriginalPath { get; set; } - - /// - /// Gets or sets the name of the original file. - /// - /// The name of the original file. - public string OriginalFileName { get; set; } - - /// - /// Gets or sets the name of the extracted. - /// - /// The name of the extracted. - public string ExtractedName { get; set; } - - /// - /// Gets or sets the extracted year. - /// - /// The extracted year. - public int? ExtractedYear { get; set; } - - /// - /// Gets or sets the extracted season number. - /// - /// The extracted season number. - public int? ExtractedSeasonNumber { get; set; } - - /// - /// Gets or sets the extracted episode number. - /// - /// The extracted episode number. - public int? ExtractedEpisodeNumber { get; set; } - - /// - /// Gets or sets the extracted ending episode number. - /// - /// The extracted ending episode number. - public int? ExtractedEndingEpisodeNumber { get; set; } - - /// - /// Gets or sets the target path. - /// - /// The target path. - public string TargetPath { get; set; } - - /// - /// Gets or sets the date. - /// - /// The date. - public DateTime Date { get; set; } - - /// - /// Gets or sets the error message. - /// - /// The error message. - public string StatusMessage { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public FileSortingStatus Status { get; set; } - - /// - /// Gets or sets the type. - /// - /// The type. - public FileOrganizerType Type { get; set; } - - /// - /// Gets or sets the duplicate paths. - /// - /// The duplicate paths. - public List DuplicatePaths { get; set; } - - /// - /// Gets or sets the size of the file. - /// - /// The size of the file. - public long FileSize { get; set; } - - /// - /// Indicates if the item is currently being processed. - /// - /// Runtime property not persisted to the store. - public bool IsInProgress { get; set; } - - public FileOrganizationResult() - { - DuplicatePaths = new List(); - } - } -} diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs b/MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs deleted file mode 100644 index 18287534e..000000000 --- a/MediaBrowser.Model/FileOrganization/FileOrganizationResultQuery.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class FileOrganizationResultQuery - { - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - public int? Limit { get; set; } - } -} diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizerType.cs b/MediaBrowser.Model/FileOrganization/FileOrganizerType.cs deleted file mode 100644 index cbbeb9ce2..000000000 --- a/MediaBrowser.Model/FileOrganization/FileOrganizerType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Model.FileOrganization -{ - public enum FileOrganizerType - { - Movie, - Episode, - Song - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/FileOrganization/FileSortingStatus.cs b/MediaBrowser.Model/FileOrganization/FileSortingStatus.cs deleted file mode 100644 index 8a467c05f..000000000 --- a/MediaBrowser.Model/FileOrganization/FileSortingStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Model.FileOrganization -{ - public enum FileSortingStatus - { - Success, - Failure, - SkippedExisting - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs b/MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs deleted file mode 100644 index 28c99b89b..000000000 --- a/MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class SmartMatchInfo - { - public string ItemName { get; set; } - public string DisplayName { get; set; } - public FileOrganizerType OrganizerType { get; set; } - public string[] MatchStrings { get; set; } - - public SmartMatchInfo() - { - MatchStrings = new string[] { }; - } - } -} diff --git a/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs b/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs deleted file mode 100644 index 973ecf6e7..000000000 --- a/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs +++ /dev/null @@ -1,40 +0,0 @@ - -namespace MediaBrowser.Model.FileOrganization -{ - public class TvFileOrganizationOptions - { - public bool IsEnabled { get; set; } - public int MinFileSizeMb { get; set; } - public string[] LeftOverFileExtensionsToDelete { get; set; } - public string[] WatchLocations { get; set; } - - public string SeasonFolderPattern { get; set; } - - public string SeasonZeroFolderName { get; set; } - - public string EpisodeNamePattern { get; set; } - public string MultiEpisodeNamePattern { get; set; } - - public bool OverwriteExistingEpisodes { get; set; } - - public bool DeleteEmptyFolders { get; set; } - - public bool CopyOriginalFile { get; set; } - - public TvFileOrganizationOptions() - { - MinFileSizeMb = 50; - - LeftOverFileExtensionsToDelete = new string[] { }; - - WatchLocations = new string[] { }; - - EpisodeNamePattern = "%sn - %sx%0e - %en.%ext"; - MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext"; - SeasonFolderPattern = "Season %s"; - SeasonZeroFolderName = "Season 0"; - - CopyOriginalFile = false; - } - } -} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index e6cc58868..249e970dd 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -143,7 +143,6 @@ - @@ -160,8 +159,6 @@ - - @@ -248,11 +245,6 @@ - - - - - diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index cf3e221b3..c6bbca672 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -174,6 +174,9 @@ namespace MediaBrowser.WebDashboard.Api IPlugin plugin = null; Stream stream = null; + var isJs = false; + var isTemplate = false; + var page = ServerEntryPoint.Instance.PluginConfigurationPages.FirstOrDefault(p => string.Equals(p.Name, request.Name, StringComparison.OrdinalIgnoreCase)); if (page != null) { @@ -188,11 +191,23 @@ namespace MediaBrowser.WebDashboard.Api { plugin = altPage.Item2; stream = _assemblyInfo.GetManifestResourceStream(plugin.GetType(), altPage.Item1.EmbeddedResourcePath); + + isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); + isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html"); } } if (plugin != null && stream != null) { + if (isJs) + { + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.js"), () => Task.FromResult(stream)); + } + if (isTemplate) + { + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); + } + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion.ToString(), null)); } diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 26c19c79a..ff0973b0f 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.708 + 3.0.709 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 641db709a..31b8ee026 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.708 + 3.0.709 Emby.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + -- cgit v1.2.3 From f7f0d3eba0e7aeb21a8c6404919408585258f57b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 28 Jul 2017 12:16:06 -0400 Subject: 3.2.26.6 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 7 ------- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 2 -- SharedVersion.cs | 2 +- 3 files changed, 1 insertion(+), 10 deletions(-) (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 528acd069..3e2dbeefd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -190,7 +190,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "IsLocked", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "Name", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "OfficialRating", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "MediaType", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Overview", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ParentIndexNumber", "INT", existingColumnNames); @@ -200,28 +199,22 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "Genres", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "SortName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "HomePageUrl", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "DisplayMediaType", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsLive", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsNews", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsPremiere", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "EpisodeTitle", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsRepeat", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "PreferredMetadataLanguage", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "PreferredMetadataCountryCode", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsHD", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExternalEtag", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateLastRefreshed", "DATETIME", existingColumnNames); - AddColumn(db, "TypedBaseItems", "DateLastSaved", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsInMixedFolder", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "LockedFields", "Text", existingColumnNames); diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index da43dd63a..bd9ef3e9f 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -39,8 +39,6 @@ namespace MediaBrowser.WebDashboard.Api if (resourceStream != null) { - // Don't apply any caching for html pages - // jQuery ajax doesn't seem to handle if-modified-since correctly if (IsFormat(virtualPath, "html")) { if (IsCoreHtml(virtualPath)) diff --git a/SharedVersion.cs b/SharedVersion.cs index e799ddfeb..4926fb574 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.26.5")] +[assembly: AssemblyVersion("3.2.26.6")] -- cgit v1.2.3 From 4e52c027bcc2dc4cd100d450a7195233e48fb5ff Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 1 Aug 2017 12:45:57 -0400 Subject: improve nextup queries --- .../Data/SqliteItemRepository.cs | 23 +++ .../HttpServer/HttpResultFactory.cs | 188 ++++++--------------- .../Library/UserViewManager.cs | 39 ++++- .../LiveTv/EmbyTV/EmbyTV.cs | 3 +- .../LiveTv/LiveTvManager.cs | 9 +- .../Session/SessionManager.cs | 4 +- Emby.Server.Implementations/TV/TVSeriesManager.cs | 51 ++++-- .../UserViews/CollectionFolderImageProvider.cs | 4 +- .../UserViews/DynamicImageProvider.cs | 8 +- MediaBrowser.Api/FilterService.cs | 4 +- MediaBrowser.Api/TvShowsService.cs | 4 +- MediaBrowser.Controller/Entities/Folder.cs | 33 +++- .../Entities/InternalItemsQuery.cs | 1 + MediaBrowser.Controller/Entities/TV/Series.cs | 2 +- MediaBrowser.Controller/Entities/UserView.cs | 8 +- MediaBrowser.Controller/Playlists/Playlist.cs | 5 +- MediaBrowser.Model/Querying/ItemFields.cs | 3 +- MediaBrowser.Server.Mono/Program.cs | 2 +- 18 files changed, 198 insertions(+), 193 deletions(-) (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3e2dbeefd..80a5defd6 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2622,6 +2622,11 @@ namespace Emby.Server.Implementations.Data groups.Add("PresentationUniqueKey"); } + if (query.GroupBySeriesPresentationUniqueKey) + { + groups.Add("SeriesPresentationUniqueKey"); + } + if (groups.Count > 0) { return " Group by " + string.Join(",", groups.ToArray()); @@ -2934,6 +2939,10 @@ namespace Emby.Server.Implementations.Data { commandText += " select count (distinct PresentationUniqueKey)" + GetFromText(); } + else if (query.GroupBySeriesPresentationUniqueKey) + { + commandText += " select count (distinct SeriesPresentationUniqueKey)" + GetFromText(); + } else { commandText += " select count (guid)" + GetFromText(); @@ -3079,6 +3088,11 @@ namespace Emby.Server.Implementations.Data } if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase)) { + if (query.GroupBySeriesPresentationUniqueKey) + { + return new Tuple("MAX(LastPlayedDate)", false); + } + return new Tuple("LastPlayedDate", false); } if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase)) @@ -3353,6 +3367,10 @@ namespace Emby.Server.Implementations.Data { commandText += " select count (distinct PresentationUniqueKey)" + GetFromText(); } + else if (query.GroupBySeriesPresentationUniqueKey) + { + commandText += " select count (distinct SeriesPresentationUniqueKey)" + GetFromText(); + } else { commandText += " select count (guid)" + GetFromText(); @@ -4640,6 +4658,11 @@ namespace Emby.Server.Implementations.Data return false; } + if (query.GroupBySeriesPresentationUniqueKey) + { + return false; + } + if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) { return false; diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 396bd8e88..7bd8fe2bf 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -6,19 +6,16 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.IO.Compression; using System.Net; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using System.Xml; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Services; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using IRequest = MediaBrowser.Model.Services.IRequest; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; -using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter; namespace Emby.Server.Implementations.HttpServer { @@ -193,50 +190,37 @@ namespace Emby.Server.Implementations.HttpServer /// public object ToOptimizedResult(IRequest request, T dto) { - var compressionType = GetCompressionType(request); - if (compressionType == null) - { - var contentType = request.ResponseContentType; - - switch (GetRealContentType(contentType)) - { - case "application/xml": - case "text/xml": - case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml - return SerializeToXmlString(dto); - - case "application/json": - case "text/json": - return _jsonSerializer.SerializeToString(dto); - } - } + var contentType = request.ResponseContentType; - // Do not use the memoryStreamFactory here, they don't place nice with compression - using (var ms = new MemoryStream()) + switch (GetRealContentType(contentType)) { - var contentType = request.ResponseContentType; - var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); + case "application/xml": + case "text/xml": + case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml + return SerializeToXmlString(dto); - writerFn(dto, ms); + case "application/json": + case "text/json": + return _jsonSerializer.SerializeToString(dto); + default: + { + var ms = new MemoryStream(); + var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); - ms.Position = 0; + writerFn(dto, ms); + + ms.Position = 0; - var responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase)) + { + return GetHttpResult(new byte[] { }, contentType, true); + } - return GetCompressedResult(ms, compressionType, responseHeaders, false, request.ResponseContentType).Result; + return GetHttpResult(ms, contentType, true); + } } } - private static Stream GetCompressionStream(Stream outputStream, string compressionType) - { - if (compressionType == "deflate") - return new DeflateStream(outputStream, CompressionMode.Compress, true); - if (compressionType == "gzip") - return new GZipStream(outputStream, CompressionMode.Compress, true); - - throw new NotSupportedException(compressionType); - } - public static string GetRealContentType(string contentType) { return contentType == null @@ -568,123 +552,47 @@ namespace Emby.Server.Implementations.HttpServer var contentType = options.ContentType; var responseHeaders = options.ResponseHeaders; - var requestedCompressionType = GetCompressionType(requestContext); + //var requestedCompressionType = GetCompressionType(requestContext); - if (!compress || string.IsNullOrEmpty(requestedCompressionType)) - { - var rangeHeader = requestContext.Headers.Get("Range"); - - if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) - { - return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) - { - OnComplete = options.OnComplete, - OnError = options.OnError, - FileShare = options.FileShare - }; - } - - if (!string.IsNullOrWhiteSpace(rangeHeader)) - { - var stream = await factoryFn().ConfigureAwait(false); - - return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) - { - OnComplete = options.OnComplete - }; - } - else - { - var stream = await factoryFn().ConfigureAwait(false); - - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); - - if (isHeadRequest) - { - stream.Dispose(); - - return GetHttpResult(new byte[] { }, contentType, true); - } - - return new StreamWriter(stream, contentType, _logger) - { - OnComplete = options.OnComplete, - OnError = options.OnError - }; - } - } + var rangeHeader = requestContext.Headers.Get("Range"); - using (var stream = await factoryFn().ConfigureAwait(false)) + if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) { - return await GetCompressedResult(stream, requestedCompressionType, responseHeaders, isHeadRequest, contentType).ConfigureAwait(false); + return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + { + OnComplete = options.OnComplete, + OnError = options.OnError, + FileShare = options.FileShare + }; } - } - private async Task GetCompressedResult(Stream stream, - string requestedCompressionType, - IDictionary responseHeaders, - bool isHeadRequest, - string contentType) - { - using (var reader = new MemoryStream()) + if (!string.IsNullOrWhiteSpace(rangeHeader)) { - await stream.CopyToAsync(reader).ConfigureAwait(false); - - reader.Position = 0; - var content = reader.ToArray(); + var stream = await factoryFn().ConfigureAwait(false); - if (content.Length >= 1024) + return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { - content = Compress(content, requestedCompressionType); - responseHeaders["Content-Encoding"] = requestedCompressionType; - } + OnComplete = options.OnComplete + }; + } + else + { + var stream = await factoryFn().ConfigureAwait(false); - responseHeaders["Vary"] = "Accept-Encoding"; - responseHeaders["Content-Length"] = content.Length.ToString(UsCulture); + responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); if (isHeadRequest) { + stream.Dispose(); + return GetHttpResult(new byte[] { }, contentType, true); } - return GetHttpResult(content, contentType, true, responseHeaders); - } - } - - private byte[] Compress(byte[] bytes, string compressionType) - { - if (compressionType == "deflate") - return Deflate(bytes); - - if (compressionType == "gzip") - return GZip(bytes); - - throw new NotSupportedException(compressionType); - } - - private byte[] Deflate(byte[] bytes) - { - // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream - // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream - using (var ms = new MemoryStream()) - using (var zipStream = new DeflateStream(ms, CompressionMode.Compress)) - { - zipStream.Write(bytes, 0, bytes.Length); - zipStream.Dispose(); - - return ms.ToArray(); - } - } - - private byte[] GZip(byte[] buffer) - { - using (var ms = new MemoryStream()) - using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) - { - zipStream.Write(buffer, 0, buffer.Length); - zipStream.Dispose(); - - return ms.ToArray(); + return new StreamWriter(stream, contentType, _logger) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; } } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index a6ed84f29..a277b693a 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -269,7 +269,41 @@ namespace Emby.Server.Implementations.Library return new List(); } - var excludeItemTypes = includeItemTypes.Length == 0 ? new[] + var mediaTypes = new List(); + + if (includeItemTypes.Length == 0) + { + foreach (var parent in parents.OfType()) + { + switch (parent.CollectionType) + { + case CollectionType.Books: + mediaTypes.Add(MediaType.Book); + break; + case CollectionType.Games: + mediaTypes.Add(MediaType.Game); + break; + case CollectionType.Music: + mediaTypes.Add(MediaType.Audio); + break; + case CollectionType.Photos: + mediaTypes.Add(MediaType.Photo); + mediaTypes.Add(MediaType.Video); + break; + case CollectionType.HomeVideos: + mediaTypes.Add(MediaType.Photo); + mediaTypes.Add(MediaType.Video); + break; + default: + mediaTypes.Add(MediaType.Video); + break; + } + } + + mediaTypes = mediaTypes.Distinct().ToList(); + } + + var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[] { typeof(Person).Name, typeof(Studio).Name, @@ -290,7 +324,8 @@ namespace Emby.Server.Implementations.Library IsVirtualItem = false, Limit = limit * 5, IsPlayed = isPlayed, - DtoOptions = options + DtoOptions = options, + MediaTypes = mediaTypes.ToArray() }; if (parents.Count == 0) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 9ac599846..99b5558a2 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1632,7 +1632,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - var episodesToDelete = (librarySeries.GetItems(new InternalItemsQuery + var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery { SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, @@ -1642,7 +1642,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV DtoOptions = new DtoOptions(true) })) - .Items .Where(i => i.LocationType == LocationType.FileSystem && _fileSystem.FileExists(i.Path)) .Skip(seriesTimer.KeepUpTo - 1) .ToList(); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1f9817e20..10aab4054 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1845,6 +1845,9 @@ namespace Emby.Server.Implementations.LiveTv public async Task AddInfoToProgramDto(List> tuples, List fields, User user = null) { var programTuples = new List>(); + var hasChannelImage = fields.Contains(ItemFields.ChannelImage); + var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo); + var hasServiceName = fields.Contains(ItemFields.ServiceName); foreach (var tuple in tuples) { @@ -1887,7 +1890,7 @@ namespace Emby.Server.Implementations.LiveTv dto.IsPremiere = program.IsPremiere; } - if (fields.Contains(ItemFields.ChannelInfo)) + if (hasChannelInfo || hasChannelImage) { var channel = GetInternalChannel(program.ChannelId); @@ -1897,7 +1900,7 @@ namespace Emby.Server.Implementations.LiveTv dto.MediaType = channel.MediaType; dto.ChannelNumber = channel.Number; - if (channel.HasImage(ImageType.Primary)) + if (hasChannelImage && channel.HasImage(ImageType.Primary)) { dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel); } @@ -1906,7 +1909,7 @@ namespace Emby.Server.Implementations.LiveTv var serviceName = program.ServiceName; - if (fields.Contains(ItemFields.ServiceName)) + if (hasServiceName) { dto.ServiceName = serviceName; } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 317f40a37..763ca9f24 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1089,7 +1089,7 @@ namespace Emby.Server.Implementations.Session { var folder = (Folder)item; - var itemsResult = folder.GetItems(new InternalItemsQuery(user) + var itemsResult = folder.GetItemList(new InternalItemsQuery(user) { Recursive = true, IsFolder = false, @@ -1104,7 +1104,7 @@ namespace Emby.Server.Implementations.Session }); - return FilterToSingleMediaType(itemsResult.Items) + return FilterToSingleMediaType(itemsResult) .OrderBy(i => i.SortName) .ToList(); } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 876c5d58b..03283031e 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.TV int? limit = null; if (!string.IsNullOrWhiteSpace(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId); + var series = _libraryManager.GetItemById(request.SeriesId) as Series; if (series != null) { @@ -51,17 +51,22 @@ namespace Emby.Server.Implementations.TV } } - if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue) + if (!string.IsNullOrWhiteSpace(presentationUniqueKey)) + { + return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request); + } + + if (limit.HasValue) { limit = limit.Value + 10; } var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(Series).Name }, - SortBy = new[] { ItemSortBy.SeriesDatePlayed }, + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.DatePlayed }, SortOrder = SortOrder.Descending, - PresentationUniqueKey = presentationUniqueKey, + SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, ParentId = parentIdGuid, Recursive = true, @@ -69,11 +74,12 @@ namespace Emby.Server.Implementations.TV { Fields = new List { - ItemFields.PresentationUniqueKey + ItemFields.SeriesPresentationUniqueKey } - } + }, + GroupBySeriesPresentationUniqueKey = true - }).Cast().Select(GetUniqueSeriesKey); + }).Cast().Select(GetUniqueSeriesKey); // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items, dtoOptions); @@ -94,7 +100,7 @@ namespace Emby.Server.Implementations.TV int? limit = null; if (!string.IsNullOrWhiteSpace(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId); + var series = _libraryManager.GetItemById(request.SeriesId) as Series; if (series != null) { @@ -103,28 +109,34 @@ namespace Emby.Server.Implementations.TV } } - if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue) + if (!string.IsNullOrWhiteSpace(presentationUniqueKey)) + { + return GetResult(GetNextUpEpisodes(request, user, new [] { presentationUniqueKey }, dtoOptions), request); + } + + if (limit.HasValue) { limit = limit.Value + 10; } var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { typeof(Series).Name }, - SortBy = new[] { ItemSortBy.SeriesDatePlayed }, + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.DatePlayed }, SortOrder = SortOrder.Descending, - PresentationUniqueKey = presentationUniqueKey, + SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions { Fields = new List { - ItemFields.PresentationUniqueKey + ItemFields.SeriesPresentationUniqueKey }, EnableImages = false - } + }, + GroupBySeriesPresentationUniqueKey = true - }, parentsFolders.Cast().ToList()).Cast().Select(GetUniqueSeriesKey); + }, parentsFolders.Cast().ToList()).Cast().Select(GetUniqueSeriesKey); // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items, dtoOptions); @@ -167,7 +179,12 @@ namespace Emby.Server.Implementations.TV .Where(i => i != null); } - private string GetUniqueSeriesKey(BaseItem series) + private string GetUniqueSeriesKey(Episode episode) + { + return episode.SeriesPresentationUniqueKey; + } + + private string GetUniqueSeriesKey(Series series) { return series.GetPresentationUniqueKey(); } diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index f54613384..863391eea 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.UserViews var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var result = view.GetItems(new InternalItemsQuery + var result = view.GetItemList(new InternalItemsQuery { CollapseBoxSetItems = false, Recursive = recursive, @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.UserViews }); - var items = result.Items.Select(i => + var items = result.Select(i => { var episode = i as Episode; if (episode != null) diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index cd2c4728f..5230da8a6 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -70,19 +70,19 @@ namespace Emby.Server.Implementations.UserViews if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) || string.Equals(view.ViewType, SpecialFolder.TvGenre, StringComparison.OrdinalIgnoreCase)) { - var userItemsResult = view.GetItems(new InternalItemsQuery + var userItemsResult = view.GetItemList(new InternalItemsQuery { CollapseBoxSetItems = false, DtoOptions = new DtoOptions(false) }); - return userItemsResult.Items.ToList(); + return userItemsResult.ToList(); } var isUsingCollectionStrip = IsUsingCollectionStrip(view); var recursive = isUsingCollectionStrip && !new[] { CollectionType.Channels, CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var result = view.GetItems(new InternalItemsQuery + var result = view.GetItemList(new InternalItemsQuery { User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, CollapseBoxSetItems = false, @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.UserViews DtoOptions = new DtoOptions(false) }); - var items = result.Items.Select(i => + var items = result.Select(i => { var episode = i as Episode; if (episode != null) diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index a1f891506..5d81e9ea5 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -61,9 +61,9 @@ namespace MediaBrowser.Api user == null ? _libraryManager.RootFolder : user.RootFolder : parentItem; - var result = ((Folder)item).GetItems(GetItemsQuery(request, user)); + var result = ((Folder)item).GetItemList(GetItemsQuery(request, user)); - return ToOptimizedResult(GetFilters(result.Items)); + return ToOptimizedResult(GetFilters(result.ToArray())); } private QueryFilters GetFilters(BaseItem[] items) diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 95366c79e..3f00f8ecf 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -438,14 +438,14 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("Series not found"); } - var seasons = (series.GetItems(new InternalItemsQuery(user) + var seasons = (series.GetItemList(new InternalItemsQuery(user) { IsMissing = request.IsMissing, IsVirtualUnaired = request.IsVirtualUnaired, IsSpecialSeason = request.IsSpecialSeason, AdjacentTo = request.AdjacentTo - })).Items.OfType(); + })).OfType(); var dtoOptions = GetDtoOptions(_authContext, request); diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 5d74cf218..a3f097f24 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -970,6 +970,27 @@ namespace MediaBrowser.Controller.Entities return GetItemsInternal(query); } + public IEnumerable GetItemList(InternalItemsQuery query) + { + query.EnableTotalRecordCount = false; + + if (query.ItemIds.Length > 0) + { + var result = LibraryManager.GetItemList(query); + + if (query.SortBy.Length == 0) + { + var ids = query.ItemIds.ToList(); + + // Try to preserve order + result = result.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); + } + return result; + } + + return GetItemsInternal(query).Items; + } + protected virtual QueryResult GetItemsInternal(InternalItemsQuery query) { if (SourceType == SourceType.Channel) @@ -1375,10 +1396,10 @@ namespace MediaBrowser.Controller.Entities query.IsVirtualItem = false; } - var itemsResult = GetItems(query); + var itemsResult = GetItemList(query); // Sweep through recursively and update status - var tasks = itemsResult.Items.Select(c => c.MarkPlayed(user, datePlayed, resetPosition)); + var tasks = itemsResult.Select(c => c.MarkPlayed(user, datePlayed, resetPosition)); await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -1390,7 +1411,7 @@ namespace MediaBrowser.Controller.Entities /// Task. public override async Task MarkUnplayed(User user) { - var itemsResult = GetItems(new InternalItemsQuery + var itemsResult = GetItemList(new InternalItemsQuery { User = user, Recursive = true, @@ -1400,14 +1421,14 @@ namespace MediaBrowser.Controller.Entities }); // Sweep through recursively and update status - var tasks = itemsResult.Items.Select(c => c.MarkUnplayed(user)); + var tasks = itemsResult.Select(c => c.MarkUnplayed(user)); await Task.WhenAll(tasks).ConfigureAwait(false); } public override bool IsPlayed(User user) { - var itemsResult = GetItems(new InternalItemsQuery(user) + var itemsResult = GetItemList(new InternalItemsQuery(user) { Recursive = true, IsFolder = false, @@ -1416,7 +1437,7 @@ namespace MediaBrowser.Controller.Entities }); - return itemsResult.Items + return itemsResult .All(i => i.IsPlayed(user)); } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 4f21aaa56..d9c8223c1 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -161,6 +161,7 @@ namespace MediaBrowser.Controller.Entities public string SeriesPresentationUniqueKey { get; set; } public bool GroupByPresentationUniqueKey { get; set; } + public bool GroupBySeriesPresentationUniqueKey { get; set; } public bool EnableTotalRecordCount { get; set; } public bool ForceDirect { get; set; } public Dictionary ExcludeProviderIds { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 8b73b80b0..229e63f13 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -193,7 +193,7 @@ namespace MediaBrowser.Controller.Entities.TV if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }; + query.IncludeItemTypes = new[] { typeof(Episode).Name }; } query.IsVirtualItem = false; query.Limit = 0; diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 0d2d69c94..4c44a613b 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Controller.Entities public override IEnumerable GetChildren(User user, bool includeLinkedChildren) { - var result = GetItems(new InternalItemsQuery + var result = GetItemList(new InternalItemsQuery { User = user, EnableTotalRecordCount = false, @@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities }); - return result.Items; + return result; } public override bool CanDelete() @@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Entities public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) { - var result = GetItems(new InternalItemsQuery + var result = GetItemList(new InternalItemsQuery { User = user, Recursive = true, @@ -117,7 +117,7 @@ namespace MediaBrowser.Controller.Entities }); - return result.Items.Where(i => UserViewBuilder.FilterItem(i, query)); + return result.Where(i => UserViewBuilder.FilterItem(i, query)); } protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index c992ac56a..eaadc6871 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -182,10 +182,7 @@ namespace MediaBrowser.Controller.Playlists DtoOptions = options }; - var itemsResult = folder.GetItems(query); - var items = itemsResult.Items; - - return items; + return folder.GetItemList(query); } return new[] { item }; diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index e088771b5..a294e4a3a 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -224,6 +224,7 @@ SeriesPresentationUniqueKey, DateLastRefreshed, DateLastSaved, - RefreshState + RefreshState, + ChannelImage } } diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 0a70c446f..2d5bf85e5 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Mono private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options) { // Allow all https requests - ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); + //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); var environmentInfo = GetEnvironmentInfo(); -- cgit v1.2.3 From 9b0e289602bc170474a756abed121da9fb3b1df1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Aug 2017 02:35:24 -0400 Subject: update databases --- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 462ff3e47..72c069073 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -131,6 +131,10 @@ namespace Emby.Server.Implementations.Data { queries.Add("PRAGMA temp_store = memory"); } + else + { + queries.Add("PRAGMA temp_store = file"); + } ////foreach (var query in queries) ////{ -- cgit v1.2.3 From db315c4e3279a0f7c5fd5507887b32c6436b90af Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 5 Aug 2017 15:02:33 -0400 Subject: update media source methods --- Emby.Dlna/Didl/DidlBuilder.cs | 4 +- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- .../Data/SqliteItemRepository.cs | 25 +--- .../Data/SqliteUserDataRepository.cs | 2 +- Emby.Server.Implementations/Dto/DtoService.cs | 4 +- .../Emby.Server.Implementations.csproj | 3 + .../Library/MediaSourceManager.cs | 15 ++- .../LiveTv/LiveTvManager.cs | 4 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 3 +- .../Localization/Ratings/es.txt | 1 + .../Localization/Ratings/ro.txt | 1 + .../MediaEncoder/EncodingManager.cs | 22 ++-- MediaBrowser.Controller/Entities/Audio/Audio.cs | 22 +++- MediaBrowser.Controller/Entities/IHasId.cs | 9 -- MediaBrowser.Controller/Entities/IHasImages.cs | 2 +- .../Entities/IHasMediaSources.cs | 4 +- MediaBrowser.Controller/Entities/IHasMetadata.cs | 1 + MediaBrowser.Controller/Entities/IHasUserData.cs | 7 +- MediaBrowser.Controller/Entities/Video.cs | 66 +++++++--- .../Library/IMediaSourceManager.cs | 8 +- .../LiveTv/LiveTvAudioRecording.cs | 4 +- MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 7 +- .../LiveTv/LiveTvVideoRecording.cs | 4 +- .../MediaBrowser.Controller.csproj | 1 - .../MediaEncoding/EncodingHelper.cs | 28 ++++- .../Persistence/IItemRepository.cs | 2 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 - .../MediaInfo/AudioImageProvider.cs | 4 +- .../MediaInfo/FFProbeVideoInfo.cs | 78 +++++------- .../MediaInfo/VideoImageProvider.cs | 138 +++++++-------------- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 9 +- 31 files changed, 235 insertions(+), 247 deletions(-) create mode 100644 Emby.Server.Implementations/Localization/Ratings/ro.txt delete mode 100644 MediaBrowser.Controller/Entities/IHasId.cs (limited to 'Emby.Server.Implementations/Data') diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index a1b692d98..71a049394 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -193,7 +193,7 @@ namespace Emby.Dlna.Didl { if (streamInfo == null) { - var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); + var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user); streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions { @@ -508,7 +508,7 @@ namespace Emby.Dlna.Didl if (streamInfo == null) { - var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); + var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user); streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 15d73e824..7164cf598 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -503,7 +503,7 @@ namespace Emby.Dlna.PlayTo var hasMediaSources = item as IHasMediaSources; var mediaSources = hasMediaSources != null - ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() + ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)) : new List(); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 80a5defd6..3825389bf 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -113,7 +113,7 @@ namespace Emby.Server.Implementations.Data { get { - return true; + return false; } } @@ -1972,20 +1972,7 @@ namespace Emby.Server.Implementations.Data /// Task{IEnumerable{ItemReview}}. public IEnumerable GetCriticReviews(Guid itemId) { - try - { - var path = Path.Combine(_criticReviewsPath, itemId + ".json"); - - return _jsonSerializer.DeserializeFromFile>(path); - } - catch (FileNotFoundException) - { - return new List(); - } - catch (IOException) - { - return new List(); - } + return new List(); } private readonly Task _cachedTask = Task.FromResult(true); @@ -1997,12 +1984,6 @@ namespace Emby.Server.Implementations.Data /// Task. public Task SaveCriticReviews(Guid itemId, IEnumerable criticReviews) { - _fileSystem.CreateDirectory(_criticReviewsPath); - - var path = Path.Combine(_criticReviewsPath, itemId + ".json"); - - _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); - return _cachedTask; } @@ -5610,7 +5591,7 @@ namespace Emby.Server.Implementations.Data return item; } - public IEnumerable GetMediaStreams(MediaStreamQuery query) + public List GetMediaStreams(MediaStreamQuery query) { CheckDisposed(); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index bf6388f5d..b76555bde 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Data { get { - return true; + return false; } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index a57ee0b9b..c50e5600a 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -359,11 +359,11 @@ namespace Emby.Server.Implementations.Dto { if (user == null) { - dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true).ToList(); + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true); } else { - dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true, user).ToList(); + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true, user); } } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 3d6e02816..ed69c9755 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -413,6 +413,9 @@ + + + - \ No newline at end of file diff --git a/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs deleted file mode 100644 index 2c7e6a487..000000000 --- a/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Threading; -using Mono.Nat; -using System.Threading.Tasks; - -namespace Emby.Server.Core.EntryPoints -{ - public class ExternalPortForwarding : IServerEntryPoint - { - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly IServerConfigurationManager _config; - private readonly IDeviceDiscovery _deviceDiscovery; - - private ITimer _timer; - private bool _isStarted; - private readonly ITimerFactory _timerFactory; - - public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory) - { - _logger = logmanager.GetLogger("PortMapper"); - _appHost = appHost; - _config = config; - _deviceDiscovery = deviceDiscovery; - _httpClient = httpClient; - _timerFactory = timerFactory; - } - - private string _lastConfigIdentifier; - private string GetConfigIdentifier() - { - var values = new List(); - var config = _config.Configuration; - - values.Add(config.EnableUPnP.ToString()); - values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture)); - values.Add(config.EnableHttps.ToString()); - values.Add(_appHost.EnableHttps.ToString()); - - return string.Join("|", values.ToArray()); - } - - void _config_ConfigurationUpdated(object sender, EventArgs e) - { - if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) - { - if (_isStarted) - { - DisposeNat(); - } - - Run(); - } - } - - public void Run() - { - NatUtility.Logger = _logger; - NatUtility.HttpClient = _httpClient; - - if (_config.Configuration.EnableUPnP) - { - Start(); - } - - _config.ConfigurationUpdated -= _config_ConfigurationUpdated; - _config.ConfigurationUpdated += _config_ConfigurationUpdated; - } - - private void Start() - { - _logger.Debug("Starting NAT discovery"); - NatUtility.EnabledProtocols = new List - { - NatProtocol.Pmp - }; - NatUtility.DeviceFound += NatUtility_DeviceFound; - - // Mono.Nat does never rise this event. The event is there however it is useless. - // You could remove it with no risk. - NatUtility.DeviceLost += NatUtility_DeviceLost; - - - NatUtility.StartDiscovery(); - - _timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); - - _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; - - _lastConfigIdentifier = GetConfigIdentifier(); - - _isStarted = true; - } - - private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) - { - if (_disposed) - { - return; - } - - var info = e.Argument; - - string usn; - if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty; - - string nt; - if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty; - - // Filter device type - if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1) - { - return; - } - - var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; - - if (info.Location == null) - { - return; - } - - lock (_usnsHandled) - { - if (_usnsHandled.Contains(identifier)) - { - return; - } - _usnsHandled.Add(identifier); - } - - _logger.Debug("Found NAT device: " + identifier); - - IPAddress address; - if (IPAddress.TryParse(info.Location.Host, out address)) - { - // The Handle method doesn't need the port - var endpoint = new IPEndPoint(address, info.Location.Port); - - IPAddress localAddress = null; - - try - { - var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false); - - Uri uri; - if (Uri.TryCreate(localAddressString, UriKind.Absolute, out uri)) - { - localAddressString = uri.Host; - - if (!IPAddress.TryParse(localAddressString, out localAddress)) - { - return; - } - } - } - catch (Exception ex) - { - return; - } - - if (_disposed) - { - return; - } - - _logger.Debug("Calling Nat.Handle on " + identifier); - NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp); - } - } - - private void ClearCreatedRules(object state) - { - lock (_createdRules) - { - _createdRules.Clear(); - } - lock (_usnsHandled) - { - _usnsHandled.Clear(); - } - } - - void NatUtility_DeviceFound(object sender, DeviceEventArgs e) - { - if (_disposed) - { - return; - } - - try - { - var device = e.Device; - _logger.Debug("NAT device found: {0}", device.LocalAddress.ToString()); - - CreateRules(device); - } - 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 - // CreatePortMap is invoked it can fails with a 718-ConflictInMappingEntry or not. That depends - // on the router's upnp implementation (specs says it should fail however some routers don't do it) - // It also can fail with others like 727-ExternalPortOnlySupportsWildcard, 728-NoPortMapsAvailable - // and those errors (upnp errors) could be useful for diagnosting. - - // Commenting out because users are reporting problems out of our control - //_logger.ErrorException("Error creating port forwarding rules", ex); - } - } - - private List _createdRules = new List(); - private List _usnsHandled = new List(); - private async void CreateRules(INatDevice device) - { - if (_disposed) - { - throw new ObjectDisposedException("PortMapper"); - } - - // On some systems the device discovered event seems to fire repeatedly - // This check will help ensure we're not trying to port map the same device over and over - - var address = device.LocalAddress.ToString(); - - lock (_createdRules) - { - if (!_createdRules.Contains(address)) - { - _createdRules.Add(address); - } - else - { - return; - } - } - - var success = await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false); - - if (success) - { - await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false); - } - } - - private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort) - { - _logger.Debug("Creating port map on port {0}", privatePort); - - try - { - await device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) - { - Description = _appHost.Name - - }).ConfigureAwait(false); - - return true; - } - catch (Exception ex) - { - _logger.Error("Error creating port map: " + ex.Message); - - return false; - } - } - - // As I said before, this method will be never invoked. You can remove it. - void NatUtility_DeviceLost(object sender, DeviceEventArgs e) - { - var device = e.Device; - _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString()); - } - - private bool _disposed = false; - public void Dispose() - { - _disposed = true; - DisposeNat(); - } - - private void DisposeNat() - { - _logger.Debug("Stopping NAT discovery"); - - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - - _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; - - try - { - // This is not a significant improvement - NatUtility.StopDiscovery(); - NatUtility.DeviceFound -= NatUtility_DeviceFound; - NatUtility.DeviceLost -= NatUtility_DeviceLost; - } - // Statements in try-block will no fail because StopDiscovery is a one-line - // method that was no chances to fail. - // public static void StopDiscovery () - // { - // searching.Reset(); - // } - // IMO you could remove the catch-block - catch (Exception ex) - { - _logger.ErrorException("Error stopping NAT Discovery", ex); - } - finally - { - _isStarted = false; - } - } - } -} \ No newline at end of file diff --git a/Emby.Server.Core/HttpServerFactory.cs b/Emby.Server.Core/HttpServerFactory.cs deleted file mode 100644 index e16cbea0e..000000000 --- a/Emby.Server.Core/HttpServerFactory.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.IO; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; -using Emby.Common.Implementations.Net; -using Emby.Server.Implementations.HttpServer; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Text; -using ServiceStack.Text.Jsv; -using SocketHttpListener.Primitives; - -namespace Emby.Server.Core -{ - /// - /// Class ServerFactory - /// - public static class HttpServerFactory - { - /// - /// Creates the server. - /// - /// IHttpServer. - public static IHttpServer CreateServer(IServerApplicationHost applicationHost, - ILogManager logManager, - IServerConfigurationManager config, - INetworkManager networkmanager, - IMemoryStreamFactory streamProvider, - string serverName, - string defaultRedirectpath, - ITextEncoding textEncoding, - ISocketFactory socketFactory, - ICryptoProvider cryptoProvider, - IJsonSerializer json, - IXmlSerializer xml, - IEnvironmentInfo environment, - ICertificate certificate, - IFileSystem fileSystem, - bool enableDualModeSockets) - { - var logger = logManager.GetLogger("HttpServer"); - - return new HttpListenerHost(applicationHost, - logger, - config, - serverName, - defaultRedirectpath, - networkmanager, - streamProvider, - textEncoding, - socketFactory, - cryptoProvider, - json, - xml, - environment, - certificate, - new StreamFactory(), - GetParseFn, - enableDualModeSockets, - fileSystem); - } - - private static Func GetParseFn(Type propertyType) - { - return s => JsvReader.GetParseFn(propertyType)(s); - } - } - - public class StreamFactory : IStreamFactory - { - public Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket) - { - var netSocket = (NetAcceptSocket)acceptSocket; - - return new SocketStream(netSocket.Socket, ownsSocket); - } - - public Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate) - { - var sslStream = (SslStream)stream; - var cert = (Certificate)certificate; - - return sslStream.AuthenticateAsServerAsync(cert.X509Certificate); - } - - public Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen) - { - return new SslStream(innerStream, leaveInnerStreamOpen); - } - } - - public class Certificate : ICertificate - { - public Certificate(X509Certificate x509Certificate) - { - X509Certificate = x509Certificate; - } - - public X509Certificate X509Certificate { get; private set; } - } -} diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs deleted file mode 100644 index ebc5e5e55..000000000 --- a/Emby.Server.Core/IO/LibraryMonitor.cs +++ /dev/null @@ -1,653 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; -using Emby.Server.Implementations.IO; - -namespace Emby.Server.Core.IO -{ - public class LibraryMonitor : ILibraryMonitor - { - /// - /// The file system watchers - /// - private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// The affected paths - /// - private readonly List _activeRefreshers = new List(); - - /// - /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. - /// - private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Any file name ending in any of these will be ignored by the watchers - /// - private readonly IReadOnlyList _alwaysIgnoreFiles = new List - { - "small.jpg", - "albumart.jpg", - - // WMC temp recording directories that will constantly be written to - "TempRec", - "TempSBE" - }; - - private readonly IReadOnlyList _alwaysIgnoreSubstrings = new List - { - // Synology - "eaDir", - "#recycle", - ".wd_tv", - ".actors" - }; - - private readonly IReadOnlyList _alwaysIgnoreExtensions = new List - { - // thumbs.db - ".db", - - // bts sync files - ".bts", - ".sync" - }; - - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - private void TemporarilyIgnore(string path) - { - _tempIgnoredPaths[path] = path; - } - - public void ReportFileSystemChangeBeginning(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - TemporarilyIgnore(path); - } - - public bool IsPathLocked(string path) - { - var lockedPaths = _tempIgnoredPaths.Keys.ToList(); - return lockedPaths.Any(i => _fileSystem.AreEqual(i, path) || _fileSystem.ContainsSubPath(i, path)); - } - - public async void ReportFileSystemChangeComplete(string path, bool refreshPath) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - // This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to. - // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds - // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata - await Task.Delay(45000).ConfigureAwait(false); - - string val; - _tempIgnoredPaths.TryRemove(path, out val); - - if (refreshPath) - { - try - { - ReportFileSystemChanged(path); - } - catch (Exception ex) - { - Logger.ErrorException("Error in ReportFileSystemChanged for {0}", ex, path); - } - } - } - - /// - /// Gets or sets the logger. - /// - /// The logger. - private ILogger Logger { get; set; } - - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private ITaskManager TaskManager { get; set; } - - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - - private readonly IFileSystem _fileSystem; - private readonly ITimerFactory _timerFactory; - private readonly IEnvironmentInfo _environmentInfo; - - /// - /// Initializes a new instance of the class. - /// - public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents, IEnvironmentInfo environmentInfo) - { - if (taskManager == null) - { - throw new ArgumentNullException("taskManager"); - } - - LibraryManager = libraryManager; - TaskManager = taskManager; - Logger = logManager.GetLogger(GetType().Name); - ConfigurationManager = configurationManager; - _fileSystem = fileSystem; - _timerFactory = timerFactory; - _environmentInfo = environmentInfo; - - systemEvents.Resume += _systemEvents_Resume; - } - - private void _systemEvents_Resume(object sender, EventArgs e) - { - Restart(); - } - - private void Restart() - { - Stop(); - - if (!_disposed) - { - Start(); - } - } - - private bool IsLibraryMonitorEnabaled(BaseItem item) - { - if (item is BasePluginFolder) - { - return false; - } - - var options = LibraryManager.GetLibraryOptions(item); - - if (options != null) - { - return options.EnableRealtimeMonitor; - } - - return false; - } - - public void Start() - { - LibraryManager.ItemAdded += LibraryManager_ItemAdded; - LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; - - var pathsToWatch = new List { }; - - var paths = LibraryManager - .RootFolder - .Children - .Where(IsLibraryMonitorEnabaled) - .OfType() - .SelectMany(f => f.PhysicalLocations) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(i => i) - .ToList(); - - foreach (var path in paths) - { - if (!ContainsParentFolder(pathsToWatch, path)) - { - pathsToWatch.Add(path); - } - } - - foreach (var path in pathsToWatch) - { - StartWatchingPath(path); - } - } - - private void StartWatching(BaseItem item) - { - if (IsLibraryMonitorEnabaled(item)) - { - StartWatchingPath(item.Path); - } - } - - /// - /// Handles the ItemRemoved event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) - { - if (e.Item.GetParent() is AggregateFolder) - { - StopWatchingPath(e.Item.Path); - } - } - - /// - /// Handles the ItemAdded event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (e.Item.GetParent() is AggregateFolder) - { - StartWatching(e.Item); - } - } - - /// - /// Examine a list of strings assumed to be file paths to see if it contains a parent of - /// the provided path. - /// - /// The LST. - /// The path. - /// true if [contains parent folder] [the specified LST]; otherwise, false. - /// path - private static bool ContainsParentFolder(IEnumerable lst, string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - path = path.TrimEnd(Path.DirectorySeparatorChar); - - return lst.Any(str => - { - //this should be a little quicker than examining each actual parent folder... - var compare = str.TrimEnd(Path.DirectorySeparatorChar); - - return path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar); - }); - } - - /// - /// Starts the watching path. - /// - /// The path. - private void StartWatchingPath(string path) - { - if (!_fileSystem.DirectoryExists(path)) - { - // Seeing a crash in the mono runtime due to an exception being thrown on a different thread - Logger.Info("Skipping realtime monitor for {0} because the path does not exist", path); - return; - } - - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) - { - if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase)) - { - // not supported - return; - } - } - - // Already being watched - if (_fileSystemWatchers.ContainsKey(path)) - { - return; - } - - // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel - Task.Run(() => - { - try - { - var newWatcher = new FileSystemWatcher(path, "*") - { - IncludeSubdirectories = true - }; - - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - newWatcher.InternalBufferSize = 32767; - } - - newWatcher.NotifyFilter = NotifyFilters.CreationTime | - NotifyFilters.DirectoryName | - NotifyFilters.FileName | - NotifyFilters.LastWrite | - NotifyFilters.Size | - NotifyFilters.Attributes; - - newWatcher.Created += watcher_Changed; - - // Seeing mono crashes on background threads we can't catch, testing if this might help - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - newWatcher.Deleted += watcher_Changed; - } - - newWatcher.Renamed += watcher_Changed; - newWatcher.Changed += watcher_Changed; - - newWatcher.Error += watcher_Error; - - if (_fileSystemWatchers.TryAdd(path, newWatcher)) - { - newWatcher.EnableRaisingEvents = true; - Logger.Info("Watching directory " + path); - } - else - { - newWatcher.Dispose(); - } - - } - catch (Exception ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - }); - } - - /// - /// Stops the watching path. - /// - /// The path. - private void StopWatchingPath(string path) - { - FileSystemWatcher watcher; - - if (_fileSystemWatchers.TryGetValue(path, out watcher)) - { - DisposeWatcher(watcher); - } - } - - /// - /// Disposes the watcher. - /// - /// The watcher. - private void DisposeWatcher(FileSystemWatcher watcher) - { - try - { - using (watcher) - { - Logger.Info("Stopping directory watching for path {0}", watcher.Path); - - watcher.EnableRaisingEvents = false; - } - } - catch - { - - } - finally - { - RemoveWatcherFromList(watcher); - } - } - - /// - /// Removes the watcher from list. - /// - /// The watcher. - private void RemoveWatcherFromList(FileSystemWatcher watcher) - { - FileSystemWatcher removed; - - _fileSystemWatchers.TryRemove(watcher.Path, out removed); - } - - /// - /// Handles the Error event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Error(object sender, ErrorEventArgs e) - { - var ex = e.GetException(); - var dw = (FileSystemWatcher)sender; - - Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); - - DisposeWatcher(dw); - } - - /// - /// Handles the Changed event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Changed(object sender, FileSystemEventArgs e) - { - try - { - //Logger.Debug("Changed detected of type " + e.ChangeType + " to " + e.FullPath); - - var path = e.FullPath; - - ReportFileSystemChanged(path); - } - catch (Exception ex) - { - Logger.ErrorException("Exception in ReportFileSystemChanged. Path: {0}", ex, e.FullPath); - } - } - - public void ReportFileSystemChanged(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - var filename = Path.GetFileName(path); - - var monitorPath = !string.IsNullOrEmpty(filename) && - !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && - !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) && - _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1); - - // Ignore certain files - var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); - - // If the parent of an ignored path has a change event, ignore that too - if (tempIgnorePaths.Any(i => - { - if (_fileSystem.AreEqual(i, path)) - { - Logger.Debug("Ignoring change to {0}", path); - return true; - } - - if (_fileSystem.ContainsSubPath(i, path)) - { - Logger.Debug("Ignoring change to {0}", path); - return true; - } - - // Go up a level - var parent = _fileSystem.GetDirectoryName(i); - if (!string.IsNullOrEmpty(parent)) - { - if (_fileSystem.AreEqual(parent, path)) - { - Logger.Debug("Ignoring change to {0}", path); - return true; - } - } - - return false; - - })) - { - monitorPath = false; - } - - if (monitorPath) - { - // Avoid implicitly captured closure - CreateRefresher(path); - } - } - - private void CreateRefresher(string path) - { - var parentPath = _fileSystem.GetDirectoryName(path); - - lock (_activeRefreshers) - { - var refreshers = _activeRefreshers.ToList(); - foreach (var refresher in refreshers) - { - // Path is already being refreshed - if (_fileSystem.AreEqual(path, refresher.Path)) - { - refresher.RestartTimer(); - return; - } - - // Parent folder is already being refreshed - if (_fileSystem.ContainsSubPath(refresher.Path, path)) - { - refresher.AddPath(path); - return; - } - - // New path is a parent - if (_fileSystem.ContainsSubPath(path, refresher.Path)) - { - refresher.ResetPath(path, null); - return; - } - - // They are siblings. Rebase the refresher to the parent folder. - if (string.Equals(parentPath, _fileSystem.GetDirectoryName(refresher.Path), StringComparison.Ordinal)) - { - refresher.ResetPath(parentPath, path); - return; - } - } - - var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo, LibraryManager); - newRefresher.Completed += NewRefresher_Completed; - _activeRefreshers.Add(newRefresher); - } - } - - private void NewRefresher_Completed(object sender, EventArgs e) - { - var refresher = (FileRefresher)sender; - DisposeRefresher(refresher); - } - - /// - /// Stops this instance. - /// - public void Stop() - { - LibraryManager.ItemAdded -= LibraryManager_ItemAdded; - LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; - - foreach (var watcher in _fileSystemWatchers.Values.ToList()) - { - watcher.Created -= watcher_Changed; - watcher.Deleted -= watcher_Changed; - watcher.Renamed -= watcher_Changed; - watcher.Changed -= watcher_Changed; - - try - { - watcher.EnableRaisingEvents = false; - } - catch (InvalidOperationException) - { - // Seeing this under mono on linux sometimes - // Collection was modified; enumeration operation may not execute. - } - - watcher.Dispose(); - } - - _fileSystemWatchers.Clear(); - DisposeRefreshers(); - } - - private void DisposeRefresher(FileRefresher refresher) - { - lock (_activeRefreshers) - { - refresher.Dispose(); - _activeRefreshers.Remove(refresher); - } - } - - private void DisposeRefreshers() - { - lock (_activeRefreshers) - { - foreach (var refresher in _activeRefreshers.ToList()) - { - refresher.Dispose(); - } - _activeRefreshers.Clear(); - } - } - - private bool _disposed; - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - _disposed = true; - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - Stop(); - } - } - } - - public class LibraryMonitorStartup : IServerEntryPoint - { - private readonly ILibraryMonitor _monitor; - - public LibraryMonitorStartup(ILibraryMonitor monitor) - { - _monitor = monitor; - } - - public void Run() - { - _monitor.Start(); - } - - public void Dispose() - { - } - } -} diff --git a/Emby.Server.Core/IO/MemoryStreamProvider.cs b/Emby.Server.Core/IO/MemoryStreamProvider.cs deleted file mode 100644 index f6dd1ecbc..000000000 --- a/Emby.Server.Core/IO/MemoryStreamProvider.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.IO; -using MediaBrowser.Model.IO; -using Microsoft.IO; - -namespace Emby.Server.Core.IO -{ - public class RecyclableMemoryStreamProvider : IMemoryStreamFactory - { - readonly RecyclableMemoryStreamManager _manager = new RecyclableMemoryStreamManager(); - - public MemoryStream CreateNew() - { - return _manager.GetStream(); - } - - public MemoryStream CreateNew(int capacity) - { - return _manager.GetStream("RecyclableMemoryStream", capacity); - } - - public MemoryStream CreateNew(byte[] buffer) - { - return _manager.GetStream("RecyclableMemoryStream", buffer, 0, buffer.Length); - } - - public bool TryGetBuffer(MemoryStream stream, out byte[] buffer) - { - buffer = stream.GetBuffer(); - return true; - } - } - - public class MemoryStreamProvider : IMemoryStreamFactory - { - public MemoryStream CreateNew() - { - return new MemoryStream(); - } - - public MemoryStream CreateNew(int capacity) - { - return new MemoryStream(capacity); - } - - public MemoryStream CreateNew(byte[] buffer) - { - return new MemoryStream(buffer); - } - - public bool TryGetBuffer(MemoryStream stream, out byte[] buffer) - { - buffer = stream.GetBuffer(); - return true; - } - } -} diff --git a/Emby.Server.Core/Localization/TextLocalizer.cs b/Emby.Server.Core/Localization/TextLocalizer.cs deleted file mode 100644 index 1e8ccbbfa..000000000 --- a/Emby.Server.Core/Localization/TextLocalizer.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Emby.Server.Implementations.Localization; - -namespace Emby.Server.Core.Localization -{ - public class TextLocalizer : ITextLocalizer - { - public string RemoveDiacritics(string text) - { - if (text == null) - { - throw new ArgumentNullException("text"); - } - - var chars = Normalize(text, NormalizationForm.FormD) - .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark); - - return Normalize(String.Concat(chars), NormalizationForm.FormC); - } - - private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true) - { - if (stripStringOnFailure) - { - try - { - return text.Normalize(form); - } - catch (ArgumentException) - { - // will throw if input contains invalid unicode chars - // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ - text = StripInvalidUnicodeCharacters(text); - return Normalize(text, form, false); - } - } - - try - { - return text.Normalize(form); - } - catch (ArgumentException) - { - // if it still fails, return the original text - return text; - } - } - - private static string StripInvalidUnicodeCharacters(string str) - { - var invalidCharactersRegex = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((? - - - - - - - - - - diff --git a/Emby.Server.Core/packages.config b/Emby.Server.Core/packages.config deleted file mode 100644 index 6311b55eb..000000000 --- a/Emby.Server.Core/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 567f139fd..702917832 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Activity { @@ -436,7 +437,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), Type = "ScheduledTaskFailed", - Overview = string.Join(Environment.NewLine, vals.ToArray()), + Overview = string.Join(Environment.NewLine, vals.ToArray(vals.Count)), ShortOverview = runningTime, Severity = LogSeverity.Error }); diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index e9b6f7a40..7720f8f2f 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -10,6 +10,7 @@ using MediaBrowser.Model.Activity; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using SQLitePCL.pretty; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Activity { @@ -94,13 +95,13 @@ namespace Emby.Server.Implementations.Activity var whereTextWithoutPaging = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); if (startIndex.HasValue && startIndex.Value > 0) { var pagingWhereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", pagingWhereText, @@ -109,7 +110,7 @@ namespace Emby.Server.Implementations.Activity var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.Activity result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } - result.Items = list.ToArray(); + result.Items = list.ToArray(list.Count); return result; }, ReadTransactionMode); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs new file mode 100644 index 000000000..f1f24660c --- /dev/null +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -0,0 +1,1749 @@ +using Emby.Common.Implementations; +using Emby.Common.Implementations.Archiving; +using Emby.Common.Implementations.IO; +using Emby.Common.Implementations.Reflection; +using Emby.Common.Implementations.ScheduledTasks; +using Emby.Common.Implementations.Serialization; +using Emby.Common.Implementations.TextEncoding; +using Emby.Common.Implementations.Xml; +using Emby.Dlna; +using Emby.Dlna.ConnectionManager; +using Emby.Dlna.ContentDirectory; +using Emby.Dlna.Main; +using Emby.Dlna.MediaReceiverRegistrar; +using Emby.Dlna.Ssdp; +using Emby.Drawing; +using Emby.Photos; +using Emby.Server.Implementations.Activity; +using Emby.Server.Implementations.Channels; +using Emby.Server.Implementations.Collections; +using Emby.Server.Implementations.Configuration; +using Emby.Server.Implementations.Data; +using Emby.Server.Implementations.Devices; +using Emby.Server.Implementations.Dto; +using Emby.Server.Implementations.FFMpeg; +using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.HttpServer.Security; +using Emby.Server.Implementations.IO; +using Emby.Server.Implementations.Library; +using Emby.Server.Implementations.LiveTv; +using Emby.Server.Implementations.Localization; +using Emby.Server.Implementations.MediaEncoder; +using Emby.Server.Implementations.Migrations; +using Emby.Server.Implementations.Notifications; +using Emby.Server.Implementations.Playlists; +using Emby.Server.Implementations.Security; +using Emby.Server.Implementations.Session; +using Emby.Server.Implementations.Social; +using Emby.Server.Implementations.TV; +using Emby.Server.Implementations.Updates; +using MediaBrowser.Api; +using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Progress; +using MediaBrowser.Common.Security; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Connect; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Controller.TV; +using MediaBrowser.LocalMetadata.Savers; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.News; +using MediaBrowser.Model.Reflection; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Social; +using MediaBrowser.Model.System; +using MediaBrowser.Model.Text; +using MediaBrowser.Model.Updates; +using MediaBrowser.Model.Xml; +using MediaBrowser.Providers.Chapters; +using MediaBrowser.Providers.Manager; +using MediaBrowser.Providers.Subtitles; +using MediaBrowser.WebDashboard.Api; +using MediaBrowser.XbmcMetadata.Providers; +using OpenSubtitlesHandler; +using ServiceStack; +using SocketHttpListener.Primitives; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.MediaEncoding.Subtitles; +using MediaBrowser.MediaEncoding.BdInfo; +using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions; + +namespace Emby.Server.Implementations +{ + /// + /// Class CompositionRoot + /// + public abstract class ApplicationHost : BaseApplicationHost, IServerApplicationHost, IDependencyContainer + { + /// + /// Gets the server configuration manager. + /// + /// The server configuration manager. + public IServerConfigurationManager ServerConfigurationManager + { + get { return (IServerConfigurationManager)ConfigurationManager; } + } + + /// + /// Gets the configuration manager. + /// + /// IConfigurationManager. + protected override IConfigurationManager GetConfigurationManager() + { + return new ServerConfigurationManager(ApplicationPaths, LogManager, XmlSerializer, FileSystemManager); + } + + /// + /// Gets or sets the server manager. + /// + /// The server manager. + private IServerManager ServerManager { get; set; } + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// + /// Gets or sets the library manager. + /// + /// The library manager. + internal ILibraryManager LibraryManager { get; set; } + /// + /// Gets or sets the directory watchers. + /// + /// The directory watchers. + private ILibraryMonitor LibraryMonitor { get; set; } + /// + /// Gets or sets the provider manager. + /// + /// The provider manager. + private IProviderManager ProviderManager { get; set; } + /// + /// Gets or sets the HTTP server. + /// + /// The HTTP server. + private IHttpServer HttpServer { get; set; } + private IDtoService DtoService { get; set; } + public IImageProcessor ImageProcessor { get; set; } + + /// + /// Gets or sets the media encoder. + /// + /// The media encoder. + private IMediaEncoder MediaEncoder { get; set; } + private ISubtitleEncoder SubtitleEncoder { get; set; } + + private IConnectManager ConnectManager { get; set; } + private ISessionManager SessionManager { get; set; } + + private ILiveTvManager LiveTvManager { get; set; } + + public ILocalizationManager LocalizationManager { get; set; } + + private IEncodingManager EncodingManager { get; set; } + private IChannelManager ChannelManager { get; set; } + private ISyncManager SyncManager { get; set; } + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + private IUserDataManager UserDataManager { get; set; } + private IUserRepository UserRepository { get; set; } + internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } + internal IItemRepository ItemRepository { get; set; } + private INotificationsRepository NotificationsRepository { get; set; } + + private INotificationManager NotificationManager { get; set; } + private ISubtitleManager SubtitleManager { get; set; } + private IChapterManager ChapterManager { get; set; } + private IDeviceManager DeviceManager { get; set; } + + internal IUserViewManager UserViewManager { get; set; } + + private IAuthenticationRepository AuthenticationRepository { get; set; } + private ISyncRepository SyncRepository { get; set; } + private ITVSeriesManager TVSeriesManager { get; set; } + private ICollectionManager CollectionManager { get; set; } + private IMediaSourceManager MediaSourceManager { get; set; } + private IPlaylistManager PlaylistManager { get; set; } + + /// + /// Gets or sets the installation manager. + /// + /// The installation manager. + protected IInstallationManager InstallationManager { get; private set; } + /// + /// Gets the security manager. + /// + /// The security manager. + protected ISecurityManager SecurityManager { get; private set; } + + /// + /// Gets or sets the zip client. + /// + /// The zip client. + protected IZipClient ZipClient { get; private set; } + + protected IAuthService AuthService { get; private set; } + + protected readonly StartupOptions StartupOptions; + private readonly string _releaseAssetFilename; + + internal IPowerManagement PowerManagement { get; private set; } + internal IImageEncoder ImageEncoder { get; private set; } + + private readonly Action _certificateGenerator; + private readonly Func _defaultUserNameFactory; + + /// + /// Initializes a new instance of the class. + /// + public ApplicationHost(ServerApplicationPaths applicationPaths, + ILogManager logManager, + StartupOptions options, + IFileSystem fileSystem, + IPowerManagement powerManagement, + string releaseAssetFilename, + IEnvironmentInfo environmentInfo, + IImageEncoder imageEncoder, + ISystemEvents systemEvents, + IMemoryStreamFactory memoryStreamFactory, + INetworkManager networkManager, + Action certificateGenerator, + Func defaultUsernameFactory) + : base(applicationPaths, + logManager, + fileSystem, + environmentInfo, + systemEvents, + memoryStreamFactory, + networkManager) + { + StartupOptions = options; + _certificateGenerator = certificateGenerator; + _releaseAssetFilename = releaseAssetFilename; + _defaultUserNameFactory = defaultUsernameFactory; + PowerManagement = powerManagement; + + ImageEncoder = imageEncoder; + + SetBaseExceptionMessage(); + + if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + fileSystem.AddShortcutHandler(new LnkShortcutHandler()); + } + + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + } + + private Version _version; + /// + /// Gets the current application version + /// + /// The application version. + public override Version ApplicationVersion + { + get + { + return _version ?? (_version = GetAssembly(GetType()).GetName().Version); + } + } + + public virtual bool SupportsRunningAsService + { + get + { + return false; + } + } + + /// + /// Gets the name. + /// + /// The name. + public override string Name + { + get + { + return "Emby Server"; + } + } + + public virtual bool IsRunningAsService + { + get + { + return false; + } + } + + private Assembly GetAssembly(Type type) + { + return type.GetTypeInfo().Assembly; + } + + public virtual bool SupportsAutoRunAtStartup + { + get + { + return EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; + } + } + + private void SetBaseExceptionMessage() + { + var builder = GetBaseExceptionMessage(ApplicationPaths); + + builder.Insert(0, string.Format("Version: {0}{1}", ApplicationVersion, Environment.NewLine)); + builder.Insert(0, "*** Error Report ***" + Environment.NewLine); + + LogManager.ExceptionMessagePrefix = builder.ToString(); + } + + /// + /// Runs the startup tasks. + /// + public override async Task RunStartupTasks() + { + await PerformPreInitMigrations().ConfigureAwait(false); + + await base.RunStartupTasks().ConfigureAwait(false); + + await MediaEncoder.Init().ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) + { + if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + { + ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false; + ServerConfigurationManager.SaveConfiguration(); + } + } + + Logger.Info("ServerId: {0}", SystemId); + Logger.Info("Core startup complete"); + HttpServer.GlobalResponse = null; + + PerformPostInitMigrations(); + Logger.Info("Post-init migrations complete"); + + foreach (var entryPoint in GetExports().ToList()) + { + var name = entryPoint.GetType().FullName; + Logger.Info("Starting entry point {0}", name); + var now = DateTime.UtcNow; + try + { + entryPoint.Run(); + } + catch (Exception ex) + { + Logger.ErrorException("Error in {0}", ex, name); + } + Logger.Info("Entry point completed: {0}. Duration: {1} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture), "ImageInfos"); + } + Logger.Info("All entry points have started"); + + LogManager.RemoveConsoleOutput(); + } + + protected override IJsonSerializer CreateJsonSerializer() + { + try + { + // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4 + Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0="); + } + catch + { + // Failing under mono + } + + var result = new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer")); + + ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; + ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; + ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; + ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; + ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; + ServiceStack.Text.JsConfig