#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.
pull/470/head
tidusjar 9 years ago
parent af1c93620f
commit 2608e53399

@ -34,7 +34,6 @@ using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Exceptions;
using RestSharp;
@ -81,7 +80,7 @@ namespace PlexRequests.Api
request.AddJsonBody(userModel);
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;
}

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

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

@ -29,9 +29,8 @@ using System.Collections.Generic;
using NUnit.Framework;
using PlexRequests.Core.Models;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Tests
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class StringHelperTests
@ -48,6 +47,12 @@ namespace PlexRequests.UI.Tests
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
{
get
@ -71,5 +76,19 @@ namespace PlexRequests.UI.Tests
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");
}
}
}
}

@ -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; }
}
}
}

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

@ -25,9 +25,10 @@
// ************************************************************************/
#endregion
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace PlexRequests.UI.Helpers
namespace PlexRequests.Helpers
{
public static class StringHelper
{
@ -46,5 +47,22 @@ namespace PlexRequests.UI.Helpers
return input;
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();
}
}
}

@ -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();
}
}
}

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

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

@ -29,6 +29,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using NLog;
using PlexRequests.Api.Interfaces;
@ -53,7 +55,7 @@ namespace PlexRequests.Services.Jobs
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
{
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;
RequestService = request;
@ -62,9 +64,11 @@ namespace PlexRequests.Services.Jobs
Notification = notify;
Job = rec;
UserNotifyRepo = users;
EpisodeRepo = repo;
}
private ISettingsService<PlexSettings> Plex { get; }
private IRepository<PlexEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IPlexApi PlexApi { get; }
@ -241,15 +245,23 @@ namespace PlexRequests.Services.Jobs
public bool IsEpisodeAvailable(string theTvDbId, int season, int episode)
{
var episodes = Cache.Get<HashSet<PlexEpisodeModel>>(CacheKeys.PlexEpisodes);
if (episodes == null)
var ep = EpisodeRepo.Custom(
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);
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;
}
@ -258,35 +270,43 @@ namespace PlexRequests.Services.Jobs
}
/// <summary>
/// Gets the episode's stored in the cache.
/// Gets the episode's db in the cache.
/// </summary>
/// <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)
{
Log.Info("Episode cache info is not available.");
return new HashSet<PlexEpisodeModel>();
return new HashSet<PlexEpisodes>();
}
return episodes;
}
/// <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>
/// <param name="theTvDbId">The tv database identifier.</param>
/// <returns></returns>
public IEnumerable<PlexEpisodeModel> GetEpisodeCache(int theTvDbId)
public async Task<IEnumerable<PlexEpisodes>> GetEpisodes(int theTvDbId)
{
var episodes = Cache.Get<List<PlexEpisodeModel>>(CacheKeys.PlexEpisodes);
if (episodes == null)
var ep = await EpisodeRepo.CustomAsync(async connection =>
{
connection.Open();
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 cache info is not available.");
return new List<PlexEpisodeModel>();
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()

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

@ -31,6 +31,10 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<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="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>

@ -2,6 +2,7 @@
<packages>
<package id="Common.Logging" 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="MimeKit" version="1.2.22" targetFramework="net45" />
<package id="NLog" version="4.3.6" targetFramework="net45" />

@ -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; }
}
}

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

@ -24,10 +24,14 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using Dapper.Contrib.Extensions;
using Mono.Data.Sqlite;
@ -53,6 +57,24 @@ namespace PlexRequests.Store.Repository
public abstract Task<T> GetAsync(int id);
public abstract T Get(int 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)
{
@ -251,5 +273,50 @@ namespace PlexRequests.Store.Repository
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;
}
}
}
}

@ -1,81 +1,89 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: IRepository.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.Collections.Generic;
using System.Threading.Tasks;
namespace PlexRequests.Store.Repository
{
public interface IRepository<T>
{
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
long Insert(T entity);
Task<int> InsertAsync(T entity);
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
IEnumerable<T> GetAll();
Task<IEnumerable<T>> GetAllAsync();
/// <summary>
/// Gets the specified identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns></returns>
T Get(string id);
Task<T> GetAsync(string id);
T Get(int id);
Task<T> GetAsync(int id);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
void Delete(T entity);
Task DeleteAsync(T entity);
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Update(T entity);
Task<bool> UpdateAsync(T entity);
/// <summary>
/// Updates all.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool UpdateAll(IEnumerable<T> entity);
Task<bool> UpdateAllAsync(IEnumerable<T> entity);
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: IRepository.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 System.Data;
using System.Threading.Tasks;
namespace PlexRequests.Store.Repository
{
public interface IRepository<T>
{
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
long Insert(T entity);
Task<int> InsertAsync(T entity);
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
IEnumerable<T> GetAll();
Task<IEnumerable<T>> GetAllAsync();
/// <summary>
/// Gets the specified identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns></returns>
T Get(string id);
Task<T> GetAsync(string id);
T Get(int id);
Task<T> GetAsync(int id);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
void Delete(T entity);
Task DeleteAsync(T entity);
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Update(T entity);
Task<bool> UpdateAsync(T entity);
/// <summary>
/// Updates all.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool UpdateAll(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);
}
}

@ -99,4 +99,17 @@ CREATE TABLE IF NOT EXISTS PlexUsers
PlexUserId INTEGER 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);

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

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

@ -53,8 +53,6 @@ using Nancy.Json;
using Ninject;
using StackExchange.Profiling;
namespace PlexRequests.UI
{
public class Bootstrapper : NinjectNancyBootstrapper
@ -88,10 +86,7 @@ namespace PlexRequests.UI
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 baseUrl = settings.GetSettings().BaseUrl;
var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login";
@ -198,15 +193,5 @@ namespace PlexRequests.UI
loc.SetContainer(container);
}
private static Response StartProfiler(NancyContext ctx)
{
MiniProfiler.Start();
return null;
}
private static void EndProfiler(NancyContext ctx)
{
MiniProfiler.Stop();
}
}
}

@ -608,10 +608,11 @@ namespace PlexRequests.UI.Modules
}
if (episodeRequest)
{
var cachedEpisodes = Checker.GetEpisodeCache().ToList();
var cachedEpisodesTask = await Checker.GetEpisodes();
var cachedEpisodes = cachedEpisodesTask.ToList();
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}" });
}
@ -983,15 +984,15 @@ namespace PlexRequests.UI.Modules
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)
{
var requested = dbDbShow?.Episodes
.Any(episodesModel =>
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);
model.Add(new EpisodeListViewModel

@ -44,7 +44,6 @@ using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Models;
using StackExchange.Profiling;
using Action = PlexRequests.Helpers.Analytics.Action;
@ -78,17 +77,8 @@ namespace PlexRequests.UI.Modules
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();
return View["Index", settings];
}
}
var settings = await AuthService.GetSettingsAsync();
return View["Index", settings];
}
private async Task<Response> LoginUser()

@ -65,14 +65,6 @@
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
<Private>True</Private>
</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">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
<Private>True</Private>
@ -219,7 +211,6 @@
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\AngularViewBase.cs" />
<Compile Include="Helpers\ServiceLocator.cs" />
<Compile Include="Helpers\StringHelper.cs" />
<Compile Include="Helpers\Themes.cs" />
<Compile Include="Helpers\TvSender.cs" />
<Compile Include="Helpers\ValidationHelper.cs" />

@ -34,4 +34,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("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")]

@ -34,7 +34,7 @@
</div>
<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">
</div>

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

@ -17,8 +17,6 @@
<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.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="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
<package id="Nancy" version="1.4.3" targetFramework="net45" />

Loading…
Cancel
Save