#254 Removed the cache, we are now storing the plex information into the database.

There is a big structure change around this, also increased the default check time to be in hours.
This commit is contained in:
tidusjar 2016-08-05 11:34:00 +01:00
parent af1c93620f
commit 2608e53399
29 changed files with 479 additions and 170 deletions

View file

@ -34,7 +34,6 @@ using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex; using PlexRequests.Api.Models.Plex;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Exceptions;
using RestSharp; using RestSharp;
@ -81,7 +80,7 @@ namespace PlexRequests.Api
request.AddJsonBody(userModel); request.AddJsonBody(userModel);
var obj = RetryHandler.Execute<PlexAuthentication>(() => Api.Execute<PlexAuthentication> (request, new Uri(SignInUri)), var obj = RetryHandler.Execute<PlexAuthentication>(() => Api.Execute<PlexAuthentication> (request, new Uri(SignInUri)),
(exception, timespan) => Log.Error (exception, "Exception when calling SignIn for Plex, Retrying {0}", timespan), null); (exception, timespan) => Log.Error (exception, "Exception when calling SignIn for Plex, Retrying {0}", timespan));
return obj; return obj;
} }

View file

@ -37,7 +37,7 @@ namespace PlexRequests.Core.SettingModels
StoreBackup = 24; StoreBackup = 24;
StoreCleanup = 24; StoreCleanup = 24;
UserRequestLimitResetter = 12; UserRequestLimitResetter = 12;
PlexEpisodeCacher = 20; PlexEpisodeCacher = 2;
} }
public int PlexAvailabilityChecker { get; set; } public int PlexAvailabilityChecker { get; set; }

View file

@ -79,6 +79,8 @@
<Compile Include="AssemblyHelperTests.cs" /> <Compile Include="AssemblyHelperTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PlexHelperTests.cs" /> <Compile Include="PlexHelperTests.cs" />
<Compile Include="TypeHelperTests.cs" />
<Compile Include="StringHelperTests.cs" />
<Compile Include="UriHelperTests.cs" /> <Compile Include="UriHelperTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -86,10 +88,18 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj"> <ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336d-42a3-482a-804c-836e60173dfa}</Project> <Project>{1252336d-42a3-482a-804c-836e60173dfa}</Project>
<Name>PlexRequests.Helpers</Name> <Name>PlexRequests.Helpers</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Choose> <Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">

View file

@ -29,9 +29,8 @@ using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using PlexRequests.Core.Models; using PlexRequests.Core.Models;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Tests namespace PlexRequests.Helpers.Tests
{ {
[TestFixture] [TestFixture]
public class StringHelperTests public class StringHelperTests
@ -48,6 +47,12 @@ namespace PlexRequests.UI.Tests
return input.ToCamelCaseWords(); return input.ToCamelCaseWords();
} }
[TestCaseSource(nameof(PrefixData))]
public string AddPrefix(string[] input, string prefix, string separator)
{
return input.AddPrefix(prefix, separator);
}
private static IEnumerable<TestCaseData> StringData private static IEnumerable<TestCaseData> StringData
{ {
get get
@ -71,5 +76,19 @@ namespace PlexRequests.UI.Tests
yield return new TestCaseData(IssueStatus.ResolvedIssue.ToString()).Returns("Resolved Issue").SetName("enum resolved"); yield return new TestCaseData(IssueStatus.ResolvedIssue.ToString()).Returns("Resolved Issue").SetName("enum resolved");
} }
} }
private static IEnumerable<TestCaseData> PrefixData
{
get
{
yield return new TestCaseData(new[] {"abc","def","ghi"}, "@", ",").Returns("@abc,@def,@ghi").SetName("Happy Path");
yield return new TestCaseData(new[] {"abc","def","ghi"}, "!!", "").Returns("!!abc!!def!!ghi").SetName("Different Separator Path");
yield return new TestCaseData(new[] {"abc"}, "", "").Returns("abc").SetName("Single Item");
yield return new TestCaseData(new string[0], "", "").Returns(string.Empty).SetName("Empty Array");
yield return new TestCaseData(new [] {"abc","aaaa"}, null, ",").Returns("abc,aaaa").SetName("Null prefix");
yield return new TestCaseData(new [] {"abc","aaaa"}, "@", null).Returns("@abc@aaaa").SetName("Null separator test");
yield return new TestCaseData(new [] {"abc","aaaa"}, null, null).Returns("abcaaaa").SetName("Null separator and prefix");
}
}
} }
} }

View file

@ -0,0 +1,73 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StringHelperTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using NUnit.Framework;
using PlexRequests.Store;
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class TypeHelperTests
{
[TestCaseSource(nameof(TypeData))]
public string[] FirstCharToUpperTest(Type input)
{
return input.GetPropertyNames();
}
private static IEnumerable<TestCaseData> TypeData
{
get
{
yield return new TestCaseData(typeof(TestClass1)).Returns(new[] { "Test1", "Test2", "Test3" }).SetName("Simple Class");
yield return new TestCaseData(typeof(int)).Returns(new string[0]).SetName("NoPropeties Class");
yield return new TestCaseData(typeof(IEnumerable<>)).Returns(new string[0]).SetName("Interface");
yield return new TestCaseData(typeof(string)).Returns(new[] { "Chars", "Length" }).SetName("String");
yield return new TestCaseData(typeof(RequestedModel)).Returns(
new[]
{
"ProviderId", "ImdbId", "TvDbId", "Overview", "Title", "PosterPath", "ReleaseDate", "Type",
"Status", "Approved", "RequestedBy", "RequestedDate", "Available", "Issues", "OtherMessage", "AdminNote",
"SeasonList", "SeasonCount", "SeasonsRequested", "MusicBrainzId", "RequestedUsers","ArtistName",
"ArtistId","IssueId","Episodes","AllUsers","CanApprove","Id"
}).SetName("Requested Model");
}
}
private sealed class TestClass1
{
public string Test1 { get; set; }
public int Test2 { get; set; }
public long[] Test3 { get; set; }
}
}
}

View file

@ -82,6 +82,8 @@
<Compile Include="SerializerSettings.cs" /> <Compile Include="SerializerSettings.cs" />
<Compile Include="StringCipher.cs" /> <Compile Include="StringCipher.cs" />
<Compile Include="StringHasher.cs" /> <Compile Include="StringHasher.cs" />
<Compile Include="StringHelper.cs" />
<Compile Include="TypeHelper.cs" />
<Compile Include="UriHelper.cs" /> <Compile Include="UriHelper.cs" />
<Compile Include="UserClaims.cs" /> <Compile Include="UserClaims.cs" />
</ItemGroup> </ItemGroup>

View file

@ -25,9 +25,10 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace PlexRequests.UI.Helpers namespace PlexRequests.Helpers
{ {
public static class StringHelper public static class StringHelper
{ {
@ -46,5 +47,22 @@ namespace PlexRequests.UI.Helpers
return input; return input;
return Regex.Replace(input.FirstCharToUpper(), "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 "); return Regex.Replace(input.FirstCharToUpper(), "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ");
} }
public static string AddPrefix(this string[] values, string prefix, string separator)
{
var sb = new StringBuilder();
var len = values.Length;
for (var i = 0; i < len; i++)
{
sb.Append(prefix).Append(values[i]);
// If it's not the last item in the collection, then add a separator
if (i < len - 1)
{
sb.Append(separator);
}
}
return sb.ToString();
}
} }
} }

View file

@ -0,0 +1,39 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: TypeHelper.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
namespace PlexRequests.Helpers
{
public static class TypeHelper
{
public static string[] GetPropertyNames(this Type t)
{
return t.GetProperties().Select(x => x.Name).ToArray();
}
}
}

View file

@ -53,6 +53,7 @@ namespace PlexRequests.Services.Tests
private Mock<INotificationService> NotificationMock { get; set; } private Mock<INotificationService> NotificationMock { get; set; }
private Mock<IJobRecord> JobRec { get; set; } private Mock<IJobRecord> JobRec { get; set; }
private Mock<IRepository<UsersToNotify>> NotifyUsers { get; set; } private Mock<IRepository<UsersToNotify>> NotifyUsers { get; set; }
private Mock<IRepository<PlexEpisodes>> PlexEpisodes { get; set; }
[SetUp] [SetUp]
public void Setup() public void Setup()
@ -64,8 +65,9 @@ namespace PlexRequests.Services.Tests
NotificationMock = new Mock<INotificationService>(); NotificationMock = new Mock<INotificationService>();
CacheMock = new Mock<ICacheProvider>(); CacheMock = new Mock<ICacheProvider>();
NotifyUsers = new Mock<IRepository<UsersToNotify>>(); NotifyUsers = new Mock<IRepository<UsersToNotify>>();
PlexEpisodes = new Mock<IRepository<PlexEpisodes>>();
JobRec = new Mock<IJobRecord>(); JobRec = new Mock<IJobRecord>();
Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object); Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object);
} }

View file

@ -26,6 +26,9 @@
#endregion #endregion
using PlexRequests.Services.Models; using PlexRequests.Services.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using PlexRequests.Store.Models;
namespace PlexRequests.Services.Interfaces namespace PlexRequests.Services.Interfaces
{ {
@ -43,12 +46,12 @@ namespace PlexRequests.Services.Interfaces
/// Gets the episode's stored in the cache. /// Gets the episode's stored in the cache.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
HashSet<PlexEpisodeModel> GetEpisodeCache(); Task<IEnumerable<PlexEpisodes>> GetEpisodes();
/// <summary> /// <summary>
/// Gets the episode's stored in the cache and then filters on the TheTvDBId. /// Gets the episode's stored in the cache and then filters on the TheTvDBId.
/// </summary> /// </summary>
/// <param name="theTvDbId">The tv database identifier.</param> /// <param name="theTvDbId">The tv database identifier.</param>
/// <returns></returns> /// <returns></returns>
IEnumerable<PlexEpisodeModel> GetEpisodeCache(int theTvDbId); Task<IEnumerable<PlexEpisodes>> GetEpisodes(int theTvDbId);
} }
} }

View file

@ -29,6 +29,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper;
using NLog; using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
@ -53,7 +55,7 @@ namespace PlexRequests.Services.Jobs
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
{ {
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache, public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users) INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo)
{ {
Plex = plexSettings; Plex = plexSettings;
RequestService = request; RequestService = request;
@ -62,9 +64,11 @@ namespace PlexRequests.Services.Jobs
Notification = notify; Notification = notify;
Job = rec; Job = rec;
UserNotifyRepo = users; UserNotifyRepo = users;
EpisodeRepo = repo;
} }
private ISettingsService<PlexSettings> Plex { get; } private ISettingsService<PlexSettings> Plex { get; }
private IRepository<PlexEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; } private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
@ -241,15 +245,23 @@ namespace PlexRequests.Services.Jobs
public bool IsEpisodeAvailable(string theTvDbId, int season, int episode) public bool IsEpisodeAvailable(string theTvDbId, int season, int episode)
{ {
var episodes = Cache.Get<HashSet<PlexEpisodeModel>>(CacheKeys.PlexEpisodes); var ep = EpisodeRepo.Custom(
if (episodes == null) connection =>
{
connection.Open();
var result = connection.Query<PlexEpisodes>("select * from PlexEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId});
return result;
}).ToList();
if (!ep.Any())
{ {
Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}",theTvDbId, season, episode); Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}",theTvDbId, season, episode);
return false; return false;
} }
foreach (var result in episodes) foreach (var result in ep)
{ {
if (result.Episodes.ProviderId.Equals(theTvDbId) && result.Episodes.EpisodeNumber == episode && result.Episodes.SeasonNumber == season) if (result.ProviderId.Equals(theTvDbId) && result.EpisodeNumber == episode && result.SeasonNumber == season)
{ {
return true; return true;
} }
@ -258,35 +270,43 @@ namespace PlexRequests.Services.Jobs
} }
/// <summary> /// <summary>
/// Gets the episode's stored in the cache. /// Gets the episode's db in the cache.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public HashSet<PlexEpisodeModel> GetEpisodeCache() public async Task<IEnumerable<PlexEpisodes>> GetEpisodes()
{ {
var episodes = Cache.Get<HashSet<PlexEpisodeModel>>(CacheKeys.PlexEpisodes); var episodes = await EpisodeRepo.GetAllAsync();
if (episodes == null) if (episodes == null)
{ {
Log.Info("Episode cache info is not available."); Log.Info("Episode cache info is not available.");
return new HashSet<PlexEpisodeModel>(); return new HashSet<PlexEpisodes>();
} }
return episodes; return episodes;
} }
/// <summary> /// <summary>
/// Gets the episode's stored in the cache and then filters on the TheTvDBId. /// Gets the episode's stored in the db and then filters on the TheTvDBId.
/// </summary> /// </summary>
/// <param name="theTvDbId">The tv database identifier.</param> /// <param name="theTvDbId">The tv database identifier.</param>
/// <returns></returns> /// <returns></returns>
public IEnumerable<PlexEpisodeModel> GetEpisodeCache(int theTvDbId) public async Task<IEnumerable<PlexEpisodes>> GetEpisodes(int theTvDbId)
{ {
var episodes = Cache.Get<List<PlexEpisodeModel>>(CacheKeys.PlexEpisodes); var ep = await EpisodeRepo.CustomAsync(async connection =>
if (episodes == null)
{ {
Log.Info("Episode cache info is not available."); connection.Open();
return new List<PlexEpisodeModel>(); var result = await connection.QueryAsync<PlexEpisodes>("select * from PlexEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
});
var plexEpisodeses = ep as PlexEpisodes[] ?? ep.ToArray();
if (!plexEpisodeses.Any())
{
Log.Info("Episode db info is not available.");
return new List<PlexEpisodes>();
} }
return episodes.Where(x => x.Episodes.ProviderId == theTvDbId.ToString()); return plexEpisodeses;
} }
public List<PlexAlbum> GetPlexAlbums() public List<PlexAlbum> GetPlexAlbums()

View file

@ -37,6 +37,8 @@ using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Models; using PlexRequests.Services.Models;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using Quartz; using Quartz;
@ -45,12 +47,13 @@ namespace PlexRequests.Services.Jobs
public class PlexEpisodeCacher : IJob public class PlexEpisodeCacher : IJob
{ {
public PlexEpisodeCacher(ISettingsService<PlexSettings> plexSettings, IPlexApi plex, ICacheProvider cache, public PlexEpisodeCacher(ISettingsService<PlexSettings> plexSettings, IPlexApi plex, ICacheProvider cache,
IJobRecord rec) IJobRecord rec, IRepository<PlexEpisodes> repo)
{ {
Plex = plexSettings; Plex = plexSettings;
PlexApi = plex; PlexApi = plex;
Cache = cache; Cache = cache;
Job = rec; Job = rec;
Repo = repo;
} }
private ISettingsService<PlexSettings> Plex { get; } private ISettingsService<PlexSettings> Plex { get; }
@ -58,19 +61,23 @@ namespace PlexRequests.Services.Jobs
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private ICacheProvider Cache { get; } private ICacheProvider Cache { get; }
private IJobRecord Job { get; } private IJobRecord Job { get; }
private IRepository<PlexEpisodes> Repo { get; }
private const int ResultCount = 25; private const int ResultCount = 25;
private const string PlexType = "episode"; private const string PlexType = "episode";
private const string TableName = "PlexEpisodes";
public void CacheEpisodes() public void CacheEpisodes()
{ {
var videoHashset = new HashSet<Video>(); var videoHashset = new HashSet<Video>();
var settings = Plex.GetSettings(); var settings = Plex.GetSettings();
// Ensure Plex is setup correctly
if (string.IsNullOrEmpty(settings.PlexAuthToken)) if (string.IsNullOrEmpty(settings.PlexAuthToken))
{ {
return; return;
} }
// Get the librarys and then get the tv section
var sections = PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri); var sections = PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri);
var tvSection = sections.Directories.FirstOrDefault(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); var tvSection = sections.Directories.FirstOrDefault(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase));
var tvSectionId = tvSection?.Key; var tvSectionId = tvSection?.Key;
@ -78,10 +85,15 @@ namespace PlexRequests.Services.Jobs
var currentPosition = 0; var currentPosition = 0;
int totalSize; int totalSize;
// Get the first 25 episodes (Paged)
var episodes = PlexApi.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, tvSectionId, currentPosition, ResultCount); var episodes = PlexApi.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, tvSectionId, currentPosition, ResultCount);
// Parse the total amount of episodes
int.TryParse(episodes.TotalSize, out totalSize); int.TryParse(episodes.TotalSize, out totalSize);
currentPosition += ResultCount; currentPosition += ResultCount;
// Get all of the episodes in batches until we them all (Got'a catch 'em all!)
while (currentPosition < totalSize) while (currentPosition < totalSize)
{ {
videoHashset.UnionWith(PlexApi.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, tvSectionId, currentPosition, ResultCount).Video videoHashset.UnionWith(PlexApi.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, tvSectionId, currentPosition, ResultCount).Video
@ -89,29 +101,40 @@ namespace PlexRequests.Services.Jobs
currentPosition += ResultCount; currentPosition += ResultCount;
} }
var episodesModel = new HashSet<PlexEpisodeModel>(); var entities = new HashSet<PlexEpisodes>();
foreach (var video in videoHashset) foreach (var video in videoHashset)
{ {
var ratingKey = video.RatingKey;
var metadata = PlexApi.GetEpisodeMetaData(settings.PlexAuthToken, settings.FullUri, ratingKey);
// Get the individual episode Metadata (This is for us to get the TheTVDBId which also includes the episode number and season number)
var metadata = PlexApi.GetEpisodeMetaData(settings.PlexAuthToken, settings.FullUri, video.RatingKey);
// Loop through the metadata and create the model to insert into the DB
foreach (var metadataVideo in metadata.Video) foreach (var metadataVideo in metadata.Video)
{ {
episodesModel.Add(new PlexEpisodeModel var epInfo = PlexHelper.GetSeasonsAndEpisodesFromPlexGuid(metadataVideo.Guid);
entities.Add(
new PlexEpisodes
{ {
RatingKey = metadataVideo.RatingKey, EpisodeNumber = epInfo.EpisodeNumber,
EpisodeTitle = metadataVideo.Title, EpisodeTitle = metadataVideo.Title,
Guid = metadataVideo.Guid, ProviderId = epInfo.ProviderId,
ShowTitle = metadataVideo.GrandparentTitle RatingKey = metadataVideo.RatingKey,
SeasonNumber = epInfo.SeasonNumber,
ShowTitle = metadataVideo.Title
}); });
} }
} }
// Delete all of the current items
Repo.DeleteAll(TableName);
if (episodesModel.Any()) // Insert the new items
var result = Repo.BatchInsert(entities, TableName, typeof(PlexEpisodes).GetPropertyNames());
if (!result)
{ {
Cache.Set(CacheKeys.PlexEpisodes, episodesModel, CacheKeys.TimeFrameMinutes.SchedulerCaching); Log.Error("Saving the plex episodes to the DB Failed");
} }
} }

View file

@ -31,6 +31,10 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Dapper, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Build.Framework" /> <Reference Include="Microsoft.Build.Framework" />
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>

View file

@ -2,6 +2,7 @@
<packages> <packages>
<package id="Common.Logging" version="3.0.0" targetFramework="net45" /> <package id="Common.Logging" version="3.0.0" targetFramework="net45" />
<package id="Common.Logging.Core" version="3.0.0" targetFramework="net45" /> <package id="Common.Logging.Core" version="3.0.0" targetFramework="net45" />
<package id="Dapper" version="1.50.0-beta8" targetFramework="net45" />
<package id="MailKit" version="1.2.21" targetFramework="net45" requireReinstallation="True" /> <package id="MailKit" version="1.2.21" targetFramework="net45" requireReinstallation="True" />
<package id="MimeKit" version="1.2.22" targetFramework="net45" /> <package id="MimeKit" version="1.2.22" targetFramework="net45" />
<package id="NLog" version="4.3.6" targetFramework="net45" /> <package id="NLog" version="4.3.6" targetFramework="net45" />

View file

@ -0,0 +1,41 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: LogEntity.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Dapper.Contrib.Extensions;
namespace PlexRequests.Store.Models
{
[Table("PlexEpisodes")]
public class PlexEpisodes : Entity
{
public string EpisodeTitle { get; set; }
public string ShowTitle { get; set; }
public string RatingKey { get; set; }
public string ProviderId { get; set; }
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
}
}

View file

@ -65,6 +65,7 @@
<Compile Include="DbConfiguration.cs" /> <Compile Include="DbConfiguration.cs" />
<Compile Include="Entity.cs" /> <Compile Include="Entity.cs" />
<Compile Include="Models\IssueBlobs.cs" /> <Compile Include="Models\IssueBlobs.cs" />
<Compile Include="Models\PlexEpisodes.cs" />
<Compile Include="Models\PlexUsers.cs" /> <Compile Include="Models\PlexUsers.cs" />
<Compile Include="Models\ScheduledJobs.cs" /> <Compile Include="Models\ScheduledJobs.cs" />
<Compile Include="Models\RequestLimit.cs" /> <Compile Include="Models\RequestLimit.cs" />

View file

@ -24,10 +24,14 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper;
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
using Mono.Data.Sqlite; using Mono.Data.Sqlite;
@ -53,6 +57,24 @@ namespace PlexRequests.Store.Repository
public abstract Task<T> GetAsync(int id); public abstract Task<T> GetAsync(int id);
public abstract T Get(int id); public abstract T Get(int id);
public abstract Task<T> GetAsync(string id); public abstract Task<T> GetAsync(string id);
private IDbConnection Connection => Config.DbConnection();
public IEnumerable<T> Custom(Func<IDbConnection , IEnumerable<T>> func)
{
using (var cnn = Connection)
{
return func(cnn);
}
}
public async Task<IEnumerable<T>> CustomAsync(Func<IDbConnection, Task<IEnumerable<T>>> func)
{
using (var cnn = Connection)
{
return await func(cnn);
}
}
public long Insert(T entity) public long Insert(T entity)
{ {
@ -251,5 +273,50 @@ namespace PlexRequests.Store.Repository
throw; throw;
} }
} }
public bool BatchInsert(IEnumerable<T> entities, string tableName, params string[] values)
{
// If we have nothing to update, then it didn't fail...
if (!entities.Any())
{
return true;
}
try
{
ResetCache();
using (var db = Config.DbConnection())
{
var format = values.AddPrefix("@", ",");
var processQuery = $"INSERT INTO {tableName} VALUES ({format})";
var result = db.Execute(processQuery, entities);
return result == values.Length;
}
}
catch (SqliteException e) when (e.ErrorCode == SQLiteErrorCode.Corrupt)
{
Log.Fatal(CorruptMessage);
throw;
}
}
public void DeleteAll(string tableName)
{
try
{
ResetCache();
using (var db = Config.DbConnection())
{
db.Open();
db.Execute($"delete from {tableName}");
}
}
catch (SqliteException e) when (e.ErrorCode == SQLiteErrorCode.Corrupt)
{
Log.Fatal(CorruptMessage);
throw;
}
}
} }
} }

View file

@ -24,7 +24,9 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace PlexRequests.Store.Repository namespace PlexRequests.Store.Repository
@ -77,5 +79,11 @@ namespace PlexRequests.Store.Repository
/// <returns></returns> /// <returns></returns>
bool UpdateAll(IEnumerable<T> entity); bool UpdateAll(IEnumerable<T> entity);
Task<bool> UpdateAllAsync(IEnumerable<T> entity); Task<bool> UpdateAllAsync(IEnumerable<T> entity);
bool BatchInsert(IEnumerable<T> entities, string tableName, params string[] values);
IEnumerable<T> Custom(Func<IDbConnection, IEnumerable<T>> func);
Task<IEnumerable<T>> CustomAsync(Func<IDbConnection, Task<IEnumerable<T>>> func);
void DeleteAll(string tableName);
} }
} }

View file

@ -99,4 +99,17 @@ CREATE TABLE IF NOT EXISTS PlexUsers
PlexUserId INTEGER NOT NULL, PlexUserId INTEGER NOT NULL,
UserAlias varchar(100) NOT NULL UserAlias varchar(100) NOT NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON RequestLimit (Id); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id);
CREATE TABLE IF NOT EXISTS PlexEpisodes
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
EpisodeTitle VARCHAR(100) NOT NULL,
ShowTitle VARCHAR(100) NOT NULL,
RatingKey VARCHAR(100) NOT NULL,
ProviderId VARCHAR(100) NOT NULL,
SeasonNumber INTEGER NOT NULL,
EpisodeNumber INTEGER NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS PlexEpisodes_Id ON PlexEpisodes (Id);
CREATE UNIQUE INDEX IF NOT EXISTS PlexEpisodes_ProviderId ON PlexEpisodes (ProviderId);

View file

@ -72,8 +72,6 @@ namespace PlexRequests.Store
return selected; return selected;
} }
} }
} }
} }

View file

@ -108,7 +108,6 @@
<Compile Include="LandingPageTests.cs" /> <Compile Include="LandingPageTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SearchModuleTests.cs" /> <Compile Include="SearchModuleTests.cs" />
<Compile Include="StringHelperTests.cs" />
<Compile Include="TestRootPathProvider.cs" /> <Compile Include="TestRootPathProvider.cs" />
<Compile Include="TvSenderTests.cs" /> <Compile Include="TvSenderTests.cs" />
<Compile Include="UserLoginModuleTests.cs" /> <Compile Include="UserLoginModuleTests.cs" />

View file

@ -53,8 +53,6 @@ using Nancy.Json;
using Ninject; using Ninject;
using StackExchange.Profiling;
namespace PlexRequests.UI namespace PlexRequests.UI
{ {
public class Bootstrapper : NinjectNancyBootstrapper public class Bootstrapper : NinjectNancyBootstrapper
@ -88,10 +86,7 @@ namespace PlexRequests.UI
base.ApplicationStartup(container, pipelines); base.ApplicationStartup(container, pipelines);
#if DEBUG
pipelines.BeforeRequest += StartProfiler;
pipelines.AfterRequest += EndProfiler;
#endif
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var baseUrl = settings.GetSettings().BaseUrl; var baseUrl = settings.GetSettings().BaseUrl;
var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login"; var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login";
@ -198,15 +193,5 @@ namespace PlexRequests.UI
loc.SetContainer(container); loc.SetContainer(container);
} }
private static Response StartProfiler(NancyContext ctx)
{
MiniProfiler.Start();
return null;
}
private static void EndProfiler(NancyContext ctx)
{
MiniProfiler.Stop();
}
} }
} }

View file

@ -608,10 +608,11 @@ namespace PlexRequests.UI.Modules
} }
if (episodeRequest) if (episodeRequest)
{ {
var cachedEpisodes = Checker.GetEpisodeCache().ToList(); var cachedEpisodesTask = await Checker.GetEpisodes();
var cachedEpisodes = cachedEpisodesTask.ToList();
foreach (var d in difference) foreach (var d in difference)
{ {
if (cachedEpisodes.Any(x => x.Episodes.SeasonNumber == d.SeasonNumber && x.Episodes.EpisodeNumber == d.EpisodeNumber && x.Episodes.ProviderId == providerId)) if (cachedEpisodes.Any(x => x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && x.ProviderId == providerId))
{ {
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" });
} }
@ -983,15 +984,15 @@ namespace PlexRequests.UI.Modules
sonarrEpisodes = sonarrEp?.ToList() ?? new List<SonarrEpisodes>(); sonarrEpisodes = sonarrEp?.ToList() ?? new List<SonarrEpisodes>();
} }
var plexCache = Checker.GetEpisodeCache(seriesId).ToList(); var plexCacheTask = await Checker.GetEpisodes(seriesId);
var plexCache = plexCacheTask.ToList();
foreach (var ep in seasons) foreach (var ep in seasons)
{ {
var requested = dbDbShow?.Episodes var requested = dbDbShow?.Episodes
.Any(episodesModel => .Any(episodesModel =>
ep.number == episodesModel.EpisodeNumber && ep.season == episodesModel.SeasonNumber) ?? false; ep.number == episodesModel.EpisodeNumber && ep.season == episodesModel.SeasonNumber) ?? false;
var alreadyInPlex = plexCache.Any(x => x.Episodes.EpisodeNumber == ep.number && x.Episodes.SeasonNumber == ep.season); var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season);
var inSonarr = sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.monitored); var inSonarr = sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.monitored);
model.Add(new EpisodeListViewModel model.Add(new EpisodeListViewModel

View file

@ -44,7 +44,6 @@ using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
using StackExchange.Profiling;
using Action = PlexRequests.Helpers.Analytics.Action; using Action = PlexRequests.Helpers.Analytics.Action;
@ -77,20 +76,11 @@ namespace PlexRequests.UI.Modules
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
public async Task<Negotiator> Index() public async Task<Negotiator> Index()
{
var profiler = MiniProfiler.Current;
using (profiler.Step("Loading Index"))
{
using (profiler.Step("Loading AuthSettingsAsync and returning View"))
{ {
var settings = await AuthService.GetSettingsAsync(); var settings = await AuthService.GetSettingsAsync();
return View["Index", settings]; return View["Index", settings];
} }
}
}
private async Task<Response> LoginUser() private async Task<Response> LoginUser()
{ {
var dateTimeOffset = Request.Form.DateTimeOffset; var dateTimeOffset = Request.Form.DateTimeOffset;

View file

@ -65,14 +65,6 @@
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath> <HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="MiniProfiler, Version=3.0.10.0, Culture=neutral, PublicKeyToken=b44f9351044011a3, processorArchitecture=MSIL">
<HintPath>..\packages\MiniProfiler.3.0.10\lib\net40\MiniProfiler.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MiniProfiler.Mvc, Version=3.0.11.0, Culture=neutral, PublicKeyToken=b44f9351044011a3, processorArchitecture=MSIL">
<HintPath>..\packages\MiniProfiler.MVC4.3.0.11\lib\net40\MiniProfiler.Mvc.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath> <HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -219,7 +211,6 @@
<Compile Include="Helpers\HeadphonesSender.cs" /> <Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\AngularViewBase.cs" /> <Compile Include="Helpers\AngularViewBase.cs" />
<Compile Include="Helpers\ServiceLocator.cs" /> <Compile Include="Helpers\ServiceLocator.cs" />
<Compile Include="Helpers\StringHelper.cs" />
<Compile Include="Helpers\Themes.cs" /> <Compile Include="Helpers\Themes.cs" />
<Compile Include="Helpers\TvSender.cs" /> <Compile Include="Helpers\TvSender.cs" />
<Compile Include="Helpers\ValidationHelper.cs" /> <Compile Include="Helpers\ValidationHelper.cs" />

View file

@ -35,3 +35,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] [assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]
[assembly: InternalsVisibleTo("PlexRequests.UI.Tests")] [assembly: InternalsVisibleTo("PlexRequests.UI.Tests")]
[assembly: InternalsVisibleTo("PlexRequests.UI.Tests1")]
[assembly: InternalsVisibleTo("PlexRequests.Explorables")]

View file

@ -34,7 +34,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="PlexEpisodeCacher" class="control-label">Plex Episode Cacher (min)</label> <label for="PlexEpisodeCacher" class="control-label">Plex Episode Cacher (hour)</label>
<input type="text" class="form-control form-control-custom " id="PlexEpisodeCacher" name="PlexEpisodeCacher" value="@Model.PlexEpisodeCacher"> <input type="text" class="form-control form-control-custom " id="PlexEpisodeCacher" name="PlexEpisodeCacher" value="@Model.PlexEpisodeCacher">
</div> </div>

View file

@ -1,5 +1,6 @@
@using System.Linq @using System.Linq
@using PlexRequests.Core.Models @using PlexRequests.Core.Models
@using PlexRequests.Helpers
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();

View file

@ -17,8 +17,6 @@
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" /> <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="MiniProfiler" version="3.0.10" targetFramework="net45" />
<package id="MiniProfiler.MVC4" version="3.0.11" targetFramework="net45" />
<package id="Moment.js" version="2.9.0" targetFramework="net45" /> <package id="Moment.js" version="2.9.0" targetFramework="net45" />
<package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" /> <package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
<package id="Nancy" version="1.4.3" targetFramework="net45" /> <package id="Nancy" version="1.4.3" targetFramework="net45" />