diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/LibraryManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/LibraryManager.cs | 700 |
1 files changed, 345 insertions, 355 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 2934a5147..31af9370c 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -44,6 +44,9 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Tasks; +using Emby.Server.Implementations.Playlists; +using MediaBrowser.Providers.MediaInfo; +using MediaBrowser.Controller; namespace Emby.Server.Implementations.Library { @@ -71,12 +74,6 @@ namespace Emby.Server.Implementations.Library private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } /// <summary> - /// Gets the list of BasePluginFolders added by plugins - /// </summary> - /// <value>The plugin folders.</value> - private IVirtualFolderCreator[] PluginFolderCreators { get; set; } - - /// <summary> /// Gets the list of currently registered entity resolvers /// </summary> /// <value>The entity resolvers enumerable.</value> @@ -140,6 +137,7 @@ namespace Emby.Server.Implementations.Library private readonly Func<IProviderManager> _providerManagerFactory; private readonly Func<IUserViewManager> _userviewManager; public bool IsScanRunning { get; private set; } + private IServerApplicationHost _appHost; /// <summary> /// The _library items cache @@ -167,7 +165,7 @@ namespace Emby.Server.Implementations.Library /// <param name="userManager">The user manager.</param> /// <param name="configurationManager">The configuration manager.</param> /// <param name="userDataRepository">The user data repository.</param> - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Func<IProviderManager> providerManagerFactory, Func<IUserViewManager> userviewManager) + public LibraryManager(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Func<IProviderManager> providerManagerFactory, Func<IUserViewManager> userviewManager) { _logger = logger; _taskManager = taskManager; @@ -178,6 +176,7 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; + _appHost = appHost; _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -195,14 +194,12 @@ namespace Emby.Server.Implementations.Library /// <param name="itemComparers">The item comparers.</param> /// <param name="postscanTasks">The postscan tasks.</param> public void AddParts(IEnumerable<IResolverIgnoreRule> rules, - IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, IEnumerable<ILibraryPostScanTask> postscanTasks) { EntityResolutionIgnoreRules = rules.ToArray(); - PluginFolderCreators = pluginFolders.ToArray(); EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray(); IntroProviders = introProviders.ToArray(); @@ -302,7 +299,7 @@ namespace Emby.Server.Implementations.Library } else { - if (!(item is Video)) + if (!(item is Video) && !(item is LiveTvChannel)) { return; } @@ -311,13 +308,47 @@ namespace Emby.Server.Implementations.Library LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); } - public async Task DeleteItem(BaseItem item, DeleteOptions options) + public void DeleteItem(BaseItem item, DeleteOptions options) + { + DeleteItem(item, options, false); + } + + public void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + var parent = item.GetOwner() ?? item.GetParent(); + + DeleteItem(item, options, parent, notifyParentItem); + } + + public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem) { if (item == null) { throw new ArgumentNullException("item"); } + if (item.SourceType == SourceType.Channel) + { + if (options.DeleteFromExternalProvider) + { + try + { + var task = BaseItem.ChannelManager.DeleteItem(item); + Task.WaitAll(task); + } + catch (ArgumentException) + { + // channel no longer installed + } + } + options.DeleteFileLocation = false; + } + if (item is LiveTvProgram) { _logger.Debug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", @@ -335,10 +366,6 @@ namespace Emby.Server.Implementations.Library item.Id); } - var parent = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); - - var locationType = item.LocationType; - var children = item.IsFolder ? ((Folder)item).GetRecursiveChildren(false).ToList() : new List<BaseItem>(); @@ -361,7 +388,7 @@ namespace Emby.Server.Implementations.Library } } - if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual) + if (options.DeleteFileLocation && item.IsFileProtocol) { // Assume only the first is required // Add this flag to GetDeletePaths if required in the future @@ -407,33 +434,10 @@ namespace Emby.Server.Implementations.Library isRequiredForDelete = false; } - - if (parent != null) - { - var parentFolder = parent as Folder; - if (parentFolder != null) - { - await parentFolder.ValidateChildren(new SimpleProgress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false); - } - else - { - await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); - } - } - } - else if (parent != null) - { - var parentFolder = parent as Folder; - if (parentFolder != null) - { - parentFolder.RemoveChild(item); - } - else - { - await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); - } } + item.SetParent(null); + ItemRepository.DeleteItem(item.Id, CancellationToken.None); foreach (var child in children) { @@ -497,7 +501,7 @@ namespace Emby.Server.Implementations.Library private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive) { - if (string.IsNullOrWhiteSpace(key)) + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } @@ -544,7 +548,7 @@ namespace Emby.Server.Implementations.Library var fullPath = fileInfo.FullName; - if (string.IsNullOrWhiteSpace(collectionType) && parent != null) + if (string.IsNullOrEmpty(collectionType) && parent != null) { collectionType = GetContentTypeOverride(fullPath, true); } @@ -572,7 +576,26 @@ namespace Emby.Server.Implementations.Library // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + FileSystemMetadata[] files; + var isVf = args.IsVf; + + try + { + files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _appHost, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || isVf); + } + catch (Exception ex) + { + if (parent != null && parent.IsPhysicalRoot) + { + _logger.ErrorException("Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", ex, isPhysicalRoot, isVf); + + files = new FileSystemMetadata[] { }; + } + else + { + throw; + } + } // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action @@ -717,42 +740,43 @@ namespace Emby.Server.Implementations.Library } // Add in the plug-in folders - foreach (var child in PluginFolderCreators) + var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists"); + + _fileSystem.CreateDirectory(path); + + Folder folder = new PlaylistsFolder { - var folder = child.GetFolder(); + Path = path + }; - if (folder != null) + if (folder.Id.Equals(Guid.Empty)) + { + if (string.IsNullOrEmpty(folder.Path)) { - if (folder.Id == Guid.Empty) - { - if (string.IsNullOrWhiteSpace(folder.Path)) - { - folder.Id = GetNewItemId(folder.GetType().Name, folder.GetType()); - } - else - { - folder.Id = GetNewItemId(folder.Path, folder.GetType()); - } - } + folder.Id = GetNewItemId(folder.GetType().Name, folder.GetType()); + } + else + { + folder.Id = GetNewItemId(folder.Path, folder.GetType()); + } + } - var dbItem = GetItemById(folder.Id) as BasePluginFolder; + var dbItem = GetItemById(folder.Id) as BasePluginFolder; - if (dbItem != null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) - { - folder = dbItem; - } + if (dbItem != null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) + { + folder = dbItem; + } - if (folder.ParentId != rootFolder.Id) - { - folder.ParentId = rootFolder.Id; - folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - } + if (folder.ParentId != rootFolder.Id) + { + folder.ParentId = rootFolder.Id; + folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); + } - rootFolder.AddVirtualChild(folder); + rootFolder.AddVirtualChild(folder); - RegisterItem(folder); - } - } + RegisterItem(folder); return rootFolder; } @@ -798,16 +822,18 @@ namespace Emby.Server.Implementations.Library // If this returns multiple items it could be tricky figuring out which one is correct. // In most cases, the newest one will be and the others obsolete but not yet cleaned up - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } + //_logger.Info("FindByPath {0}", path); + var query = new InternalItemsQuery { Path = path, IsFolder = isFolder, - OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -957,7 +983,7 @@ namespace Emby.Server.Implementations.Library Path = path }; - CreateItem(item, CancellationToken.None); + CreateItem(item, null); } return item; @@ -997,7 +1023,7 @@ namespace Emby.Server.Implementations.Library // Just run the scheduled task so that the user can see it _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(); - return Task.FromResult(true); + return Task.CompletedTask; } /// <summary> @@ -1031,48 +1057,45 @@ namespace Emby.Server.Implementations.Library } } - private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken) + private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) { - _logger.Info("Validating media library"); - - // Ensure these objects are lazy loaded. - // Without this there is a deadlock that will need to be investigated var rootChildren = RootFolder.Children.ToList(); rootChildren = GetUserRootFolder().Children.ToList(); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); - progress.Report(.5); - // Start by just validating the children of the root, but go no further await RootFolder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(_fileSystem), recursive: false); - progress.Report(1); - await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); await GetUserRootFolder().ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(_fileSystem), recursive: false).ConfigureAwait(false); - progress.Report(2); // Quickly scan CollectionFolders for changes foreach (var folder in GetUserRootFolder().Children.OfType<Folder>().ToList()) { await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false); } - progress.Report(3); + } + + private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken) + { + _logger.Info("Validating media library"); + + await ValidateTopLibraryFolders(cancellationToken).ConfigureAwait(false); var innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(3 + pct * .72)); + innerProgress.RegisterAction(pct => progress.Report(pct * .96)); // Now validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(_fileSystem), recursive: true).ConfigureAwait(false); - progress.Report(75); + progress.Report(96); innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(75 + pct * .25)); + innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04))); // Run post-scan tasks await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false); @@ -1102,8 +1125,13 @@ namespace Emby.Server.Implementations.Library innerProgress.RegisterAction(pct => { - double innerPercent = currentNumComplete * 100 + pct; + double innerPercent = pct; + innerPercent /= 100; + innerPercent += currentNumComplete; + innerPercent /= numTasks; + innerPercent *= 100; + progress.Report(innerPercent); }); @@ -1163,7 +1191,19 @@ namespace Emby.Server.Implementations.Library Locations = _fileSystem.GetFilePaths(dir, false) .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) - .Select(_fileSystem.ResolveShortcut) + .Select(i => + { + try + { + return _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(i)); + } + catch (Exception ex) + { + _logger.ErrorException("Error resolving shortcut file {0}", ex, i); + return null; + } + }) + .Where(i => i != null) .OrderBy(i => i) .ToArray(), @@ -1197,7 +1237,7 @@ namespace Emby.Server.Implementations.Library { return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false) .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } /// <summary> @@ -1208,7 +1248,7 @@ namespace Emby.Server.Implementations.Library /// <exception cref="System.ArgumentNullException">id</exception> public BaseItem GetItemById(Guid id) { - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -1234,9 +1274,9 @@ namespace Emby.Server.Implementations.Library public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) { - if (query.Recursive && query.ParentId.HasValue) + if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) { - var parent = GetItemById(query.ParentId.Value); + var parent = GetItemById(query.ParentId); if (parent != null) { SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); @@ -1258,9 +1298,9 @@ namespace Emby.Server.Implementations.Library public int GetCount(InternalItemsQuery query) { - if (query.Recursive && query.ParentId.HasValue) + if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) { - var parent = GetItemById(query.ParentId.Value); + var parent = GetItemById(query.ParentId); if (parent != null) { SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); @@ -1391,7 +1431,7 @@ namespace Emby.Server.Implementations.Library return; } - var parents = query.AncestorIds.Select(i => GetItemById(new Guid(i))).ToList(); + var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); if (parents.All(i => { @@ -1406,13 +1446,13 @@ namespace Emby.Server.Implementations.Library })) { // Optimize by querying against top level views - query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).Select(i => i.ToString("N")).ToArray(); - query.AncestorIds = new string[] { }; + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); + query.AncestorIds = Array.Empty<Guid>(); // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid().ToString("N") }; + query.TopParentIds = new[] { Guid.NewGuid() }; } } } @@ -1430,9 +1470,9 @@ namespace Emby.Server.Implementations.Library public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query) { - if (query.Recursive && query.ParentId.HasValue) + if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) { - var parent = GetItemById(query.ParentId.Value); + var parent = GetItemById(query.ParentId); if (parent != null) { SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent }); @@ -1472,23 +1512,23 @@ namespace Emby.Server.Implementations.Library })) { // Optimize by querying against top level views - query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).Select(i => i.ToString("N")).ToArray(); + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid().ToString("N") }; + query.TopParentIds = new[] { Guid.NewGuid() }; } } else { // We need to be able to query from any arbitrary ancestor up the tree - query.AncestorIds = parents.SelectMany(i => i.GetIdsForAncestorQuery()).Select(i => i.ToString("N")).ToArray(); + query.AncestorIds = parents.SelectMany(i => i.GetIdsForAncestorQuery()).ToArray(); // Prevent searching in all libraries due to empty filter if (query.AncestorIds.Length == 0) { - query.AncestorIds = new[] { Guid.NewGuid().ToString("N") }; + query.AncestorIds = new[] { Guid.NewGuid() }; } } @@ -1498,22 +1538,21 @@ namespace Emby.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) { if (query.AncestorIds.Length == 0 && - !query.ParentId.HasValue && + query.ParentId.Equals(Guid.Empty) && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && - string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) && - string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey) && + string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && + string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) && query.ItemIds.Length == 0) { var userViews = _userviewManager().GetUserViews(new UserViewQuery { - UserId = user.Id.ToString("N"), + UserId = user.Id, IncludeHidden = true, IncludeExternalContent = allowExternalContent + }); - }, CancellationToken.None).Result; - - query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).Select(i => i.ToString("N")).ToArray(); + query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).ToArray(); } } @@ -1527,48 +1566,38 @@ namespace Emby.Server.Implementations.Library { return new[] { view.Id }; } - if (string.Equals(view.ViewType, CollectionType.Channels)) - { - var channelResult = BaseItem.ChannelManager.GetChannelsInternal(new ChannelQuery - { - UserId = user.Id.ToString("N") - - }, CancellationToken.None).Result; - - return channelResult.Items.Select(i => i.Id); - } // Translate view into folders - if (view.DisplayParentId != Guid.Empty) + if (!view.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.DisplayParentId); if (displayParent != null) { return GetTopParentIdsForQuery(displayParent, user); } - return new Guid[] { }; + return Array.Empty<Guid>(); } - if (view.ParentId != Guid.Empty) + if (!view.ParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.ParentId); if (displayParent != null) { return GetTopParentIdsForQuery(displayParent, user); } - return new Guid[] { }; + return Array.Empty<Guid>(); } // Handle grouping - if (user != null && !string.IsNullOrWhiteSpace(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) + if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) { - return user.RootFolder + return GetUserRootFolder() .GetChildren(user, true) .OfType<CollectionFolder>() - .Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase)) + .Where(i => string.IsNullOrEmpty(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase)) .Where(i => user.IsFolderGrouped(i.Id)) .SelectMany(i => GetTopParentIdsForQuery(i, user)); } - return new Guid[] { }; + return Array.Empty<Guid>(); } var collectionFolder = item as CollectionFolder; @@ -1582,7 +1611,7 @@ namespace Emby.Server.Implementations.Library { return new[] { topParent.Id }; } - return new Guid[] { }; + return Array.Empty<Guid>(); } /// <summary> @@ -1737,7 +1766,7 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } - public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<Tuple<string, SortOrder>> orderByList) + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderByList) { var isFirst = true; @@ -1802,9 +1831,9 @@ namespace Emby.Server.Implementations.Library /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public void CreateItem(BaseItem item, CancellationToken cancellationToken) + public void CreateItem(BaseItem item, BaseItem parent) { - CreateItems(new[] { item }, item.GetParent(), cancellationToken); + CreateItems(new[] { item }, parent, CancellationToken.None); } /// <summary> @@ -1828,6 +1857,12 @@ namespace Emby.Server.Implementations.Library { foreach (var item in list) { + // With the live tv guide this just creates too much noise + if (item.SourceType != SourceType.Library) + { + continue; + } + try { ItemAdded(this, new ItemChangeEventArgs @@ -1854,46 +1889,65 @@ namespace Emby.Server.Implementations.Library /// <summary> /// Updates the item. /// </summary> - /// <param name="item">The item.</param> - /// <param name="updateReason">The update reason.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public void UpdateItem(BaseItem item, ItemUpdateType updateReason, CancellationToken cancellationToken) + public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - var locationType = item.LocationType; - if (locationType != LocationType.Remote && locationType != LocationType.Virtual) + foreach (var item in items) { - _providerManagerFactory().SaveMetadata(item, updateReason); - } + if (item.IsFileProtocol) + { + _providerManagerFactory().SaveMetadata(item, updateReason); + } - item.DateLastSaved = DateTime.UtcNow; + item.DateLastSaved = DateTime.UtcNow; - var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; - _logger.Debug("Saving {0} to database.", logName); + RegisterItem(item); + } - ItemRepository.SaveItem(item, cancellationToken); + //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; + //_logger.Debug("Saving {0} to database.", logName); - RegisterItem(item); + ItemRepository.SaveItems(items, cancellationToken); if (ItemUpdated != null) { - try + foreach (var item in items) { - ItemUpdated(this, new ItemChangeEventArgs + // With the live tv guide this just creates too much noise + if (item.SourceType != SourceType.Library) { - Item = item, - Parent = item.GetParent(), - UpdateReason = updateReason - }); - } - catch (Exception ex) - { - _logger.ErrorException("Error in ItemUpdated event handler", ex); + continue; + } + + try + { + ItemUpdated(this, new ItemChangeEventArgs + { + Item = item, + Parent = parent, + UpdateReason = updateReason + }); + } + catch (Exception ex) + { + _logger.ErrorException("Error in ItemUpdated event handler", ex); + } } } } /// <summary> + /// Updates the item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateReason">The update reason.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + { + UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken); + } + + /// <summary> /// Reports the item removed. /// </summary> /// <param name="item">The item.</param> @@ -1995,12 +2049,12 @@ namespace Emby.Server.Implementations.Library public string GetContentType(BaseItem item) { string configuredContentType = GetConfiguredContentType(item, false); - if (!string.IsNullOrWhiteSpace(configuredContentType)) + if (!string.IsNullOrEmpty(configuredContentType)) { return configuredContentType; } configuredContentType = GetConfiguredContentType(item, true); - if (!string.IsNullOrWhiteSpace(configuredContentType)) + if (!string.IsNullOrEmpty(configuredContentType)) { return configuredContentType; } @@ -2011,14 +2065,14 @@ namespace Emby.Server.Implementations.Library { var type = GetTopFolderContentType(item); - if (!string.IsNullOrWhiteSpace(type)) + if (!string.IsNullOrEmpty(type)) { return type; } return item.GetParents() .Select(GetConfiguredContentType) - .LastOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .LastOrDefault(i => !string.IsNullOrEmpty(i)); } public string GetConfiguredContentType(BaseItem item) @@ -2043,7 +2097,7 @@ namespace Emby.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); + var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); if (nameValuePair != null) { return nameValuePair.Value; @@ -2058,16 +2112,21 @@ namespace Emby.Server.Implementations.Library return null; } - while (!(item.GetParent() is AggregateFolder) && item.GetParent() != null) + while (!item.ParentId.Equals(Guid.Empty)) { - item = item.GetParent(); + var parent = item.GetParent(); + if (parent == null || parent is AggregateFolder) + { + break; + } + item = parent; } return GetUserRootFolder().Children .OfType<ICollectionFolder>() .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path)) .Select(i => i.CollectionType) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); @@ -2076,18 +2135,16 @@ namespace Emby.Server.Implementations.Library public UserView GetNamedView(User user, string name, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { - return GetNamedView(user, name, null, viewType, sortName, cancellationToken); + return GetNamedView(user, name, Guid.Empty, viewType, sortName); } public UserView GetNamedView(string name, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, "views"); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views"); path = Path.Combine(path, _fileSystem.GetValidFilename(viewType)); @@ -2111,32 +2168,15 @@ namespace Emby.Server.Implementations.Library ForcedSortName = sortName }; - CreateItem(item, cancellationToken); + CreateItem(item, null); refresh = true; } - if (!refresh) - { - refresh = DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - } - - if (!refresh && item.DisplayParentId != Guid.Empty) - { - var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; - } - if (refresh) { item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem) - { - // Not sure why this is necessary but need to figure it out - // View images are not getting utilized without this - ForceSave = true - - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Normal); } return item; @@ -2144,12 +2184,12 @@ namespace Emby.Server.Implementations.Library public UserView GetNamedView(User user, string name, - string parentId, + Guid parentId, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { - var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty) + (viewType ?? string.Empty); + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); + var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); var id = GetNewItemId(idValues, typeof(UserView)); @@ -2174,19 +2214,16 @@ namespace Emby.Server.Implementations.Library UserId = user.Id }; - if (!string.IsNullOrWhiteSpace(parentId)) - { - item.DisplayParentId = new Guid(parentId); - } + item.DisplayParentId = parentId; - CreateItem(item, cancellationToken); + CreateItem(item, null); isNew = true; } var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && item.DisplayParentId != Guid.Empty) + if (!refresh && !item.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2207,8 +2244,7 @@ namespace Emby.Server.Implementations.Library public UserView GetShadowView(BaseItem parent, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { if (parent == null) { @@ -2244,14 +2280,14 @@ namespace Emby.Server.Implementations.Library item.DisplayParentId = parentId; - CreateItem(item, cancellationToken); + CreateItem(item, null); isNew = true; } var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && item.DisplayParentId != Guid.Empty) + if (!refresh && !item.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2271,19 +2307,19 @@ namespace Emby.Server.Implementations.Library } public UserView GetNamedView(string name, - string parentId, + Guid parentId, string viewType, string sortName, - string uniqueId, - CancellationToken cancellationToken) + string uniqueId) { - if (string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } - var idValues = "37_namedview_" + name + (parentId ?? string.Empty) + (viewType ?? string.Empty); - if (!string.IsNullOrWhiteSpace(uniqueId)) + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); + var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); + if (!string.IsNullOrEmpty(uniqueId)) { idValues += uniqueId; } @@ -2310,12 +2346,9 @@ namespace Emby.Server.Implementations.Library ForcedSortName = sortName }; - if (!string.IsNullOrWhiteSpace(parentId)) - { - item.DisplayParentId = new Guid(parentId); - } + item.DisplayParentId = parentId; - CreateItem(item, cancellationToken); + CreateItem(item, null); isNew = true; } @@ -2323,12 +2356,12 @@ namespace Emby.Server.Implementations.Library if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase)) { item.ViewType = viewType; - item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken); + item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && item.DisplayParentId != Guid.Empty) + if (!refresh && !item.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2346,6 +2379,13 @@ namespace Emby.Server.Implementations.Library return item; } + public void AddExternalSubtitleStreams(List<MediaStream> streams, + string videoPath, + string[] files) + { + new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); + } + public bool IsVideoFile(string path, LibraryOptions libraryOptions) { var resolver = new VideoResolver(GetNamingOptions()); @@ -2370,19 +2410,25 @@ namespace Emby.Server.Implementations.Library public int? GetSeasonNumberFromPath(string path) { - return new SeasonPathParser(GetNamingOptions(), new RegexProvider()).Parse(path, true, true).SeasonNumber; + return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber; } - public bool FillMissingEpisodeNumbersFromPath(Episode episode) + public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { + var series = episode.Series; + bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); + if (!isAbsoluteNaming.Value) + { + // In other words, no filter applied + isAbsoluteNaming = null; + } + var resolver = new EpisodeResolver(GetNamingOptions()); var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; - var locationType = episode.LocationType; - - var episodeInfo = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? - resolver.Resolve(episode.Path, isFolder) : + var episodeInfo = episode.IsFileProtocol ? + resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) : new Emby.Naming.TV.EpisodeInfo(); if (episodeInfo == null) @@ -2428,105 +2474,67 @@ namespace Emby.Server.Implementations.Library changed = true; } } - - if (!episode.ParentIndexNumber.HasValue) - { - var season = episode.Season; - - if (season != null) - { - episode.ParentIndexNumber = season.IndexNumber; - } - - if (episode.ParentIndexNumber.HasValue) - { - changed = true; - } - } } else { - if (!episode.IndexNumber.HasValue) + if (!episode.IndexNumber.HasValue || forceRefresh) { - episode.IndexNumber = episodeInfo.EpisodeNumber; - - if (episode.IndexNumber.HasValue) + if (episode.IndexNumber != episodeInfo.EpisodeNumber) { changed = true; } + episode.IndexNumber = episodeInfo.EpisodeNumber; } - if (!episode.IndexNumberEnd.HasValue) + if (!episode.IndexNumberEnd.HasValue || forceRefresh) { - episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; - - if (episode.IndexNumberEnd.HasValue) + if (episode.IndexNumberEnd != episodeInfo.EndingEpsiodeNumber) { changed = true; } + episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; } - if (!episode.ParentIndexNumber.HasValue) + if (!episode.ParentIndexNumber.HasValue || forceRefresh) { - episode.ParentIndexNumber = episodeInfo.SeasonNumber; - - if (!episode.ParentIndexNumber.HasValue) - { - var season = episode.Season; - - if (season != null) - { - episode.ParentIndexNumber = season.IndexNumber; - } - } - - if (episode.ParentIndexNumber.HasValue) + if (episode.ParentIndexNumber != episodeInfo.SeasonNumber) { changed = true; } + episode.ParentIndexNumber = episodeInfo.SeasonNumber; } } - return changed; - } - - public NamingOptions GetNamingOptions() - { - return GetNamingOptions(true); - } - - public NamingOptions GetNamingOptions(bool allowOptimisticEpisodeDetection) - { - if (!allowOptimisticEpisodeDetection) + if (!episode.ParentIndexNumber.HasValue) { - if (_namingOptionsWithoutOptimisticEpisodeDetection == null) - { - var namingOptions = new ExtendedNamingOptions(); + var season = episode.Season; - InitNamingOptions(namingOptions); - namingOptions.EpisodeExpressions = namingOptions.EpisodeExpressions - .Where(i => i.IsNamed && !i.IsOptimistic) - .ToList(); - - _namingOptionsWithoutOptimisticEpisodeDetection = namingOptions; + if (season != null) + { + episode.ParentIndexNumber = season.IndexNumber; } - return _namingOptionsWithoutOptimisticEpisodeDetection; + if (episode.ParentIndexNumber.HasValue) + { + changed = true; + } } + return changed; + } + + public NamingOptions GetNamingOptions() + { return GetNamingOptionsInternal(); } - private NamingOptions _namingOptionsWithoutOptimisticEpisodeDetection; private NamingOptions _namingOptions; private string[] _videoFileExtensions; private NamingOptions GetNamingOptionsInternal() { if (_namingOptions == null) { - var options = new ExtendedNamingOptions(); - - InitNamingOptions(options); + var options = new NamingOptions(); _namingOptions = options; _videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray(); @@ -2535,27 +2543,6 @@ namespace Emby.Server.Implementations.Library return _namingOptions; } - private void InitNamingOptions(NamingOptions options) - { - // These cause apps to have problems - options.AudioFileExtensions.Remove(".m3u"); - options.AudioFileExtensions.Remove(".wpl"); - - //if (!libraryOptions.EnableArchiveMediaFiles) - { - options.AudioFileExtensions.Remove(".rar"); - options.AudioFileExtensions.Remove(".zip"); - } - - //if (!libraryOptions.EnableArchiveMediaFiles) - { - options.VideoFileExtensions.Remove(".rar"); - options.VideoFileExtensions.Remove(".zip"); - } - - options.VideoFileExtensions.Add(".tp"); - } - public ItemLookupInfo ParseName(string name) { var resolver = new VideoResolver(GetNamingOptions()); @@ -2606,12 +2593,11 @@ namespace Emby.Server.Implementations.Library { video = dbItem; } - else - { - // item is new - video.ExtraType = ExtraType.Trailer; - } - video.TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer }; + + video.ParentId = Guid.Empty; + video.OwnerId = owner.Id; + video.ExtraType = ExtraType.Trailer; + video.TrailerTypes = new [] { TrailerType.LocalTrailer }; return video; @@ -2625,7 +2611,7 @@ namespace Emby.Server.Implementations.Library { var namingOptions = GetNamingOptions(); - var files = fileSystemChildren.Where(i => i.IsDirectory) + var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) .Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); @@ -2653,6 +2639,9 @@ namespace Emby.Server.Implementations.Library video = dbItem; } + video.ParentId = Guid.Empty; + video.OwnerId = owner.Id; + SetExtraTypeFromFilename(video); return video; @@ -2756,7 +2745,7 @@ namespace Emby.Server.Implementations.Library private void SetExtraTypeFromFilename(Video item) { - var resolver = new ExtraResolver(GetNamingOptions(), new RegexProvider()); + var resolver = new ExtraResolver(GetNamingOptions()); var result = resolver.GetExtraInfo(item.Path); @@ -2841,7 +2830,7 @@ namespace Emby.Server.Implementations.Library ItemRepository.UpdatePeople(item.Id, people); } - public async Task<ItemImageInfo> ConvertImageToLocal(IHasMetadata item, ItemImageInfo image, int imageIndex) + public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) { foreach (var url in image.Path.Split('|')) { @@ -2872,7 +2861,7 @@ namespace Emby.Server.Implementations.Library throw new InvalidOperationException(); } - public void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary) + public async Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) { @@ -2910,7 +2899,7 @@ namespace Emby.Server.Implementations.Library { var path = Path.Combine(virtualFolderPath, collectionType + ".collection"); - _fileSystem.WriteAllBytes(path, new byte[] { }); + _fileSystem.WriteAllBytes(path, Array.Empty<byte>()); } CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); @@ -2925,26 +2914,30 @@ namespace Emby.Server.Implementations.Library } finally { - Task.Run(() => + if (refreshLibrary) { - // No need to start if scanning the library because it will handle it - if (refreshLibrary) - { - ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); + await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); - _libraryMonitorFactory().Start(); - } - }); + StartScanInBackground(); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitorFactory().Start(); + } } } + private void StartScanInBackground() + { + Task.Run(() => + { + // No need to start if scanning the library because it will handle it + ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); + }); + } + private bool ValidateNetworkPath(string path) { //if (Environment.OSVersion.Platform == PlatformID.Win32NT) @@ -3003,7 +2996,7 @@ namespace Emby.Server.Implementations.Library lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); } - _fileSystem.CreateShortcut(lnk, path); + _fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path)); RemoveContentTypeOverrides(path); @@ -3079,7 +3072,7 @@ namespace Emby.Server.Implementations.Library } } - public void RemoveVirtualFolder(string name, bool refreshLibrary) + public async Task RemoveVirtualFolder(string name, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) { @@ -3103,23 +3096,20 @@ namespace Emby.Server.Implementations.Library } finally { - Task.Run(() => + CollectionFolder.OnCollectionFolderChange(); + + if (refreshLibrary) { - // No need to start if scanning the library because it will handle it - if (refreshLibrary) - { - ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); + await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); - _libraryMonitorFactory().Start(); - } - }); + StartScanInBackground(); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitorFactory().Start(); + } } } @@ -3157,7 +3147,7 @@ namespace Emby.Server.Implementations.Library public void RemoveMediaPath(string virtualFolderName, string mediaPath) { - if (string.IsNullOrWhiteSpace(mediaPath)) + if (string.IsNullOrEmpty(mediaPath)) { throw new ArgumentNullException("mediaPath"); } @@ -3172,7 +3162,7 @@ namespace Emby.Server.Implementations.Library var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true) .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) - .FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(f => _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(f)).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(shortcut)) { |
