From ff33d6b5166d9d01ae32f9b66d9253290a56ef42 Mon Sep 17 00:00:00 2001
From: tidusjar <tidusjar@gmail.com>
Date: Mon, 11 Apr 2022 20:36:49 +0100
Subject: [PATCH] added the watchlist history

---
 .../PlexWatchlistImportTests.cs               | 93 +++++++++++++++++++
 .../Jobs/Plex/PlexContentSync.cs              |  2 +-
 .../Jobs/Plex/PlexWatchlistImport.cs          | 27 +++++-
 src/Ombi.Store/Context/ExternalContext.cs     |  1 +
 .../Entities/PlexWatchlistHistory.cs          | 10 ++
 5 files changed, 131 insertions(+), 2 deletions(-)
 create mode 100644 src/Ombi.Store/Entities/PlexWatchlistHistory.cs

diff --git a/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs b/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs
index b4759e280..b45f471d2 100644
--- a/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs
+++ b/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs
@@ -10,9 +10,11 @@ using Ombi.Core.Settings;
 using Ombi.Core.Settings.Models.External;
 using Ombi.Schedule.Jobs.Plex;
 using Ombi.Store.Entities;
+using Ombi.Store.Repository;
 using Ombi.Test.Common;
 using Quartz;
 using System.Collections.Generic;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -45,6 +47,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
         }
         [Test]
         public async Task TerminatesWhenWatchlistIsNotEnabled()
@@ -54,6 +58,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
         }
 
         [Test]
@@ -65,6 +71,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
         }
 
         [Test]
@@ -86,6 +94,31 @@ namespace Ombi.Schedule.Tests
             await _subject.Execute(_context.Object);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
+        }
+
+
+        [Test]
+        public async Task MultipleUsers()
+        {
+            _mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
+            var um = MockHelper.MockUserManager(new List<OmbiUser>
+            {
+                new OmbiUser { Id = "abc1", UserType = UserType.PlexUser, MediaServerToken = "abc1", UserName = "abc1", NormalizedUserName = "ABC1" },
+                new OmbiUser { Id = "abc2", UserType = UserType.PlexUser, MediaServerToken = "abc2", UserName = "abc2", NormalizedUserName = "ABC2" },
+                new OmbiUser { Id = "abc3", UserType = UserType.PlexUser, MediaServerToken = "abc3", UserName = "abc3", NormalizedUserName = "ABC3" },
+                new OmbiUser { Id = "abc4", UserType = UserType.PlexUser, MediaServerToken = "abc4", UserName = "abc4", NormalizedUserName = "ABC4" },
+                new OmbiUser { Id = "abc5", UserType = UserType.PlexUser, MediaServerToken = "abc5", UserName = "abc5", NormalizedUserName = "ABC5" },
+            });
+            _mocker.Use(um);
+            _subject = _mocker.CreateInstance<PlexWatchlistImport>();
+
+            await _subject.Execute(_context.Object);
+            _mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
+            _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
         }
 
 
@@ -134,6 +167,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.Is<MovieRequestViewModel>(x => x.TheMovieDbId == 123)), Times.Once);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<IMovieRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Once);
         }
 
 
@@ -182,6 +217,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<ITvRequestEngine>(x => x.RequestTvShow(It.Is<TvRequestViewModelV2>(x => x.TheMovieDbId == 123)), Times.Once);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<ITvRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Once);
         }
 
         [Test]
@@ -229,6 +266,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.Is<MovieRequestViewModel>(x => x.TheMovieDbId == 123)), Times.Once);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<IMovieRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
         }
 
         [Test]
@@ -276,6 +315,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<ITvRequestEngine>(x => x.RequestTvShow(It.Is<TvRequestViewModelV2>(x => x.TheMovieDbId == 123)), Times.Once);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<ITvRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
         }
 
         [Test]
@@ -323,6 +364,8 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<IMovieRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
         }
 
         [Test]
@@ -370,6 +413,56 @@ namespace Ombi.Schedule.Tests
             _mocker.Verify<ITvRequestEngine>(x => x.RequestTvShow(It.IsAny<TvRequestViewModelV2>()), Times.Never);
             _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
             _mocker.Verify<ITvRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
+        }
+
+        [Test]
+        public async Task MovieRequestFromWatchList_AlreadyImported()
+        {
+            _mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
+            _mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
+            {
+                MediaContainer = new PlexWatchlist
+                {
+                    Metadata = new List<Metadata>
+                    {
+                        new Metadata
+                        {
+                            type = "movie",
+                            ratingKey = "abc"
+                        }
+                    }
+                }
+            });
+            _mocker.Setup<IPlexApi, Task<PlexWatchlistMetadataContainer>>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()))
+                .ReturnsAsync(new PlexWatchlistMetadataContainer
+                {
+                    MediaContainer = new PlexWatchlistMetadata
+                    {
+                        Metadata = new WatchlistMetadata[]
+                        {
+                            new WatchlistMetadata
+                            {
+                                Guid = new List<PlexGuids>
+                                {
+                                    new PlexGuids
+                                    {
+                                        Id = "tmdb://123"
+                                    }
+                                }
+                            }
+                        }
+
+                    }
+                });
+            _mocker.Setup<IRepository<PlexWatchlistHistory>, IQueryable<PlexWatchlistHistory>>(x => x.GetAll()).Returns(new List<PlexWatchlistHistory> { new PlexWatchlistHistory { Id = 1, TmdbId = "123" } }.AsQueryable());
+            await _subject.Execute(_context.Object);
+            _mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
+            _mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
+            _mocker.Verify<IMovieRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
+            _mocker.Verify<IRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
         }
     }
 }
diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs
index 170f6b943..9c5a651e2 100644
--- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs
+++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs
@@ -385,7 +385,7 @@ namespace Ombi.Schedule.Jobs.Plex
                         continue;
                     }
 
-                    var qualities = movie.Media?.Select(x => x.videoResolution);
+                    var qualities = movie?.Media?.Select(x => x?.videoResolution ?? string.Empty) ?? Enumerable.Empty<string>();
                     var is4k = qualities != null && qualities.Any(x => x.Equals("4k", StringComparison.InvariantCultureIgnoreCase));
                     var selectedQuality = is4k ? null : qualities?.OrderBy(x => x)?.FirstOrDefault() ?? string.Empty;
 
diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs b/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs
index ac5d21b16..b62ab0ddf 100644
--- a/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs
+++ b/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs
@@ -12,6 +12,7 @@ using Ombi.Helpers;
 using Ombi.Hubs;
 using Ombi.Store.Entities;
 using Ombi.Store.Entities.Requests;
+using Ombi.Store.Repository;
 using Quartz;
 using System;
 using System.Collections.Generic;
@@ -30,10 +31,11 @@ namespace Ombi.Schedule.Jobs.Plex
         private readonly ITvRequestEngine _tvRequestEngine;
         private readonly IHubContext<NotificationHub> _hub;
         private readonly ILogger _logger;
+        private readonly IRepository<PlexWatchlistHistory> _watchlistRepo;
 
         public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
             IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IHubContext<NotificationHub> hub,
-            ILogger<PlexWatchlistImport> logger)
+            ILogger<PlexWatchlistImport> logger, IRepository<PlexWatchlistHistory> watchlistRepo)
         {
             _plexApi = plexApi;
             _settings = settings;
@@ -42,6 +44,7 @@ namespace Ombi.Schedule.Jobs.Plex
             _tvRequestEngine = tvRequestEngine;
             _hub = hub;
             _logger = logger;
+            _watchlistRepo = watchlistRepo;
         }
 
         public async Task Execute(IJobExecutionContext context)
@@ -82,6 +85,15 @@ namespace Ombi.Schedule.Jobs.Plex
                             // We need a MovieDbId to support this;
                             continue;
                         }
+
+                        // Check to see if we have already imported this item
+                        var alreadyImported = _watchlistRepo.GetAll().Any(x => x.TmdbId == providerIds.TheMovieDb);
+                        if (alreadyImported)
+                        {
+                            _logger.LogDebug($"{item.title} already imported via Plex WatchList, skipping");
+                            continue;
+                        }
+
                         switch (item.type)
                         {
                             case "show":
@@ -118,6 +130,13 @@ namespace Ombi.Schedule.Jobs.Plex
             }
             else
             {
+                // Add to the watchlist history
+                var history = new PlexWatchlistHistory
+                {
+                    TmdbId = theMovieDbId.ToString()
+                };
+                await _watchlistRepo.Add(history);
+                
                 _logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}");
             }
         }
@@ -138,6 +157,12 @@ namespace Ombi.Schedule.Jobs.Plex
             }
             else
             {
+                // Add to the watchlist history
+                var history = new PlexWatchlistHistory
+                {
+                    TmdbId = theMovieDbId.ToString()
+                };
+                await _watchlistRepo.Add(history);
                 _logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}");
             }
         }
diff --git a/src/Ombi.Store/Context/ExternalContext.cs b/src/Ombi.Store/Context/ExternalContext.cs
index b6f8d6485..f13c1e74f 100644
--- a/src/Ombi.Store/Context/ExternalContext.cs
+++ b/src/Ombi.Store/Context/ExternalContext.cs
@@ -27,6 +27,7 @@ namespace Ombi.Store.Context
         public DbSet<PlexServerContent> PlexServerContent { get; set; }
         public DbSet<PlexSeasonsContent> PlexSeasonsContent { get; set; }
         public DbSet<PlexEpisode> PlexEpisode { get; set; }
+        public DbSet<PlexWatchlistHistory> PlexWatchlistHistory { get; set; }
         public DbSet<RadarrCache> RadarrCache { get; set; }
         public DbSet<CouchPotatoCache> CouchPotatoCache { get; set; }
         public DbSet<EmbyContent> EmbyContent { get; set; }
diff --git a/src/Ombi.Store/Entities/PlexWatchlistHistory.cs b/src/Ombi.Store/Entities/PlexWatchlistHistory.cs
new file mode 100644
index 000000000..e6aee29b4
--- /dev/null
+++ b/src/Ombi.Store/Entities/PlexWatchlistHistory.cs
@@ -0,0 +1,10 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Ombi.Store.Entities
+{
+    [Table(nameof(PlexWatchlistHistory))]
+    public class PlexWatchlistHistory : Entity
+    {
+        public string TmdbId { get; set; }
+    }
+}