diff --git a/MediaBrowser.Model/Sync/SyncJobItem.cs b/MediaBrowser.Model/Sync/SyncJobItem.cs index 195d1e17ef..77464be58d 100644 --- a/MediaBrowser.Model/Sync/SyncJobItem.cs +++ b/MediaBrowser.Model/Sync/SyncJobItem.cs @@ -96,6 +96,11 @@ namespace MediaBrowser.Model.Sync /// /// true if this instance is marked for removal; otherwise, false. public bool IsMarkedForRemoval { get; set; } + /// + /// Gets or sets the index of the job item. + /// + /// The index of the job item. + public int JobItemIndex { get; set; } public SyncJobItem() { diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index e195df7dd7..3ac3cccb3d 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -2,14 +2,12 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/MediaBrowser.Providers/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs index 29b75d8302..3eaef59fbe 100644 --- a/MediaBrowser.Providers/Photos/PhotoProvider.cs +++ b/MediaBrowser.Providers/Photos/PhotoProvider.cs @@ -15,7 +15,7 @@ using TagLib.IFD.Tags; namespace MediaBrowser.Providers.Photos { - public class PhotoProvider : ICustomMetadataProvider, IHasItemChangeMonitor + public class PhotoProvider : ICustomMetadataProvider, IHasItemChangeMonitor, IForcedProvider { private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs b/MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs new file mode 100644 index 0000000000..27ec6cc3b1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Server.Implementations.Photos; +using MoreLinq; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Collections +{ + public class CollectionImageProvider : BaseDynamicImageProvider, ICustomMetadataProvider + { + public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager) + : base(fileSystem, providerManager) + { + } + + protected override Task> GetItemsWithImages(IHasImages item) + { + var playlist = (BoxSet)item; + + var items = playlist.Children.Concat(playlist.GetLinkedChildren()) + .Select(i => + { + var subItem = i; + + var episode = subItem as Episode; + + if (episode != null) + { + var series = episode.Series; + if (series != null && series.HasImage(ImageType.Primary)) + { + return series; + } + } + + if (subItem.HasImage(ImageType.Primary)) + { + return subItem; + } + + var parent = subItem.Parent; + + if (parent != null && parent.HasImage(ImageType.Primary)) + { + if (parent is MusicAlbum) + { + return parent; + } + } + + return null; + }) + .Where(i => i != null) + .DistinctBy(i => i.Id) + .ToList(); + + return Task.FromResult(GetFinalItems(items)); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index b02c52874c..fa4de728b9 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -13,8 +13,8 @@ namespace MediaBrowser.Server.Implementations.Collections public override bool IsVisible(User user) { - return GetChildren(user, true).Any() && - base.IsVisible(user); + return base.IsVisible(user) && GetChildren(user, false) + .Any(); } public override bool IsHidden diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 4206ccf591..5d37007e06 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -34,6 +34,7 @@ "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "HeaderSearch": "Search", + "ValueDateCreated": "Date created: {0}", "LabelArtist": "Artist", "LabelMovie": "Movie", "LabelMusicVideo": "Music Video", @@ -46,6 +47,7 @@ "ButtonSave": "Save", "HeaderDevices": "Devices", "ButtonScheduledTasks": "Scheduled tasks", + "HeaderSelectCertificatePath": "Select Certificate Path", "ConfirmMessageScheduledTaskButton": "This operation normally runs automatically as a scheduled task. It can also be run manually here. To configure the scheduled task, see:", "HeaderSupporterBenefit": "A supporter membership provides additional benefits such as access to premium plugins, internet channel content, and more. {0}Learn more{1}.", "HeaderWelcomeToMediaBrowserServerDashboard": "Welcome to the Media Browser Dashboard", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index e4677e4d10..3a5e7c77e7 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -55,6 +55,8 @@ "HeaderAudio": "Audio", "HeaderVideo": "Video", "HeaderPaths": "Paths", + "LabelCustomCertificatePath": "Custom certificate path:", + "LabelCustomCertificatePathHelp": "Supply your own ssl certificate. If omitted, the server will create a self-signed certificate.", "TitleNotifications": "Notifications", "ButtonDonateWithPayPal": "Donate with PayPal", "OptionDetectArchiveFilesAsMedia": "Detect archive files as media", diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 1737e6fd39..c0cc0395cd 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -117,6 +117,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index cd79282909..99c59abc5d 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -1,7 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index dbaf23656d..369e8512fa 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Photos { - public abstract class BaseDynamicImageProvider : IHasChangeMonitor + public abstract class BaseDynamicImageProvider : IHasChangeMonitor, IForcedProvider where T : IHasImages { protected IFileSystem FileSystem { get; private set; } diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 89395a00b5..2b2c524061 100644 --- a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Server.Implementations.Playlists public override bool IsVisible(User user) { - return base.IsVisible(user) && GetRecursiveChildren(user, false) + return base.IsVisible(user) && GetChildren(user, false) .OfType() .Any(i => i.IsVisible(user)); } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index 67f9d363e2..716584084c 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -90,6 +90,10 @@ namespace MediaBrowser.Server.Implementations.Sync continue; } + var index = jobItems.Count == 0 ? + 0 : + (jobItems.Select(i => i.JobItemIndex).Max() + 1); + jobItem = new SyncJobItem { Id = Guid.NewGuid().ToString("N"), @@ -97,7 +101,8 @@ namespace MediaBrowser.Server.Implementations.Sync ItemName = GetSyncJobItemName(item), JobId = job.Id, TargetId = job.TargetId, - DateCreated = DateTime.UtcNow + DateCreated = DateTime.UtcNow, + JobItemIndex = index }; await _syncRepo.Create(jobItem).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index 1cd8e8a9d4..0e527ea540 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task Initialize() { - var dbFile = Path.Combine(_appPaths.DataPath, "sync13.db"); + var dbFile = Path.Combine(_appPaths.DataPath, "sync14.db"); _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); @@ -50,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.Sync "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Quality TEXT NOT NULL, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", "create index if not exists idx_SyncJobs on SyncJobs(Id)", - "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT)", + "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT)", "create index if not exists idx_SyncJobItems on SyncJobs(Id)", //pragmas @@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemCount"); _saveJobItemCommand = _connection.CreateCommand(); - _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval)"; + _saveJobItemCommand.CommandText = "replace into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex)"; _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@Id"); _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@ItemId"); @@ -111,10 +111,11 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@AdditionalFiles"); _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@MediaSource"); _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@IsMarkedForRemoval"); + _saveJobItemCommand.Parameters.Add(_saveJobItemCommand, "@JobItemIndex"); } private const string BaseJobSelectText = "select Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs"; - private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval from SyncJobItems"; + private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex from SyncJobItems"; public SyncJob GetJob(string id) { @@ -496,7 +497,7 @@ namespace MediaBrowser.Server.Implementations.Sync var startIndex = query.StartIndex ?? 0; if (startIndex > 0) { - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY DateCreated LIMIT {0})", + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})", startIndex.ToString(_usCulture))); } @@ -505,7 +506,7 @@ namespace MediaBrowser.Server.Implementations.Sync cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); } - cmd.CommandText += " ORDER BY DateCreated"; + cmd.CommandText += " ORDER BY JobItemIndex, DateCreated"; if (query.Limit.HasValue) { @@ -574,6 +575,7 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobItemCommand.GetParameter(index++).Value = _json.SerializeToString(jobItem.AdditionalFiles); _saveJobItemCommand.GetParameter(index++).Value = jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource); _saveJobItemCommand.GetParameter(index++).Value = jobItem.IsMarkedForRemoval; + _saveJobItemCommand.GetParameter(index++).Value = jobItem.JobItemIndex; _saveJobItemCommand.Transaction = transaction; @@ -648,7 +650,7 @@ namespace MediaBrowser.Server.Implementations.Sync info.TargetId = reader.GetString(8); - info.DateCreated = reader.GetDateTime(9); + info.DateCreated = reader.GetDateTime(9).ToUniversalTime(); if (!reader.IsDBNull(10)) { @@ -676,7 +678,8 @@ namespace MediaBrowser.Server.Implementations.Sync } info.IsMarkedForRemoval = reader.GetBoolean(13); - + info.JobItemIndex = reader.GetInt32(14); + return info; } diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs index 66e771aa37..8c21727a4b 100644 --- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs +++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs @@ -163,7 +163,10 @@ namespace MediaBrowser.Server.Implementations.TV return new Tuple(nextUp, lastWatchedDate); } - return new Tuple(null, lastWatchedDate); + var firstEpisode = allEpisodes.LastOrDefault(i => i.LocationType != LocationType.Virtual && !i.IsPlayed(user)); + + // Return the first episode + return new Tuple(firstEpisode, DateTime.MinValue); } private IEnumerable FilterSeries(NextUpQuery request, IEnumerable items) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 7eb68187a3..4fc6ae6fa1 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -796,35 +796,35 @@ namespace MediaBrowser.Server.Startup.Common private string GetCertificatePath(bool generateCertificate) { - if (string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.CertificatePath)) + if (!string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.CertificatePath)) { - // Generate self-signed cert - var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); - var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + certHost.GetMD5().ToString("N") + ".pfx"); - - if (generateCertificate) - { - if (!File.Exists(certPath)) - { - Directory.CreateDirectory(Path.GetDirectoryName(certPath)); - - try - { - NetworkManager.GenerateSelfSignedSslCertificate(certPath, certHost); - } - catch (Exception ex) - { - Logger.ErrorException("Error creating ssl cert", ex); - return null; - } - } - } - - return certPath; + // Custom cert + return ServerConfigurationManager.Configuration.CertificatePath; } - // Custom cert - return ServerConfigurationManager.Configuration.CertificatePath; + // Generate self-signed cert + var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); + var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + certHost.GetMD5().ToString("N") + ".pfx"); + + if (generateCertificate) + { + if (!File.Exists(certPath)) + { + Directory.CreateDirectory(Path.GetDirectoryName(certPath)); + + try + { + NetworkManager.GenerateSelfSignedSslCertificate(certPath, certHost); + } + catch (Exception ex) + { + Logger.ErrorException("Error creating ssl cert", ex); + return null; + } + } + } + + return certPath; } ///