Merge pull request #284 from tidusjar/dev

1.7.5
pull/291/head v1.7.5
Jamie 8 years ago
commit f4e75f20ac

@ -26,6 +26,8 @@
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
using PlexRequests.Store;
namespace PlexRequests.Core
@ -33,13 +35,21 @@ namespace PlexRequests.Core
public interface IRequestService
{
long AddRequest(RequestedModel model);
Task<int> AddRequestAsync(RequestedModel model);
RequestedModel CheckRequest(int providerId);
Task<RequestedModel> CheckRequestAsync(int providerId);
RequestedModel CheckRequest(string musicId);
Task<RequestedModel> CheckRequestAsync(string musicId);
void DeleteRequest(RequestedModel request);
Task DeleteRequestAsync(RequestedModel request);
bool UpdateRequest(RequestedModel model);
Task<bool> UpdateRequestAsync(RequestedModel model);
RequestedModel Get(int id);
Task<RequestedModel> GetAsync(int id);
IEnumerable<RequestedModel> GetAll();
Task<IEnumerable<RequestedModel>> GetAllAsync();
bool BatchUpdate(List<RequestedModel> model);
bool BatchDelete(List<RequestedModel> model);
}
}

@ -24,12 +24,18 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Threading.Tasks;
namespace PlexRequests.Core
{
public interface ISettingsService<T>
{
T GetSettings();
Task<T> GetSettingsAsync();
bool SaveSettings(T model);
Task<bool> SaveSettingsAsync(T model);
bool Delete(T model);
Task<bool> DeleteAsync(T model);
}
}

@ -27,6 +27,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
@ -57,6 +58,19 @@ namespace PlexRequests.Core
return result ? id : -1;
}
public async Task<int> AddRequestAsync(RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId };
var id = await Repo.InsertAsync(entity);
model.Id = id;
entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = id, MusicId = model.MusicBrainzId };
var result = await Repo.UpdateAsync(entity);
return result ? id : -1;
}
public RequestedModel CheckRequest(int providerId)
{
var blobs = Repo.GetAll();
@ -64,6 +78,13 @@ namespace PlexRequests.Core
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
}
public async Task<RequestedModel> CheckRequestAsync(int providerId)
{
var blobs = await Repo.GetAllAsync();
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId);
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
}
public RequestedModel CheckRequest(string musicId)
{
var blobs = Repo.GetAll();
@ -71,18 +92,37 @@ namespace PlexRequests.Core
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
}
public async Task<RequestedModel> CheckRequestAsync(string musicId)
{
var blobs = await Repo.GetAllAsync();
var blob = blobs.FirstOrDefault(x => x.MusicId == musicId);
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
}
public void DeleteRequest(RequestedModel request)
{
var blob = Repo.Get(request.Id);
Repo.Delete(blob);
}
public async Task DeleteRequestAsync(RequestedModel request)
{
var blob = await Repo.GetAsync(request.Id);
await Repo.DeleteAsync(blob);
}
public bool UpdateRequest(RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id };
return Repo.Update(entity);
}
public async Task<bool> UpdateRequestAsync(RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id };
return await Repo.UpdateAsync(entity);
}
public RequestedModel Get(int id)
{
var blob = Repo.Get(id);
@ -94,6 +134,17 @@ namespace PlexRequests.Core
return model;
}
public async Task<RequestedModel> GetAsync(int id)
{
var blob = await Repo.GetAsync(id);
if (blob == null)
{
return new RequestedModel();
}
var model = ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content);
return model;
}
public IEnumerable<RequestedModel> GetAll()
{
var blobs = Repo.GetAll();
@ -102,10 +153,24 @@ namespace PlexRequests.Core
.ToList();
}
public async Task<IEnumerable<RequestedModel>> GetAllAsync()
{
var blobs = await Repo.GetAllAsync();
return blobs.Select(b => Encoding.UTF8.GetString(b.Content))
.Select(JsonConvert.DeserializeObject<RequestedModel>)
.ToList();
}
public bool BatchUpdate(List<RequestedModel> model)
{
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ByteConverterHelper.ReturnBytes(m), ProviderId = m.ProviderId, Id = m.Id }).ToList();
return Repo.UpdateAll(entities);
}
public bool BatchDelete(List<RequestedModel> model)
{
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ByteConverterHelper.ReturnBytes(m), ProviderId = m.ProviderId, Id = m.Id }).ToList();
return Repo.DeleteAll(entities);
}
}
}

@ -24,6 +24,8 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Threading.Tasks;
using Newtonsoft.Json;
using PlexRequests.Core.SettingModels;
@ -62,6 +64,21 @@ namespace PlexRequests.Core
return model;
}
public async Task<T> GetSettingsAsync()
{
var result = await Repo.GetAsync(EntityName);
if (result == null)
{
return new T();
}
result.Content = DecryptSettings(result);
var obj = string.IsNullOrEmpty(result.Content) ? null : JsonConvert.DeserializeObject<T>(result.Content, SerializerSettings.Settings);
var model = obj;
return model;
}
public bool SaveSettings(T model)
{
var entity = Repo.Get(EntityName);
@ -88,6 +105,31 @@ namespace PlexRequests.Core
return result;
}
public async Task<bool> SaveSettingsAsync(T model)
{
var entity = await Repo.GetAsync(EntityName);
if (entity == null)
{
var newEntity = model;
var settings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(newEntity, SerializerSettings.Settings) };
settings.Content = EncryptSettings(settings);
var insertResult = await Repo.InsertAsync(settings);
return insertResult != int.MinValue;
}
var modified = model;
modified.Id = entity.Id;
var globalSettings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(modified, SerializerSettings.Settings), Id = entity.Id };
globalSettings.Content = EncryptSettings(globalSettings);
var result = await Repo.UpdateAsync(globalSettings);
return result;
}
public bool Delete(T model)
{
var entity = Repo.Get(EntityName);
@ -100,6 +142,17 @@ namespace PlexRequests.Core
return true;
}
public async Task<bool> DeleteAsync(T model)
{
var entity = Repo.Get(EntityName);
if (entity != null)
{
return await Repo.DeleteAsync(entity);
}
return true;
}
private string EncryptSettings(GlobalSettings settings)
{
return StringCipher.Encrypt(settings.Content, settings.SettingsName);

@ -53,7 +53,7 @@ namespace PlexRequests.Core
{
CreateDefaultSettingsPage(urlBase);
}
var version = CheckSchema();
if (version > 0)
{
@ -61,6 +61,10 @@ namespace PlexRequests.Core
{
MigrateToVersion1700();
}
if (version > 1800 && version <= 1899)
{
MigrateToVersion1800();
}
}
return Db.DbConnection().ConnectionString;
@ -73,7 +77,7 @@ namespace PlexRequests.Core
{
var productVersion = AssemblyHelper.GetProductVersion();
var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0');
var version = int.Parse(trimStatus);
var version = int.Parse(trimStatus);
var connection = Db.DbConnection();
var schema = connection.GetSchemaVersion();
@ -173,5 +177,50 @@ namespace PlexRequests.Core
TableCreation.DropTable(Db.DbConnection(), "User");
TableCreation.DropTable(Db.DbConnection(), "Log");
}
/// <summary>
/// Migrates to version 1.8.
/// <para>This includes updating the admin account to have all roles.</para>
/// <para>Set the log level to info</para>
/// </summary>
private void MigrateToVersion1800()
{
try
{
var userMapper = new UserMapper(new UserRepository<UsersModel>(Db, new MemoryCacheProvider()));
var users = userMapper.GetUsers();
foreach (var u in users)
{
var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser };
u.Claims = ByteConverterHelper.ReturnBytes(claims);
userMapper.EditUser(u);
}
}
catch (Exception e)
{
Log.Error(e);
throw;
}
try
{
var settingsService = new SettingsServiceV2<LogSettings>(new SettingsJsonRepository(Db, new MemoryCacheProvider()));
var logSettings = settingsService.GetSettings();
logSettings.Level = LogLevel.Info.Ordinal;
settingsService.SaveSettings(logSettings);
LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level));
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
}
}

@ -81,6 +81,16 @@ namespace PlexRequests.Core
return null;
}
public UsersModel EditUser(UsersModel user)
{
var existingUser = Repo.Get(user.UserGuid);
user.Id = existingUser.Id;
user.UserGuid = existingUser.UserGuid;
Repo.Update(user);
return user;
}
public bool DoUsersExist()
{
var users = Repo.GetAll();
@ -88,7 +98,7 @@ namespace PlexRequests.Core
return users.Any();
}
public Guid? CreateUser(string username, string password, string[] claims = default(string[]))
private Guid? CreateUser(string username, string password, string[] claims = default(string[]))
{
var salt = PasswordHasher.GenerateSalt();
@ -108,6 +118,21 @@ namespace PlexRequests.Core
return new Guid(userRecord.UserGuid);
}
public Guid? CreateAdmin(string username, string password)
{
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser, UserClaims.Admin });
}
public Guid? CreatePowerUser(string username, string password)
{
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser });
}
public Guid? CreateRegularUser(string username, string password)
{
return CreateUser(username, password, new[] { UserClaims.User });
}
public bool UpdatePassword(string username, string oldPassword, string newPassword)
{
var users = Repo.GetAll();
@ -134,15 +159,25 @@ namespace PlexRequests.Core
{
return Repo.GetAll();
}
public UsersModel GetUser(Guid userId)
{
var user = Repo.Get(userId.ToString());
return user;
}
}
public interface ICustomUserMapper
{
IEnumerable<UsersModel> GetUsers();
Guid? CreateUser(string username, string password, string[] claims = default(string[]));
UsersModel GetUser(Guid userId);
UsersModel EditUser(UsersModel user);
bool DoUsersExist();
Guid? ValidateUser(string username, string password);
bool UpdatePassword(string username, string oldPassword, string newPassword);
Guid? CreateAdmin(string username, string password);
Guid? CreatePowerUser(string username, string password);
Guid? CreateRegularUser(string username, string password);
}
}

@ -24,7 +24,6 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Diagnostics;
using NUnit.Framework;
@ -41,7 +40,7 @@ namespace PlexRequests.Helpers.Tests
var hash = PasswordHasher.ComputeHash(password, salt);
Assert.That(hash, Is.Not.EqualTo(password));
var match = PasswordHasher.VerifyPassword(password, salt, hash);
Assert.That(match, Is.True);

@ -8,7 +8,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Helpers.Tests</RootNamespace>
<AssemblyName>PlexRequests.Helpers.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
@ -16,6 +16,7 @@
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>

@ -26,7 +26,8 @@
#endregion
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using NUnit.Framework;
namespace PlexRequests.Helpers.Tests
@ -35,11 +36,9 @@ namespace PlexRequests.Helpers.Tests
public class UriHelperTests
{
[TestCaseSource(nameof(UriData))]
public void CreateUri1(string uri, Uri expected)
public Uri CreateUri1(string uri)
{
var result = uri.ReturnUri();
Assert.That(result, Is.EqualTo(expected));
return uri.ReturnUri();
}
[Test]
@ -52,54 +51,58 @@ namespace PlexRequests.Helpers.Tests
}
[TestCaseSource(nameof(UriDataWithPort))]
public void CreateUri2(string uri, int port, Uri expected)
public Uri CreateUri2(string uri, int port)
{
var result = uri.ReturnUri(port);
Assert.That(result, Is.EqualTo(expected));
return uri.ReturnUri(port);
}
[TestCaseSource(nameof(UriDataWithSubDir))]
public void CreateUriWithSubDir(string uri, int port, bool ssl, string subDir, Uri expected)
public Uri CreateUriWithSubDir(string uri, int port, bool ssl, string subDir)
{
var result = uri.ReturnUriWithSubDir(port, ssl, subDir);
Assert.That(result, Is.EqualTo(expected));
return uri.ReturnUriWithSubDir(port, ssl, subDir);
}
static readonly object[] UriData =
private static IEnumerable<TestCaseData> UriData
{
new object[] { "google.com", new Uri("http://google.com/"), },
new object[] { "http://google.com", new Uri("http://google.com/"), },
new object[] { "https://google.com", new Uri("https://google.com/"), },
new object[] { "192.168.1.1", new Uri("http://192.168.1.1")},
new object[] { "0.0.0.0:5533", new Uri("http://0.0.0.0:5533")},
new object[] {"www.google.com", new Uri("http://www.google.com/")},
new object[] {"http://www.google.com/", new Uri("http://www.google.com/") },
new object[] {"https://www.google.com", new Uri("https://www.google.com/") },
new object[] {"www.google.com:443", new Uri("http://www.google.com:443/") },
new object[] {"https://www.google.com:443", new Uri("https://www.google.com:443/") },
new object[] {"http://www.google.com:443/id=2", new Uri("http://www.google.com:443/id=2") },
new object[] {"www.google.com:4438/id=22", new Uri("http://www.google.com:4438/id=22") }
};
get
{
yield return new TestCaseData("google.com").Returns(new Uri("http://google.com/"));
yield return new TestCaseData("http://google.com").Returns(new Uri("http://google.com/"));
yield return new TestCaseData("https://google.com").Returns(new Uri("https://google.com/"));
yield return new TestCaseData("192.168.1.1").Returns(new Uri("http://192.168.1.1"));
yield return new TestCaseData("0.0.0.0:5533").Returns(new Uri("http://0.0.0.0:5533"));
yield return new TestCaseData("www.google.com").Returns(new Uri("http://www.google.com/"));
yield return new TestCaseData("http://www.google.com/").Returns(new Uri("http://www.google.com/"));
yield return new TestCaseData("https://www.google.com").Returns(new Uri("https://www.google.com/"));
yield return new TestCaseData("www.google.com:443").Returns(new Uri("http://www.google.com:443/"));
yield return new TestCaseData("https://www.google.com:443").Returns(new Uri("https://www.google.com/"));
yield return new TestCaseData("http://www.google.com:443/id=2").Returns(new Uri("http://www.google.com:443/id=2"));
yield return new TestCaseData("www.google.com:4438/id=22").Returns(new Uri("http://www.google.com:4438/id=22"));
}
}
static readonly object[] UriDataWithPort =
private static IEnumerable<TestCaseData> UriDataWithPort
{
new object[] {"www.google.com", 80, new Uri("http://www.google.com:80/"), },
new object[] {"www.google.com", 443, new Uri("http://www.google.com:443/") },
new object[] {"http://www.google.com", 443, new Uri("http://www.google.com:443/") },
new object[] {"https://www.google.com", 443, new Uri("https://www.google.com:443/") },
new object[] {"http://www.google.com/id=2", 443, new Uri("http://www.google.com:443/id=2") },
new object[] {"http://www.google.com/id=2", 443, new Uri("http://www.google.com:443/id=2") },
new object[] {"https://www.google.com/id=2", 443, new Uri("https://www.google.com:443/id=2") },
};
get
{
yield return new TestCaseData("www.google.com", 80).Returns(new Uri("http://www.google.com:80/"));
yield return new TestCaseData("www.google.com", 443).Returns(new Uri("http://www.google.com:443/"));
yield return new TestCaseData("http://www.google.com", 443).Returns(new Uri("http://www.google.com:443/"));
yield return new TestCaseData("https://www.google.com", 443).Returns(new Uri("https://www.google.com:443/"));
yield return new TestCaseData("http://www.google.com/id=2", 443).Returns(new Uri("http://www.google.com:443/id=2"));
yield return new TestCaseData("https://www.google.com/id=2", 443).Returns(new Uri("https://www.google.com:443/id=2"));
}
}
static readonly object[] UriDataWithSubDir =
{
new object[] {"www.google.com", 80, false,"test", new Uri("http://www.google.com:80/test"), },
new object[] {"www.google.com", 443, false,"test", new Uri("http://www.google.com:443/test") },
new object[] {"http://www.google.com", 443, true,"test", new Uri("https://www.google.com:443/test") },
new object[] {"https://www.google.com", 443,true,"test", new Uri("https://www.google.com:443/test") },
};
private static IEnumerable<TestCaseData> UriDataWithSubDir
{
get
{
yield return new TestCaseData("www.google.com", 80, false, "test").Returns(new Uri("http://www.google.com:80/test"));
yield return new TestCaseData("www.google.com", 443, false, "test").Returns(new Uri("http://www.google.com:443/test"));
yield return new TestCaseData("http://www.google.com", 443, true, "test").Returns(new Uri("https://www.google.com:443/test"));
yield return new TestCaseData("https://www.google.com", 443, true, "test").Returns(new Uri("https://www.google.com:443/test"));
}
}
}
}

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>

@ -25,6 +25,7 @@
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
namespace PlexRequests.Helpers
{
@ -40,6 +41,7 @@ namespace PlexRequests.Helpers
/// <param name="cacheTime">The amount of time we want to cache the object</param>
/// <returns><see cref="T"/></returns>
T GetOrSet<T>(string key, Func<T> itemCallback, int cacheTime = 20) where T : class;
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> itemCallback, int cacheTime = 20) where T : class;
/// <summary>
/// Gets the specified item from the cache.

@ -27,6 +27,7 @@
using System;
using System.Linq;
using System.Runtime.Caching;
using System.Threading.Tasks;
namespace PlexRequests.Helpers
{
@ -65,6 +66,23 @@ namespace PlexRequests.Helpers
return item.CloneJson();
}
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> itemCallback, int cacheTime = 20) where T : class
{
var item = Get<T>(key);
if (item == null)
{
item = await itemCallback();
if (item != null)
{
Set(key, item, cacheTime);
}
}
// Return a copy, not the stored cache reference
// The cached object will not change
return item.CloneJson();
}
/// <summary>
/// Gets the specified item from the cache.
/// </summary>

@ -87,7 +87,7 @@ namespace PlexRequests.Services.Jobs
public int[] QueuedIds()
{
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
return movies?.movies.Select(x => x.info.tmdb_id).ToArray() ?? new int[] { };
return movies?.movies?.Select(x => x.info.tmdb_id).ToArray() ?? new int[] { };
}
public void Execute(IJobExecutionContext context)

@ -24,7 +24,7 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Services
namespace PlexRequests.Services.Jobs
{
public static class JobNames
{
@ -33,5 +33,6 @@ namespace PlexRequests.Services
public const string SonarrCacher = "Sonarr Cacher";
public const string SrCacher = "SickRage Cacher";
public const string PlexChecker = "Plex Availability Cacher";
public const string StoreCleanup = "Database Cleanup";
}
}

@ -326,33 +326,36 @@ namespace PlexRequests.Services.Jobs
try
{
var plexUser = PlexApi.GetUsers(apiKey);
if (plexUser?.User == null || plexUser.User.Length == 0)
{
return;
}
var userAccount = PlexApi.GetAccount(apiKey);
var adminUsername = userAccount.Username ?? string.Empty;
var users = UserNotifyRepo.GetAll().ToList();
Log.Debug("Notifying Users Count {0}", users.Count);
foreach (var model in modelChanged)
{
var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers);
Log.Debug("Selected Users {0}", selectedUsers.DumpJson());
foreach (var user in selectedUsers)
{
Log.Info("Notifying user {0}", user);
if (user == adminUsername)
{
Log.Info("This user is the Plex server owner");
PublishUserNotification(userAccount.Username, userAccount.Email, model.Title);
return;
}
var email = plexUser.User.FirstOrDefault(x => x.Username == user);
if (email == null)
{
Log.Info("There is no email address for this Plex user, cannot send notification");
// We do not have a plex user that requested this!
continue;
}
var notificationModel = new NotificationModel
{
User = email.Username,
UserEmail = email.Email,
NotificationType = NotificationType.RequestAvailable,
Title = model.Title
};
// Send the notification to the user.
Notification.Publish(notificationModel);
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);
PublishUserNotification(email.Username, email.Email, model.Title);
}
}
}
@ -362,6 +365,20 @@ namespace PlexRequests.Services.Jobs
}
}
private void PublishUserNotification(string username, string email, string title)
{
var notificationModel = new NotificationModel
{
User = username,
UserEmail = email,
NotificationType = NotificationType.RequestAvailable,
Title = title
};
// Send the notification to the user.
Notification.Publish(notificationModel);
}
public void Execute(IJobExecutionContext context)
{
CheckAndUpdateAll();

@ -87,7 +87,7 @@ namespace PlexRequests.Services.Jobs
public int[] QueuedIds()
{
var tv = Cache.Get<SickrageShows>(CacheKeys.SickRageQueued);
return tv?.data.Values.Select(x => x.tvdbid).ToArray() ?? new int[] { };
return tv?.data?.Values.Select(x => x.tvdbid).ToArray() ?? new int[] { };
}
public void Execute(IJobExecutionContext context)

@ -26,15 +26,11 @@
#endregion
using System;
using System.IO;
using System.Linq;
using System.Globalization;
using NLog;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using Quartz;
@ -57,7 +53,7 @@ namespace PlexRequests.Services.Jobs
public void Execute(IJobExecutionContext context)
{
TakeBackup();
Cleanup ();
Cleanup();
}
private void TakeBackup()
@ -81,11 +77,11 @@ namespace PlexRequests.Services.Jobs
try
{
if(DoWeNeedToBackup(backupDir.FullName))
{
File.Copy(dbPath, Path.Combine(backupDir.FullName, $"PlexRequests.sqlite_{DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss")}.bak"));
}
}
if (DoWeNeedToBackup(backupDir.FullName))
{
File.Copy(dbPath, Path.Combine(backupDir.FullName, $"PlexRequests.sqlite_{DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss")}.bak"));
}
}
catch (Exception e)
{
Log.Warn(e);
@ -98,53 +94,58 @@ namespace PlexRequests.Services.Jobs
}
private void Cleanup()
{
Log.Trace("Starting DB Cleanup");
var dbPath = Sql.CurrentPath;
var dir = Path.GetDirectoryName(dbPath);
if (dir == null)
{
Log.Warn("We couldn't find the DB path. We cannot backup.");
return;
}
var backupDir = Directory.CreateDirectory(Path.Combine(dir, "Backup"));
var files = backupDir.GetFiles();
foreach (var file in files) {
var dt = ParseName(file.Name);
if(dt < DateTime.Now.AddDays(-7)){
try {
File.Delete(file.FullName);
} catch (Exception ex) {
Log.Error(ex);
}
}
}
}
private bool DoWeNeedToBackup(string backupPath)
{
var files = Directory.GetFiles(backupPath);
//TODO Get the latest file and if it's within an hour of DateTime.Now then don't bother backing up.
return true;
}
private DateTime ParseName(string fileName)
{
var names = fileName.Split(new []{'_','.',' '}, StringSplitOptions.RemoveEmptyEntries);
if(names.Count() > 1)
{
DateTime parsed;
//DateTime.TryParseExcat(names[1], "yyyy-MM-dd hh.mm.ss",CultureInfo.CurrentUICulture, DateTimeStyles.None, out parsed);
DateTime.TryParse(names[2], out parsed);
return parsed;
}
return DateTime.MinValue;
}
private void Cleanup()
{
Log.Trace("Starting DB Cleanup");
var dbPath = Sql.CurrentPath;
var dir = Path.GetDirectoryName(dbPath);
if (dir == null)
{
Log.Warn("We couldn't find the DB path. We cannot backup.");
return;
}
var backupDir = Directory.CreateDirectory(Path.Combine(dir, "Backup"));
var files = backupDir.GetFiles();
foreach (var file in files)
{
var dt = ParseName(file.Name);
if (dt < DateTime.Now.AddDays(-7))
{
try
{
File.Delete(file.FullName);
}
catch (Exception ex)
{
Log.Error(ex);
}
}
}
}
private bool DoWeNeedToBackup(string backupPath)
{
var files = Directory.GetFiles(backupPath);
//TODO Get the latest file and if it's within an hour of DateTime.Now then don't bother backing up.
return true;
}
private DateTime ParseName(string fileName)
{
var names = fileName.Split(new[] { '_', '.', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (names.Length > 1)
{
DateTime parsed;
//DateTime.TryParseExcat(names[1], "yyyy-MM-dd hh.mm.ss",CultureInfo.CurrentUICulture, DateTimeStyles.None, out parsed);
DateTime.TryParse(names[2], out parsed);
return parsed;
}
return DateTime.MinValue;
}
}
}

@ -0,0 +1,82 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StoreCleanup.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;
using NLog;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using Quartz;
namespace PlexRequests.Services.Jobs
{
public class StoreCleanup : IJob
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public StoreCleanup(IRepository<LogEntity> repo, IJobRecord rec)
{
Repo = repo;
JobRecord = rec;
}
private IJobRecord JobRecord { get; }
private IRepository<LogEntity> Repo { get; }
private void Cleanup()
{
try
{
var items = Repo.GetAll();
var orderedItems = items.Where(x => x.Date < DateTime.Now.AddDays(-7));
foreach (var o in orderedItems)
{
Repo.Delete(o);
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
JobRecord.Record(JobNames.StoreCleanup);
}
}
public void Execute(IJobExecutionContext context)
{
Cleanup();
}
}
}

@ -62,7 +62,10 @@ namespace PlexRequests.Services.Notification
var emailSettings = (EmailNotificationSettings)settings;
if (!ValidateConfiguration(emailSettings)) return;
if (!ValidateConfiguration(emailSettings))
{
return;
}
switch (model.NotificationType)
{
@ -96,13 +99,17 @@ namespace PlexRequests.Services.Notification
private bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (!settings.Enabled)
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
{
return false;
}
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
if (!settings.EnableUserEmailNotifications)
{
return false;
if (!settings.Enabled)
{
return false;
}
}
return true;

@ -76,7 +76,7 @@ namespace PlexRequests.Services.Notification
Observers.TryRemove(notification.NotificationName, out notification);
}
private static async Task NotifyAsync(INotification notification, NotificationModel model)
private async Task NotifyAsync(INotification notification, NotificationModel model)
{
try
{
@ -89,7 +89,7 @@ namespace PlexRequests.Services.Notification
}
private static async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings)
private async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings)
{
try
{

@ -73,6 +73,7 @@
<Compile Include="Jobs\JobRecord.cs" />
<Compile Include="Jobs\JobNames.cs" />
<Compile Include="Jobs\StoreBackup.cs" />
<Compile Include="Jobs\StoreCleanup.cs" />
<Compile Include="Jobs\CouchPotatoCacher.cs" />
<Compile Include="Jobs\PlexAvailabilityChecker.cs" />
<Compile Include="Jobs\SickRageCacher.cs" />

@ -31,11 +31,20 @@
<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="Dapper.Contrib, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.Contrib.1.50.0-beta8\lib\net45\Dapper.Contrib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Data.Sqlite">
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Linq" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -43,12 +52,6 @@
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="Dapper, Version=1.40.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Dapper.1.42\lib\net45\Dapper.dll</HintPath>
</Reference>
<Reference Include="Dapper.Contrib, Version=1.40.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Dapper.Contrib.1.43\lib\net45\Dapper.Contrib.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
@ -61,6 +64,7 @@
<Compile Include="Entity.cs" />
<Compile Include="Models\ScheduledJobs.cs" />
<Compile Include="Models\UsersToNotify.cs" />
<Compile Include="Repository\BaseGenericRepository.cs" />
<Compile Include="Repository\IRequestRepository.cs" />
<Compile Include="Repository\ISettingsRepository.cs" />
<Compile Include="ISqliteConfiguration.cs" />

@ -0,0 +1,179 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseGenericRepository.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.Linq;
using System.Threading.Tasks;
using Dapper.Contrib.Extensions;
using NLog;
using PlexRequests.Helpers;
namespace PlexRequests.Store.Repository
{
public abstract class BaseGenericRepository<T> where T : class
{
protected BaseGenericRepository(ISqliteConfiguration config, ICacheProvider cache)
{
Config = config;
Cache = cache;
}
protected ICacheProvider Cache { get; }
protected ISqliteConfiguration Config { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public abstract T Get(string id);
public abstract Task<T> GetAsync(int id);
public abstract T Get(int id);
public abstract Task<T> GetAsync(string id);
public long Insert(T entity)
{
ResetCache();
using (var cnn = Config.DbConnection())
{
cnn.Open();
return cnn.Insert(entity);
}
}
public void Delete(T entity)
{
ResetCache();
using (var db = Config.DbConnection())
{
db.Open();
db.Delete(entity);
}
}
public async Task DeleteAsync(T entity)
{
ResetCache();
using (var db = Config.DbConnection())
{
db.Open();
await db.DeleteAsync(entity);
}
}
public bool Update(T entity)
{
ResetCache();
Log.Trace("Updating entity");
Log.Trace(entity.DumpJson());
using (var db = Config.DbConnection())
{
db.Open();
return db.Update(entity);
}
}
public async Task<bool> UpdateAsync(T entity)
{
ResetCache();
Log.Trace("Updating entity");
Log.Trace(entity.DumpJson());
using (var db = Config.DbConnection())
{
db.Open();
return await db.UpdateAsync(entity);
}
}
public bool UpdateAll(IEnumerable<T> entity)
{
ResetCache();
Log.Trace("Updating all entities");
var result = new HashSet<bool>();
using (var db = Config.DbConnection())
{
db.Open();
foreach (var e in entity)
{
result.Add(db.Update(e));
}
}
return result.All(x => true);
}
public async Task<bool> UpdateAllAsync(IEnumerable<T> entity)
{
ResetCache();
Log.Trace("Updating all entities");
var result = new HashSet<bool>();
using (var db = Config.DbConnection())
{
db.Open();
foreach (var e in entity)
{
result.Add(await db.UpdateAsync(e));
}
}
return result.All(x => true);
}
public async Task<int> InsertAsync(T entity)
{
ResetCache();
using (var cnn = Config.DbConnection())
{
cnn.Open();
return await cnn.InsertAsync(entity);
}
}
private void ResetCache()
{
Cache.Remove("Get");
Cache.Remove("GetAll");
}
public IEnumerable<T> GetAll()
{
using (var db = Config.DbConnection())
{
db.Open();
var result = db.GetAll<T>();
return result;
}
}
public async Task<IEnumerable<T>> GetAllAsync()
{
using (var db = Config.DbConnection())
{
db.Open();
var result = await db.GetAllAsync<T>();
return result;
}
}
}
}

@ -1,4 +1,5 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: GenericRepository.cs
@ -23,59 +24,38 @@
// 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.Linq;
using System.Threading.Tasks;
using Dapper.Contrib.Extensions;
using NLog;
using PlexRequests.Helpers;
namespace PlexRequests.Store.Repository
{
public class GenericRepository<T> : IRepository<T> where T : Entity
public class GenericRepository<T> : BaseGenericRepository<T>, IRepository<T> where T : Entity
{
private ICacheProvider Cache { get; }
public GenericRepository(ISqliteConfiguration config, ICacheProvider cache)
{
Config = config;
Cache = cache;
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISqliteConfiguration Config { get; }
public long Insert(T entity)
public GenericRepository(ISqliteConfiguration config, ICacheProvider cache) : base(config, cache)
{
ResetCache();
using (var cnn = Config.DbConnection())
{
cnn.Open();
return cnn.Insert(entity);
}
}
public IEnumerable<T> GetAll()
public override T Get(string id)
{
using (var db = Config.DbConnection())
{
db.Open();
var result = db.GetAll<T>();
return result;
}
throw new NotSupportedException("Get(string) is not supported. Use Get(int)");
}
public T Get(string id)
public override Task<T> GetAsync(string id)
{
throw new NotSupportedException("Get(string) is not supported. Use Get(int)");
throw new NotSupportedException("GetAsync(string) is not supported. Use GetAsync(int)");
}
public T Get(int id)
public override T Get(int id)
{
var key = "Get" + id;
var item = Cache.GetOrSet(
@ -91,49 +71,22 @@ namespace PlexRequests.Store.Repository
return item;
}
public void Delete(T entity)
{
ResetCache();
using (var db = Config.DbConnection())
{
db.Open();
db.Delete(entity);
}
}
public bool Update(T entity)
{
ResetCache();
Log.Trace("Updating entity");
Log.Trace(entity.DumpJson());
using (var db = Config.DbConnection())
{
db.Open();
return db.Update(entity);
}
}
public bool UpdateAll(IEnumerable<T> entity)
public override async Task<T> GetAsync(int id)
{
ResetCache();
Log.Trace("Updating all entities");
var result = new HashSet<bool>();
using (var db = Config.DbConnection())
{
db.Open();
foreach (var e in entity)
var key = "Get" + id;
var item = await Cache.GetOrSetAsync(
key,
async () =>
{
result.Add(db.Update(e));
}
}
return result.All(x => true);
using (var db = Config.DbConnection())
{
db.Open();
return await db.GetAsync<T>(id);
}
});
return item;
}
private void ResetCache()
{
Cache.Remove("Get");
Cache.Remove("GetAll");
}
}
}
}

@ -25,6 +25,7 @@
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PlexRequests.Store.Repository
{
@ -35,12 +36,15 @@ namespace PlexRequests.Store.Repository
/// </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.
@ -48,12 +52,15 @@ namespace PlexRequests.Store.Repository
/// <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.
@ -61,6 +68,7 @@ namespace PlexRequests.Store.Repository
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Update(T entity);
Task<bool> UpdateAsync(T entity);
/// <summary>
/// Updates all.
@ -68,5 +76,6 @@ namespace PlexRequests.Store.Repository
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool UpdateAll(IEnumerable<T> entity);
Task<bool> UpdateAllAsync(IEnumerable<T> entity);
}
}

@ -25,6 +25,7 @@
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
using PlexRequests.Store.Models;
@ -38,13 +39,18 @@ namespace PlexRequests.Store.Repository
/// <param name="entity">The entity.</param>
long Insert(RequestBlobs entity);
Task<int> InsertAsync(RequestBlobs entity);
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
IEnumerable<RequestBlobs> GetAll();
Task<IEnumerable<RequestBlobs>> GetAllAsync();
RequestBlobs Get(int id);
Task<RequestBlobs> GetAsync(int id);
/// <summary>
/// Deletes the specified entity.
@ -52,6 +58,10 @@ namespace PlexRequests.Store.Repository
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Delete(RequestBlobs entity);
Task<bool> DeleteAsync(RequestBlobs entity);
bool DeleteAll(IEnumerable<RequestBlobs> entity);
Task<bool> DeleteAllAsync(IEnumerable<RequestBlobs> entity);
/// <summary>
/// Updates the specified entity.
@ -59,7 +69,9 @@ namespace PlexRequests.Store.Repository
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Update(RequestBlobs entity);
Task<bool> UpdateAsync(RequestBlobs entity);
bool UpdateAll(IEnumerable<RequestBlobs> entity);
Task<bool> UpdateAllAsync(IEnumerable<RequestBlobs> entity);
}
}

@ -25,7 +25,7 @@
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
using PlexRequests.Store.Models;
namespace PlexRequests.Store.Repository
@ -37,12 +37,14 @@ namespace PlexRequests.Store.Repository
/// </summary>
/// <param name="entity">The entity.</param>
long Insert(GlobalSettings entity);
Task<int> InsertAsync(GlobalSettings entity);
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
IEnumerable<GlobalSettings> GetAll();
Task<IEnumerable<GlobalSettings>> GetAllAsync();
/// <summary>
/// Gets the specified identifier.
@ -50,12 +52,14 @@ namespace PlexRequests.Store.Repository
/// <param name="settingsName">Name of the settings.</param>
/// <returns></returns>
GlobalSettings Get(string settingsName);
Task<GlobalSettings> GetAsync(string settingsName);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
Task<bool> DeleteAsync(GlobalSettings entity);
bool Delete(GlobalSettings entity);
/// <summary>
@ -63,6 +67,7 @@ namespace PlexRequests.Store.Repository
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
Task<bool> UpdateAsync(GlobalSettings entity);
bool Update(GlobalSettings entity);

@ -26,6 +26,7 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper.Contrib.Extensions;
@ -56,6 +57,16 @@ namespace PlexRequests.Store.Repository
}
}
public async Task<int> InsertAsync(RequestBlobs entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
var id = await con.InsertAsync(entity);
return id;
}
}
public IEnumerable<RequestBlobs> GetAll()
{
var key = "GetAll";
@ -70,6 +81,20 @@ namespace PlexRequests.Store.Repository
return item;
}
public async Task<IEnumerable<RequestBlobs>> GetAllAsync()
{
var key = "GetAll";
var item = await Cache.GetOrSetAsync(key, async() =>
{
using (var con = Db.DbConnection())
{
var page = await con.GetAllAsync<RequestBlobs>();
return page;
}
}, 5);
return item;
}
public RequestBlobs Get(int id)
{
var key = "Get" + id;
@ -84,6 +109,20 @@ namespace PlexRequests.Store.Repository
return item;
}
public async Task<RequestBlobs> GetAsync(int id)
{
var key = "Get" + id;
var item = await Cache.GetOrSetAsync(key, async () =>
{
using (var con = Db.DbConnection())
{
var page = await con.GetAsync<RequestBlobs>(id);
return page;
}
}, 5);
return item;
}
public bool Delete(RequestBlobs entity)
{
ResetCache();
@ -93,6 +132,30 @@ namespace PlexRequests.Store.Repository
}
}
public async Task<bool> DeleteAsync(RequestBlobs entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return await con.DeleteAsync(entity);
}
}
public async Task<bool> DeleteAllAsync(IEnumerable<RequestBlobs> entity)
{
ResetCache();
var result = new HashSet<bool>();
using (var db = Db.DbConnection())
{
db.Open();
foreach (var e in entity)
{
result.Add(await db.DeleteAsync(e));
}
}
return result.All(x => true);
}
public bool Update(RequestBlobs entity)
{
ResetCache();
@ -102,6 +165,15 @@ namespace PlexRequests.Store.Repository
}
}
public async Task<bool> UpdateAsync(RequestBlobs entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return await con.UpdateAsync(entity);
}
}
private void ResetCache()
{
Cache.Remove("Get");
@ -122,5 +194,35 @@ namespace PlexRequests.Store.Repository
}
return result.All(x => true);
}
public async Task<bool> UpdateAllAsync(IEnumerable<RequestBlobs> entity)
{
ResetCache();
var result = new HashSet<bool>();
using (var db = Db.DbConnection())
{
db.Open();
foreach (var e in entity)
{
result.Add(await db.UpdateAsync(e));
}
}
return result.All(x => true);
}
public bool DeleteAll(IEnumerable<RequestBlobs> entity)
{
ResetCache();
var result = new HashSet<bool>();
using (var db = Db.DbConnection())
{
db.Open();
foreach (var e in entity)
{
result.Add(db.Delete(e));
}
}
return result.All(x => true);
}
}
}

@ -26,7 +26,7 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper.Contrib.Extensions;
using PlexRequests.Helpers;
@ -57,6 +57,15 @@ namespace PlexRequests.Store.Repository
}
}
public async Task<int> InsertAsync(GlobalSettings entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return await con.InsertAsync(entity);
}
}
public IEnumerable<GlobalSettings> GetAll()
{
var key = TypeName + "GetAll";
@ -71,6 +80,20 @@ namespace PlexRequests.Store.Repository
return item;
}
public async Task<IEnumerable<GlobalSettings>> GetAllAsync()
{
var key = TypeName + "GetAll";
var item = await Cache.GetOrSetAsync(key, async() =>
{
using (var con = Db.DbConnection())
{
var page = await con.GetAllAsync<GlobalSettings>();
return page;
}
}, 5);
return item;
}
public GlobalSettings Get(string pageName)
{
var key = pageName + "Get";
@ -85,6 +108,38 @@ namespace PlexRequests.Store.Repository
return item;
}
public async Task<GlobalSettings> GetAsync(string settingsName)
{
var key = settingsName + "Get";
var item = await Cache.GetOrSetAsync(key, async() =>
{
using (var con = Db.DbConnection())
{
var page = await con.GetAllAsync<GlobalSettings>();
return page.SingleOrDefault(x => x.SettingsName == settingsName);
}
}, 5);
return item;
}
public async Task<bool> DeleteAsync(GlobalSettings entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return await con.DeleteAsync(entity);
}
}
public async Task<bool> UpdateAsync(GlobalSettings entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return await con.UpdateAsync(entity);
}
}
public bool Delete(GlobalSettings entity)
{
ResetCache();

@ -27,41 +27,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper.Contrib.Extensions;
using PlexRequests.Helpers;
using PlexRequests.Store.Repository;
namespace PlexRequests.Store
{
public class UserRepository<T> : IRepository<T> where T : UserEntity
public class UserRepository<T> : BaseGenericRepository<T>, IRepository<T> where T : UserEntity
{
public UserRepository(ISqliteConfiguration config)
public UserRepository(ISqliteConfiguration config, ICacheProvider cache) : base(config, cache)
{
Config = config;
}
private ISqliteConfiguration Config { get; }
public long Insert(T entity)
public override T Get(int id)
{
using (var cnn = Config.DbConnection())
{
cnn.Open();
return cnn.Insert(entity);
}
throw new NotSupportedException("Get(int) is not supported. Use Get(string)");
}
public IEnumerable<T> GetAll()
public override Task<T> GetAsync(int id)
{
using (var db = Config.DbConnection())
{
db.Open();
var result = db.GetAll<T>();
return result;
}
throw new NotSupportedException("GetAsync(int) is not supported. Use GetAsync(string)");
}
public T Get(string id)
public override T Get(string id)
{
using (var db = Config.DbConnection())
{
@ -72,33 +62,18 @@ namespace PlexRequests.Store
}
}
public T Get(int id)
{
throw new NotSupportedException("Get(int) is not supported. Use Get(string)");
}
public void Delete(T entity)
public override async Task<T> GetAsync(string id)
{
using (var db = Config.DbConnection())
{
db.Open();
db.Delete(entity);
var result = await db.GetAllAsync<T>();
var selected = result.FirstOrDefault(x => x.UserGuid == id);
return selected;
}
}
public bool Update(T entity)
{
using (var db = Config.DbConnection())
{
db.Open();
return db.Update(entity);
}
}
public bool UpdateAll(IEnumerable<T> entity)
{
throw new NotSupportedException();
}
}
}

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Dapper" version="1.42" targetFramework="net45" />
<package id="Dapper.Contrib" version="1.43" targetFramework="net45" />
<package id="Dapper" version="1.50.0-beta8" targetFramework="net45" />
<package id="Dapper.Contrib" version="1.50.0-beta8" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
<package id="NLog" version="4.2.3" targetFramework="net45" />
</packages>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -1,295 +0,0 @@
@media (min-width: 768px) {
.row {
position: relative; }
.bottom-align-text {
position: absolute;
bottom: 0;
right: 0; } }
@media (max-width: 48em) {
.home {
padding-top: 1rem; } }
@media (min-width: 48em) {
.home {
padding-top: 4rem; } }
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
color: #fff; }
hr {
border-color: #777; }
body.update-available {
margin-top: 80px; }
.btn {
border-radius: 0.25rem !important; }
.multiSelect {
background-color: #4e5d6c; }
.form-control-custom {
background-color: #333333 !important;
color: white !important;
border-radius: 0;
box-shadow: 0 0 0 !important; }
h1 {
font-size: 3.5rem !important;
font-weight: 600 !important; }
.request-title {
margin-top: 0 !important;
font-size: 1.9rem !important; }
p {
font-size: 1.1rem !important; }
label {
display: inline-block !important;
margin-bottom: 0.5rem !important;
font-size: 16px !important; }
.nav-tabs > li {
font-size: 13px;
line-height: 21px; }
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: #df691a; }
.nav-tabs > li > a > .fa {
padding: 3px 5px 3px 3px; }
.nav-tabs > li.nav-tab-right {
float: right; }
.nav-tabs > li.nav-tab-right a {
margin-right: 0;
margin-left: 2px; }
.nav-tabs > li.nav-tab-icononly .fa {
padding: 3px; }
.navbar .nav a .fa,
.dropdown-menu a .fa {
font-size: 130%;
top: 1px;
position: relative;
display: inline-block;
margin-right: 5px; }
.dropdown-menu a .fa {
top: 2px; }
.btn-danger-outline {
color: #d9534f !important;
background-color: transparent;
background-image: none;
border-color: #d9534f !important; }
.btn-danger-outline:focus,
.btn-danger-outline.focus,
.btn-danger-outline:active,
.btn-danger-outline.active,
.btn-danger-outline:hover,
.open > .btn-danger-outline.dropdown-toggle {
color: #fff !important;
background-color: #d9534f !important;
border-color: #d9534f !important; }
.btn-primary-outline {
color: #ff761b !important;
background-color: transparent;
background-image: none;
border-color: #ff761b !important; }
.btn-primary-outline:focus,
.btn-primary-outline.focus,
.btn-primary-outline:active,
.btn-primary-outline.active,
.btn-primary-outline:hover,
.open > .btn-primary-outline.dropdown-toggle {
color: #fff !important;
background-color: #df691a !important;
border-color: #df691a !important; }
.btn-info-outline {
color: #5bc0de !important;
background-color: transparent;
background-image: none;
border-color: #5bc0de !important; }
.btn-info-outline:focus,
.btn-info-outline.focus,
.btn-info-outline:active,
.btn-info-outline.active,
.btn-info-outline:hover,
.open > .btn-info-outline.dropdown-toggle {
color: #fff !important;
background-color: #5bc0de !important;
border-color: #5bc0de !important; }
.btn-warning-outline {
color: #f0ad4e !important;
background-color: transparent;
background-image: none;
border-color: #f0ad4e !important; }
.btn-warning-outline:focus,
.btn-warning-outline.focus,
.btn-warning-outline:active,
.btn-warning-outline.active,
.btn-warning-outline:hover,
.open > .btn-warning-outline.dropdown-toggle {
color: #fff !important;
background-color: #f0ad4e !important;
border-color: #f0ad4e !important; }
.btn-success-outline {
color: #5cb85c !important;
background-color: transparent;
background-image: none;
border-color: #5cb85c !important; }
.btn-success-outline:focus,
.btn-success-outline.focus,
.btn-success-outline:active,
.btn-success-outline.active,
.btn-success-outline:hover,
.open > .btn-success-outline.dropdown-toggle {
color: #fff !important;
background-color: #5cb85c !important;
border-color: #5cb85c !important; }
#movieList .mix {
display: none; }
#tvList .mix {
display: none; }
.scroll-top-wrapper {
position: fixed;
opacity: 0;
visibility: hidden;
overflow: hidden;
text-align: center;
z-index: 99999999;
background-color: #333333;
color: #eeeeee;
width: 50px;
height: 48px;
line-height: 48px;
right: 30px;
bottom: 30px;
padding-top: 2px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out; }
.scroll-top-wrapper:hover {
background-color: #df691a; }
.scroll-top-wrapper.show {
visibility: visible;
cursor: pointer;
opacity: 1.0; }
.scroll-top-wrapper i.fa {
line-height: inherit; }
.no-search-results {
text-align: center; }
.no-search-results .no-search-results-icon {
font-size: 10em;
color: #4e5d6c; }
.no-search-results .no-search-results-text {
margin: 20px 0;
color: #ccc; }
.form-control-search {
padding: 13px 105px 13px 16px;
height: 100%; }
.form-control-withbuttons {
padding-right: 105px; }
.input-group-addon .btn-group {
position: absolute;
right: 45px;
z-index: 3;
top: 10px;
box-shadow: 0 0 0; }
.input-group-addon .btn-group .btn {
border: 1px solid rgba(255, 255, 255, 0.7) !important;
padding: 3px 12px;
color: rgba(255, 255, 255, 0.7) !important; }
.btn-split .btn {
border-radius: 0 !important; }
.btn-split .btn:not(.dropdown-toggle) {
border-radius: 0.25rem 0 0 0.25rem !important; }
.btn-split .btn.dropdown-toggle {
border-radius: 0 0.25rem 0.25rem 0 !important;
padding: 12px 8px; }
#updateAvailable {
background-color: #df691a;
text-align: center;
font-size: 15px;
padding: 3px 0; }
.checkbox label {
display: inline-block;
cursor: pointer;
position: relative;
padding-left: 25px;
margin-right: 15px;
font-size: 13px;
margin-bottom: 10px; }
.checkbox label:before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
margin-right: 10px;
position: absolute;
left: 0;
bottom: 1px;
border: 2px solid #eee;
border-radius: 3px; }
.checkbox input[type=checkbox] {
display: none; }
.checkbox input[type=checkbox]:checked + label:before {
content: "\2713";
font-size: 13px;
color: #fafafa;
text-align: center;
line-height: 13px; }
.input-group-sm {
padding-top: 2px;
padding-bottom: 2px; }
.tab-pane .form-horizontal .form-group {
margin-right: 15px;
margin-left: 15px; }

File diff suppressed because one or more lines are too long

@ -1,365 +0,0 @@
$form-color: #4e5d6c;
$form-color-lighter: #637689;
$primary-colour: #df691a;
$primary-colour-outline: #ff761b;
$info-colour: #5bc0de;
$warning-colour: #f0ad4e;
$danger-colour: #d9534f;
$success-colour: #5cb85c;
$bg-colour: #333333;
$i:
!important;
@media (min-width: 768px ) {
.row {
position: relative;
}
.bottom-align-text {
position: absolute;
bottom: 0;
right: 0;
}
}
@media (max-width: 48em) {
.home {
padding-top: 1rem;
}
}
@media (min-width: 48em) {
.home {
padding-top: 4rem;
}
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
color: #fff;
}
hr {
border-color: #777;
}
body.update-available {
margin-top: 80px;
}
.btn {
border-radius: .25rem $i;
}
.multiSelect {
background-color: $form-color;
}
.form-control-custom {
background-color: $bg-colour $i;
color: white $i;
border-radius: 0;
box-shadow: 0 0 0 !important;
}
h1 {
font-size: 3.5rem $i;
font-weight: 600 $i;
}
.request-title {
margin-top: 0 $i;
font-size: 1.9rem $i;
}
p {
font-size: 1.1rem $i;
}
label {
display: inline-block $i;
margin-bottom: .5rem $i;
font-size: 16px $i;
}
.nav-tabs > li {
font-size: 13px;
line-height: 21px;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: $primary-colour;
}
.nav-tabs > li > a > .fa {
padding: 3px 5px 3px 3px;
}
.nav-tabs > li.nav-tab-right {
float: right;
}
.nav-tabs > li.nav-tab-right a {
margin-right: 0;
margin-left: 2px;
}
.nav-tabs > li.nav-tab-icononly .fa {
padding: 3px;
}
.navbar .nav a .fa,
.dropdown-menu a .fa {
font-size: 130%;
top: 1px;
position: relative;
display: inline-block;
margin-right: 5px;
}
.dropdown-menu a .fa {
top: 2px;
}
.btn-danger-outline {
color: $danger-colour $i;
background-color: transparent;
background-image: none;
border-color: $danger-colour $i;
}
.btn-danger-outline:focus,
.btn-danger-outline.focus,
.btn-danger-outline:active,
.btn-danger-outline.active,
.btn-danger-outline:hover,
.open > .btn-danger-outline.dropdown-toggle {
color: #fff $i;
background-color: $danger-colour $i;
border-color: $danger-colour $i;
}
.btn-primary-outline {
color: $primary-colour-outline $i;
background-color: transparent;
background-image: none;
border-color: $primary-colour-outline $i;
}
.btn-primary-outline:focus,
.btn-primary-outline.focus,
.btn-primary-outline:active,
.btn-primary-outline.active,
.btn-primary-outline:hover,
.open > .btn-primary-outline.dropdown-toggle {
color: #fff $i;
background-color: $primary-colour $i;
border-color: $primary-colour $i;
}
.btn-info-outline {
color: $info-colour $i;
background-color: transparent;
background-image: none;
border-color: $info-colour $i;
}
.btn-info-outline:focus,
.btn-info-outline.focus,
.btn-info-outline:active,
.btn-info-outline.active,
.btn-info-outline:hover,
.open > .btn-info-outline.dropdown-toggle {
color: #fff $i;
background-color: $info-colour $i;
border-color: $info-colour $i;
}
.btn-warning-outline {
color: $warning-colour $i;
background-color: transparent;
background-image: none;
border-color: $warning-colour $i;
}
.btn-warning-outline:focus,
.btn-warning-outline.focus,
.btn-warning-outline:active,
.btn-warning-outline.active,
.btn-warning-outline:hover,
.open > .btn-warning-outline.dropdown-toggle {
color: #fff $i;
background-color: $warning-colour $i;
border-color: $warning-colour $i;
}
.btn-success-outline {
color: $success-colour $i;
background-color: transparent;
background-image: none;
border-color: $success-colour $i;
}
.btn-success-outline:focus,
.btn-success-outline.focus,
.btn-success-outline:active,
.btn-success-outline.active,
.btn-success-outline:hover,
.open > .btn-success-outline.dropdown-toggle {
color: #fff $i;
background-color: $success-colour $i;
border-color: $success-colour $i;
}
#movieList .mix {
display: none;
}
#tvList .mix {
display: none;
}
$border-radius: 10px;
.scroll-top-wrapper {
position: fixed;
opacity: 0;
visibility: hidden;
overflow: hidden;
text-align: center;
z-index: 99999999;
background-color: $bg-colour;
color: #eeeeee;
width: 50px;
height: 48px;
line-height: 48px;
right: 30px;
bottom: 30px;
padding-top: 2px;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
border-bottom-left-radius: $border-radius;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
}
.scroll-top-wrapper:hover {
background-color: $primary-colour;
}
.scroll-top-wrapper.show {
visibility: visible;
cursor: pointer;
opacity: 1.0;
}
.scroll-top-wrapper i.fa {
line-height: inherit;
}
.no-search-results {
text-align: center;
}
.no-search-results .no-search-results-icon {
font-size: 10em;
color: $form-color;
}
.no-search-results .no-search-results-text {
margin: 20px 0;
color: #ccc;
}
.form-control-search {
padding: 13px 105px 13px 16px;
height: 100%;
}
.form-control-withbuttons {
padding-right: 105px;
}
.input-group-addon .btn-group {
position: absolute;
right: 45px;
z-index: 3;
top: 10px;
box-shadow: 0 0 0;
}
.input-group-addon .btn-group .btn {
border: 1px solid rgba(255,255,255,.7) !important;
padding: 3px 12px;
color: rgba(255,255,255,.7) !important;
}
.btn-split .btn {
border-radius: 0 !important;
}
.btn-split .btn:not(.dropdown-toggle) {
border-radius: .25rem 0 0 .25rem $i;
}
.btn-split .btn.dropdown-toggle {
border-radius: 0 .25rem .25rem 0 $i;
padding: 12px 8px;
}
#updateAvailable {
background-color: #df691a;
text-align: center;
font-size: 15px;
padding: 3px 0;
}
.checkbox label {
display: inline-block;
cursor: pointer;
position: relative;
padding-left: 25px;
margin-right: 15px;
font-size: 13px;
margin-bottom: 10px; }
.checkbox label:before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
margin-right: 10px;
position: absolute;
left: 0;
bottom: 1px;
border: 2px solid #eee;
border-radius: 3px; }
.checkbox input[type=checkbox] {
display: none; }
.checkbox input[type=checkbox]:checked + label:before {
content: "\2713";
font-size: 13px;
color: #fafafa;
text-align: center;
line-height: 13px; }
.input-group-sm{
padding-top: 2px;
padding-bottom: 2px;
}
.tab-pane .form-horizontal .form-group {
margin-right: 15px;
margin-left: 15px; }

@ -0,0 +1,158 @@
.form-control-custom {
background-color: #333333 !important; }
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: #df691a; }
scroll-top-wrapper {
background-color: #333333; }
.scroll-top-wrapper:hover {
background-color: #df691a; }
body {
font-family: Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;
color: #eee;
background-color: #1f1f1f; }
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #333; }
.table-hover > tbody > tr:hover {
background-color: #282828; }
fieldset {
padding: 15px; }
legend {
border-bottom: 1px solid #333333; }
.form-control {
color: #fefefe;
background-color: #333; }
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
margin-left: -0px; }
.form-horizontal .radio,
.form-horizontal .checkbox,
.form-horizontal .radio-inline,
.form-horizontal .checkbox-inline {
margin-top: -15px; }
.dropdown-menu {
background-color: #282828; }
.dropdown-menu .divider {
background-color: #333333; }
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #333; }
.input-group-addon {
background-color: #333333; }
.nav > li > a:hover,
.nav > li > a:focus {
background-color: #df691a; }
.nav-tabs > li > a:hover {
border-color: #df691a #df691a transparent; }
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background-color: #df691a;
border: 1px solid #df691a; }
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
border: 1px solid #df691a; }
/*.navbar {
position: relative;
min-height: 40px;
margin-bottom: 21px;
z-index: 1000;
padding: 0px 3px;
font-size: 24px;
background-color: #000;
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.2);
}*/
.navbar-default {
background-color: #0a0a0a; }
.navbar-default .navbar-brand {
color: #DF691A; }
.navbar-default .navbar-nav > li > a:hover,
.navbar-default .navbar-nav > li > a:focus {
color: #F0ad4e;
background-color: #282828; }
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
background-color: #282828; }
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .open > a:hover,
.navbar-default .navbar-nav > .open > a:focus {
background-color: #df691a;
color: #fff; }
.pagination > li > a,
.pagination > li > span {
background-color: #282828; }
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
background-color: #333; }
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #fefefe;
background-color: #333333; }
.list-group-item {
background-color: #282828; }
a.list-group-item:hover,
button.list-group-item:hover,
a.list-group-item:focus,
button.list-group-item:focus {
background-color: #333333; }
.input-addon,
.input-group-addon {
color: #df691a; }
.modal-header,
.modal-footer {
background-color: #282828; }
.modal-content {
position: relative;
background-color: #282828;
border: 1px solid transparent;
border-radius: 0;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
outline: 0; }

@ -0,0 +1 @@
.form-control-custom{background-color:#333 !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#df691a;}scroll-top-wrapper{background-color:#333;}.scroll-top-wrapper:hover{background-color:#df691a;}body{font-family:Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;color:#eee;background-color:#1f1f1f;}.table-striped>tbody>tr:nth-of-type(odd){background-color:#333;}.table-hover>tbody>tr:hover{background-color:#282828;}fieldset{padding:15px;}legend{border-bottom:1px solid #333;}.form-control{color:#fefefe;background-color:#333;}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{margin-left:-0;}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:-15px;}.dropdown-menu{background-color:#282828;}.dropdown-menu .divider{background-color:#333;}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#333;}.input-group-addon{background-color:#333;}.nav>li>a:hover,.nav>li>a:focus{background-color:#df691a;}.nav-tabs>li>a:hover{border-color:#df691a #df691a transparent;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background-color:#df691a;border:1px solid #df691a;}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #df691a;}.navbar-default{background-color:#0a0a0a;}.navbar-default .navbar-brand{color:#df691a;}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#f0ad4e;background-color:#282828;}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background-color:#282828;}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#df691a;color:#fff;}.pagination>li>a,.pagination>li>span{background-color:#282828;}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#333;}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#fefefe;background-color:#333;}.list-group-item{background-color:#282828;}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{background-color:#333;}.input-addon,.input-group-addon{color:#df691a;}.modal-header,.modal-footer{background-color:#282828;}.modal-content{position:relative;background-color:#282828;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:0;}

@ -0,0 +1,196 @@
$primary-colour: #df691a;
$primary-colour-outline: #ff761b;
$bg-colour: #333333;
$i: !important;
.form-control-custom {
background-color: $bg-colour $i;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: $primary-colour;
}
scroll-top-wrapper {
background-color: $bg-colour;
}
.scroll-top-wrapper:hover {
background-color: $primary-colour;
}
body {
font-family: Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;
color: #eee;
background-color: #1f1f1f;
}
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #333;
}
.table-hover > tbody > tr:hover {
background-color: #282828;
}
fieldset {
padding: 15px;
}
legend {
border-bottom: 1px solid #333333;
}
.form-control {
color: #fefefe;
background-color: #333;
}
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
margin-left: -0px;
}
.form-horizontal .radio,
.form-horizontal .checkbox,
.form-horizontal .radio-inline,
.form-horizontal .checkbox-inline {
margin-top: -15px;
}
.dropdown-menu {
background-color: #282828;
}
.dropdown-menu .divider {
background-color: #333333;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #333;
}
.input-group-addon {
background-color: #333333;
}
.nav > li > a:hover,
.nav > li > a:focus {
background-color: #df691a;
}
.nav-tabs > li > a:hover {
border-color: #df691a #df691a transparent;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background-color: #df691a;
border: 1px solid #df691a;
}
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
border: 1px solid #df691a;
}
/*.navbar {
position: relative;
min-height: 40px;
margin-bottom: 21px;
z-index: 1000;
padding: 0px 3px;
font-size: 24px;
background-color: #000;
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.2);
}*/
.navbar-default {
background-color: #0a0a0a;
}
.navbar-default .navbar-brand {
color: #DF691A;
}
.navbar-default .navbar-nav > li > a:hover,
.navbar-default .navbar-nav > li > a:focus {
color: #F0ad4e;
background-color: #282828;
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
background-color: #282828;
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .open > a:hover,
.navbar-default .navbar-nav > .open > a:focus {
background-color: #df691a;
color: #fff;
}
.pagination > li > a,
.pagination > li > span {
background-color: #282828;
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
background-color: #333;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #fefefe;
background-color: #333333;
}
.list-group-item {
background-color: #282828;
}
a.list-group-item:hover,
button.list-group-item:hover,
a.list-group-item:focus,
button.list-group-item:focus {
background-color: #333333;
}
.input-addon,
.input-group-addon {
color: #df691a;
}
.modal-header,
.modal-footer {
background-color: #282828;
}
.modal-content {
position: relative;
background-color: #282828;
border: 1px solid transparent;
border-radius: 0;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
outline: 0;
}

@ -20,11 +20,15 @@
color: #fff; }
hr {
border-color: #777; }
border: 1px dashed #777; }
.btn {
border-radius: 0.25rem !important; }
.btn-group-separated .btn,
.btn-group-separated .btn + .btn {
margin-left: 3px; }
.multiSelect {
background-color: #4e5d6c; }

File diff suppressed because one or more lines are too long

@ -6,8 +6,7 @@ $info-colour: #5bc0de;
$warning-colour: #f0ad4e;
$danger-colour: #d9534f;
$success-colour: #5cb85c;
$i:
!important;
$i: !important;
@media (min-width: 768px ) {
.row {
@ -40,13 +39,18 @@ $i:
}
hr {
border-color: #777;
border: 1px dashed #777;
}
.btn {
border-radius: .25rem $i;
}
.btn-group-separated .btn,
.btn-group-separated .btn + .btn {
margin-left: 3px;
}
.multiSelect {
background-color: $form-color;
}

@ -12,6 +12,8 @@ var albumTemplate = Handlebars.compile(albumSource);
var movieTimer = 0;
var tvimer = 0;
var base = $('#baseUrl').text();
var tvLoaded = false;
var albumLoaded = false;
var mixItUpDefault = {
animation: { enable: true },
@ -40,9 +42,14 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var $tvl = $('#tvList');
var $musicL = $('#musicList');
$('.approve-category').hide();
$('.approve-category,.delete-category').hide();
if (target === "#TvShowTab") {
$('#approveTVShows').show();
if (!tvLoaded) {
tvLoaded = true;
tvLoad();
}
$('#approveTVShows,#deleteTVShows').show();
if ($ml.mixItUp('isLoaded')) {
activeState = $ml.mixItUp('getState');
$ml.mixItUp('destroy');
@ -51,11 +58,11 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
activeState = $musicL.mixItUp('getState');
$musicL.mixItUp('destroy');
}
if ($tvl.mixItUp('isLoaded')) $tvl.mixItUp('destroy');
$tvl.mixItUp(mixItUpConfig(activeState)); // init or reinit
//if ($tvl.mixItUp('isLoaded')) $tvl.mixItUp('destroy');
//$tvl.mixItUp(mixItUpConfig(activeState)); // init or reinit
}
if (target === "#MoviesTab") {
$('#approveMovies').show();
$('#approveMovies,#deleteMovies').show();
if ($tvl.mixItUp('isLoaded')) {
activeState = $tvl.mixItUp('getState');
$tvl.mixItUp('destroy');
@ -69,7 +76,11 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
}
if (target === "#MusicTab") {
$('#approveMusic').show();
if (!albumLoaded) {
albumLoaded = true;
albumLoad();
}
$('#approveMusic,#deleteMusic').show();
if ($tvl.mixItUp('isLoaded')) {
activeState = $tvl.mixItUp('getState');
$tvl.mixItUp('destroy');
@ -124,7 +135,7 @@ $('#approveTVShows').click(function (e) {
return;
}
loadingButton(buttonId, "success");
loadingButton(buttonId, "warning");
var url = createBaseUrl(base, '/approval/approvealltvshows');
$.ajax({
type: 'post',
@ -140,6 +151,72 @@ $('#approveTVShows').click(function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
},
complete: function (e) {
finishLoading(buttonId, "warning", origHtml);
}
});
});
$('#deleteMovies').click(function (e) {
e.preventDefault();
if (!confirm("Are you sure you want to delete all TV show requests?")) return;
var buttonId = e.target.id;
var origHtml = $(this).html();
if ($('#' + buttonId).text() === " Loading...") {
return;
}
loadingButton(buttonId, "warning");
var url = createBaseUrl(base, '/approval/deleteallmovies');
$.ajax({
type: 'post',
url: url,
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
generateNotify("Success! All Movie requests deleted!", "success");
movieLoad();
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
},
complete: function (e) {
finishLoading(buttonId, "warning", origHtml);
}
});
});
$('#deleteTVShows').click(function (e) {
e.preventDefault();
if (!confirm("Are you sure you want to delete all TV show requests?")) return;
var buttonId = e.target.id;
var origHtml = $(this).html();
if ($('#' + buttonId).text() === " Loading...") {
return;
}
loadingButton(buttonId, "warning");
var url = createBaseUrl(base, '/approval/deletealltvshows');
$.ajax({
type: 'post',
url: url,
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
generateNotify("Success! All TV Show requests deleted!", "success");
tvLoad();
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
},
complete: function (e) {
finishLoading(buttonId, "success", origHtml);
}
@ -448,10 +525,10 @@ function mixItUpConfig(activeState) {
return conf;
};
var position = 0;
function initLoad() {
movieLoad();
tvLoad();
albumLoad();
}
function movieLoad() {

@ -57,12 +57,15 @@ namespace PlexRequests.UI.Helpers
{
settings.ThemeName = Themes.PlexTheme;
}
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/Themes/{settings.ThemeName}\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/Themes/{settings.ThemeName.Replace(".css",string.Empty)}Custom.min.css\" type=\"text/css\" />");
if (settings.ThemeName == "PlexBootstrap.css") settings.ThemeName = Themes.PlexTheme;
if (settings.ThemeName == "OriginalBootstrap.css") settings.ThemeName = Themes.OriginalTheme;
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/bootstrap.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/font-awesome.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/pace.min.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/awesome-bootstrap-checkbox.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/base.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/Themes/{settings.ThemeName}\" type=\"text/css\"/>");
sb.AppendLine($"<script src=\"{content}/Content/jquery-2.2.1.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/handlebars.min.js\"></script>");

@ -28,7 +28,7 @@ namespace PlexRequests.UI.Helpers
{
public static class Themes
{
public const string OriginalTheme = "OriginalBootstrap.css";
public const string PlexTheme = "PlexBootstrap.css";
public const string OriginalTheme = "original.css";
public const string PlexTheme = "plex.css";
}
}

@ -56,13 +56,15 @@ namespace PlexRequests.UI.Jobs
var sickrage = JobBuilder.Create<SickRageCacher>().WithIdentity("SickRageCacher", "Cache").Build();
var sonarr = JobBuilder.Create<SonarrCacher>().WithIdentity("SonarrCacher", "Cache").Build();
var cp = JobBuilder.Create<CouchPotatoCacher>().WithIdentity("CouchPotatoCacher", "Cache").Build();
var store = JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Backup").Build();
var store = JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build();
var storeClean = JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build();
jobs.Add(plex);
jobs.Add(sickrage);
jobs.Add(sonarr);
jobs.Add(cp);
jobs.Add(store);
jobs.Add(storeClean);
return jobs;
}
@ -129,17 +131,25 @@ namespace PlexRequests.UI.Jobs
var storeBackup =
TriggerBuilder.Create()
.WithIdentity("StoreBackup", "Backup")
.WithIdentity("StoreBackup", "Database")
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInHours(24).RepeatForever())
.Build();
var storeCleanup =
TriggerBuilder.Create()
.WithIdentity("StoreCleanup", "Database")
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInHours(24).RepeatForever())
.Build();
triggers.Add(plexAvailabilityChecker);
triggers.Add(srCacher);
triggers.Add(sonarrCacher);
triggers.Add(cpCacher);
triggers.Add(storeBackup);
triggers.Add(storeCleanup);
return triggers;
}

@ -481,8 +481,7 @@ namespace PlexRequests.UI.Modules
{
return Response.AsJson(valid.SendJsonError());
}
Log.Trace(settings.DumpJson());
var result = EmailService.SaveSettings(settings);
if (settings.Enabled)

@ -50,7 +50,7 @@ namespace PlexRequests.UI.Modules
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr) : base("approval", pr)
{
this.RequiresClaims(UserClaims.Admin);
this.RequiresClaims(UserClaims.Admin);
Service = service;
CpService = cpService;
@ -66,6 +66,8 @@ namespace PlexRequests.UI.Modules
Post["/approveall"] = x => ApproveAll();
Post["/approveallmovies"] = x => ApproveAllMovies();
Post["/approvealltvshows"] = x => ApproveAllTVShows();
Post["/deleteallmovies"] = x => DeleteAllMovies();
Post["/deletealltvshows"] = x => DeleteAllTVShows();
}
private IRequestService Service { get; }
@ -130,7 +132,7 @@ namespace PlexRequests.UI.Modules
Log.Trace("Approval result: {0}", requestResult);
if (requestResult)
{
return Response.AsJson(new JsonResponseModel {Result = true});
return Response.AsJson(new JsonResponseModel { Result = true });
}
return
Response.AsJson(new JsonResponseModel
@ -156,11 +158,9 @@ namespace PlexRequests.UI.Modules
request.Approved = true;
var requestResult = Service.UpdateRequest(request);
Log.Trace("Approval result: {0}", requestResult);
if (requestResult)
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Updated SickRage but could not approve it in PlexRequests :(" });
return Response.AsJson(requestResult
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Updated SickRage but could not approve it in PlexRequests :(" });
}
return Response.AsJson(new JsonResponseModel
{
@ -168,17 +168,19 @@ namespace PlexRequests.UI.Modules
Message = result?.message != null ? "<b>Message From SickRage: </b>" + result.message : "Could not add the series to SickRage"
});
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "SickRage or Sonarr are not set up!"
});
request.Approved = true;
var res = Service.UpdateRequest(request);
return Response.AsJson(res
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to Sonarr/SickRage because it has not been configured" }
: new JsonResponseModel { Result = false, Message = "Updated SickRage but could not approve it in PlexRequests :(" });
}
private Response RequestMovieAndUpdateStatus(RequestedModel request, string qualityId)
{
var cpSettings = CpService.GetSettings();
var cp = new CouchPotatoApi();
Log.Info("Adding movie to CouchPotato : {0}", request.Title);
if (!cpSettings.Enabled)
{
@ -197,7 +199,7 @@ namespace PlexRequests.UI.Modules
});
}
var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, string.IsNullOrEmpty(qualityId) ? cpSettings.ProfileId : qualityId);
var result = CpApi.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, string.IsNullOrEmpty(qualityId) ? cpSettings.ProfileId : qualityId);
Log.Trace("Adding movie to CP result {0}", result);
if (result)
{
@ -248,9 +250,9 @@ namespace PlexRequests.UI.Modules
var sender = new HeadphonesSender(HeadphoneApi, hpSettings, Service);
var result = sender.AddAlbum(request);
return Response.AsJson( new JsonResponseModel { Result = true, Message = "We have sent the approval to Headphones for processing, This can take a few minutes."} );
return Response.AsJson(new JsonResponseModel { Result = true, Message = "We have sent the approval to Headphones for processing, This can take a few minutes." });
}
private Response ApproveAllMovies()
@ -274,6 +276,27 @@ namespace PlexRequests.UI.Modules
}
}
private Response DeleteAllMovies()
{
var requests = Service.GetAll().Where(x => x.Type == RequestType.Movie);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no movie requests to delete. Please refresh." });
}
try
{
return DeleteRequests(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private Response ApproveAllTVShows()
{
var requests = Service.GetAll().Where(x => x.CanApprove && x.Type == RequestType.TvShow);
@ -294,6 +317,27 @@ namespace PlexRequests.UI.Modules
}
}
private Response DeleteAllTVShows()
{
var requests = Service.GetAll().Where(x => x.Type == RequestType.TvShow);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no tv show requests to delete. Please refresh." });
}
try
{
return DeleteRequests(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
/// <summary>
/// Approves all.
/// </summary>
@ -319,6 +363,22 @@ namespace PlexRequests.UI.Modules
}
private Response DeleteRequests(RequestedModel[] requestedModels)
{
try
{
var result = Service.BatchDelete(requestedModels.ToList());
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not delete all of the requests. Please try again or check the logs." });
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private Response UpdateRequests(RequestedModel[] requestedModels)
{
var cpSettings = CpService.GetSettings();
@ -327,15 +387,23 @@ namespace PlexRequests.UI.Modules
{
if (r.Type == RequestType.Movie)
{
var res = SendMovie(cpSettings, r, CpApi);
if (res)
if (cpSettings.Enabled)
{
r.Approved = true;
updatedRequests.Add(r);
var res = SendMovie(cpSettings, r, CpApi);
if (res)
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
}
}
else
{
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
r.Approved = true;
updatedRequests.Add(r);
}
}
if (r.Type == RequestType.TvShow)
@ -358,7 +426,7 @@ namespace PlexRequests.UI.Modules
}
}
if (sonarr.Enabled)
else if (sonarr.Enabled)
{
var res = sender.SendToSonarr(sonarr, r);
if (!string.IsNullOrEmpty(res?.title))
@ -372,11 +440,15 @@ namespace PlexRequests.UI.Modules
res?.ErrorMessages.ForEach(x => Log.Error("Error messages: {0}", x));
}
}
else
{
r.Approved = true;
updatedRequests.Add(r);
}
}
}
try
{
var result = Service.BatchUpdate(updatedRequests);
return Response.AsJson(result
? new JsonResponseModel { Result = true }

@ -35,6 +35,7 @@ using Nancy.Security;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
@ -103,7 +104,7 @@ namespace PlexRequests.UI.Modules
{
return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true");
}
var userId = UserMapper.CreateUser(username, Request.Form.Password, new[] { "Admin" });
var userId = UserMapper.CreateAdmin(username, Request.Form.Password);
Session[SessionKeys.UsernameKey] = username;
return this.LoginAndRedirect((Guid)userId);
};

@ -74,17 +74,17 @@ namespace PlexRequests.UI.Modules
Cache = cache;
Get["/"] = _ => LoadRequests();
Get["/movies"] = _ => GetMovies();
Get["/tvshows"] = _ => GetTvShows();
Get["/albums"] = _ => GetAlbumRequests();
Post["/delete"] = _ => DeleteRequest((int)Request.Form.id);
Post["/reportissue"] = _ => ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
Post["/reportissuecomment"] = _ => ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
Get["/movies", true] = async (x, ct) => await GetMovies();
Get["/tvshows", true] = async (c, ct) => await GetTvShows();
Get["/albums", true] = async (x,ct) => await GetAlbumRequests();
Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id);
Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
Post["/clearissues"] = _ => ClearIssue((int)Request.Form.Id);
Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id);
Post["/changeavailability"] = _ => ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
Post["/addnote"] = _ => AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea);
Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
Post["/addnote", true] = async (x, ct) => await AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea);
}
private IRequestService Service { get; }
@ -105,122 +105,94 @@ namespace PlexRequests.UI.Modules
return View["Index", settings];
}
private Response GetMovies() // TODO: async await the API calls
private async Task<Response> GetMovies()
{
var settings = PrSettings.GetSettings();
List<Task> taskList = new List<Task>();
var allRequests = await Service.GetAllAsync();
allRequests = allRequests.Where(x => x.Type == RequestType.Movie);
List<RequestedModel> dbMovies = new List<RequestedModel>();
taskList.Add(Task.Factory.StartNew(() =>
{
return Service.GetAll().Where(x => x.Type == RequestType.Movie);
var dbMovies = allRequests.ToList();
}).ContinueWith((t) =>
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbMovies = t.Result.ToList();
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList();
}
}));
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList();
}
List<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
if (IsAdmin)
{
var cpSettings = CpSettings.GetSettings();
if (cpSettings.Enabled)
{
taskList.Add(Task.Factory.StartNew(() =>
{
return Cache.GetOrSet(CacheKeys.CouchPotatoQualityProfiles, () =>
{
return CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey); // TODO: cache this!
});
}).ContinueWith((t) =>
var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
{
qualities = t.Result.list.Select(x => new QualityModel() { Id = x._id, Name = x.label }).ToList();
}));
return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false);
});
qualities = result.list.Select(x => new QualityModel() { Id = x._id, Name = x.label }).ToList();
}
}
Task.WaitAll(taskList.ToArray());
var viewModel = dbMovies.Select(movie =>
var viewModel = dbMovies.Select(movie => new RequestViewModel
{
return new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
ReleaseDateTicks = movie.ReleaseDate.Ticks,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { },
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = IsAdmin,
Issues = movie.Issues.ToString().CamelCaseToWords(),
OtherMessage = movie.OtherMessage,
AdminNotes = movie.AdminNote,
Qualities = qualities.ToArray()
};
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
ReleaseDateTicks = movie.ReleaseDate.Ticks,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { },
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = IsAdmin,
Issues = movie.Issues.ToString().CamelCaseToWords(),
OtherMessage = movie.OtherMessage,
AdminNotes = movie.AdminNote,
Qualities = qualities.ToArray()
}).ToList();
return Response.AsJson(viewModel);
}
private Response GetTvShows() // TODO: async await the API calls
private async Task<Response> GetTvShows()
{
var settings = PrSettings.GetSettings();
List<Task> taskList = new List<Task>();
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.TvShow);
List<RequestedModel> dbTv = new List<RequestedModel>();
taskList.Add(Task.Factory.StartNew(() =>
{
return Service.GetAll().Where(x => x.Type == RequestType.TvShow);
var dbTv = requests;
}).ContinueWith((t) =>
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbTv = t.Result.ToList();
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
}
}));
dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
}
List<QualityModel> qualities = new List<QualityModel>();
IEnumerable<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
{
var sonarrSettings = SonarrSettings.GetSettings();
if (sonarrSettings.Enabled)
{
taskList.Add(Task.Factory.StartNew(() =>
var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () =>
{
return Cache.GetOrSet(CacheKeys.SonarrQualityProfiles, () =>
{
return SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri); // TODO: cache this!
return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
}).ContinueWith((t) =>
{
qualities = t.Result.Select(x => new QualityModel() { Id = x.id.ToString(), Name = x.name }).ToList();
}));
qualities = result.Result.Select(x => new QualityModel() { Id = x.id.ToString(), Name = x.name }).ToList();
}
else {
else
{
var sickRageSettings = SickRageSettings.GetSettings();
if (sickRageSettings.Enabled)
{
@ -229,8 +201,6 @@ namespace PlexRequests.UI.Modules
}
}
Task.WaitAll(taskList.ToArray());
var viewModel = dbTv.Select(tv =>
{
return new RequestViewModel
@ -264,10 +234,11 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(viewModel);
}
private Response GetAlbumRequests()
private async Task<Response> GetAlbumRequests()
{
var settings = PrSettings.GetSettings();
var dbAlbum = Service.GetAll().Where(x => x.Type == RequestType.Album);
var dbAlbum = await Service.GetAllAsync();
dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album);
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
@ -308,12 +279,12 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(viewModel);
}
private Response DeleteRequest(int requestid)
{
this.RequiresClaims (UserClaims.Admin);
private async Task<Response> DeleteRequest(int requestid)
{
this.RequiresClaims(UserClaims.Admin);
var currentEntity = Service.Get(requestid);
Service.DeleteRequest(currentEntity);
var currentEntity = await Service.GetAsync(requestid);
await Service.DeleteRequestAsync(currentEntity);
return Response.AsJson(new JsonResponseModel { Result = true });
}
@ -325,9 +296,9 @@ namespace PlexRequests.UI.Modules
/// <param name="issue">The issue.</param>
/// <param name="comment">The comment.</param>
/// <returns></returns>
private Response ReportIssue(int requestId, IssueState issue, string comment)
private async Task<Response> ReportIssue(int requestId, IssueState issue, string comment)
{
var originalRequest = Service.Get(requestId);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
@ -338,7 +309,7 @@ namespace PlexRequests.UI.Modules
: string.Empty;
var result = Service.UpdateRequest(originalRequest);
var result = await Service.UpdateRequestAsync(originalRequest);
var model = new NotificationModel
{
@ -348,18 +319,18 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
Body = issue == IssueState.Other ? comment : issue.ToString().CamelCaseToWords()
};
NotificationService.Publish(model);
await NotificationService.Publish(model);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
private Response ClearIssue(int requestId)
private async Task<Response> ClearIssue(int requestId)
{
this.RequiresClaims ( UserClaims.Admin);
this.RequiresClaims(UserClaims.Admin);
var originalRequest = Service.Get(requestId);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" });
@ -367,16 +338,16 @@ namespace PlexRequests.UI.Modules
originalRequest.Issues = IssueState.None;
originalRequest.OtherMessage = string.Empty;
var result = Service.UpdateRequest(originalRequest);
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
}
private Response ChangeRequestAvailability(int requestId, bool available)
{
this.RequiresClaims (UserClaims.Admin);
var originalRequest = Service.Get(requestId);
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
{
this.RequiresClaims(UserClaims.Admin);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" });
@ -384,16 +355,16 @@ namespace PlexRequests.UI.Modules
originalRequest.Available = available;
var result = Service.UpdateRequest(originalRequest);
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new { Result = true, Available = available, Message = string.Empty }
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
}
private Response AddNote(int requestId, string noteArea)
{
this.RequiresClaims (UserClaims.Admin);
var originalRequest = Service.Get(requestId);
private async Task<Response> AddNote(int requestId, string noteArea)
{
this.RequiresClaims(UserClaims.Admin);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to add a note!" });
@ -401,7 +372,7 @@ namespace PlexRequests.UI.Modules
originalRequest.AdminNote = noteArea;
var result = Service.UpdateRequest(originalRequest);
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not update the notes, please try again or check the logs" });

@ -47,7 +47,7 @@ using PlexRequests.Store;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
using System.Threading.Tasks;
using System.Windows.Forms;
using Nancy.Extensions;
using PlexRequests.Api.Models.Tv;
using PlexRequests.Store.Models;
@ -92,22 +92,22 @@ namespace PlexRequests.UI.Modules
EmailNotificationSettings = email;
Get["/"] = parameters => RequestLoad();
Get["/", true] = async (x, ct) => await RequestLoad();
Get["movie/{searchTerm}"] = parameters => SearchMovie((string)parameters.searchTerm);
Get["tv/{searchTerm}"] = parameters => SearchTvShow((string)parameters.searchTerm);
Get["music/{searchTerm}"] = parameters => SearchMusic((string)parameters.searchTerm);
Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm);
Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm);
Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm);
Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id);
Get["movie/upcoming"] = parameters => UpcomingMovies();
Get["movie/playing"] = parameters => CurrentlyPlayingMovies();
Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies();
Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies();
Post["request/movie"] = parameters => RequestMovie((int)Request.Form.movieId);
Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons);
Post["request/album"] = parameters => RequestAlbum((string)Request.Form.albumId);
Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId);
Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons);
Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId);
Post["/notifyuser"] = x => NotifyUser((bool)Request.Form.notify);
Get["/notifyuser"] = x => GetUserNotificationSettings();
Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify);
Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings();
Get["/seasons"] = x => GetSeasons();
}
@ -136,87 +136,86 @@ namespace PlexRequests.UI.Modules
private IRepository<UsersToNotify> UsersToNotifyRepo { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private Negotiator RequestLoad()
private async Task<Negotiator> RequestLoad()
{
var settings = PrService.GetSettings();
var settings = await PrService.GetSettingsAsync();
Log.Trace("Loading Index");
return View["Search/Index", settings];
}
private Response UpcomingMovies()
private async Task<Response> UpcomingMovies()
{
Log.Trace("Loading upcoming movies");
return ProcessMovies(MovieSearchType.Upcoming, string.Empty);
return await ProcessMovies(MovieSearchType.Upcoming, string.Empty);
}
private Response CurrentlyPlayingMovies()
private async Task<Response> CurrentlyPlayingMovies()
{
Log.Trace("Loading currently playing movies");
return ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty);
return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty);
}
private Response SearchMovie(string searchTerm)
private async Task<Response> SearchMovie(string searchTerm)
{
Log.Trace("Searching for Movie {0}", searchTerm);
return ProcessMovies(MovieSearchType.Search, searchTerm);
return await ProcessMovies(MovieSearchType.Search, searchTerm);
}
private Response ProcessMovies(MovieSearchType searchType, string searchTerm)
private async Task<Response> ProcessMovies(MovieSearchType searchType, string searchTerm)
{
var taskList = new List<Task>();
var apiMovies = new List<MovieResult>();
taskList.Add(Task.Factory.StartNew(() =>
{
switch (searchType)
await Task.Factory.StartNew(
() =>
{
case MovieSearchType.Search:
return MovieApi.SearchMovie(searchTerm).Result.Select(x => new MovieResult()
{
Adult = x.Adult,
BackdropPath = x.BackdropPath,
GenreIds = x.GenreIds,
Id = x.Id,
OriginalLanguage = x.OriginalLanguage,
OriginalTitle = x.OriginalTitle,
Overview = x.Overview,
Popularity = x.Popularity,
PosterPath = x.PosterPath,
ReleaseDate = x.ReleaseDate,
Title = x.Title,
Video = x.Video,
VoteAverage = x.VoteAverage,
VoteCount = x.VoteCount
}).ToList();
case MovieSearchType.CurrentlyPlaying:
return MovieApi.GetCurrentPlayingMovies().Result.ToList();
case MovieSearchType.Upcoming:
return MovieApi.GetUpcomingMovies().Result.ToList();
default:
return new List<MovieResult>();
}
}).ContinueWith((t) =>
{
apiMovies = t.Result;
}));
switch (searchType)
{
case MovieSearchType.Search:
return
MovieApi.SearchMovie(searchTerm)
.Result.Select(
x =>
new MovieResult()
{
Adult = x.Adult,
BackdropPath = x.BackdropPath,
GenreIds = x.GenreIds,
Id = x.Id,
OriginalLanguage = x.OriginalLanguage,
OriginalTitle = x.OriginalTitle,
Overview = x.Overview,
Popularity = x.Popularity,
PosterPath = x.PosterPath,
ReleaseDate = x.ReleaseDate,
Title = x.Title,
Video = x.Video,
VoteAverage = x.VoteAverage,
VoteCount = x.VoteCount
})
.ToList();
case MovieSearchType.CurrentlyPlaying:
return MovieApi.GetCurrentPlayingMovies().Result.ToList();
case MovieSearchType.Upcoming:
return MovieApi.GetUpcomingMovies().Result.ToList();
default:
return new List<MovieResult>();
}
}).ContinueWith(
(t) =>
{
apiMovies = t.Result;
});
Dictionary<int, RequestedModel> dbMovies = new Dictionary<int, RequestedModel>();
taskList.Add(Task.Factory.StartNew(() =>
{
return RequestService.GetAll().Where(x => x.Type == RequestType.Movie);
var allResults = await RequestService.GetAllAsync();
allResults = allResults.Where(x => x.Type == RequestType.Movie);
}).ContinueWith((t) =>
{
var distinctResults = t.Result.DistinctBy(x => x.ProviderId);
dbMovies = distinctResults.ToDictionary(x => x.ProviderId);
}));
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
var dbMovies = distinctResults.ToDictionary(x => x.ProviderId);
Task.WaitAll(taskList.ToArray());
var cpCached = CpCacher.QueuedIds();
var plexMovies = Checker.GetPlexMovies();
var settings = PrService.GetSettings();
var settings = await PrService.GetSettingsAsync();
var viewMovies = new List<SearchMovieViewModel>();
foreach (MovieResult movie in apiMovies)
{
@ -272,33 +271,20 @@ namespace PlexRequests.UI.Modules
return true;
}
private Response SearchTvShow(string searchTerm)
private async Task<Response> SearchTvShow(string searchTerm)
{
Log.Trace("Searching for TV Show {0}", searchTerm);
var taskList = new List<Task>();
var apiTv = new List<TvMazeSearch>();
taskList.Add(Task.Factory.StartNew(() =>
{
return new TvMazeApi().Search(searchTerm);
}).ContinueWith((t) =>
await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) =>
{
apiTv = t.Result;
}));
var dbTv = new Dictionary<int, RequestedModel>();
taskList.Add(Task.Factory.StartNew(() =>
{
return RequestService.GetAll().Where(x => x.Type == RequestType.TvShow);
});
}).ContinueWith((t) =>
{
dbTv = t.Result.ToDictionary(x => x.ProviderId);
}));
var allResults = await RequestService.GetAllAsync();
allResults = allResults.Where(x => x.Type == RequestType.TvShow);
Task.WaitAll(taskList.ToArray());
var dbTv = allResults.ToDictionary(x => x.ProviderId);
if (!apiTv.Any())
{
@ -361,31 +347,18 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(viewTv);
}
private Response SearchMusic(string searchTerm)
private async Task<Response> SearchMusic(string searchTerm)
{
var taskList = new List<Task>();
var apiAlbums = new List<Release>();
taskList.Add(Task.Factory.StartNew(() =>
{
return MusicBrainzApi.SearchAlbum(searchTerm);
}).ContinueWith((t) =>
await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) =>
{
apiAlbums = t.Result.releases ?? new List<Release>();
}));
var dbAlbum = new Dictionary<string, RequestedModel>();
taskList.Add(Task.Factory.StartNew(() =>
{
return RequestService.GetAll().Where(x => x.Type == RequestType.Album);
});
}).ContinueWith((t) =>
{
dbAlbum = t.Result.ToDictionary(x => x.MusicBrainzId);
}));
var allResults = await RequestService.GetAllAsync();
allResults = allResults.Where(x => x.Type == RequestType.Album);
Task.WaitAll(taskList.ToArray());
var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId);
var plexAlbums = Checker.GetPlexAlbums();
@ -407,7 +380,7 @@ namespace PlexRequests.UI.Modules
DateTime release;
DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release);
var artist = a.ArtistCredit?.FirstOrDefault()?.artist;
if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist.name))
if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name))
{
viewA.Available = true;
}
@ -425,27 +398,26 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(viewAlbum);
}
private Response RequestMovie(int movieId)
private async Task<Response> RequestMovie(int movieId)
{
var movieApi = new TheMovieDbApi();
var movieInfo = movieApi.GetMovieInformation(movieId).Result;
var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}";
Log.Trace("Getting movie info from TheMovieDb");
Log.Trace(movieInfo.DumpJson);
//#if !DEBUG
var settings = PrService.GetSettings();
var settings = await PrService.GetSettingsAsync();
// check if the movie has already been requested
Log.Info("Requesting movie with id {0}", movieId);
var existingRequest = RequestService.CheckRequest(movieId);
var existingRequest = await RequestService.CheckRequestAsync(movieId);
if (existingRequest != null)
{
// check if the current user is already marked as a requester for this movie, if not, add them
if (!existingRequest.UserHasRequested(Username))
{
existingRequest.RequestedUsers.Add(Username);
RequestService.UpdateRequest(existingRequest);
await RequestService.UpdateRequestAsync(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" });
@ -461,8 +433,9 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" });
}
}
catch (ApplicationSettingsException)
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" });
}
//#endif
@ -473,7 +446,7 @@ namespace PlexRequests.UI.Modules
Type = RequestType.Movie,
Overview = movieInfo.Overview,
ImdbId = movieInfo.ImdbId,
PosterPath = "http://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath,
PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath,
Title = movieInfo.Title,
ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue,
Status = movieInfo.Status,
@ -484,13 +457,10 @@ namespace PlexRequests.UI.Modules
};
Log.Trace(settings.DumpJson());
if (ShouldAutoApprove(RequestType.Movie, settings))
{
var cpSettings = CpService.GetSettings();
var cpSettings = await CpService.GetSettingsAsync();
Log.Trace("Settings: ");
Log.Trace(cpSettings.DumpJson);
if (cpSettings.Enabled)
{
Log.Info("Adding movie to CP (No approval required)");
@ -501,8 +471,7 @@ namespace PlexRequests.UI.Modules
{
model.Approved = true;
Log.Info("Adding movie to database (No approval required)");
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
@ -513,7 +482,7 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
NotificationService.Publish(notificationModel);
await NotificationService.Publish(notificationModel);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
}
@ -529,7 +498,7 @@ namespace PlexRequests.UI.Modules
{
model.Approved = true;
Log.Info("Adding movie to database (No approval required)");
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
@ -540,7 +509,7 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
NotificationService.Publish(notificationModel);
await NotificationService.Publish(notificationModel);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
@ -550,10 +519,10 @@ namespace PlexRequests.UI.Modules
try
{
Log.Info("Adding movie to database");
var id = RequestService.AddRequest(model);
var id = await RequestService.AddRequestAsync(model);
var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest };
NotificationService.Publish(notificationModel);
await NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
}
@ -570,9 +539,8 @@ namespace PlexRequests.UI.Modules
/// </summary>
/// <param name="showId">The show identifier.</param>
/// <param name="seasons">The seasons.</param>
/// <param name="notify">if set to <c>true</c> [notify].</param>
/// <returns></returns>
private Response RequestTvShow(int showId, string seasons)
private async Task<Response> RequestTvShow(int showId, string seasons)
{
var tvApi = new TvMazeApi();
@ -582,18 +550,18 @@ namespace PlexRequests.UI.Modules
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
//#if !DEBUG
var settings = PrService.GetSettings();
var settings = await PrService.GetSettingsAsync();
// check if the show has already been requested
Log.Info("Requesting tv show with id {0}", showId);
var existingRequest = RequestService.CheckRequest(showId);
var existingRequest = await RequestService.CheckRequestAsync(showId);
if (existingRequest != null)
{
// check if the current user is already marked as a requester for this show, if not, add them
if (!existingRequest.UserHasRequested(Username))
{
existingRequest.RequestedUsers.Add(Username);
RequestService.UpdateRequest(existingRequest);
await RequestService.UpdateRequestAsync(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" });
}
@ -606,7 +574,7 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" });
}
}
catch (ApplicationSettingsException)
catch (Exception)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" });
}
@ -627,7 +595,7 @@ namespace PlexRequests.UI.Modules
RequestedUsers = new List<string> { Username },
Issues = IssueState.None,
ImdbId = showInfo.externals?.imdb ?? string.Empty,
SeasonCount = showInfo.seasonCount
SeasonCount = showInfo.seasonCount,
};
var seasonsList = new List<int>();
@ -645,6 +613,7 @@ namespace PlexRequests.UI.Modules
model.SeasonsRequested = "All";
break;
default:
model.SeasonsRequested = seasons;
var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var seasonsCount = new int[split.Length];
for (var i = 0; i < split.Length; i++)
@ -661,16 +630,16 @@ namespace PlexRequests.UI.Modules
if (ShouldAutoApprove(RequestType.TvShow, settings))
{
var sonarrSettings = SonarrService.GetSettings();
var sonarrSettings = await SonarrService.GetSettingsAsync();
var sender = new TvSender(SonarrApi, SickrageApi);
if (sonarrSettings.Enabled)
{
var result = sender.SendToSonarr(sonarrSettings, model);
if (result != null && !string.IsNullOrEmpty(result.title))
if (!string.IsNullOrEmpty(result?.title))
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & Sonarr)");
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
@ -681,7 +650,7 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
NotificationService.Publish(notify1);
await NotificationService.Publish(notify1);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
}
@ -699,7 +668,7 @@ namespace PlexRequests.UI.Modules
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & SickRage)");
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notify2 = new NotificationModel
@ -709,7 +678,7 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
NotificationService.Publish(notify2);
await NotificationService.Publish(notify2);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
@ -717,14 +686,33 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "<b>Message From SickRage: </b>" + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." });
}
if (!srSettings.Enabled && !sonarrSettings.Enabled)
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
await NotificationService.Publish(notify2);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." });
}
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest };
NotificationService.Publish(notificationModel);
await NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
}
@ -735,7 +723,8 @@ namespace PlexRequests.UI.Modules
var claims = Context.CurrentUser?.Claims;
if (claims != null)
{
if (claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser))
var enumerable = claims as string[] ?? claims.ToArray();
if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser))
{
sendNotification = false; // Don't bother sending a notification if the user is an admin
}
@ -744,10 +733,10 @@ namespace PlexRequests.UI.Modules
}
private Response RequestAlbum(string releaseId)
private async Task<Response> RequestAlbum(string releaseId)
{
var settings = PrService.GetSettings();
var existingRequest = RequestService.CheckRequest(releaseId);
var settings = await PrService.GetSettingsAsync();
var existingRequest = await RequestService.CheckRequestAsync(releaseId);
Log.Debug("Checking for an existing request");
if (existingRequest != null)
@ -758,12 +747,11 @@ namespace PlexRequests.UI.Modules
Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username);
existingRequest.RequestedUsers.Add(Username);
RequestService.UpdateRequest(existingRequest);
await RequestService.UpdateRequestAsync(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" });
}
Log.Debug("This is a new request");
var albumInfo = MusicBrainzApi.GetAlbum(releaseId);
@ -823,7 +811,7 @@ namespace PlexRequests.UI.Modules
if (!hpSettings.Enabled)
{
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
return
Response.AsJson(new JsonResponseModel
{
@ -833,9 +821,9 @@ namespace PlexRequests.UI.Modules
}
var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService);
sender.AddAlbum(model).Wait();
await sender.AddAlbum(model);
model.Approved = true;
RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification())
{
@ -846,7 +834,7 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
NotificationService.Publish(notify2);
await NotificationService.Publish(notify2);
}
return
@ -866,9 +854,9 @@ namespace PlexRequests.UI.Modules
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest
};
NotificationService.Publish(notify2);
await NotificationService.Publish(notify2);
}
var result = RequestService.AddRequest(model);
await RequestService.AddRequestAsync(model);
return Response.AsJson(new JsonResponseModel
{
Result = true,
@ -909,20 +897,22 @@ namespace PlexRequests.UI.Modules
}
}
private Response NotifyUser(bool notify)
private async Task<Response> NotifyUser(bool notify)
{
var auth = Auth.GetSettings().UserAuthentication;
var email = EmailNotificationSettings.GetSettings().EnableUserEmailNotifications;
var authSettings = await Auth.GetSettingsAsync();
var auth = authSettings.UserAuthentication;
var emailSettings = await EmailNotificationSettings.GetSettingsAsync();
var email = emailSettings.EnableUserEmailNotifications;
if (!auth)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts"});
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" });
}
if (!email)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." });
}
var username = Username;
var originalList = UsersToNotifyRepo.GetAll();
var originalList = await UsersToNotifyRepo.GetAllAsync();
if (!notify)
{
if (originalList == null)
@ -932,7 +922,7 @@ namespace PlexRequests.UI.Modules
var userToRemove = originalList.FirstOrDefault(x => x.Username == username);
if (userToRemove != null)
{
UsersToNotifyRepo.Delete(userToRemove);
await UsersToNotifyRepo.DeleteAsync(userToRemove);
}
return Response.AsJson(new JsonResponseModel { Result = true });
}
@ -941,7 +931,7 @@ namespace PlexRequests.UI.Modules
if (originalList == null)
{
var userModel = new UsersToNotify { Username = username };
var insertResult = UsersToNotifyRepo.Insert(userModel);
var insertResult = await UsersToNotifyRepo.InsertAsync(userModel);
return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" });
}
@ -953,15 +943,16 @@ namespace PlexRequests.UI.Modules
else
{
var userModel = new UsersToNotify { Username = username };
var insertResult = UsersToNotifyRepo.Insert(userModel);
var insertResult = await UsersToNotifyRepo.InsertAsync(userModel);
return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" });
}
}
private Response GetUserNotificationSettings()
private async Task<Response> GetUserNotificationSettings()
{
var retval = UsersToNotifyRepo.GetAll().FirstOrDefault(x => x.Username == Username);
return Response.AsJson(retval != null);
var all = await UsersToNotifyRepo.GetAllAsync();
var retVal = all.FirstOrDefault(x => x.Username == Username);
return Response.AsJson(retVal != null);
}
private Response GetSeasons()

@ -46,24 +46,24 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(users);
}
private Response CreateUser(string username, string password, string claims)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Please enter in a valid Username and Password"
});
}
var user = UserMapper.CreateUser(username, password, new string[] {claims});
if (user.HasValue)
{
return Response.AsJson(new JsonResponseModel {Result = true});
}
//private Response CreateUser(string username, string password, string claims)
//{
// if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
// {
// return Response.AsJson(new JsonResponseModel
// {
// Result = true,
// Message = "Please enter in a valid Username and Password"
// });
// }
// var user = UserMapper.CreateUser(username, password, new string[] {claims});
// if (user.HasValue)
// {
// return Response.AsJson(new JsonResponseModel {Result = true});
// }
return Response.AsJson(new JsonResponseModel {Result = false, Message = "Could not save user"});
}
// return Response.AsJson(new JsonResponseModel {Result = false, Message = "Could not save user"});
//}
}
}

@ -210,7 +210,15 @@
<Content Include="Content\bootstrap-notify.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\Themes\OriginalBootstrap.css">
<Content Include="Content\base.css">
<DependentUpon>base.scss</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\base.min.css">
<DependentUpon>base.css</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\bootstrap.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="Models\JsonResponseModel.cs" />
@ -286,22 +294,21 @@
<Content Include="Content\swagger\underscore-min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\Themes\OriginalBootstrapCustom.css">
<DependentUpon>OriginalBootstrapCustom.scss</DependentUpon>
</Content>
<Content Include="Content\Themes\OriginalBootstrapCustom.min.css">
<DependentUpon>OriginalBootstrapCustom.css</DependentUpon>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Content Include="Content\Themes\original.css">
<DependentUpon>original.scss</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\Themes\PlexBootstrap.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Content Include="Content\Themes\original.min.css">
<DependentUpon>original.css</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\Themes\PlexBootstrapCustom.css">
<DependentUpon>PlexBootstrapCustom.scss</DependentUpon>
<Content Include="Content\Themes\plex.css">
<DependentUpon>plex.scss</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\Themes\PlexBootstrapCustom.min.css">
<DependentUpon>PlexBootstrapCustom.css</DependentUpon>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Content Include="Content\Themes\plex.min.css">
<DependentUpon>plex.css</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Views\ApiDocs\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -387,8 +394,9 @@
<None Include="Content\awesome-bootstrap-checkbox.scss">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Content\Themes\PlexBootstrapCustom.scss" />
<None Include="Content\Themes\OriginalBootstrapCustom.scss" />
<None Include="Content\base.scss" />
<None Include="Content\Themes\original.scss" />
<None Include="Content\Themes\plex.scss" />
<Content Include="Content\pace.min.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

@ -77,7 +77,7 @@
<p class="form-group">Current users that are allowed to authenticate: </p>
<div class="form-group">
<select id="users" multiple="" class="form-control-custom "></select>
<select id="users" multiple="" class="form-control-custom" style="height: 180px;"></select>
</div>
<div class="form-group">
@ -161,7 +161,7 @@
$('#users').append("<option>Error!</option>");
return;
}
if (response.users.length > 1) {
if (response.users.length > 0) {
$(response.users).each(function () {
$('#users').append("<option>" + this + "</option>");
});

@ -54,10 +54,10 @@
var dots = new Array(count % 10).join('.');
document.getElementById('autoUpdate').innerHTML = "Updating" + dots;
}, 1000);
var url = createBaseUrl(base, "autoupdate");
$.ajax({
type: "Post",
url: url,
url: "autoupdate",
data: { url: "@Model.DownloadUri" },
dataType: "json",
error: function () {

@ -35,19 +35,22 @@
<div class="row">
<div class="col-sm-12">
<div class="pull-right">
<div class="btn-group">
<div class="btn-group btn-group-separated">
@if (Context.CurrentUser.IsAuthenticated()) //TODO replace with IsAdmin
{
@if (Model.SearchForMovies)
{
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> Delete Movies</button>
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> Approve Movies</button>
}
@if (Model.SearchForTvShows)
{
<button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete TV Shows</button>
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve TV Shows</button>
}
@if (Model.SearchForMusic)
{
<button id="deleteMusics" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete Music</button>
<button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve Music</button>
}
}
@ -132,7 +135,7 @@
<div class="col-sm-2">
{{#if_eq type "movie"}}
{{#if posterPath}}
<img class="img-responsive" src="http://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster">
<img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
{{#if_eq type "tv"}}
@ -169,7 +172,7 @@
{{/if_eq}}
</div>
{{#if_eq type "tv"}}
<div>Series Requested: {{seriesRequested}}</div>
<div>Seasons Requested: {{seriesRequested}}</div>
{{/if_eq}}
{{#if requestedUsers}}
<div>Requested By: {{requestedUsers}}</div>

@ -144,7 +144,7 @@
{{#if_eq type "movie"}}
{{#if posterPath}}
<img class="img-responsive" src="http://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster">
<img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
{{#if_eq type "tv"}}

@ -1,8 +1,4 @@
[
{
"outputFile": "Content/custom.css",
"inputFile": "Content/custom.scss"
},
{
"outputFile": "Content/pace.css",
"inputFile": "Content/pace.scss"
@ -18,5 +14,17 @@
{
"outputFile": "Content/Themes/PlexBootstrapCustom.css",
"inputFile": "Content/Themes/PlexBootstrapCustom.scss"
},
{
"outputFile": "Content/base.css",
"inputFile": "Content/base.scss"
},
{
"outputFile": "Content/Themes/plex.css",
"inputFile": "Content/Themes/plex.scss"
},
{
"outputFile": "Content/Themes/original.css",
"inputFile": "Content/Themes/original.scss"
}
]

@ -1,6 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI", "PlexRequests.UI\PlexRequests.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}"
@ -35,20 +35,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services.Tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Updater", "PlexRequests.Updater\PlexRequests.Updater.csproj", "{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.Build.0 = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.Build.0 = Release|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -57,26 +51,38 @@ Global
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.Build.0 = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.Build.0 = Release|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.Build.0 = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.Build.0 = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.Build.0 = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.Build.0 = Release|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.Build.0 = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.Build.0 = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.Build.0 = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.Build.0 = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.Build.0 = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -85,12 +91,10 @@ Global
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.Build.0 = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -3,9 +3,9 @@ configuration: Release
assembly_info:
patch: true
file: '**\AssemblyInfo.*'
assembly_version: '1.7.4'
assembly_version: '1.7.5'
assembly_file_version: '{version}'
assembly_informational_version: '1.7.4'
assembly_informational_version: '1.7.5'
before_build:
- cmd: appveyor-retry nuget restore
build:

Loading…
Cancel
Save