Răsfoiți Sursa

update owned items

Luke Pulverenti 7 ani în urmă
părinte
comite
cdd79ec7e2
30 a modificat fișierele cu 373 adăugiri și 177 ștergeri
  1. 1 1
      Emby.Server.Implementations/Collections/CollectionImageProvider.cs
  2. 38 12
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  3. 1 1
      Emby.Server.Implementations/Dto/DtoService.cs
  4. 6 4
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  5. 45 14
      Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
  6. 1 1
      Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
  7. 1 1
      Emby.Server.Implementations/IO/FileRefresher.cs
  8. 24 12
      Emby.Server.Implementations/Library/LibraryManager.cs
  9. 5 4
      Emby.Server.Implementations/Library/UserManager.cs
  10. 4 0
      Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
  11. 27 27
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  12. 2 4
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  13. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs
  14. 1 1
      Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
  15. 1 1
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  16. 85 23
      MediaBrowser.Controller/Entities/BaseItem.cs
  17. 1 1
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  18. 67 36
      MediaBrowser.Controller/Entities/Folder.cs
  19. 1 1
      MediaBrowser.Controller/Entities/IHasTrailers.cs
  20. 0 6
      MediaBrowser.Controller/Entities/ItemImageInfo.cs
  21. 14 1
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  22. 8 2
      MediaBrowser.Controller/Entities/TV/Series.cs
  23. 3 0
      MediaBrowser.Controller/Entities/User.cs
  24. 26 2
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  25. 0 1
      MediaBrowser.Model/LiveTv/LiveTvOptions.cs
  26. 1 14
      MediaBrowser.Providers/Manager/GenericPriorityQueue.cs
  27. 2 3
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  28. 1 2
      MediaBrowser.Providers/Manager/MetadataService.cs
  29. 5 0
      MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
  30. 1 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

+ 1 - 1
Emby.Server.Implementations/Collections/CollectionImageProvider.cs

@@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.Collections
                         return subItem;
                     }
 
-                    var parent = subItem.GetParent();
+                    var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent();
 
                     if (parent != null && parent.HasImage(ImageType.Primary))
                     {

+ 38 - 12
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -253,6 +253,7 @@ namespace Emby.Server.Implementations.Data
                     AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
+                    AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
 
                     existingColumnNames = GetColumnNames(db, "ItemValues");
                     AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames);
@@ -459,7 +460,8 @@ namespace Emby.Server.Implementations.Data
             "AlbumArtists",
             "ExternalId",
             "SeriesPresentationUniqueKey",
-            "ShowId"
+            "ShowId",
+            "OwnerId"
         };
 
         private readonly string[] _mediaStreamSaveColumns =
@@ -580,7 +582,8 @@ namespace Emby.Server.Implementations.Data
                 "AlbumArtists",
                 "ExternalId",
                 "SeriesPresentationUniqueKey",
-                "ShowId"
+                "ShowId",
+                "OwnerId"
             };
 
             var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -784,13 +787,14 @@ namespace Emby.Server.Implementations.Data
             saveItemStatement.TryBind("@PremiereDate", item.PremiereDate);
             saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
 
-            if (item.ParentId == Guid.Empty)
+            var parentId = item.ParentId;
+            if (parentId == Guid.Empty)
             {
                 saveItemStatement.TryBindNull("@ParentId");
             }
             else
             {
-                saveItemStatement.TryBind("@ParentId", item.ParentId);
+                saveItemStatement.TryBind("@ParentId", parentId);
             }
 
             if (item.Genres.Count > 0)
@@ -1057,6 +1061,16 @@ namespace Emby.Server.Implementations.Data
                 saveItemStatement.TryBindNull("@ShowId");
             }
 
+            var ownerId = item.OwnerId;
+            if (ownerId != Guid.Empty)
+            {
+                saveItemStatement.TryBind("@OwnerId", ownerId);
+            }
+            else
+            {
+                saveItemStatement.TryBindNull("@OwnerId");
+            }
+
             saveItemStatement.MoveNext();
         }
 
@@ -1156,16 +1170,14 @@ namespace Emby.Server.Implementations.Data
                    delimeter +
                    image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
                    delimeter +
-                   image.Type +
-                   delimeter +
-                   image.IsPlaceholder;
+                   image.Type;
         }
 
         public ItemImageInfo ItemImageInfoFromValueString(string value)
         {
             var parts = value.Split(new[] { '*' }, StringSplitOptions.None);
 
-            if (parts.Length != 4)
+            if (parts.Length < 3)
             {
                 return null;
             }
@@ -1173,9 +1185,18 @@ namespace Emby.Server.Implementations.Data
             var image = new ItemImageInfo();
 
             image.Path = parts[0];
-            image.DateModified = new DateTime(long.Parse(parts[1], CultureInfo.InvariantCulture), DateTimeKind.Utc);
-            image.Type = (ImageType)Enum.Parse(typeof(ImageType), parts[2], true);
-            image.IsPlaceholder = string.Equals(parts[3], true.ToString(), StringComparison.OrdinalIgnoreCase);
+
+            long ticks;
+            if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out ticks))
+            {
+                image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
+            }
+
+            ImageType type;
+            if (Enum.TryParse(parts[2], true, out type))
+            {
+                image.Type = type;
+            }
 
             return image;
         }
@@ -1965,6 +1986,12 @@ namespace Emby.Server.Implementations.Data
                 }
             }
 
+            if (!reader.IsDBNull(index))
+            {
+                item.OwnerId = reader.GetGuid(index);
+            }
+            index++;
+
             return item;
         }
 
@@ -4467,7 +4494,6 @@ namespace Emby.Server.Implementations.Data
                 }
             }
 
-
             var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList();
             var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
 

+ 1 - 1
Emby.Server.Implementations/Dto/DtoService.cs

@@ -1487,7 +1487,7 @@ namespace Emby.Server.Implementations.Dto
                 }
             }
 
-            var parent = currentItem.DisplayParent ?? currentItem.GetParent();
+            var parent = currentItem.DisplayParent ?? (currentItem.IsOwnedItem ? currentItem.GetOwner() : currentItem.GetParent());
 
             if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel))
             {

+ 6 - 4
Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -198,9 +198,10 @@ namespace Emby.Server.Implementations.EntryPoints
                     LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
                 }
 
-                if (e.Item.Parent != null)
+                var parent = e.Item.GetParent() as Folder;
+                if (parent != null)
                 {
-                    _foldersAddedTo.Add(e.Item.Parent);
+                    _foldersAddedTo.Add(parent);
                 }
 
                 _itemsAdded.Add(e.Item);
@@ -259,9 +260,10 @@ namespace Emby.Server.Implementations.EntryPoints
                     LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
                 }
 
-                if (e.Item.Parent != null)
+                var parent = e.Item.GetParent() as Folder;
+                if (parent != null)
                 {
-                    _foldersRemovedFrom.Add(e.Item.Parent);
+                    _foldersRemovedFrom.Add(parent);
                 }
 
                 _itemsRemoved.Add(e.Item);

+ 45 - 14
Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs

@@ -1,43 +1,74 @@
 using System;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
 using System.Threading;
+using MediaBrowser.Model.Tasks;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
 
 namespace Emby.Server.Implementations.EntryPoints
 {
     /// <summary>
     /// Class RefreshUsersMetadata
     /// </summary>
-    public class RefreshUsersMetadata : IServerEntryPoint
+    public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
     {
         /// <summary>
         /// The _user manager
         /// </summary>
         private readonly IUserManager _userManager;
+        private IFileSystem _fileSystem;
+
+        public string Name => "Refresh Users";
+
+        public string Key => "RefreshUsers";
+
+        public string Description => "Refresh user infos";
+
+        public string Category
+        {
+            get { return "Library"; }
+        }
+
+        public bool IsHidden => true;
+
+        public bool IsEnabled => true;
+
+        public bool IsLogged => true;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
         /// </summary>
-        /// <param name="userManager">The user manager.</param>
-        public RefreshUsersMetadata(IUserManager userManager)
+        public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
         {
             _userManager = userManager;
+            _fileSystem = fileSystem;
         }
 
-        /// <summary>
-        /// Runs this instance.
-        /// </summary>
-        public async void Run()
+        public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            await _userManager.RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false);
+            var users = _userManager.Users.ToList();
+
+            foreach (var user in users)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+
+                await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken).ConfigureAwait(false);
+            }
         }
 
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
-        public void Dispose()
+        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         {
-            GC.SuppressFinalize(this);
+            return new List<TaskTriggerInfo>
+            {
+                new TaskTriggerInfo
+                {
+                    IntervalTicks = TimeSpan.FromDays(1).Ticks,
+                    Type = TaskTriggerInfo.TriggerInterval
+                }
+            };
         }
     }
 }

+ 1 - 1
Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs

@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.EntryPoints
                 // Go up one level for indicators
                 if (baseItem != null)
                 {
-                    var parent = baseItem.GetParent();
+                    var parent = baseItem.IsOwnedItem ? baseItem.GetOwner() : baseItem.GetParent();
 
                     if (parent != null)
                     {

+ 1 - 1
Emby.Server.Implementations/IO/FileRefresher.cs

@@ -209,7 +209,7 @@ namespace Emby.Server.Implementations.IO
                 // If the item has been deleted find the first valid parent that still exists
                 while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
                 {
-                    item = item.GetParent();
+                    item = item.IsOwnedItem ? item.GetOwner() : item.GetParent();
 
                     if (item == null)
                     {

+ 24 - 12
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -386,7 +386,7 @@ namespace Emby.Server.Implementations.Library
                     item.Id);
             }
 
-            var parent = item.Parent;
+            var parent = item.IsOwnedItem ? item.GetOwner() : item.GetParent();
 
             var locationType = item.LocationType;
 
@@ -453,12 +453,28 @@ namespace Emby.Server.Implementations.Library
 
                 if (parent != null)
                 {
-                    await parent.ValidateChildren(new SimpleProgress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false);
+                    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)
             {
-                parent.RemoveChild(item);
+                var parentFolder = parent as Folder;
+                if (parentFolder != null)
+                {
+                    parentFolder.RemoveChild(item);
+                }
+                else
+                {
+                    await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false);
+                }
             }
 
             ItemRepository.DeleteItem(item.Id, CancellationToken.None);
@@ -2604,8 +2620,11 @@ namespace Emby.Server.Implementations.Library
                     {
                         video = dbItem;
                     }
-
-                    video.ExtraType = ExtraType.Trailer;
+                    else
+                    {
+                        // item is new
+                        video.ExtraType = ExtraType.Trailer;
+                    }
                     video.TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer };
 
                     return video;
@@ -2846,13 +2865,6 @@ namespace Emby.Server.Implementations.Library
 
                     await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
 
-                    var newImage = item.GetImageInfo(image.Type, imageIndex);
-
-                    if (newImage != null)
-                    {
-                        newImage.IsPlaceholder = image.IsPlaceholder;
-                    }
-
                     await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
                     return item.GetImageInfo(image.Type, imageIndex);

+ 5 - 4
Emby.Server.Implementations/Library/UserManager.cs

@@ -518,11 +518,12 @@ namespace Emby.Server.Implementations.Library
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        public Task RefreshUsersMetadata(CancellationToken cancellationToken)
+        public async Task RefreshUsersMetadata(CancellationToken cancellationToken)
         {
-            var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken)).ToList();
-
-            return Task.WhenAll(tasks);
+            foreach (var user in Users)
+            {
+                await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken).ConfigureAwait(false);
+            }
         }
 
         /// <summary>

+ 4 - 0
Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs

@@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
+
             using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
             {
                 onStarted();
@@ -76,6 +78,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
                 _logger.Info("Opened recording stream from tuner provider");
 
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
+
                 using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
                 {
                     onStarted();

+ 27 - 27
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -1429,14 +1429,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             string liveStreamId = null;
 
-            OnRecordingStatusChanged();
-
             try
             {
                 var recorder = await GetRecorder().ConfigureAwait(false);
 
                 var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false);
 
+                _logger.Info("Opening recording stream from tuner provider");
                 var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None)
                             .ConfigureAwait(false);
 
@@ -1450,23 +1449,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 recordPath = EnsureFileUnique(recordPath, timer.Id);
 
                 _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
-                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath));
                 activeRecordingInfo.Path = recordPath;
 
                 var duration = recordingEndDate - DateTime.UtcNow;
 
-                _logger.Info("Beginning recording. Will record for {0} minutes.",
-                    duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+                _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
 
                 _logger.Info("Writing file to path: " + recordPath);
-                _logger.Info("Opening recording stream from tuner provider");
 
-                Action onStarted = () =>
+                Action onStarted = async () =>
                 {
                     timer.Status = RecordingStatus.InProgress;
                     _timerProvider.AddOrUpdate(timer, false);
 
-                    SaveRecordingMetadata(timer, recordPath, seriesPath);
+                    await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false);
                     TriggerRefresh(recordPath);
                     EnforceKeepUpTo(timer, seriesPath);
                 };
@@ -1500,7 +1496,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
 
             TriggerRefresh(recordPath);
-            _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
+            _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
 
             ActiveRecordingInfo removed;
             _activeRecordings.TryRemove(timer.Id, out removed);
@@ -1526,17 +1522,29 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
                 _timerProvider.Delete(timer);
             }
-
-            OnRecordingStatusChanged();
         }
 
         private void TriggerRefresh(string path)
         {
+            _logger.Debug("Triggering refresh on {0}", path);
+
             var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path));
 
             if (item != null)
             {
-                item.ChangedExternally();
+                _logger.Debug("Refreshing recording parent {0}", item.Path);
+
+                _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
+                {
+                    ValidateChildren = true,
+                    RefreshPaths = new List<string>
+                    {
+                        path,
+                        _fileSystem.GetDirectoryName(path),
+                        _fileSystem.GetDirectoryName(_fileSystem.GetDirectoryName(path))
+                    }
+
+                }, RefreshPriority.High);
             }
         }
 
@@ -1544,6 +1552,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         {
             BaseItem item = null;
 
+            var parentPath = _fileSystem.GetDirectoryName(path);
+
             while (item == null && !string.IsNullOrEmpty(path))
             {
                 item = _libraryManager.FindByPath(path, null);
@@ -1553,14 +1563,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             if (item != null)
             {
-                // If the item has been deleted find the first valid parent that still exists
-                while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
+                if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
                 {
-                    item = item.GetParent();
-
-                    if (item == null)
+                    var parentItem = item.GetParent();
+                    if (parentItem != null && !(parentItem is AggregateFolder))
                     {
-                        break;
+                        item = parentItem;
                     }
                 }
             }
@@ -1568,14 +1576,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return item;
         }
 
-        private void OnRecordingStatusChanged()
-        {
-            EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs
-            {
-
-            }, _logger);
-        }
-
         private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath)
         {
             if (string.IsNullOrWhiteSpace(timer.SeriesTimerId))
@@ -1960,7 +1960,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
         }
 
-        private async void SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath)
+        private async Task SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath)
         {
             try
             {

+ 2 - 4
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -843,8 +843,7 @@ namespace Emby.Server.Implementations.LiveTv
                     item.SetImage(new ItemImageInfo
                     {
                         Path = info.ImagePath,
-                        Type = ImageType.Primary,
-                        IsPlaceholder = true
+                        Type = ImageType.Primary
                     }, 0);
                 }
                 else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
@@ -852,8 +851,7 @@ namespace Emby.Server.Implementations.LiveTv
                     item.SetImage(new ItemImageInfo
                     {
                         Path = info.ImageUrl,
-                        Type = ImageType.Primary,
-                        IsPlaceholder = true
+                        Type = ImageType.Primary
                     }, 0);
                 }
             }

+ 1 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs

@@ -134,7 +134,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 }
 
                 _liveStreamTaskCompletionSource.TrySetResult(true);
-                //await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
+                await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
             });
         }
 

+ 1 - 1
Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs

@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Playlists
                         return subItem;
                     }
 
-                    var parent = subItem.GetParent();
+                    var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent();
 
                     if (parent != null && parent.HasImage(ImageType.Primary))
                     {

+ 1 - 1
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 FileInfo = FileSystem.GetDirectoryInfo(path),
                 Path = path,
-                Parent = Parent
+                Parent = GetParent() as Folder
             };
 
             // Gather child folder and files

+ 85 - 23
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -97,7 +97,7 @@ namespace MediaBrowser.Controller.Entities
         public string Tagline { get; set; }
 
         [IgnoreDataMember]
-        public ItemImageInfo[] ImageInfos { get; set; }
+        public virtual ItemImageInfo[] ImageInfos { get; set; }
 
         [IgnoreDataMember]
         public bool IsVirtualItem { get; set; }
@@ -216,6 +216,9 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public Guid Id { get; set; }
 
+        [IgnoreDataMember]
+        public Guid OwnerId { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance is hd.
         /// </summary>
@@ -321,12 +324,31 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
+                if (OwnerId != Guid.Empty)
+                {
+                    return true;
+                }
+
+                // legacy 
+
                 // Local trailer, special feature, theme video, etc.
                 // An item that belongs to another item but is not part of the Parent-Child tree
-                return !IsFolder && ParentId == Guid.Empty && LocationType == LocationType.FileSystem;
+                // This is a hack for now relying on ExtraType. Eventually we may need to persist this
+                if (ParentId == Guid.Empty && !IsFolder && LocationType == LocationType.FileSystem)
+                {
+                    return true;
+                }
+
+                return false;
             }
         }
 
+        public BaseItem GetOwner()
+        {
+            var ownerId = OwnerId;
+            return ownerId == Guid.Empty ? null : LibraryManager.GetItemById(ownerId);
+        }
+
         /// <summary>
         /// Gets or sets the type of the location.
         /// </summary>
@@ -727,17 +749,12 @@ namespace MediaBrowser.Controller.Entities
             ParentId = parent == null ? Guid.Empty : parent.Id;
         }
 
-        [IgnoreDataMember]
-        public IEnumerable<Folder> Parents
-        {
-            get { return GetParents().OfType<Folder>(); }
-        }
-
         public BaseItem GetParent()
         {
-            if (ParentId != Guid.Empty)
+            var parentId = ParentId;
+            if (parentId != Guid.Empty)
             {
-                return LibraryManager.GetItemById(ParentId);
+                return LibraryManager.GetItemById(parentId);
             }
 
             return null;
@@ -779,11 +796,13 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
-                if (ParentId == Guid.Empty)
+                var parentId = ParentId;
+
+                if (parentId == Guid.Empty)
                 {
                     return null;
                 }
-                return ParentId;
+                return parentId;
             }
         }
 
@@ -1002,8 +1021,11 @@ namespace MediaBrowser.Controller.Entities
                     {
                         audio = dbItem;
                     }
-
-                    audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong;
+                    else
+                    {
+                        // item is new
+                        audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong;
+                    }
 
                     return audio;
 
@@ -1032,8 +1054,11 @@ namespace MediaBrowser.Controller.Entities
                     {
                         item = dbItem;
                     }
-
-                    item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo;
+                    else
+                    {
+                        // item is new
+                        item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo;
+                    }
 
                     return item;
 
@@ -1178,8 +1203,25 @@ namespace MediaBrowser.Controller.Entities
             var newItemIds = newItems.Select(i => i.Id).ToArray();
 
             var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
+            var ownerId = item.Id;
 
-            var tasks = newItems.Select(i => RefreshMetadataForOwnedItem(i, true, options, cancellationToken));
+            var tasks = newItems.Select(i =>
+            {
+                var subOptions = new MetadataRefreshOptions(options);
+
+                if (!i.ExtraType.HasValue ||
+                    i.ExtraType.Value != Model.Entities.ExtraType.Trailer ||
+                    i.OwnerId != ownerId ||
+                    i.ParentId != Guid.Empty)
+                {
+                    i.ExtraType = Model.Entities.ExtraType.Trailer;
+                    i.OwnerId = ownerId;
+                    i.ParentId = Guid.Empty;
+                    subOptions.ForceSave = true;
+                }
+
+                return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
+            });
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
 
@@ -1196,13 +1238,20 @@ namespace MediaBrowser.Controller.Entities
 
             var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
 
+            var ownerId = item.Id;
+
             var tasks = newThemeVideos.Select(i =>
             {
                 var subOptions = new MetadataRefreshOptions(options);
 
-                if (!i.IsThemeMedia)
+                if (!i.ExtraType.HasValue ||
+                    i.ExtraType.Value != Model.Entities.ExtraType.ThemeVideo ||
+                    i.OwnerId != ownerId ||
+                    i.ParentId != Guid.Empty)
                 {
-                    i.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo;
+                    i.ExtraType = Model.Entities.ExtraType.ThemeVideo;
+                    i.OwnerId = ownerId;
+                    i.ParentId = Guid.Empty;
                     subOptions.ForceSave = true;
                 }
 
@@ -1226,13 +1275,20 @@ namespace MediaBrowser.Controller.Entities
 
             var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
 
+            var ownerId = item.Id;
+
             var tasks = newThemeSongs.Select(i =>
             {
                 var subOptions = new MetadataRefreshOptions(options);
 
-                if (!i.IsThemeMedia)
+                if (!i.ExtraType.HasValue || 
+                    i.ExtraType.Value != Model.Entities.ExtraType.ThemeSong || 
+                    i.OwnerId != ownerId ||
+                    i.ParentId != Guid.Empty)
                 {
-                    i.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong;
+                    i.ExtraType = Model.Entities.ExtraType.ThemeSong;
+                    i.OwnerId = ownerId;
+                    i.ParentId = Guid.Empty;
                     subOptions.ForceSave = true;
                 }
 
@@ -1868,7 +1924,6 @@ namespace MediaBrowser.Controller.Entities
             {
                 existingImage.Path = image.Path;
                 existingImage.DateModified = image.DateModified;
-                existingImage.IsPlaceholder = image.IsPlaceholder;
             }
 
             else
@@ -1902,7 +1957,6 @@ namespace MediaBrowser.Controller.Entities
 
                 image.Path = file.FullName;
                 image.DateModified = imageInfo.DateModified;
-                image.IsPlaceholder = false;
             }
         }
 
@@ -2359,6 +2413,14 @@ namespace MediaBrowser.Controller.Entities
                 newOptions.ForceSave = true;
             }
 
+            //var parentId = Id;
+            //if (!video.IsOwnedItem || video.ParentId != parentId)
+            //{
+            //    video.IsOwnedItem = true;
+            //    video.ParentId = parentId;
+            //    newOptions.ForceSave = true;
+            //}
+
             if (video == null)
             {
                 return Task.FromResult(true);

+ 1 - 1
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -280,7 +280,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 FileInfo = FileSystem.GetDirectoryInfo(path),
                 Path = path,
-                Parent = Parent,
+                Parent = GetParent() as Folder,
                 CollectionType = CollectionType
             };
 

+ 67 - 36
MediaBrowser.Controller/Entities/Folder.cs

@@ -378,6 +378,7 @@ namespace MediaBrowser.Controller.Entities
             cancellationToken.ThrowIfCancellationRequested();
 
             var validChildren = new List<BaseItem>();
+            var validChildrenNeedGeneration = false;
 
             var allLibraryPaths = LibraryManager
               .GetVirtualFolders()
@@ -474,11 +475,7 @@ namespace MediaBrowser.Controller.Entities
             }
             else
             {
-                if (recursive || refreshChildMetadata)
-                {
-                    // used below
-                    validChildren = Children.ToList();
-                }
+                validChildrenNeedGeneration = true;
             }
 
             progress.Report(10);
@@ -502,6 +499,12 @@ namespace MediaBrowser.Controller.Entities
                         ProviderManager.OnRefreshProgress(folder, newPct);
                     });
 
+                    if (validChildrenNeedGeneration)
+                    {
+                        validChildren = Children.ToList();
+                        validChildrenNeedGeneration = false;
+                    }
+
                     await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), directoryService, innerProgress, cancellationToken).ConfigureAwait(false);
                 }
             }
@@ -536,6 +539,12 @@ namespace MediaBrowser.Controller.Entities
                     }
                     else
                     {
+                        if (validChildrenNeedGeneration)
+                        {
+                            validChildren = Children.ToList();
+                            validChildrenNeedGeneration = false;
+                        }
+
                         await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken);
                     }
                 }
@@ -565,7 +574,7 @@ namespace MediaBrowser.Controller.Entities
                     });
 
                     await RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken)
-                      .ConfigureAwait(false);
+                        .ConfigureAwait(false);
                 }
 
                 numComplete++;
@@ -588,7 +597,10 @@ namespace MediaBrowser.Controller.Entities
             }
             else
             {
-                await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                if (refreshOptions.RefreshItem(child))
+                {
+                    await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                }
 
                 if (recursive)
                 {
@@ -1196,11 +1208,21 @@ namespace MediaBrowser.Controller.Entities
         /// Gets the linked children.
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        public IEnumerable<BaseItem> GetLinkedChildren()
+        public List<BaseItem> GetLinkedChildren()
         {
-            return LinkedChildren
-                .Select(GetLinkedChild)
-                .Where(i => i != null);
+            var linkedChildren = LinkedChildren;
+            var list = new List<BaseItem>(linkedChildren.Length);
+
+            foreach (var i in linkedChildren)
+            {
+                var child = GetLinkedChild(i);
+
+                if (child != null)
+                {
+                    list.Add(child);
+                }
+            }
+            return list;
         }
 
         protected virtual bool FilterLinkedChildrenPerUser
@@ -1211,16 +1233,19 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        public IEnumerable<BaseItem> GetLinkedChildren(User user)
+        public List<BaseItem> GetLinkedChildren(User user)
         {
             if (!FilterLinkedChildrenPerUser || user == null)
             {
                 return GetLinkedChildren();
             }
 
-            if (LinkedChildren.Length == 0)
+            var linkedChildren = LinkedChildren;
+            var list = new List<BaseItem>(linkedChildren.Length);
+
+            if (linkedChildren.Length == 0)
             {
-                return new List<BaseItem>();
+                return list;
             }
 
             var allUserRootChildren = user.RootFolder.Children.OfType<Folder>().ToList();
@@ -1231,37 +1256,43 @@ namespace MediaBrowser.Controller.Entities
                 .Select(i => i.Id)
                 .ToList();
 
-            return LinkedChildren
-                .Select(i =>
+            foreach (var i in linkedChildren)
+            {
+                var child = GetLinkedChild(i);
+
+                if (child == null)
                 {
-                    var child = GetLinkedChild(i);
+                    continue;
+                }
+
+                var childOwner = child.IsOwnedItem ? (child.GetOwner() ?? child) : child;
 
-                    if (child != null)
+                if (childOwner != null)
+                {
+                    var childLocationType = childOwner.LocationType;
+                    if (childLocationType == LocationType.Remote || childLocationType == LocationType.Virtual)
                     {
-                        var childLocationType = child.LocationType;
-                        if (childLocationType == LocationType.Remote || childLocationType == LocationType.Virtual)
+                        if (!childOwner.IsVisibleStandalone(user))
                         {
-                            if (!child.IsVisibleStandalone(user))
-                            {
-                                return null;
-                            }
+                            continue;
                         }
-                        else if (childLocationType == LocationType.FileSystem)
-                        {
-                            var itemCollectionFolderIds =
-                                LibraryManager.GetCollectionFolders(child, allUserRootChildren)
-                                .Select(f => f.Id).ToList();
+                    }
+                    else if (childLocationType == LocationType.FileSystem)
+                    {
+                        var itemCollectionFolderIds =
+                            LibraryManager.GetCollectionFolders(childOwner, allUserRootChildren).Select(f => f.Id);
 
-                            if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains))
-                            {
-                                return null;
-                            }
+                        if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains))
+                        {
+                            continue;
                         }
                     }
+                }
+
+                list.Add(child);
+            }
 
-                    return child;
-                })
-                .Where(i => i != null);
+            return list;
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Controller/Entities/IHasTrailers.cs

@@ -5,7 +5,7 @@ using System.Linq;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public interface IHasTrailers : IHasProviderIds
+    public interface IHasTrailers : IHasMetadata
     {
         /// <summary>
         /// Gets or sets the remote trailers.

+ 0 - 6
MediaBrowser.Controller/Entities/ItemImageInfo.cs

@@ -24,12 +24,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The date modified.</value>
         public DateTime DateModified { get; set; }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is placeholder.
-        /// </summary>
-        /// <value><c>true</c> if this instance is placeholder; otherwise, <c>false</c>.</value>
-        public bool IsPlaceholder { get; set; }
-
         [IgnoreDataMember]
         public bool IsLocalFile
         {

+ 14 - 1
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -81,7 +81,20 @@ namespace MediaBrowser.Controller.Entities.Movies
 
             var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
 
-            var tasks = newItems.Select(i => RefreshMetadataForOwnedItem(i, false, options, cancellationToken));
+            var ownerId = Id;
+
+            var tasks = newItems.Select(i =>
+            {
+                var subOptions = new MetadataRefreshOptions(options);
+
+                if (i.OwnerId != ownerId)
+                {
+                    i.OwnerId = ownerId;
+                    subOptions.ForceSave = true;
+                }
+
+                return RefreshMetadataForOwnedItem(i, false, subOptions, cancellationToken);
+            });
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
 

+ 8 - 2
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -347,7 +347,10 @@ namespace MediaBrowser.Controller.Entities.TV
 
                 cancellationToken.ThrowIfCancellationRequested();
 
-                await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                if (refreshOptions.RefreshItem(item))
+                {
+                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                }
 
                 numComplete++;
                 double percent = numComplete;
@@ -382,7 +385,10 @@ namespace MediaBrowser.Controller.Entities.TV
 
                 if (!skipItem)
                 {
-                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                    if (refreshOptions.RefreshItem(item))
+                    {
+                        await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                    }
                 }
 
                 numComplete++;

+ 3 - 0
MediaBrowser.Controller/Entities/User.cs

@@ -37,6 +37,9 @@ namespace MediaBrowser.Controller.Entities
         public UserLinkType? ConnectLinkType { get; set; }
         public string ConnectAccessKey { get; set; }
 
+        // Strictly to remove IgnoreDataMember
+        public override ItemImageInfo[] ImageInfos { get => base.ImageInfos; set => base.ImageInfos = value; }
+
         /// <summary>
         /// Gets or sets the path.
         /// </summary>

+ 26 - 2
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -1,5 +1,7 @@
-using System.Linq;
-
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
@@ -20,6 +22,8 @@ namespace MediaBrowser.Controller.Providers
         public MetadataRefreshMode MetadataRefreshMode { get; set; }
         public RemoteSearchResult SearchResult { get; set; }
 
+        public List<string> RefreshPaths { get; set; }
+
         public bool ForceSave { get; set; }
 
         public MetadataRefreshOptions(IFileSystem fileSystem)
@@ -44,6 +48,26 @@ namespace MediaBrowser.Controller.Providers
             ReplaceAllImages = copy.ReplaceAllImages;
             ReplaceImages = copy.ReplaceImages.ToList();
             SearchResult = copy.SearchResult;
+
+            if (copy.RefreshPaths != null && copy.RefreshPaths.Count > 0)
+            {
+                if (RefreshPaths == null)
+                {
+                    RefreshPaths = new List<string>();
+                }
+
+                RefreshPaths.AddRange(copy.RefreshPaths);
+            }
+        }
+
+        public bool RefreshItem(BaseItem item)
+        {
+            if (RefreshPaths != null && RefreshPaths.Count > 0)
+            {
+                return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+            }
+
+            return true;
         }
     }
 }

+ 0 - 1
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -33,7 +33,6 @@ namespace MediaBrowser.Model.LiveTv
             MediaLocationsCreated = new string[] { };
             RecordingEncodingFormat = "mkv";
             RecordingPostProcessorArguments = "\"{path}\"";
-            EnableRecordingEncoding = true;
         }
     }
 

+ 1 - 14
MediaBrowser.Providers/Manager/GenericPriorityQueue.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -66,9 +67,7 @@ namespace Priority_Queue
         /// Removes every node from the queue.
         /// O(n) (So, don't do this often!)
         /// </summary>
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         public void Clear()
         {
             Array.Clear(_nodes, 1, _numNodes);
@@ -78,9 +77,7 @@ namespace Priority_Queue
         /// <summary>
         /// Returns (in O(1)!) whether the given node is in the queue.  O(1)
         /// </summary>
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         public bool Contains(TItem node)
         {
 #if DEBUG
@@ -103,9 +100,7 @@ namespace Priority_Queue
         /// If the node is already enqueued, the result is undefined.
         /// O(log n)
         /// </summary>
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         public void Enqueue(TItem node, TPriority priority)
         {
 #if DEBUG
@@ -131,9 +126,7 @@ namespace Priority_Queue
             CascadeUp(_nodes[_numNodes]);
         }
 
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         private void Swap(TItem node1, TItem node2)
         {
             //Swap the nodes
@@ -164,9 +157,7 @@ namespace Priority_Queue
             }
         }
 
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         private void CascadeDown(TItem node)
         {
             //aka Heapify-down
@@ -228,9 +219,7 @@ namespace Priority_Queue
         /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
         /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
         /// </summary>
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         private bool HasHigherPriority(TItem higher, TItem lower)
         {
             var cmp = higher.Priority.CompareTo(lower.Priority);
@@ -319,9 +308,7 @@ namespace Priority_Queue
         /// Calling this method on a node not in the queue results in undefined behavior
         /// O(log n)
         /// </summary>
-#if NET_VERSION_4_5
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
         public void UpdatePriority(TItem node, TPriority priority)
         {
 #if DEBUG

+ 2 - 3
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -206,8 +206,7 @@ namespace MediaBrowser.Providers.Manager
         {
             var image = item.GetImageInfo(type, 0);
 
-            // if it's a placeholder image then pretend like it's not there so that we can replace it
-            return image != null && !image.IsPlaceholder;
+            return image != null;
         }
 
         /// <summary>
@@ -547,7 +546,7 @@ namespace MediaBrowser.Providers.Manager
             switch (type)
             {
                 case ImageType.Primary:
-                    return !(item is Movie || item is Series || item is Game);
+                    return true;
                 default:
                     return true;
             }

+ 1 - 2
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -262,8 +262,7 @@ namespace MediaBrowser.Providers.Manager
             personEntity.SetImage(new ItemImageInfo
             {
                 Path = imageUrl,
-                Type = ImageType.Primary,
-                IsPlaceholder = true
+                Type = ImageType.Primary
             }, 0);
         }
 

+ 5 - 0
MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs

@@ -146,6 +146,11 @@ namespace MediaBrowser.Providers.MediaInfo
                 return _cachedTask;
             }
 
+            if (!item.IsCompleteMedia)
+            {
+                return _cachedTask;
+            }
+
             if (item.IsShortcut)
             {
                 FetchShortcutInfo(item);

+ 1 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.MediaInfo
         {
             var video = item as Video;
 
-            if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut)
+            if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut && video.IsCompleteMedia)
             {
                 return true;
             }