Browse Source

Merge pull request #2657 from MediaBrowser/dev

Dev
Luke 8 years ago
parent
commit
57bce7daf3

+ 276 - 49
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -1347,6 +1347,11 @@ namespace Emby.Server.Implementations.Data
         }
 
         private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query)
+        {
+            return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query));
+        }
+
+        private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
         {
             var typeString = reader.GetString(0);
 
@@ -1394,28 +1399,34 @@ namespace Emby.Server.Implementations.Data
                 return null;
             }
 
-            if (!reader.IsDBNull(2))
+            var index = 2;
+
+            if (queryHasStartDate)
             {
-                var hasStartDate = item as IHasStartDate;
-                if (hasStartDate != null)
+                if (!reader.IsDBNull(index))
                 {
-                    hasStartDate.StartDate = reader[2].ReadDateTime();
+                    var hasStartDate = item as IHasStartDate;
+                    if (hasStartDate != null)
+                    {
+                        hasStartDate.StartDate = reader[index].ReadDateTime();
+                    }
                 }
+                index++;
             }
 
-            if (!reader.IsDBNull(3))
+            if (!reader.IsDBNull(index))
             {
-                item.EndDate = reader[3].ReadDateTime();
+                item.EndDate = reader[index].ReadDateTime();
             }
+            index++;
 
-            if (!reader.IsDBNull(4))
+            if (!reader.IsDBNull(index))
             {
-                item.ChannelId = reader.GetString(4);
+                item.ChannelId = reader.GetString(index);
             }
+            index++;
 
-            var index = 5;
-
-            if (HasProgramAttributes(query))
+            if (enableProgramAttributes)
             {
                 var hasProgramAttributes = item as IHasProgramAttributes;
                 if (hasProgramAttributes != null)
@@ -1728,15 +1739,18 @@ namespace Emby.Server.Implementations.Data
             }
             index++;
 
-            var trailer = item as Trailer;
-            if (trailer != null)
+            if (hasTrailerTypes)
             {
-                if (!reader.IsDBNull(index))
+                var trailer = item as Trailer;
+                if (trailer != null)
                 {
-                    trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList();
+                    if (!reader.IsDBNull(index))
+                    {
+                        trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)).ToList();
+                    }
                 }
+                index++;
             }
-            index++;
 
             if (HasField(query, ItemFields.OriginalTitle))
             {
@@ -1786,42 +1800,51 @@ namespace Emby.Server.Implementations.Data
             index++;
 
             var hasSeries = item as IHasSeries;
-            if (hasSeries != null)
+            if (hasSeriesFields)
             {
-                if (!reader.IsDBNull(index))
+                if (hasSeries != null)
                 {
-                    hasSeries.SeriesName = reader.GetString(index);
+                    if (!reader.IsDBNull(index))
+                    {
+                        hasSeries.SeriesName = reader.GetString(index);
+                    }
                 }
+                index++;
             }
-            index++;
 
-            var episode = item as Episode;
-            if (episode != null)
+            if (hasEpisodeAttributes)
             {
-                if (!reader.IsDBNull(index))
+                var episode = item as Episode;
+                if (episode != null)
                 {
-                    episode.SeasonName = reader.GetString(index);
+                    if (!reader.IsDBNull(index))
+                    {
+                        episode.SeasonName = reader.GetString(index);
+                    }
+                    index++;
+                    if (!reader.IsDBNull(index))
+                    {
+                        episode.SeasonId = reader.GetGuid(index);
+                    }
                 }
-                index++;
-                if (!reader.IsDBNull(index))
+                else
                 {
-                    episode.SeasonId = reader.GetGuid(index);
+                    index++;
                 }
-            }
-            else
-            {
                 index++;
             }
-            index++;
 
-            if (hasSeries != null)
+            if (hasSeriesFields)
             {
-                if (!reader.IsDBNull(index))
+                if (hasSeries != null)
                 {
-                    hasSeries.SeriesId = reader.GetGuid(index);
+                    if (!reader.IsDBNull(index))
+                    {
+                        hasSeries.SeriesId = reader.GetGuid(index);
+                    }
                 }
+                index++;
             }
-            index++;
 
             if (HasField(query, ItemFields.PresentationUniqueKey))
             {
@@ -1931,19 +1954,22 @@ namespace Emby.Server.Implementations.Data
             }
             index++;
 
-            var hasArtists = item as IHasArtist;
-            if (hasArtists != null && !reader.IsDBNull(index))
+            if (hasArtistFields)
             {
-                hasArtists.Artists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
-            }
-            index++;
+                var hasArtists = item as IHasArtist;
+                if (hasArtists != null && !reader.IsDBNull(index))
+                {
+                    hasArtists.Artists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+                }
+                index++;
 
-            var hasAlbumArtists = item as IHasAlbumArtist;
-            if (hasAlbumArtists != null && !reader.IsDBNull(index))
-            {
-                hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+                var hasAlbumArtists = item as IHasAlbumArtist;
+                if (hasAlbumArtists != null && !reader.IsDBNull(index))
+                {
+                    hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+                }
+                index++;
             }
-            index++;
 
             if (!reader.IsDBNull(index))
             {
@@ -2312,6 +2338,20 @@ namespace Emby.Server.Implementations.Data
 
         private bool HasProgramAttributes(InternalItemsQuery query)
         {
+            var excludeParentTypes = new string[]
+            {
+                "Series",
+                "Season",
+                "MusicAlbum",
+                "MusicArtist",
+                "PhotoAlbum"
+            };
+
+            if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
             if (query.IncludeItemTypes.Length == 0)
             {
                 return true;
@@ -2331,6 +2371,130 @@ namespace Emby.Server.Implementations.Data
             return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
         }
 
+        private bool HasStartDate(InternalItemsQuery query)
+        {
+            var excludeParentTypes = new string[]
+             {
+                "Series",
+                "Season",
+                "MusicAlbum",
+                "MusicArtist",
+                "PhotoAlbum"
+             };
+
+            if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                return true;
+            }
+
+            var types = new string[]
+            {
+                "Program",
+                "Recording",
+                "LiveTvAudioRecording",
+                "LiveTvVideoRecording",
+                "LiveTvProgram"
+            };
+
+            return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+        }
+
+        private bool HasEpisodeAttributes(InternalItemsQuery query)
+        {
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                return true;
+            }
+
+            var types = new string[]
+            {
+                "Episode"
+            };
+
+            return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+        }
+
+        private bool HasTrailerTypes(InternalItemsQuery query)
+        {
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                return true;
+            }
+
+            var types = new string[]
+            {
+                "Trailer"
+            };
+
+            return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+        }
+
+        private bool HasArtistFields(InternalItemsQuery query)
+        {
+            var excludeParentTypes = new string[]
+             {
+                "Series",
+                "Season",
+                "PhotoAlbum"
+             };
+
+            if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                return true;
+            }
+
+            var types = new string[]
+            {
+                "Audio",
+                "MusicAlbum",
+                "MusicVideo",
+                "AudioBook",
+                "AudioPodcast",
+                "LiveTvAudioRecording",
+                "Recording"
+            };
+
+            return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+        }
+
+        private bool HasSeriesFields(InternalItemsQuery query)
+        {
+            var excludeParentTypes = new string[]
+             {
+                "PhotoAlbum"
+             };
+
+            if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                return true;
+            }
+
+            var types = new string[]
+            {
+                "Book",
+                "AudioBook",
+                "Episode",
+                "Season"
+            };
+
+            return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+        }
+
         private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
         {
             var list = startColumns.ToList();
@@ -2359,6 +2523,40 @@ namespace Emby.Server.Implementations.Data
                 list.Remove("IsRepeat");
             }
 
+            if (!HasEpisodeAttributes(query))
+            {
+                list.Remove("SeasonName");
+                list.Remove("SeasonId");
+            }
+
+            if (!HasStartDate(query))
+            {
+                list.Remove("StartDate");
+            }
+
+            if (!HasTrailerTypes(query))
+            {
+                list.Remove("TrailerTypes");
+            }
+
+            if (!HasArtistFields(query))
+            {
+                list.Remove("AlbumArtists");
+                list.Remove("Artists");
+            }
+
+            if (!HasSeriesFields(query))
+            {
+                list.Remove("SeriesId");
+                list.Remove("SeriesName");
+            }
+
+            if (!HasEpisodeAttributes(query))
+            {
+                list.Remove("SeasonName");
+                list.Remove("SeasonId");
+            }
+
             if (!query.DtoOptions.EnableImages)
             {
                 list.Remove("Images");
@@ -2590,9 +2788,16 @@ namespace Emby.Server.Implementations.Data
                         // Running this again will bind the params
                         GetWhereClauses(query, statement);
 
+                        var hasEpisodeAttributes = HasEpisodeAttributes(query);
+                        var hasProgramAttributes = HasProgramAttributes(query);
+                        var hasStartDate = HasStartDate(query);
+                        var hasTrailerTypes = HasTrailerTypes(query);
+                        var hasArtistFields = HasArtistFields(query);
+                        var hasSeriesFields = HasSeriesFields(query);
+
                         foreach (var row in statement.ExecuteQuery())
                         {
-                            var item = GetItem(row, query);
+                            var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
                             if (item != null)
                             {
                                 list.Add(item);
@@ -2792,9 +2997,16 @@ namespace Emby.Server.Implementations.Data
                                 // Running this again will bind the params
                                 GetWhereClauses(query, statement);
 
+                                var hasEpisodeAttributes = HasEpisodeAttributes(query);
+                                var hasProgramAttributes = HasProgramAttributes(query);
+                                var hasStartDate = HasStartDate(query);
+                                var hasTrailerTypes = HasTrailerTypes(query);
+                                var hasArtistFields = HasArtistFields(query);
+                                var hasSeriesFields = HasSeriesFields(query);
+
                                 foreach (var row in statement.ExecuteQuery())
                                 {
-                                    var item = GetItem(row, query);
+                                    var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
                                     if (item != null)
                                     {
                                         list.Add(item);
@@ -3562,6 +3774,15 @@ namespace Emby.Server.Implementations.Data
                 }
             }
 
+            if (query.MinDateLastSavedForUser.HasValue)
+            {
+                whereClauses.Add("DateLastSaved>=@MinDateLastSaved");
+                if (statement != null)
+                {
+                    statement.TryBind("@MinDateLastSaved", query.MinDateLastSavedForUser.Value);
+                }
+            }
+
             //if (query.MinPlayers.HasValue)
             //{
             //    whereClauses.Add("Players>=@MinPlayers");
@@ -3756,7 +3977,6 @@ namespace Emby.Server.Implementations.Data
 
             if (!string.IsNullOrWhiteSpace(query.SlugName))
             {
-                Logger.Info("Searching by SlugName for {0}", query.SlugName);
                 whereClauses.Add("CleanName=@SlugName");
                 if (statement != null)
                 {
@@ -5137,9 +5357,16 @@ namespace Emby.Server.Implementations.Data
                                 GetWhereClauses(innerQuery, statement);
                                 GetWhereClauses(outerQuery, statement);
 
+                                var hasEpisodeAttributes = HasEpisodeAttributes(query);
+                                var hasProgramAttributes = HasProgramAttributes(query);
+                                var hasStartDate = HasStartDate(query);
+                                var hasTrailerTypes = HasTrailerTypes(query);
+                                var hasArtistFields = HasArtistFields(query);
+                                var hasSeriesFields = HasSeriesFields(query);
+
                                 foreach (var row in statement.ExecuteQuery())
                                 {
-                                    var item = GetItem(row, query);
+                                    var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
                                     if (item != null)
                                     {
                                         var countStartColumn = columns.Count - 1;

+ 1 - 1
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -1546,7 +1546,7 @@ namespace Emby.Server.Implementations.Library
                 }
             }
 
-            query.ParentId = null;
+            query.Parent = null;
         }
 
         private void AddUserToQuery(InternalItemsQuery query, User user)

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

@@ -422,7 +422,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 SupportsTranscoding = true,
                 IsInfiniteStream = true,
                 IgnoreDts = true,
-                IgnoreIndex = true
+                //IgnoreIndex = true,
+                //ReadAtNativeFramerate = true
             };
 
             mediaSource.InferTotalBitrate();

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

@@ -101,9 +101,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                                 _logger.Info("Beginning multicastStream.CopyUntilCancelled");
 
                                 FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
-                                using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous | FileOpenOptions.SequentialScan))
+                                using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
                                 {
-                                    ResolveAfterDelay(2000, openTaskCompletionSource);
+                                    ResolveAfterDelay(3000, openTaskCompletionSource);
 
                                     await response.Content.CopyToAsync(fileStream, 81920, cancellationToken).ConfigureAwait(false);
                                 }

+ 25 - 10
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs

@@ -121,11 +121,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                                 if (!cancellationToken.IsCancellationRequested)
                                 {
                                     FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
-                                    using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous | FileOpenOptions.SequentialScan))
+                                    using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
                                     {
-                                        ResolveAfterDelay(2000, openTaskCompletionSource);
-
-                                        await new UdpClientStream(udpClient).CopyToAsync(fileStream, 81920, cancellationToken).ConfigureAwait(false);
+                                        await CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
                                     }
                                 }
                             }
@@ -159,19 +157,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             });
         }
 
-        private void ResolveAfterDelay(int delayMs, TaskCompletionSource<bool> openTaskCompletionSource)
+        private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
         {
-            Task.Run(async () =>
-            {
-                await Task.Delay(delayMs).ConfigureAwait(false);
-                openTaskCompletionSource.TrySetResult(true);
-            });
+            Task.Run(() =>
+           {
+               openTaskCompletionSource.TrySetResult(true);
+           });
         }
 
         public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
         {
             return CopyFileTo(_tempFilePath, false, stream, cancellationToken);
         }
+
+        private static int RtpHeaderBytes = 12;
+        private async Task CopyTo(ISocket udpClient, Stream outputStream, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+        {
+            while (true)
+            {
+                var data = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+                var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
+
+                await outputStream.WriteAsync(data.Buffer, RtpHeaderBytes, bytesRead, cancellationToken).ConfigureAwait(false);
+
+                if (openTaskCompletionSource != null)
+                {
+                    Resolve(openTaskCompletionSource);
+                    openTaskCompletionSource = null;
+                }
+            }
+        }
     }
 
     // This handles the ReadAsync function only of a Stream object

+ 8 - 5
MediaBrowser.Api/Playback/StreamState.cs

@@ -68,15 +68,18 @@ namespace MediaBrowser.Api.Playback
                 if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     var userAgent = UserAgent ?? string.Empty;
-                    if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1)
-                    {
-                        return 10;
-                    }
-                    if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
+
+                    if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 ||
+                        userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
                         userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
                         userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
                         userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
                     {
+                        if (IsSegmentedLiveStream)
+                        {
+                            return 6;
+                        }
+
                         return 10;
                     }
 

+ 6 - 0
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -85,6 +85,12 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string MinPremiereDate { get; set; }
 
+        [ApiMember(Name = "MinDateLastSaved", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MinDateLastSaved { get; set; }
+
+        [ApiMember(Name = "MinDateLastSavedForUser", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MinDateLastSavedForUser { get; set; }
+
         [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string MaxPremiereDate { get; set; }
 

+ 10 - 0
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -294,6 +294,16 @@ namespace MediaBrowser.Api.UserLibrary
                 }
             }
 
+            if (!string.IsNullOrEmpty(request.MinDateLastSaved))
+            {
+                query.MinDateLastSaved = DateTime.Parse(request.MinDateLastSaved, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+            }
+
+            if (!string.IsNullOrEmpty(request.MinDateLastSavedForUser))
+            {
+                query.MinDateLastSavedForUser = DateTime.Parse(request.MinDateLastSavedForUser, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+            }
+
             if (!string.IsNullOrEmpty(request.MinPremiereDate))
             {
                 query.MinPremiereDate = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();

+ 6 - 3
MediaBrowser.Controller/Entities/Folder.cs

@@ -672,7 +672,7 @@ namespace MediaBrowser.Controller.Entities
         {
             return ItemRepository.GetItemList(new InternalItemsQuery
             {
-                ParentId = Id,
+                Parent = this,
                 GroupByPresentationUniqueKey = false,
                 DtoOptions = new DtoOptions(true)
             });
@@ -692,7 +692,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 Recursive = false,
                 Limit = 0,
-                ParentId = Id,
+                Parent = this,
                 DtoOptions = new DtoOptions(false)
                 {
                     EnableImages = false
@@ -743,7 +743,10 @@ namespace MediaBrowser.Controller.Entities
 
             if (!(this is UserRootFolder) && !(this is AggregateFolder))
             {
-                query.ParentId = query.ParentId ?? Id;
+                if (!query.ParentId.HasValue)
+                {
+                    query.Parent = this;
+                }
             }
 
             if (RequiresPostFiltering2(query))

+ 19 - 0
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -126,9 +126,27 @@ namespace MediaBrowser.Controller.Entities
         public bool? IsVirtualItem { get; set; }
 
         public Guid? ParentId { get; set; }
+        public string ParentType { get; set; }
         public string[] AncestorIds { get; set; }
         public string[] TopParentIds { get; set; }
 
+        public BaseItem Parent
+        {
+            set
+            {
+                if (value == null)
+                {
+                    ParentId = null;
+                    ParentType = null;
+                }
+                else
+                {
+                    ParentId = value.Id;
+                    ParentType = value.GetType().Name;
+                }
+            }
+        }
+
         public string[] PresetViews { get; set; }
         public SourceType[] SourceTypes { get; set; }
         public SourceType[] ExcludeSourceTypes { get; set; }
@@ -156,6 +174,7 @@ namespace MediaBrowser.Controller.Entities
 
         public DateTime? MinDateCreated { get; set; }
         public DateTime? MinDateLastSaved { get; set; }
+        public DateTime? MinDateLastSavedForUser { get; set; }
 
         public DtoOptions DtoOptions { get; set; }
         public int MinSimilarityScore { get; set; }

+ 18 - 18
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -130,7 +130,7 @@ namespace MediaBrowser.Controller.Entities
                         if (query.Recursive)
                         {
                             query.Recursive = true;
-                            query.ParentId = queryParent.Id;
+                            query.Parent = queryParent;
                             query.SetUser(user);
 
                             return _libraryManager.GetItemsResult(query);
@@ -355,7 +355,7 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMusicPlaylists(Folder parent, User user, InternalItemsQuery query)
         {
-            query.ParentId = null;
+            query.Parent = null;
             query.IncludeItemTypes = new[] { typeof(Playlist).Name };
             query.SetUser(user);
             query.Recursive = true;
@@ -366,7 +366,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetMusicAlbums(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
@@ -377,7 +377,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetMusicSongs(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(Audio.Audio).Name };
@@ -405,7 +405,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.IsFavorite = true;
             query.IncludeItemTypes = new[] { typeof(Audio.Audio).Name };
@@ -416,7 +416,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.IsFavorite = true;
             query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
@@ -466,7 +466,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetFavoriteMovies(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.IsFavorite = true;
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
@@ -477,7 +477,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetFavoriteSeries(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.IsFavorite = true;
             query.IncludeItemTypes = new[] { typeof(Series).Name };
@@ -488,7 +488,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetFavoriteEpisodes(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.IsFavorite = true;
             query.IncludeItemTypes = new[] { typeof(Episode).Name };
@@ -499,7 +499,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
@@ -518,7 +518,7 @@ namespace MediaBrowser.Controller.Entities
             query.SortOrder = SortOrder.Descending;
 
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
@@ -532,7 +532,7 @@ namespace MediaBrowser.Controller.Entities
             query.SortOrder = SortOrder.Descending;
             query.IsResumable = true;
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
@@ -586,7 +586,7 @@ namespace MediaBrowser.Controller.Entities
         private async Task<QueryResult<BaseItem>> GetMovieGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = queryParent.Id;
+            query.Parent = queryParent;
             query.GenreIds = new[] { displayParent.Id.ToString("N") };
             query.SetUser(user);
 
@@ -602,7 +602,7 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetBoxsetView(Folder parent, User user, InternalItemsQuery query)
         {
-            query.ParentId = null;
+            query.Parent = null;
             query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
             query.SetUser(user);
             query.Recursive = true;
@@ -644,7 +644,7 @@ namespace MediaBrowser.Controller.Entities
             query.SortOrder = SortOrder.Descending;
 
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Episode).Name };
@@ -674,7 +674,7 @@ namespace MediaBrowser.Controller.Entities
             query.SortOrder = SortOrder.Descending;
             query.IsResumable = true;
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Episode).Name };
@@ -685,7 +685,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = parent.Id;
+            query.Parent = parent;
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(Series).Name };
@@ -729,7 +729,7 @@ namespace MediaBrowser.Controller.Entities
         private QueryResult<BaseItem> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
-            query.ParentId = queryParent.Id;
+            query.Parent = queryParent;
             query.GenreIds = new[] { displayParent.Id.ToString("N") };
             query.SetUser(user);
 

+ 1 - 1
MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs

@@ -46,7 +46,7 @@ namespace MediaBrowser.Server.Implementations.Devices
         {
             if (!_hasChildren.HasValue)
             {
-                _hasChildren = LibraryManager.GetItemIds(new InternalItemsQuery { ParentId = Id }).Count > 0;
+                _hasChildren = LibraryManager.GetItemIds(new InternalItemsQuery { Parent = this }).Count > 0;
             }
 
             return _hasChildren.Value;

+ 1 - 1
SharedVersion.cs

@@ -1,3 +1,3 @@
 using System.Reflection;
 
-[assembly: AssemblyVersion("3.2.17.10")]
+[assembly: AssemblyVersion("3.2.17.11")]