mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 21:03:17 -07:00
commit
6249a503f3
204 changed files with 55606 additions and 10615 deletions
|
@ -1,41 +1,37 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexRegistry.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 FluentScheduler;
|
||||
|
||||
using PlexRequests.Services;
|
||||
|
||||
namespace PlexRequests.UI.Jobs
|
||||
{
|
||||
public class PlexRegistry : Registry
|
||||
{
|
||||
public PlexRegistry()
|
||||
{
|
||||
Schedule<AvailabilityUpdateService>().ToRunNow();
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: ISlackApi.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.Threading.Tasks;
|
||||
|
||||
using PlexRequests.Api.Models.Notifications;
|
||||
|
||||
namespace PlexRequests.Api.Interfaces
|
||||
{
|
||||
public interface ISlackApi
|
||||
{
|
||||
Task<string> PushAsync(string team, string token, string service, SlackNotificationBody message);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Api.Interfaces</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Api.Interfaces</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll</HintPath>
|
||||
<HintPath>..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
@ -51,20 +51,21 @@
|
|||
<Compile Include="IMusicBrainzApi.cs" />
|
||||
<Compile Include="IPlexApi.cs" />
|
||||
<Compile Include="IPushbulletApi.cs" />
|
||||
<Compile Include="ISlackApi.cs" />
|
||||
<Compile Include="IPushoverApi.cs" />
|
||||
<Compile Include="ISickRageApi.cs" />
|
||||
<Compile Include="ISonarrApi.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
|
||||
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
|
||||
<Name>PlexRequests.Api.Models</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="RestSharp" version="105.2.3" targetFramework="net452" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="RestSharp" version="105.2.3" targetFramework="net45" />
|
||||
</packages>
|
|
@ -0,0 +1,56 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: SlackNotificationBody.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 Newtonsoft.Json;
|
||||
|
||||
namespace PlexRequests.Api.Models.Notifications
|
||||
{
|
||||
public class SlackNotificationBody
|
||||
{
|
||||
[JsonConstructor]
|
||||
public SlackNotificationBody()
|
||||
{
|
||||
username = "Plex Requests";
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private string _username;
|
||||
public string username
|
||||
{
|
||||
get { return _username; }
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
_username = value;
|
||||
}
|
||||
}
|
||||
public string channel { get; set; }
|
||||
public string text { get; set; }
|
||||
|
||||
public string icon_url { get; set; }
|
||||
public string icon_emoji { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Api.Models</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Api.Models</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -31,10 +31,6 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
|
@ -43,6 +39,9 @@
|
|||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Movie\CouchPotatoAdd.cs" />
|
||||
|
@ -59,6 +58,7 @@
|
|||
<Compile Include="Notifications\PushbulletPush.cs" />
|
||||
<Compile Include="Notifications\PushbulletResponse.cs" />
|
||||
<Compile Include="Notifications\PushoverResponse.cs" />
|
||||
<Compile Include="Notifications\SlackNotificationBody.cs" />
|
||||
<Compile Include="Plex\PlexAccount.cs" />
|
||||
<Compile Include="Plex\PlexAuthentication.cs" />
|
||||
<Compile Include="Plex\PlexError.cs" />
|
||||
|
|
|
@ -24,5 +24,56 @@ namespace PlexRequests.Api.Models.Tv
|
|||
public int updated { get; set; }
|
||||
public Links _links { get; set; }
|
||||
public int seasonCount { get; set; }
|
||||
public Embedded _embedded { get; set; }
|
||||
}
|
||||
|
||||
public class Season
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string url { get; set; }
|
||||
public int number { get; set; }
|
||||
public string name { get; set; }
|
||||
public int? episodeOrder { get; set; }
|
||||
public string premiereDate { get; set; }
|
||||
public string endDate { get; set; }
|
||||
public Network2 network { get; set; }
|
||||
public object webChannel { get; set; }
|
||||
public Image2 image { get; set; }
|
||||
public string summary { get; set; }
|
||||
public Links2 _links { get; set; }
|
||||
}
|
||||
public class Country2
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string code { get; set; }
|
||||
public string timezone { get; set; }
|
||||
}
|
||||
|
||||
public class Network2
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public Country2 country { get; set; }
|
||||
}
|
||||
|
||||
public class Image2
|
||||
{
|
||||
public string medium { get; set; }
|
||||
public string original { get; set; }
|
||||
}
|
||||
|
||||
public class Self2
|
||||
{
|
||||
public string href { get; set; }
|
||||
}
|
||||
|
||||
public class Links2
|
||||
{
|
||||
public Self2 self { get; set; }
|
||||
}
|
||||
|
||||
public class Embedded
|
||||
{
|
||||
public List<Season> seasons { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
|
||||
</packages>
|
|
@ -25,7 +25,6 @@
|
|||
// ************************************************************************/
|
||||
#endregion
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
|
@ -34,14 +33,15 @@ using Newtonsoft.Json;
|
|||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Helpers.Exceptions;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
public class ApiRequest : IApiRequest
|
||||
{
|
||||
private JsonSerializerSettings Settings = new JsonSerializerSettings
|
||||
private readonly JsonSerializerSettings _settings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore
|
||||
|
@ -66,12 +66,13 @@ namespace PlexRequests.Api
|
|||
if (response.ErrorException != null)
|
||||
{
|
||||
var message = "Error retrieving response. Check inner details for more info.";
|
||||
throw new ApplicationException(message, response.ErrorException);
|
||||
Log.Error(response.ErrorException);
|
||||
throw new ApiRequestException(message, response.ErrorException);
|
||||
}
|
||||
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
|
||||
public IRestResponse Execute(IRestRequest request, Uri baseUri)
|
||||
{
|
||||
var client = new RestClient { BaseUrl = baseUri };
|
||||
|
@ -80,8 +81,9 @@ namespace PlexRequests.Api
|
|||
|
||||
if (response.ErrorException != null)
|
||||
{
|
||||
Log.Error(response.ErrorException);
|
||||
var message = "Error retrieving response. Check inner details for more info.";
|
||||
throw new ApplicationException(message, response.ErrorException);
|
||||
throw new ApiRequestException(message, response.ErrorException);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -95,8 +97,9 @@ namespace PlexRequests.Api
|
|||
|
||||
if (response.ErrorException != null)
|
||||
{
|
||||
Log.Error(response.ErrorException);
|
||||
var message = "Error retrieving response. Check inner details for more info.";
|
||||
throw new ApplicationException(message, response.ErrorException);
|
||||
throw new ApiRequestException(message, response.ErrorException);
|
||||
}
|
||||
|
||||
var result = DeserializeXml<T>(response.Content);
|
||||
|
@ -106,18 +109,18 @@ namespace PlexRequests.Api
|
|||
public T ExecuteJson<T>(IRestRequest request, Uri baseUri) where T : new()
|
||||
{
|
||||
var client = new RestClient { BaseUrl = baseUri };
|
||||
|
||||
var response = client.Execute(request);
|
||||
Log.Trace("Api Content Response:");
|
||||
Log.Trace(response.Content);
|
||||
if (response.ErrorException != null)
|
||||
{
|
||||
Log.Error(response.ErrorException);
|
||||
var message = "Error retrieving response. Check inner details for more info.";
|
||||
throw new ApplicationException(message, response.ErrorException);
|
||||
throw new ApiRequestException(message, response.ErrorException);
|
||||
}
|
||||
|
||||
Log.Trace("Deserialzing Object");
|
||||
var json = JsonConvert.DeserializeObject<T>(response.Content, Settings);
|
||||
var json = JsonConvert.DeserializeObject<T>(response.Content, _settings);
|
||||
Log.Trace("Finished Deserialzing Object");
|
||||
|
||||
return json;
|
||||
|
@ -133,8 +136,9 @@ namespace PlexRequests.Api
|
|||
using (var sr = new StringReader(input))
|
||||
return (T)ser.Deserialize(sr);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ using Newtonsoft.Json.Linq;
|
|||
using NLog;
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Movie;
|
||||
using PlexRequests.Helpers.Exceptions;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
|
@ -61,7 +62,12 @@ namespace PlexRequests.Api
|
|||
request.AddUrlSegment("imdbid", imdbid);
|
||||
request.AddUrlSegment("title", title);
|
||||
|
||||
var obj = Api.ExecuteJson<JObject>(request, baseUrl);
|
||||
var obj = RetryHandler.Execute<JObject>(() => Api.ExecuteJson<JObject> (request, baseUrl),new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10)},
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan));
|
||||
|
||||
Log.Trace("CP movie Add result count {0}", obj.Count);
|
||||
|
||||
if (obj.Count > 0)
|
||||
|
@ -99,7 +105,14 @@ namespace PlexRequests.Api
|
|||
|
||||
request.AddUrlSegment("apikey", apiKey);
|
||||
|
||||
return Api.Execute<CouchPotatoStatus>(request,url);
|
||||
|
||||
var obj = RetryHandler.Execute<CouchPotatoStatus>(() => Api.Execute<CouchPotatoStatus> (request, url),new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10)},
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public CouchPotatoProfiles GetProfiles(Uri url, string apiKey)
|
||||
|
@ -113,18 +126,39 @@ namespace PlexRequests.Api
|
|||
|
||||
request.AddUrlSegment("apikey", apiKey);
|
||||
|
||||
return Api.Execute<CouchPotatoProfiles>(request, url);
|
||||
var obj = RetryHandler.Execute<CouchPotatoProfiles>(() => Api.Execute<CouchPotatoProfiles> (request, url),null,
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status)
|
||||
{
|
||||
RestRequest request;
|
||||
request = new RestRequest { Resource = "/api/{apikey}/movie.list?status={status}" };
|
||||
var request = new RestRequest
|
||||
{
|
||||
Resource = "/api/{apikey}/movie.list?status={status}"
|
||||
};
|
||||
|
||||
request.AddUrlSegment("apikey", apiKey);
|
||||
request.AddUrlSegment("status", string.Join(",", status));
|
||||
|
||||
return Api.Execute<CouchPotatoMovies>(request, baseUrl);
|
||||
try
|
||||
{
|
||||
var obj = RetryHandler.Execute<CouchPotatoMovies>(() => Api.Execute<CouchPotatoMovies> (request, baseUrl),
|
||||
new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
},
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan));
|
||||
|
||||
return obj;
|
||||
}
|
||||
catch (Exception e) // Request error is already logged in the ApiRequest class
|
||||
{
|
||||
Log.Error("Error when attempting to GetMovies.");
|
||||
Log.Error (e);
|
||||
return new CouchPotatoMovies();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ namespace PlexRequests.Api
|
|||
|
||||
return albumResult;
|
||||
}
|
||||
catch (JsonSerializationException jse)
|
||||
catch (Exception jse)
|
||||
{
|
||||
Log.Error(jse);
|
||||
return false; // If there is no matching result we do not get returned a JSON string, it just returns "false".
|
||||
|
@ -94,7 +94,7 @@ namespace PlexRequests.Api
|
|||
|
||||
return result;
|
||||
}
|
||||
catch (JsonSerializationException jse)
|
||||
catch (Exception jse)
|
||||
{
|
||||
Log.Error(jse);
|
||||
return new List<HeadphonesGetIndex>();
|
||||
|
|
|
@ -23,17 +23,20 @@
|
|||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
using Polly;
|
||||
|
||||
|
||||
#endregion
|
||||
using System;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models;
|
||||
using PlexRequests.Api.Models.Plex;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Helpers.Exceptions;
|
||||
|
||||
using RestSharp;
|
||||
using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
|
@ -43,6 +46,19 @@ namespace PlexRequests.Api
|
|||
{
|
||||
Version = AssemblyHelper.GetAssemblyVersion();
|
||||
}
|
||||
|
||||
public PlexApi (IApiRequest api)
|
||||
{
|
||||
Api = api;
|
||||
}
|
||||
|
||||
private IApiRequest Api { get; }
|
||||
|
||||
private const string SignInUri = "https://plex.tv/users/sign_in.json";
|
||||
private const string FriendsUri = "https://plex.tv/pms/friends/all";
|
||||
private const string GetAccountUri = "https://plex.tv/users/account";
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private static string Version { get; }
|
||||
|
||||
public PlexAuthentication SignIn(string username, string password)
|
||||
|
@ -64,8 +80,11 @@ namespace PlexRequests.Api
|
|||
|
||||
request.AddJsonBody(userModel);
|
||||
|
||||
var api = new ApiRequest();
|
||||
return api.Execute<PlexAuthentication>(request, new Uri("https://plex.tv/users/sign_in.json"));
|
||||
var obj = RetryHandler.Execute<PlexAuthentication>(() => Api.Execute<PlexAuthentication> (request, new Uri(SignInUri)),
|
||||
null,
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling SignIn for Plex, Retrying {0}", timespan));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public PlexFriends GetUsers(string authToken)
|
||||
|
@ -77,8 +96,10 @@ namespace PlexRequests.Api
|
|||
|
||||
AddHeaders(ref request, authToken);
|
||||
|
||||
var api = new ApiRequest();
|
||||
var users = api.ExecuteXml<PlexFriends>(request, new Uri("https://plex.tv/pms/friends/all"));
|
||||
var users = RetryHandler.Execute(() => Api.ExecuteXml<PlexFriends> (request, new Uri(FriendsUri)),
|
||||
null,
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan));
|
||||
|
||||
|
||||
return users;
|
||||
}
|
||||
|
@ -101,8 +122,9 @@ namespace PlexRequests.Api
|
|||
request.AddUrlSegment("searchTerm", searchTerm);
|
||||
AddHeaders(ref request, authToken);
|
||||
|
||||
var api = new ApiRequest();
|
||||
var search = api.ExecuteXml<PlexSearch>(request, plexFullHost);
|
||||
var search = RetryHandler.Execute<PlexSearch>(() => Api.ExecuteXml<PlexSearch> (request, plexFullHost),
|
||||
null,
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling SearchContent for Plex, Retrying {0}", timespan));
|
||||
|
||||
return search;
|
||||
}
|
||||
|
@ -116,9 +138,10 @@ namespace PlexRequests.Api
|
|||
|
||||
AddHeaders(ref request, authToken);
|
||||
|
||||
var api = new ApiRequest();
|
||||
var users = api.ExecuteXml<PlexStatus>(request, uri);
|
||||
|
||||
var users = RetryHandler.Execute<PlexStatus>(() => Api.ExecuteXml<PlexStatus> (request, uri),
|
||||
null,
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for Plex, Retrying {0}", timespan));
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
|
@ -131,9 +154,10 @@ namespace PlexRequests.Api
|
|||
|
||||
AddHeaders(ref request, authToken);
|
||||
|
||||
var api = new ApiRequest();
|
||||
var account = api.ExecuteXml<PlexAccount>(request, new Uri("https://plex.tv/users/account"));
|
||||
|
||||
var account = RetryHandler.Execute<PlexAccount>(() => Api.ExecuteXml<PlexAccount> (request, new Uri(GetAccountUri)),
|
||||
null,
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetAccount for Plex, Retrying {0}", timespan));
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
|
@ -147,10 +171,23 @@ namespace PlexRequests.Api
|
|||
|
||||
AddHeaders(ref request, authToken);
|
||||
|
||||
var api = new ApiRequest();
|
||||
var sections = api.ExecuteXml<PlexLibraries>(request, plexFullHost);
|
||||
try
|
||||
{
|
||||
var lib = RetryHandler.Execute<PlexLibraries>(() => Api.ExecuteXml<PlexLibraries> (request, plexFullHost),
|
||||
new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
},
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetLibrarySections for Plex, Retrying {0}", timespan));
|
||||
|
||||
return sections;
|
||||
return lib;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e,"There has been a API Exception when attempting to get the Plex Libraries");
|
||||
return new PlexLibraries();
|
||||
}
|
||||
}
|
||||
|
||||
public PlexSearch GetLibrary(string authToken, Uri plexFullHost, string libraryId)
|
||||
|
@ -161,13 +198,26 @@ namespace PlexRequests.Api
|
|||
Resource = "library/sections/{libraryId}/all"
|
||||
};
|
||||
|
||||
request.AddUrlSegment("libraryId", libraryId.ToString());
|
||||
request.AddUrlSegment("libraryId", libraryId);
|
||||
AddHeaders(ref request, authToken);
|
||||
|
||||
var api = new ApiRequest();
|
||||
var search = api.ExecuteXml<PlexSearch>(request, plexFullHost);
|
||||
try
|
||||
{
|
||||
var lib = RetryHandler.Execute<PlexSearch>(() => Api.ExecuteXml<PlexSearch> (request, plexFullHost),
|
||||
new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
},
|
||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetLibrary for Plex, Retrying {0}", timespan));
|
||||
|
||||
return search;
|
||||
return lib;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e,"There has been a API Exception when attempting to get the Plex Library");
|
||||
return new PlexSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddHeaders(ref RestRequest request, string authToken)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Api</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Api</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -31,24 +31,8 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dapper, Version=1.40.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Dapper.1.42\lib\net45\Dapper.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll</HintPath>
|
||||
<HintPath>..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
@ -59,9 +43,23 @@
|
|||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<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="Nancy, Version=1.4.2.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.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>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Polly">
|
||||
<HintPath>..\packages\Polly-Signed.4.2.0\lib\net45\Polly.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -73,6 +71,7 @@
|
|||
<DependentUpon>MockApiData.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Mocks\MockSonarrApi.cs" />
|
||||
<Compile Include="SlackApi.cs" />
|
||||
<Compile Include="PushoverApi.cs" />
|
||||
<Compile Include="PushbulletApi.cs" />
|
||||
<Compile Include="SickrageApi.cs" />
|
||||
|
@ -87,6 +86,7 @@
|
|||
<Compile Include="TheTvDbApi.cs" />
|
||||
<Compile Include="TvMazeApi.cs" />
|
||||
<Compile Include="TvMazeBase.cs" />
|
||||
<Compile Include="RetryHandler.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
|
|
71
PlexRequests.Api/RetryHandler.cs
Normal file
71
PlexRequests.Api/RetryHandler.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using Polly.Retry;
|
||||
using Polly;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
public static class RetryHandler
|
||||
{
|
||||
|
||||
private static readonly TimeSpan[] DefaultTime = new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10)};
|
||||
|
||||
public static RetryPolicy RetryAndWaitPolicy(TimeSpan[] timeSpan, Action action)
|
||||
{
|
||||
if(timeSpan == null)
|
||||
{
|
||||
timeSpan = DefaultTime;
|
||||
}
|
||||
var policy = Policy.Handle<Exception> ()
|
||||
.WaitAndRetry(timeSpan, (e, ts) => action());
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
public static RetryPolicy RetryAndWaitPolicy(TimeSpan[] timeSpan)
|
||||
{
|
||||
if(timeSpan == null)
|
||||
{
|
||||
timeSpan = DefaultTime;
|
||||
}
|
||||
var policy = Policy.Handle<Exception> ()
|
||||
.WaitAndRetry(timeSpan);
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
public static RetryPolicy RetryAndWaitPolicy(TimeSpan[] timeSpan, Action<Exception, TimeSpan> action)
|
||||
{
|
||||
if(timeSpan == null)
|
||||
{
|
||||
timeSpan = DefaultTime;
|
||||
}
|
||||
var policy = Policy.Handle<Exception> ()
|
||||
.WaitAndRetry(timeSpan, action);
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
public static T Execute<T>(Func<T> action, TimeSpan[] timeSpan)
|
||||
{
|
||||
var policy = RetryAndWaitPolicy (timeSpan);
|
||||
|
||||
return policy.Execute (action);
|
||||
}
|
||||
|
||||
public static T Execute<T>(Func<T> func, TimeSpan[] timeSpan, Action<Exception, TimeSpan> action)
|
||||
{
|
||||
if(timeSpan == null)
|
||||
{
|
||||
timeSpan = DefaultTime;
|
||||
}
|
||||
var policy = RetryAndWaitPolicy (timeSpan, action);
|
||||
|
||||
return policy.Execute (func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
#region Copyright
|
||||
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: CouchPotatoApi.cs
|
||||
// File: SickrageApi.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
|
@ -24,28 +23,28 @@
|
|||
// 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.Linq;
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.SickRage;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Helpers.Exceptions;
|
||||
|
||||
using RestSharp;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
public class SickrageApi : ISickRageApi
|
||||
{
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public SickrageApi()
|
||||
{
|
||||
|
@ -54,22 +53,38 @@ namespace PlexRequests.Api
|
|||
|
||||
private ApiRequest Api { get; }
|
||||
|
||||
|
||||
public async Task<SickRageTvAdd> AddSeries(int tvdbId, int seasonCount, int[] seasons, string quality, string apiKey,
|
||||
Uri baseUrl)
|
||||
public SickRageSeasonList VerifyShowHasLoaded(int tvdbId, string apiKey, Uri baseUrl)
|
||||
{
|
||||
Log.Trace("Entered `VerifyShowHasLoaded({0} <- id)`", tvdbId);
|
||||
var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=show.seasonlist", Method = Method.GET };
|
||||
request.AddUrlSegment("apiKey", apiKey);
|
||||
request.AddQueryParameter("tvdbid", tvdbId.ToString());
|
||||
|
||||
try
|
||||
{
|
||||
var policy = RetryHandler.RetryAndWaitPolicy(
|
||||
null,
|
||||
(exception, timespan) => Log.Error(exception, "Exception when calling VerifyShowHasLoaded for SR, Retrying {0}", timespan));
|
||||
|
||||
var obj = policy.Execute(() => Api.ExecuteJson<SickRageSeasonList>(request, baseUrl));
|
||||
return obj;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return new SickRageSeasonList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SickRageTvAdd> AddSeries(int tvdbId, int seasonCount, int[] seasons, string quality, string apiKey, Uri baseUrl)
|
||||
{
|
||||
var futureStatus = seasons.Length > 0 && !seasons.Any(x => x == seasonCount) ? SickRageStatus.Skipped : SickRageStatus.Wanted;
|
||||
var status = seasons.Length > 0 ? SickRageStatus.Skipped : SickRageStatus.Wanted;
|
||||
|
||||
Log.Trace("Future Status: {0}", futureStatus);
|
||||
Log.Trace("Current Status: {0}", status);
|
||||
|
||||
var request = new RestRequest
|
||||
{
|
||||
Resource = "/api/{apiKey}/?cmd=show.addnew",
|
||||
Method = Method.GET
|
||||
};
|
||||
var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=show.addnew", Method = Method.GET };
|
||||
request.AddUrlSegment("apiKey", apiKey);
|
||||
request.AddQueryParameter("tvdbid", tvdbId.ToString());
|
||||
request.AddQueryParameter("status", status);
|
||||
|
@ -80,9 +95,11 @@ namespace PlexRequests.Api
|
|||
request.AddQueryParameter("initial", quality);
|
||||
}
|
||||
|
||||
Log.Trace("Entering `Execute<SickRageTvAdd>`");
|
||||
var obj = Api.Execute<SickRageTvAdd>(request, baseUrl);
|
||||
Log.Trace("Exiting `Execute<SickRageTvAdd>`");
|
||||
var policy = RetryHandler.RetryAndWaitPolicy(
|
||||
null,
|
||||
(exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for SR, Retrying {0}", timespan));
|
||||
|
||||
var obj = policy.Execute(() => Api.Execute<SickRageTvAdd>(request, baseUrl));
|
||||
Log.Trace("obj Result:");
|
||||
Log.Trace(obj.DumpJson());
|
||||
|
||||
|
@ -93,7 +110,6 @@ namespace PlexRequests.Api
|
|||
|
||||
var seasonIncrement = 0;
|
||||
var seasonList = new SickRageSeasonList();
|
||||
Log.Trace("while (seasonIncrement < seasonCount) where seasonCount = {0}", seasonCount);
|
||||
try
|
||||
{
|
||||
while (seasonIncrement < seasonCount)
|
||||
|
@ -130,6 +146,7 @@ namespace PlexRequests.Api
|
|||
foreach (var s in seasons)
|
||||
{
|
||||
Log.Trace("Adding season {0}", s);
|
||||
|
||||
var result = await AddSeason(tvdbId, s, apiKey, baseUrl);
|
||||
Log.Trace("SickRage adding season results: ");
|
||||
Log.Trace(result.DumpJson());
|
||||
|
@ -149,76 +166,62 @@ namespace PlexRequests.Api
|
|||
|
||||
public SickRagePing Ping(string apiKey, Uri baseUrl)
|
||||
{
|
||||
var request = new RestRequest
|
||||
{
|
||||
Resource = "/api/{apiKey}/?cmd=sb.ping",
|
||||
Method = Method.GET
|
||||
};
|
||||
var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=sb.ping", Method = Method.GET };
|
||||
|
||||
var policy = RetryHandler.RetryAndWaitPolicy(
|
||||
null,
|
||||
(exception, timespan) => Log.Error(exception, "Exception when calling Ping for SR, Retrying {0}", timespan));
|
||||
|
||||
request.AddUrlSegment("apiKey", apiKey);
|
||||
var obj = Api.ExecuteJson<SickRagePing>(request, baseUrl);
|
||||
var obj = policy.Execute(() => Api.ExecuteJson<SickRagePing>(request, baseUrl));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public SickRageSeasonList VerifyShowHasLoaded(int tvdbId, string apiKey, Uri baseUrl)
|
||||
{
|
||||
Log.Trace("Entered `VerifyShowHasLoaded({0} <- id)`", tvdbId);
|
||||
var request = new RestRequest
|
||||
{
|
||||
Resource = "/api/{apiKey}/?cmd=show.seasonlist",
|
||||
Method = Method.GET
|
||||
};
|
||||
request.AddUrlSegment("apiKey", apiKey);
|
||||
request.AddQueryParameter("tvdbid", tvdbId.ToString());
|
||||
|
||||
try
|
||||
{
|
||||
Log.Trace("Entering `ExecuteJson<SickRageSeasonList>`");
|
||||
var obj = Api.ExecuteJson<SickRageSeasonList>(request, baseUrl);
|
||||
Log.Trace("Exited `ExecuteJson<SickRageSeasonList>`");
|
||||
return obj;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return new SickRageSeasonList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SickRageTvAdd> AddSeason(int tvdbId, int season, string apiKey, Uri baseUrl)
|
||||
{
|
||||
var request = new RestRequest
|
||||
{
|
||||
Resource = "/api/{apiKey}/?cmd=episode.setstatus",
|
||||
Method = Method.GET
|
||||
};
|
||||
var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=episode.setstatus", Method = Method.GET };
|
||||
request.AddUrlSegment("apiKey", apiKey);
|
||||
request.AddQueryParameter("tvdbid", tvdbId.ToString());
|
||||
request.AddQueryParameter("season", season.ToString());
|
||||
request.AddQueryParameter("status", SickRageStatus.Wanted);
|
||||
|
||||
await Task.Run(() => Thread.Sleep(2000));
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
Log.Trace("Entering `Execute<SickRageTvAdd>` in a new `Task<T>`");
|
||||
var result = Api.Execute<SickRageTvAdd>(request, baseUrl);
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var policy = RetryHandler.RetryAndWaitPolicy(
|
||||
null,
|
||||
(exception, timespan) => Log.Error(exception, "Exception when calling AddSeason for SR, Retrying {0}", timespan));
|
||||
|
||||
Log.Trace("Exiting `Execute<SickRageTvAdd>` and yeilding `Task<T>` result");
|
||||
return result;
|
||||
}).ConfigureAwait(false);
|
||||
var result = policy.Execute(() => Api.Execute<SickRageTvAdd>(request, baseUrl));
|
||||
|
||||
return result;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<SickrageShows> GetShows(string apiKey, Uri baseUrl)
|
||||
public async Task<SickrageShows> GetShows(string apiKey, Uri baseUrl)
|
||||
{
|
||||
var request = new RestRequest
|
||||
{
|
||||
Resource = "/api/{apiKey}/?cmd=shows",
|
||||
Method = Method.GET
|
||||
};
|
||||
var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=shows", Method = Method.GET };
|
||||
request.AddUrlSegment("apiKey", apiKey);
|
||||
|
||||
return await Task.Run(() => Api.Execute<SickrageShows>(request, baseUrl)).ConfigureAwait(false);
|
||||
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var policy = RetryHandler.RetryAndWaitPolicy(
|
||||
new[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) },
|
||||
(exception, timespan) => Log.Error(exception, "Exception when calling GetShows for SR, Retrying {0}", timespan));
|
||||
|
||||
return policy.Execute(() => Api.Execute<SickrageShows>(request, baseUrl));
|
||||
}
|
||||
catch (ApiRequestException)
|
||||
{
|
||||
Log.Error("There has been a API exception when Getting the Sickrage shows");
|
||||
return null;
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
62
PlexRequests.Api/SlackApi.cs
Normal file
62
PlexRequests.Api/SlackApi.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexApi.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.Threading.Tasks;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Notifications;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
public class SlackApi : ISlackApi
|
||||
{
|
||||
public async Task<string> PushAsync(string team, string token, string service, SlackNotificationBody message)
|
||||
{
|
||||
var request = new RestRequest
|
||||
{
|
||||
Method = Method.POST,
|
||||
Resource = "/services/{team}/{service}/{token}"
|
||||
};
|
||||
|
||||
request.AddUrlSegment("team", team);
|
||||
request.AddUrlSegment("service", service);
|
||||
request.AddUrlSegment("token", token);
|
||||
request.AddJsonBody(message);
|
||||
|
||||
var api = new ApiRequest();
|
||||
return await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var result = api.Execute(request, new Uri("https://hooks.slack.com/"));
|
||||
return result.Content;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@ using PlexRequests.Helpers;
|
|||
using RestSharp;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PlexRequests.Helpers.Exceptions;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
public class SonarrApi : ISonarrApi
|
||||
|
@ -54,8 +56,13 @@ namespace PlexRequests.Api
|
|||
var request = new RestRequest { Resource = "/api/profile", Method = Method.GET };
|
||||
|
||||
request.AddHeader("X-Api-Key", apiKey);
|
||||
var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10)
|
||||
}, (exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for Sonarr, Retrying {0}", timespan));
|
||||
|
||||
var obj = Api.ExecuteJson<List<SonarrProfile>>(request, baseUrl);
|
||||
var obj = policy.Execute(() => Api.ExecuteJson<List<SonarrProfile>>(request, baseUrl));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -100,7 +107,13 @@ namespace PlexRequests.Api
|
|||
SonarrAddSeries result;
|
||||
try
|
||||
{
|
||||
result = Api.ExecuteJson<SonarrAddSeries>(request, baseUrl);
|
||||
var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10)
|
||||
}, (exception, timespan) => Log.Error (exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan));
|
||||
|
||||
result = policy.Execute(() => Api.ExecuteJson<SonarrAddSeries>(request, baseUrl));
|
||||
}
|
||||
catch (JsonSerializationException jse)
|
||||
{
|
||||
|
@ -119,7 +132,13 @@ namespace PlexRequests.Api
|
|||
var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET };
|
||||
request.AddHeader("X-Api-Key", apiKey);
|
||||
|
||||
var obj = Api.ExecuteJson<SystemStatus>(request, baseUrl);
|
||||
var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10)
|
||||
}, (exception, timespan) => Log.Error (exception, "Exception when calling SystemStatus for Sonarr, Retrying {0}", timespan));
|
||||
|
||||
var obj = policy.Execute(() => Api.ExecuteJson<SystemStatus>(request, baseUrl));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -128,8 +147,21 @@ namespace PlexRequests.Api
|
|||
{
|
||||
var request = new RestRequest { Resource = "/api/series", Method = Method.GET };
|
||||
request.AddHeader("X-Api-Key", apiKey);
|
||||
try
|
||||
{
|
||||
var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] {
|
||||
TimeSpan.FromSeconds (5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
}, (exception, timespan) => Log.Error (exception, "Exception when calling GetSeries for Sonarr, Retrying {0}", timespan));
|
||||
|
||||
return Api.Execute<List<Series>>(request, baseUrl);
|
||||
return policy.Execute(() => Api.ExecuteJson<List<Series>>(request, baseUrl));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "There has been an API exception when getting the Sonarr Series");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ namespace PlexRequests.Api
|
|||
return results.Results;
|
||||
}
|
||||
|
||||
[Obsolete("Should use TheTvDbApi for TV")]
|
||||
[Obsolete("Should use TvMaze for TV")]
|
||||
public async Task<List<SearchTv>> SearchTv(string searchTerm)
|
||||
{
|
||||
var results = await Client.SearchTvShow(searchTerm);
|
||||
|
@ -74,7 +74,7 @@ namespace PlexRequests.Api
|
|||
return movies;
|
||||
}
|
||||
|
||||
[Obsolete("Should use TheTvDbApi for TV")]
|
||||
[Obsolete("Should use TvMaze for TV")]
|
||||
public async Task<TvShow> GetTvShowInformation(int tmdbId)
|
||||
{
|
||||
var show = await Client.GetTvShow(tmdbId);
|
||||
|
|
|
@ -24,12 +24,15 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
using System;
|
||||
|
||||
using PlexRequests.Api.Models.Tv;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
namespace PlexRequests.Api
|
||||
{
|
||||
[Obsolete("Use TVMazeAPP")]
|
||||
public class TheTvDbApi : TvBase
|
||||
{
|
||||
public TheTvDbApi()
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace PlexRequests.Api
|
|||
return obj;
|
||||
}
|
||||
|
||||
public int GetSeasonCount(int id)
|
||||
public List<TvMazeSeasons> GetSeasons(int id)
|
||||
{
|
||||
var request = new RestRequest
|
||||
{
|
||||
|
@ -95,7 +95,11 @@ namespace PlexRequests.Api
|
|||
request.AddUrlSegment("id", id.ToString());
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
var obj = Api.Execute<List<TvMazeSeasons>>(request, new Uri(Uri));
|
||||
return Api.Execute<List<TvMazeSeasons>>(request, new Uri(Uri));
|
||||
}
|
||||
public int GetSeasonCount(int id)
|
||||
{
|
||||
var obj = GetSeasons(id);
|
||||
var seasons = obj.Select(x => x.number > 0);
|
||||
return seasons.Count();
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?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>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Dapper" version="1.42" targetFramework="net452" />
|
||||
<package id="Nancy" version="1.4.3" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net452" />
|
||||
<package id="RestSharp" version="105.2.3" targetFramework="net452" />
|
||||
<package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net452" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Dapper" version="1.42" targetFramework="net45" />
|
||||
<package id="Nancy" version="1.4.3" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net45" />
|
||||
<package id="Polly-Signed" version="4.2.0" targetFramework="net45" />
|
||||
<package id="RestSharp" version="105.2.3" targetFramework="net45" />
|
||||
<package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" />
|
||||
</packages>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?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>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
|
||||
|
|
|
@ -28,6 +28,11 @@ namespace PlexRequests.Core
|
|||
{
|
||||
public class CacheKeys
|
||||
{
|
||||
public struct TimeFrameMinutes
|
||||
{
|
||||
public const int SchedulerCaching = 60;
|
||||
}
|
||||
|
||||
public const string PlexLibaries = "PlexLibaries";
|
||||
|
||||
public const string TvDbToken = "TheTvDbApiToken";
|
||||
|
@ -41,6 +46,8 @@ namespace PlexRequests.Core
|
|||
public const string CouchPotatoQualityProfiles = "CouchPotatoQualityProfiles";
|
||||
public const string CouchPotatoQueued = "CouchPotatoQueued";
|
||||
|
||||
public const string GetBaseUrl = "GetBaseUrl";
|
||||
public const string GetPlexRequestSettings = "GetPlexRequestSettings";
|
||||
|
||||
public const string LastestProductVersion = "LatestProductVersion";
|
||||
}
|
||||
}
|
|
@ -49,10 +49,9 @@ namespace PlexRequests.Core
|
|||
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId };
|
||||
var id = Repo.Insert(entity);
|
||||
|
||||
// TODO Keep an eye on this, since we are now doing 2 DB update for 1 single request, inserting and then updating
|
||||
model.Id = (int)id;
|
||||
|
||||
entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id, MusicId = model.MusicBrainzId};
|
||||
entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = (int)id, MusicId = model.MusicBrainzId };
|
||||
var result = Repo.Update(entity);
|
||||
|
||||
return result ? id : -1;
|
||||
|
|
|
@ -24,21 +24,11 @@
|
|||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PlexRequests.Core.Models
|
||||
{
|
||||
public class StatusModel
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public int DBVersion {
|
||||
get
|
||||
{
|
||||
string trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(Version, string.Empty).PadRight(4, '0');
|
||||
return int.Parse(trimStatus);
|
||||
}
|
||||
}
|
||||
public bool UpdateAvailable { get; set; }
|
||||
public string UpdateUri { get; set; }
|
||||
public string DownloadUri { get; set; }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Core</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Core</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -34,30 +34,6 @@
|
|||
<Reference Include="Mono.Data.Sqlite">
|
||||
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Nancy.Authentication.Forms, Version=1.4.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Octokit, Version=0.19.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Octokit.0.19.0\lib\net45\Octokit.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Omu.ValueInjecter, Version=3.1.1.0, Culture=neutral, PublicKeyToken=c7694541b0ac80e4, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\valueinjecter.3.1.1.2\lib\net40\Omu.ValueInjecter.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
|
@ -69,6 +45,24 @@
|
|||
<Reference Include="TMDbLib">
|
||||
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy.Authentication.Forms, Version=1.4.1.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.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>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit, Version=0.19.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\packages\Octokit.0.19.0\lib\net45\Octokit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Omu.ValueInjecter, Version=3.1.1.0, Culture=neutral, PublicKeyToken=c7694541b0ac80e4">
|
||||
<HintPath>..\packages\valueinjecter.3.1.1.2\lib\net40\Omu.ValueInjecter.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CacheKeys.cs" />
|
||||
|
@ -79,10 +73,12 @@
|
|||
<Compile Include="Models\UserProperties.cs" />
|
||||
<Compile Include="SettingModels\AuthenticationSettings.cs" />
|
||||
<Compile Include="SettingModels\HeadphonesSettings.cs" />
|
||||
<Compile Include="SettingModels\SlackNotificationSettings.cs" />
|
||||
<Compile Include="SettingModels\PushoverNotificationSettings.cs" />
|
||||
<Compile Include="SettingModels\PushBulletNotificationSettings.cs" />
|
||||
<Compile Include="SettingModels\EmailNotificationSettings.cs" />
|
||||
<Compile Include="SettingModels\PlexSettings.cs" />
|
||||
<Compile Include="SettingModels\LogSettings.cs" />
|
||||
<Compile Include="SettingModels\SonarrSettings.cs" />
|
||||
<Compile Include="SettingModels\SickRageSettings.cs" />
|
||||
<Compile Include="SettingModels\CouchPotatoSettings.cs" />
|
||||
|
@ -101,7 +97,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
|
||||
<Project>{95834072-a675-415d-aa8f-877c91623810}</Project>
|
||||
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
|
||||
<Name>PlexRequests.Api.Interfaces</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
|
||||
|
|
|
@ -1,14 +1,40 @@
|
|||
namespace PlexRequests.Core.SettingModels
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: EmailNotificationSettings.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
|
||||
namespace PlexRequests.Core.SettingModels
|
||||
{
|
||||
public class EmailNotificationSettings : Settings
|
||||
{
|
||||
public string EmailHost { get; set; }
|
||||
public string EmailPassword { get; set; }
|
||||
public int EmailPort { get; set; }
|
||||
public bool Ssl { get; set; }
|
||||
public string RecipientEmail { get; set; }
|
||||
public string EmailSender { get; set; }
|
||||
public string EmailUsername { get; set; }
|
||||
public string EmailPassword { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool EnableUserEmailNotifications { get; set; }
|
||||
public string RecipientEmail { get; set; }
|
||||
}
|
||||
}
|
36
PlexRequests.Core/SettingModels/LogSettings.cs
Normal file
36
PlexRequests.Core/SettingModels/LogSettings.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: SickRageSettings.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 NLog;
|
||||
|
||||
namespace PlexRequests.Core.SettingModels
|
||||
{
|
||||
public class LogSettings : Settings
|
||||
{
|
||||
public int Level { get; set; }
|
||||
}
|
||||
}
|
|
@ -44,6 +44,13 @@ namespace PlexRequests.Core.SettingModels
|
|||
public int WeeklyRequestLimit { get; set; }
|
||||
public string NoApprovalUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The CSS name of the theme we want
|
||||
/// </summary>
|
||||
public string ThemeName { get; set; }
|
||||
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> ApprovalWhiteList
|
||||
{
|
||||
|
|
37
PlexRequests.Core/SettingModels/SlackNotificationSettings.cs
Normal file
37
PlexRequests.Core/SettingModels/SlackNotificationSettings.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PlexRequests.Core.SettingModels
|
||||
{
|
||||
public class SlackNotificationSettings : Settings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string WebhookUrl { get; set; }
|
||||
public string Channel { get; set; }
|
||||
public string Username { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Team => SplitWebUrl(3);
|
||||
|
||||
[JsonIgnore]
|
||||
public string Service => SplitWebUrl(4);
|
||||
|
||||
[JsonIgnore]
|
||||
public string Token => SplitWebUrl(5);
|
||||
|
||||
private string SplitWebUrl(int index)
|
||||
{
|
||||
if (!WebhookUrl.StartsWith("http", StringComparison.InvariantCulture))
|
||||
{
|
||||
WebhookUrl = "https://" + WebhookUrl;
|
||||
}
|
||||
var split = WebhookUrl.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return split.Length < index
|
||||
? string.Empty
|
||||
: split[index];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Mono.Data.Sqlite;
|
||||
using NLog;
|
||||
|
@ -58,10 +59,9 @@ namespace PlexRequests.Core
|
|||
var version = CheckSchema();
|
||||
if (version > 0)
|
||||
{
|
||||
if (version > 1300 && version <= 1699)
|
||||
if (version > 1700 && version <= 1799)
|
||||
{
|
||||
MigrateDbFrom1300();
|
||||
UpdateRequestBlobsTable();
|
||||
MigrateToVersion1700();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,18 +73,19 @@ namespace PlexRequests.Core
|
|||
|
||||
private int CheckSchema()
|
||||
{
|
||||
var checker = new StatusChecker();
|
||||
var status = checker.GetStatus();
|
||||
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 connection = Db.DbConnection();
|
||||
var schema = connection.GetSchemaVersion();
|
||||
if (schema == null)
|
||||
{
|
||||
connection.CreateSchema(status.DBVersion); // Set the default.
|
||||
connection.CreateSchema(version); // Set the default.
|
||||
schema = connection.GetSchemaVersion();
|
||||
}
|
||||
|
||||
var version = schema.SchemaVersion;
|
||||
version = schema.SchemaVersion;
|
||||
|
||||
return version;
|
||||
}
|
||||
|
@ -139,7 +140,7 @@ namespace PlexRequests.Core
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Failed to cache Sonarr quality profiles!", ex);
|
||||
Log.Error(ex, "Failed to cache Sonarr quality profiles!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,97 +162,14 @@ namespace PlexRequests.Core
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Failed to cache CouchPotato quality profiles!", ex);
|
||||
Log.Error(ex, "Failed to cache CouchPotato quality profiles!");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRequestBlobsTable() // TODO: Remove in v1.7
|
||||
public void MigrateToVersion1700()
|
||||
{
|
||||
try
|
||||
{
|
||||
TableCreation.AlterTable(Db.DbConnection(), "RequestBlobs", "ADD COLUMN", "MusicId", false, "TEXT");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Tried updating the schema to alter the request blobs table");
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
private void MigrateDbFrom1300() // TODO: Remove in v1.7
|
||||
{
|
||||
|
||||
var result = new List<long>();
|
||||
RequestedModel[] requestedModels;
|
||||
var repo = new GenericRepository<RequestedModel>(Db, new MemoryCacheProvider());
|
||||
try
|
||||
{
|
||||
var records = repo.GetAll();
|
||||
requestedModels = records as RequestedModel[] ?? records.ToArray();
|
||||
}
|
||||
catch (SqliteException)
|
||||
{
|
||||
// There is no requested table so they do not have an old version of the DB
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requestedModels.Any())
|
||||
{ return; }
|
||||
|
||||
var jsonRepo = new JsonRequestService(new RequestJsonRepository(Db, new MemoryCacheProvider()));
|
||||
|
||||
var api = new TvMazeApi();
|
||||
|
||||
foreach (var r in requestedModels.Where(x => x.Type == RequestType.TvShow))
|
||||
{
|
||||
var show = api.ShowLookupByTheTvDbId(r.ProviderId);
|
||||
|
||||
var model = new RequestedModel
|
||||
{
|
||||
Title = show.name,
|
||||
PosterPath = show.image?.medium,
|
||||
Type = RequestType.TvShow,
|
||||
ProviderId = show.externals.thetvdb ?? 0,
|
||||
ReleaseDate = r.ReleaseDate,
|
||||
AdminNote = r.AdminNote,
|
||||
Approved = r.Approved,
|
||||
Available = r.Available,
|
||||
ImdbId = show.externals.imdb,
|
||||
Issues = r.Issues,
|
||||
OtherMessage = r.OtherMessage,
|
||||
Overview = show.summary.RemoveHtml(),
|
||||
RequestedUsers = r.AllUsers, // should pull in the RequestedBy property and merge with RequestedUsers
|
||||
RequestedDate = r.ReleaseDate,
|
||||
Status = show.status
|
||||
};
|
||||
var id = jsonRepo.AddRequest(model);
|
||||
result.Add(id);
|
||||
}
|
||||
|
||||
foreach (var source in requestedModels.Where(x => x.Type == RequestType.Movie))
|
||||
{
|
||||
var id = jsonRepo.AddRequest(source);
|
||||
result.Add(id);
|
||||
}
|
||||
|
||||
|
||||
if (result.Any(x => x == -1))
|
||||
{
|
||||
throw new SqliteException("Could not migrate the DB!");
|
||||
}
|
||||
|
||||
|
||||
if (result.Count != requestedModels.Length)
|
||||
{
|
||||
throw new SqliteException("Could not migrate the DB! count is different");
|
||||
}
|
||||
|
||||
|
||||
// Now delete the old requests
|
||||
foreach (var oldRequest in requestedModels)
|
||||
{
|
||||
repo.Delete(oldRequest);
|
||||
}
|
||||
|
||||
// Drop old tables
|
||||
TableCreation.DropTable(Db.DbConnection(), "User");
|
||||
TableCreation.DropTable(Db.DbConnection(), "Log");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,21 +36,20 @@ using Nancy.Security;
|
|||
using PlexRequests.Core.Models;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Repository;
|
||||
|
||||
namespace PlexRequests.Core
|
||||
{
|
||||
public class UserMapper : IUserMapper
|
||||
public class UserMapper : IUserMapper, ICustomUserMapper
|
||||
{
|
||||
public UserMapper(ISqliteConfiguration db)
|
||||
public UserMapper(IRepository<UsersModel> repo)
|
||||
{
|
||||
Db = db;
|
||||
Repo = repo;
|
||||
}
|
||||
private static ISqliteConfiguration Db { get; set; }
|
||||
private static IRepository<UsersModel> Repo { get; set; }
|
||||
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
|
||||
{
|
||||
var repo = new UserRepository<UsersModel>(Db);
|
||||
|
||||
var user = repo.Get(identifier.ToString());
|
||||
var user = Repo.Get(identifier.ToString());
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
|
@ -64,10 +63,9 @@ namespace PlexRequests.Core
|
|||
};
|
||||
}
|
||||
|
||||
public static Guid? ValidateUser(string username, string password)
|
||||
public Guid? ValidateUser(string username, string password)
|
||||
{
|
||||
var repo = new UserRepository<UsersModel>(Db);
|
||||
var users = repo.GetAll();
|
||||
var users = Repo.GetAll();
|
||||
|
||||
foreach (var u in users)
|
||||
{
|
||||
|
@ -83,17 +81,15 @@ namespace PlexRequests.Core
|
|||
return null;
|
||||
}
|
||||
|
||||
public static bool DoUsersExist()
|
||||
public bool DoUsersExist()
|
||||
{
|
||||
var repo = new UserRepository<UsersModel>(Db);
|
||||
var users = repo.GetAll();
|
||||
var users = Repo.GetAll();
|
||||
|
||||
return users.Any();
|
||||
}
|
||||
|
||||
public static Guid? CreateUser(string username, string password, string[] claims = default(string[]))
|
||||
public Guid? CreateUser(string username, string password, string[] claims = default(string[]))
|
||||
{
|
||||
var repo = new UserRepository<UsersModel>(Db);
|
||||
var salt = PasswordHasher.GenerateSalt();
|
||||
|
||||
var userModel = new UsersModel
|
||||
|
@ -105,17 +101,16 @@ namespace PlexRequests.Core
|
|||
Claims = ByteConverterHelper.ReturnBytes(claims),
|
||||
UserProperties = ByteConverterHelper.ReturnBytes(new UserProperties())
|
||||
};
|
||||
repo.Insert(userModel);
|
||||
Repo.Insert(userModel);
|
||||
|
||||
var userRecord = repo.Get(userModel.UserGuid);
|
||||
var userRecord = Repo.Get(userModel.UserGuid);
|
||||
|
||||
return new Guid(userRecord.UserGuid);
|
||||
}
|
||||
|
||||
public static bool UpdatePassword(string username, string oldPassword, string newPassword)
|
||||
public bool UpdatePassword(string username, string oldPassword, string newPassword)
|
||||
{
|
||||
var repo = new UserRepository<UsersModel>(Db);
|
||||
var users = repo.GetAll();
|
||||
var users = Repo.GetAll();
|
||||
var userToChange = users.FirstOrDefault(x => x.UserName == username);
|
||||
if (userToChange == null)
|
||||
return false;
|
||||
|
@ -132,13 +127,22 @@ namespace PlexRequests.Core
|
|||
userToChange.Hash = newHash;
|
||||
userToChange.Salt = newSalt;
|
||||
|
||||
return repo.Update(userToChange);
|
||||
return Repo.Update(userToChange);
|
||||
}
|
||||
|
||||
public static IEnumerable<UsersModel> GetUsers()
|
||||
public IEnumerable<UsersModel> GetUsers()
|
||||
{
|
||||
var repo = new UserRepository<UsersModel>(Db);
|
||||
return repo.GetAll();
|
||||
return Repo.GetAll();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICustomUserMapper
|
||||
{
|
||||
IEnumerable<UsersModel> GetUsers();
|
||||
Guid? CreateUser(string username, string password, string[] claims = default(string[]));
|
||||
bool DoUsersExist();
|
||||
Guid? ValidateUser(string username, string password);
|
||||
bool UpdatePassword(string username, string oldPassword, string newPassword);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?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>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Nancy" version="1.4.3" targetFramework="net452" />
|
||||
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net46" />
|
||||
<package id="Octokit" version="0.19.0" targetFramework="net46" />
|
||||
<package id="valueinjecter" version="3.1.1.2" targetFramework="net452" />
|
||||
<package id="Nancy" version="1.4.3" targetFramework="net45" />
|
||||
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net45" />
|
||||
<package id="Octokit" version="0.19.0" targetFramework="net45" />
|
||||
<package id="valueinjecter" version="3.1.1.2" targetFramework="net45" />
|
||||
</packages>
|
|
@ -1,41 +1,42 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexRegistry.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 FluentScheduler;
|
||||
|
||||
using PlexRequests.Services;
|
||||
|
||||
namespace PlexRequests.UI.Jobs
|
||||
{
|
||||
public class MediaCacheRegistry : Registry
|
||||
{
|
||||
public MediaCacheRegistry()
|
||||
{
|
||||
Schedule<MediaCacheService>().ToRunNow();
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: ApplicationSettingsException.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;
|
||||
|
||||
namespace PlexRequests.Helpers.Exceptions
|
||||
{
|
||||
public class ApiRequestException : Exception
|
||||
{
|
||||
public ApiRequestException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
public ApiRequestException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ namespace PlexRequests.Helpers
|
|||
return dumpTarget.ToString();
|
||||
}
|
||||
|
||||
public static void ConfigureLogging(string connectionString)
|
||||
public static void ConfigureLogging(string connectionString)
|
||||
{
|
||||
LogManager.ThrowExceptions = true;
|
||||
// Step 1. Create configuration object
|
||||
|
|
|
@ -73,8 +73,8 @@ namespace PlexRequests.Helpers
|
|||
/// <returns></returns>
|
||||
public T Get<T>(string key) where T : class
|
||||
{
|
||||
var item = Cache.Get(key) as T;
|
||||
return item;
|
||||
lock (key)
|
||||
return Cache.Get(key) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -86,7 +86,11 @@ namespace PlexRequests.Helpers
|
|||
public void Set(string key, object data, int cacheTime = 20)
|
||||
{
|
||||
var policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime) };
|
||||
Cache.Add(new CacheItem(key, data), policy);
|
||||
lock (key)
|
||||
{
|
||||
Cache.Remove(key);
|
||||
Cache.Add(new CacheItem(key, data), policy);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -98,7 +102,10 @@ namespace PlexRequests.Helpers
|
|||
var keys = Cache.Where(x => x.Key.Contains(key));
|
||||
foreach (var k in keys)
|
||||
{
|
||||
Cache.Remove(k.Key);
|
||||
lock (key)
|
||||
{
|
||||
Cache.Remove(k.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Helpers</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Helpers</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -31,14 +31,6 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
|
@ -48,11 +40,18 @@
|
|||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<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>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AssemblyHelper.cs" />
|
||||
<Compile Include="ByteConverterHelper.cs" />
|
||||
<Compile Include="DateTimeHelper.cs" />
|
||||
<Compile Include="Exceptions\ApiRequestException.cs" />
|
||||
<Compile Include="Exceptions\ApplicationSettingsException.cs" />
|
||||
<Compile Include="HtmlRemover.cs" />
|
||||
<Compile Include="ICacheProvider.cs" />
|
||||
|
@ -65,6 +64,7 @@
|
|||
<Compile Include="SerializerSettings.cs" />
|
||||
<Compile Include="StringCipher.cs" />
|
||||
<Compile Include="UriHelper.cs" />
|
||||
<Compile Include="UserClaims.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
|
12
PlexRequests.Helpers/UserClaims.cs
Normal file
12
PlexRequests.Helpers/UserClaims.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace PlexRequests.Helpers
|
||||
{
|
||||
public class UserClaims
|
||||
{
|
||||
public const string Admin = "Admin"; // Can do everything including creating new users and editing settings
|
||||
public const string PowerUser = "PowerUser"; // Can only manage the requests, approve etc.
|
||||
public const string User = "User"; // Can only request
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net45" />
|
||||
</packages>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?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>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: AvailabilityUpdateService.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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Web.Hosting;
|
||||
|
||||
using FluentScheduler;
|
||||
|
||||
using Mono.Data.Sqlite;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Repository;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class AvailabilityUpdateService : ITask, IRegisteredObject, IAvailabilityUpdateService
|
||||
{
|
||||
|
||||
public AvailabilityUpdateService()
|
||||
{
|
||||
|
||||
var memCache = new MemoryCacheProvider();
|
||||
var dbConfig = new DbConfiguration(new SqliteFactory());
|
||||
var repo = new SettingsJsonRepository(dbConfig, memCache);
|
||||
|
||||
ConfigurationReader = new ConfigurationReader();
|
||||
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new JsonRequestService(new RequestJsonRepository(dbConfig, memCache)), new PlexApi(), memCache);
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
}
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private IConfigurationReader ConfigurationReader { get; }
|
||||
private IAvailabilityChecker Checker { get; }
|
||||
private IDisposable UpdateSubscription { get; set; }
|
||||
|
||||
public void Start(Configuration c)
|
||||
{
|
||||
UpdateSubscription?.Dispose();
|
||||
Task.Factory.StartNew(() => Checker.CheckAndUpdateAll(-1)); // cache the libraries and run the availability checks
|
||||
UpdateSubscription = Observable.Interval(c.Intervals.Notification).Subscribe(Checker.CheckAndUpdateAll);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Start(ConfigurationReader.Read());
|
||||
}
|
||||
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAvailabilityUpdateService
|
||||
{
|
||||
void Start(Configuration c);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ namespace PlexRequests.Services.Interfaces
|
|||
{
|
||||
public interface IAvailabilityChecker
|
||||
{
|
||||
void CheckAndUpdateAll(long check);
|
||||
void CheckAndUpdateAll();
|
||||
List<PlexMovie> GetPlexMovies();
|
||||
bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year);
|
||||
List<PlexTvShow> GetPlexTvShows();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public interface ICouchPotatoCacher
|
||||
{
|
||||
void Queued(long check);
|
||||
void Queued();
|
||||
int[] QueuedIds();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: IConfigurationReader.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
|
||||
namespace PlexRequests.Services.Interfaces
|
||||
{
|
||||
public interface IConfigurationReader
|
||||
{
|
||||
Configuration Read();
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: IJobRecord.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
|
||||
namespace PlexRequests.Services.Interfaces
|
||||
{
|
||||
public interface IJobRecord
|
||||
{
|
||||
void Record(string jobName);
|
||||
}
|
||||
}
|
|
@ -36,7 +36,12 @@ namespace PlexRequests.Services.Interfaces
|
|||
string NotificationName { get; }
|
||||
|
||||
Task NotifyAsync(NotificationModel model);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a notification to the user, this is usually for testing the settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
Task NotifyAsync(NotificationModel model, Settings settings);
|
||||
}
|
||||
}
|
|
@ -33,7 +33,18 @@ namespace PlexRequests.Services.Interfaces
|
|||
{
|
||||
public interface INotificationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a notification to the user. This one is used in normal notification scenarios
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
Task Publish(NotificationModel model);
|
||||
/// <summary>
|
||||
/// Sends a notification to the user, this is usually for testing the settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
Task Publish(NotificationModel model, Settings settings);
|
||||
void Subscribe(INotification notification);
|
||||
void UnSubscribe(INotification notification);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace PlexRequests.Services.Interfaces
|
||||
{
|
||||
public interface ISickRageCacher
|
||||
{
|
||||
void Queued(long check);
|
||||
int[] QueuedIds();
|
||||
}
|
||||
}
|
||||
namespace PlexRequests.Services.Interfaces
|
||||
{
|
||||
public interface ISickRageCacher
|
||||
{
|
||||
void Queued();
|
||||
int[] QueuedIds();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public interface ISonarrCacher
|
||||
{
|
||||
void Queued(long check);
|
||||
void Queued();
|
||||
int[] QueuedIds();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,77 +1,98 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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 NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Api.Models.Movie;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class CouchPotatoCacher : ICouchPotatoCacher
|
||||
{
|
||||
public CouchPotatoCacher(ISettingsService<CouchPotatoSettings> cpSettings, ICouchPotatoApi cpApi, ICacheProvider cache)
|
||||
{
|
||||
CpSettings = cpSettings;
|
||||
CpApi = cpApi;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private ICouchPotatoApi CpApi { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Queued(long check)
|
||||
{
|
||||
Log.Trace("This is check no. {0}", check);
|
||||
Log.Trace("Getting the settings");
|
||||
|
||||
var settings = CpSettings.GetSettings();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
Log.Trace("Getting all movies from CouchPotato");
|
||||
var movies = CpApi.GetMovies(settings.FullUri, settings.ApiKey, new[] { "active" });
|
||||
Cache.Set(CacheKeys.CouchPotatoQueued, movies, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to set here...
|
||||
public int[] QueuedIds()
|
||||
{
|
||||
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
|
||||
return movies != null ? movies.movies.Select(x => x.info.tmdb_id).ToArray() : new int[] { };
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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.Linq;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Movie;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace PlexRequests.Services.Jobs
|
||||
{
|
||||
public class CouchPotatoCacher : IJob, ICouchPotatoCacher
|
||||
{
|
||||
public CouchPotatoCacher(ISettingsService<CouchPotatoSettings> cpSettings, ICouchPotatoApi cpApi, ICacheProvider cache, IJobRecord rec)
|
||||
{
|
||||
CpSettings = cpSettings;
|
||||
CpApi = cpApi;
|
||||
Cache = cache;
|
||||
Job = rec;
|
||||
}
|
||||
|
||||
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private ICouchPotatoApi CpApi { get; }
|
||||
private IJobRecord Job { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Queued()
|
||||
{
|
||||
Log.Trace("Getting the settings");
|
||||
|
||||
var settings = CpSettings.GetSettings();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
Log.Trace("Getting all movies from CouchPotato");
|
||||
try
|
||||
{
|
||||
var movies = CpApi.GetMovies(settings.FullUri, settings.ApiKey, new[] { "active" });
|
||||
if (movies != null)
|
||||
{
|
||||
Cache.Set(CacheKeys.CouchPotatoQueued, movies, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed caching queued items from CouchPotato");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Job.Record(JobNames.CpCacher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to set here...
|
||||
public int[] QueuedIds()
|
||||
{
|
||||
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
|
||||
return movies?.movies.Select(x => x.info.tmdb_id).ToArray() ?? new int[] { };
|
||||
}
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
Queued();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +1,37 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: Congifuration.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 PlexRequests.Services.Interfaces;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
public Configuration(IIntervals intervals)
|
||||
{
|
||||
Intervals = intervals;
|
||||
}
|
||||
|
||||
public IIntervals Intervals { get; set; }
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: JobNames.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
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public static class JobNames
|
||||
{
|
||||
public const string StoreBackup = "Database Backup";
|
||||
public const string CpCacher = "CouchPotato Cacher";
|
||||
public const string SonarrCacher = "Sonarr Cacher";
|
||||
public const string SrCacher = "SickRage Cacher";
|
||||
public const string PlexChecker = "Plex Availability Cacher";
|
||||
}
|
||||
}
|
59
PlexRequests.Services/Jobs/JobRecord.cs
Normal file
59
PlexRequests.Services/Jobs/JobRecord.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: JobRecord.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 PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class JobRecord : IJobRecord
|
||||
{
|
||||
public JobRecord(IRepository<ScheduledJobs> repo)
|
||||
{
|
||||
Repo = repo;
|
||||
}
|
||||
private IRepository<ScheduledJobs> Repo { get; }
|
||||
public void Record(string jobName)
|
||||
{
|
||||
var allJobs = Repo.GetAll();
|
||||
var storeJob = allJobs.FirstOrDefault(x => x.Name == jobName);
|
||||
if (storeJob != null)
|
||||
{
|
||||
storeJob.LastRun = DateTime.UtcNow;
|
||||
Repo.Update(storeJob);
|
||||
}
|
||||
else
|
||||
{
|
||||
var job = new ScheduledJobs { LastRun = DateTime.UtcNow, Name = jobName };
|
||||
Repo.Insert(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,297 +1,370 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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 NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Plex;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Services.Models;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class PlexAvailabilityChecker : IAvailabilityChecker
|
||||
{
|
||||
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, ISettingsService<AuthenticationSettings> auth, IRequestService request, IPlexApi plex, ICacheProvider cache)
|
||||
{
|
||||
Plex = plexSettings;
|
||||
Auth = auth;
|
||||
RequestService = request;
|
||||
PlexApi = plex;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private ISettingsService<PlexSettings> Plex { get; }
|
||||
private ISettingsService<AuthenticationSettings> Auth { get; }
|
||||
private IRequestService RequestService { get; }
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private IPlexApi PlexApi { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
|
||||
public void CheckAndUpdateAll(long check)
|
||||
{
|
||||
Log.Trace("This is check no. {0}", check);
|
||||
Log.Trace("Getting the settings");
|
||||
var plexSettings = Plex.GetSettings();
|
||||
var authSettings = Auth.GetSettings();
|
||||
Log.Trace("Getting all the requests");
|
||||
|
||||
if (!ValidateSettings(plexSettings, authSettings))
|
||||
{
|
||||
Log.Info("Validation of the plex settings failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
var libraries = CachedLibraries(authSettings, plexSettings, true); //force setting the cache (10 min intervals via scheduler)
|
||||
var movies = GetPlexMovies().ToArray();
|
||||
var shows = GetPlexTvShows().ToArray();
|
||||
var albums = GetPlexAlbums().ToArray();
|
||||
|
||||
var requests = RequestService.GetAll();
|
||||
var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray();
|
||||
Log.Trace("Requests Count {0}", requestedModels.Length);
|
||||
|
||||
if (!requestedModels.Any())
|
||||
{
|
||||
Log.Info("There are no requests to check.");
|
||||
return;
|
||||
}
|
||||
|
||||
var modifiedModel = new List<RequestedModel>();
|
||||
foreach (var r in requestedModels)
|
||||
{
|
||||
Log.Trace("We are going to see if Plex has the following title: {0}", r.Title);
|
||||
|
||||
if (libraries == null)
|
||||
{
|
||||
libraries = new List<PlexSearch>() { PlexApi.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri) };
|
||||
if (libraries == null)
|
||||
{
|
||||
Log.Trace("Could not find any matching result for this title.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Trace("Search results from Plex for the following request: {0}", r.Title);
|
||||
//Log.Trace(results.DumpJson());
|
||||
|
||||
var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy");
|
||||
|
||||
bool matchResult;
|
||||
switch (r.Type)
|
||||
{
|
||||
case RequestType.Movie:
|
||||
matchResult = IsMovieAvailable(movies, r.Title, releaseDate);
|
||||
break;
|
||||
case RequestType.TvShow:
|
||||
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate);
|
||||
break;
|
||||
case RequestType.Album:
|
||||
matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (matchResult)
|
||||
{
|
||||
r.Available = true;
|
||||
modifiedModel.Add(r);
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.Trace("The result from Plex where the title's match was null, so that means the content is not yet in Plex.");
|
||||
}
|
||||
|
||||
Log.Trace("Updating the requests now");
|
||||
Log.Trace("Requests that will be updates:");
|
||||
Log.Trace(modifiedModel.SelectMany(x => x.Title).DumpJson());
|
||||
|
||||
if (modifiedModel.Any())
|
||||
{
|
||||
RequestService.BatchUpdate(modifiedModel);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PlexMovie> GetPlexMovies()
|
||||
{
|
||||
var movies = new List<PlexMovie>();
|
||||
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
|
||||
if (libs != null)
|
||||
{
|
||||
var movieLibs = libs.Where(x =>
|
||||
x.Video.Any(y =>
|
||||
y.Type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
).ToArray();
|
||||
|
||||
foreach (var lib in movieLibs)
|
||||
{
|
||||
movies.AddRange(lib.Video.Select(x => new PlexMovie() // movies are in the Video list
|
||||
{
|
||||
Title = x.Title,
|
||||
ReleaseYear = x.Year
|
||||
}));
|
||||
}
|
||||
}
|
||||
return movies;
|
||||
}
|
||||
|
||||
public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year)
|
||||
{
|
||||
return plexMovies.Any(x => x.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public List<PlexTvShow> GetPlexTvShows()
|
||||
{
|
||||
var shows = new List<PlexTvShow>();
|
||||
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
|
||||
if (libs != null)
|
||||
{
|
||||
var tvLibs = libs.Where(x =>
|
||||
x.Directory.Any(y =>
|
||||
y.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
).ToArray();
|
||||
|
||||
foreach (var lib in tvLibs)
|
||||
{
|
||||
shows.AddRange(lib.Directory.Select(x => new PlexTvShow() // shows are in the directory list
|
||||
{
|
||||
Title = x.Title,
|
||||
ReleaseYear = x.Year
|
||||
}));
|
||||
}
|
||||
}
|
||||
return shows;
|
||||
}
|
||||
|
||||
public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year)
|
||||
{
|
||||
return plexShows.Any(x =>
|
||||
(x.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) || x.Title.StartsWith(title, StringComparison.CurrentCultureIgnoreCase)) &&
|
||||
x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public List<PlexAlbum> GetPlexAlbums()
|
||||
{
|
||||
var albums = new List<PlexAlbum>();
|
||||
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
|
||||
if (libs != null)
|
||||
{
|
||||
var albumLibs = libs.Where(x =>
|
||||
x.Directory.Any(y =>
|
||||
y.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
).ToArray();
|
||||
|
||||
foreach (var lib in albumLibs)
|
||||
{
|
||||
albums.AddRange(lib.Directory.Select(x => new PlexAlbum()
|
||||
{
|
||||
Title = x.Title,
|
||||
ReleaseYear = x.Year,
|
||||
Artist = x.ParentTitle
|
||||
}));
|
||||
}
|
||||
}
|
||||
return albums;
|
||||
}
|
||||
|
||||
public bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist)
|
||||
{
|
||||
return plexAlbums.Any(x =>
|
||||
x.Title.Contains(title) &&
|
||||
//x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase) &&
|
||||
x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private List<PlexSearch> CachedLibraries(AuthenticationSettings authSettings, PlexSettings plexSettings, bool setCache)
|
||||
{
|
||||
Log.Trace("Obtaining library sections from Plex for the following request");
|
||||
|
||||
List<PlexSearch> results = new List<PlexSearch>();
|
||||
|
||||
if (!ValidateSettings(plexSettings, authSettings))
|
||||
{
|
||||
Log.Warn("The settings are not configured");
|
||||
return results; // don't error out here, just let it go!
|
||||
}
|
||||
|
||||
if (setCache)
|
||||
{
|
||||
results = GetLibraries(authSettings, plexSettings);
|
||||
Cache.Set(CacheKeys.PlexLibaries, results, 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
results = Cache.GetOrSet(CacheKeys.PlexLibaries, () => {
|
||||
return GetLibraries(authSettings, plexSettings);
|
||||
}, 10);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<PlexSearch> GetLibraries(AuthenticationSettings authSettings, PlexSettings plexSettings)
|
||||
{
|
||||
var sections = PlexApi.GetLibrarySections(authSettings.PlexAuthToken, plexSettings.FullUri);
|
||||
|
||||
List<PlexSearch> libs = new List<PlexSearch>();
|
||||
if (sections != null)
|
||||
{
|
||||
foreach (var dir in sections.Directories)
|
||||
{
|
||||
Log.Trace("Obtaining results from Plex for the following library section: {0}", dir.Title);
|
||||
var lib = PlexApi.GetLibrary(authSettings.PlexAuthToken, plexSettings.FullUri, dir.Key);
|
||||
if (lib != null)
|
||||
{
|
||||
libs.Add(lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return libs;
|
||||
}
|
||||
|
||||
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth)
|
||||
{
|
||||
if (plex?.Ip == null || auth?.PlexAuthToken == null)
|
||||
{
|
||||
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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 NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Plex;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Services.Models;
|
||||
using PlexRequests.Services.Notification;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace PlexRequests.Services.Jobs
|
||||
{
|
||||
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
|
||||
{
|
||||
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, ISettingsService<AuthenticationSettings> auth, IRequestService request, IPlexApi plex, ICacheProvider cache,
|
||||
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users)
|
||||
{
|
||||
Plex = plexSettings;
|
||||
Auth = auth;
|
||||
RequestService = request;
|
||||
PlexApi = plex;
|
||||
Cache = cache;
|
||||
Notification = notify;
|
||||
Job = rec;
|
||||
UserNotifyRepo = users;
|
||||
}
|
||||
|
||||
private ISettingsService<PlexSettings> Plex { get; }
|
||||
private ISettingsService<AuthenticationSettings> Auth { get; }
|
||||
private IRequestService RequestService { get; }
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private IPlexApi PlexApi { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private INotificationService Notification { get; }
|
||||
private IJobRecord Job { get; }
|
||||
private IRepository<UsersToNotify> UserNotifyRepo { get; }
|
||||
public void CheckAndUpdateAll()
|
||||
{
|
||||
Log.Trace("Getting the settings");
|
||||
var plexSettings = Plex.GetSettings();
|
||||
var authSettings = Auth.GetSettings();
|
||||
Log.Trace("Getting all the requests");
|
||||
|
||||
if (!ValidateSettings(plexSettings, authSettings))
|
||||
{
|
||||
Log.Info("Validation of the plex settings failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
var libraries = CachedLibraries(authSettings, plexSettings, true); //force setting the cache (10 min intervals via scheduler)
|
||||
|
||||
if (libraries == null || !libraries.Any())
|
||||
{
|
||||
Log.Info("Did not find any libraries in Plex.");
|
||||
return;
|
||||
}
|
||||
|
||||
var movies = GetPlexMovies().ToArray();
|
||||
var shows = GetPlexTvShows().ToArray();
|
||||
var albums = GetPlexAlbums().ToArray();
|
||||
|
||||
var requests = RequestService.GetAll();
|
||||
var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray();
|
||||
Log.Trace("Requests Count {0}", requestedModels.Length);
|
||||
|
||||
if (!requestedModels.Any())
|
||||
{
|
||||
Log.Info("There are no requests to check.");
|
||||
return;
|
||||
}
|
||||
|
||||
var modifiedModel = new List<RequestedModel>();
|
||||
foreach (var r in requestedModels)
|
||||
{
|
||||
Log.Trace("We are going to see if Plex has the following title: {0}", r.Title);
|
||||
|
||||
Log.Trace("Search results from Plex for the following request: {0}", r.Title);
|
||||
|
||||
var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy");
|
||||
|
||||
bool matchResult;
|
||||
switch (r.Type)
|
||||
{
|
||||
case RequestType.Movie:
|
||||
matchResult = IsMovieAvailable(movies, r.Title, releaseDate);
|
||||
break;
|
||||
case RequestType.TvShow:
|
||||
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate);
|
||||
break;
|
||||
case RequestType.Album:
|
||||
matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (matchResult)
|
||||
{
|
||||
r.Available = true;
|
||||
modifiedModel.Add(r);
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.Trace("The result from Plex where the title's match was null, so that means the content is not yet in Plex.");
|
||||
}
|
||||
|
||||
Log.Trace("Updating the requests now");
|
||||
Log.Trace("Requests that will be updated count {0}", modifiedModel.Count);
|
||||
|
||||
if (modifiedModel.Any())
|
||||
{
|
||||
NotifyUsers(modifiedModel, authSettings.PlexAuthToken);
|
||||
RequestService.BatchUpdate(modifiedModel);
|
||||
}
|
||||
|
||||
Job.Record(JobNames.PlexChecker);
|
||||
|
||||
}
|
||||
|
||||
public List<PlexMovie> GetPlexMovies()
|
||||
{
|
||||
var movies = new List<PlexMovie>();
|
||||
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
|
||||
if (libs != null)
|
||||
{
|
||||
var movieLibs = libs.Where(x =>
|
||||
x.Video.Any(y =>
|
||||
y.Type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
).ToArray();
|
||||
|
||||
foreach (var lib in movieLibs)
|
||||
{
|
||||
movies.AddRange(lib.Video.Select(x => new PlexMovie() // movies are in the Video list
|
||||
{
|
||||
Title = x.Title,
|
||||
ReleaseYear = x.Year
|
||||
}));
|
||||
}
|
||||
}
|
||||
return movies;
|
||||
}
|
||||
|
||||
public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year)
|
||||
{
|
||||
return plexMovies.Any(x => x.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public List<PlexTvShow> GetPlexTvShows()
|
||||
{
|
||||
var shows = new List<PlexTvShow>();
|
||||
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
|
||||
if (libs != null)
|
||||
{
|
||||
var tvLibs = libs.Where(x =>
|
||||
x.Directory.Any(y =>
|
||||
y.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
).ToArray();
|
||||
|
||||
foreach (var lib in tvLibs)
|
||||
{
|
||||
shows.AddRange(lib.Directory.Select(x => new PlexTvShow() // shows are in the directory list
|
||||
{
|
||||
Title = x.Title,
|
||||
ReleaseYear = x.Year
|
||||
}));
|
||||
}
|
||||
}
|
||||
return shows;
|
||||
}
|
||||
|
||||
public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year)
|
||||
{
|
||||
return plexShows.Any(x =>
|
||||
(x.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) || x.Title.StartsWith(title, StringComparison.CurrentCultureIgnoreCase)) &&
|
||||
x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public List<PlexAlbum> GetPlexAlbums()
|
||||
{
|
||||
var albums = new List<PlexAlbum>();
|
||||
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
|
||||
if (libs != null)
|
||||
{
|
||||
var albumLibs = libs.Where(x =>
|
||||
x.Directory.Any(y =>
|
||||
y.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
).ToArray();
|
||||
|
||||
foreach (var lib in albumLibs)
|
||||
{
|
||||
albums.AddRange(lib.Directory.Select(x => new PlexAlbum()
|
||||
{
|
||||
Title = x.Title,
|
||||
ReleaseYear = x.Year,
|
||||
Artist = x.ParentTitle
|
||||
}));
|
||||
}
|
||||
}
|
||||
return albums;
|
||||
}
|
||||
|
||||
public bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist)
|
||||
{
|
||||
return plexAlbums.Any(x =>
|
||||
x.Title.Contains(title) &&
|
||||
//x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase) &&
|
||||
x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private List<PlexSearch> CachedLibraries(AuthenticationSettings authSettings, PlexSettings plexSettings, bool setCache)
|
||||
{
|
||||
Log.Trace("Obtaining library sections from Plex");
|
||||
|
||||
List<PlexSearch> results = new List<PlexSearch>();
|
||||
|
||||
if (!ValidateSettings(plexSettings, authSettings))
|
||||
{
|
||||
Log.Warn("The settings are not configured");
|
||||
return results; // don't error out here, just let it go!
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (setCache)
|
||||
{
|
||||
Log.Trace("Plex Lib API Call");
|
||||
results = GetLibraries(authSettings, plexSettings);
|
||||
|
||||
Log.Trace("Plex Lib Cache Set Call");
|
||||
if (results != null)
|
||||
{
|
||||
Cache.Set(CacheKeys.PlexLibaries, results, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Trace("Plex Lib GetSet Call");
|
||||
results = Cache.GetOrSet(CacheKeys.PlexLibaries, () =>
|
||||
{
|
||||
Log.Trace("Plex Lib API Call (inside getset)");
|
||||
return GetLibraries(authSettings, plexSettings);
|
||||
}, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to obtain Plex libraries");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<PlexSearch> GetLibraries(AuthenticationSettings authSettings, PlexSettings plexSettings)
|
||||
{
|
||||
var sections = PlexApi.GetLibrarySections(authSettings.PlexAuthToken, plexSettings.FullUri);
|
||||
|
||||
List<PlexSearch> libs = new List<PlexSearch>();
|
||||
if (sections != null)
|
||||
{
|
||||
foreach (var dir in sections.Directories)
|
||||
{
|
||||
Log.Trace("Obtaining results from Plex for the following library section: {0}", dir.Title);
|
||||
var lib = PlexApi.GetLibrary(authSettings.PlexAuthToken, plexSettings.FullUri, dir.Key);
|
||||
if (lib != null)
|
||||
{
|
||||
libs.Add(lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Trace("Returning Plex Libs");
|
||||
return libs;
|
||||
}
|
||||
|
||||
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth)
|
||||
{
|
||||
if (plex?.Ip == null || auth?.PlexAuthToken == null)
|
||||
{
|
||||
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void NotifyUsers(IEnumerable<RequestedModel> modelChanged, string apiKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plexUser = PlexApi.GetUsers(apiKey);
|
||||
if (plexUser?.User == null || plexUser.User.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var users = UserNotifyRepo.GetAll().ToList();
|
||||
foreach (var model in modelChanged)
|
||||
{
|
||||
var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers);
|
||||
foreach (var user in selectedUsers)
|
||||
{
|
||||
var email = plexUser.User.FirstOrDefault(x => x.Username == user);
|
||||
if (email == null)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
CheckAndUpdateAll();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +1,98 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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 NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Api.Models.Movie;
|
||||
using System.Linq;
|
||||
using PlexRequests.Api.Models.SickRage;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class SickRageCacher : ISickRageCacher
|
||||
{
|
||||
public SickRageCacher(ISettingsService<SickRageSettings> srSettings, ISickRageApi srApi, ICacheProvider cache)
|
||||
{
|
||||
SrSettings = srSettings;
|
||||
SrApi = srApi;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private ISettingsService<SickRageSettings> SrSettings { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private ISickRageApi SrApi { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Queued(long check)
|
||||
{
|
||||
Log.Trace("This is check no. {0}", check);
|
||||
Log.Trace("Getting the settings");
|
||||
|
||||
var settings = SrSettings.GetSettings();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
Log.Trace("Getting all shows from SickRage");
|
||||
var movies = SrApi.GetShows(settings.ApiKey, settings.FullUri);
|
||||
Cache.Set(CacheKeys.SickRageQueued, movies.Result);
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to set here...
|
||||
public int[] QueuedIds()
|
||||
{
|
||||
var tv = Cache.Get<SickrageShows>(CacheKeys.SickRageQueued);
|
||||
return tv?.data.Values.Select(x => x.tvdbid).ToArray() ?? new int[] { };
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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.Linq;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.SickRage;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace PlexRequests.Services.Jobs
|
||||
{
|
||||
public class SickRageCacher : IJob, ISickRageCacher
|
||||
{
|
||||
public SickRageCacher(ISettingsService<SickRageSettings> srSettings, ISickRageApi srApi, ICacheProvider cache, IJobRecord rec)
|
||||
{
|
||||
SrSettings = srSettings;
|
||||
SrApi = srApi;
|
||||
Cache = cache;
|
||||
Job = rec;
|
||||
}
|
||||
|
||||
private ISettingsService<SickRageSettings> SrSettings { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private ISickRageApi SrApi { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private IJobRecord Job { get; }
|
||||
|
||||
public void Queued()
|
||||
{
|
||||
Log.Trace("Getting the settings");
|
||||
|
||||
var settings = SrSettings.GetSettings();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
Log.Trace("Getting all shows from SickRage");
|
||||
try
|
||||
{
|
||||
var shows = SrApi.GetShows(settings.ApiKey, settings.FullUri);
|
||||
if (shows != null)
|
||||
{
|
||||
Cache.Set(CacheKeys.SickRageQueued, shows.Result, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed caching queued items from SickRage");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Job.Record(JobNames.SrCacher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to set here...
|
||||
public int[] QueuedIds()
|
||||
{
|
||||
var tv = Cache.Get<SickrageShows>(CacheKeys.SickRageQueued);
|
||||
return tv?.data.Values.Select(x => x.tvdbid).ToArray() ?? new int[] { };
|
||||
}
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
Queued();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +1,101 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.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 NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using PlexRequests.Api.Models.Sonarr;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class SonarrCacher : ISonarrCacher
|
||||
{
|
||||
public SonarrCacher(ISettingsService<SonarrSettings> sonarrSettings, ISonarrApi sonarrApi, ICacheProvider cache)
|
||||
{
|
||||
SonarrSettings = sonarrSettings;
|
||||
SonarrApi = sonarrApi;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private ISettingsService<SonarrSettings> SonarrSettings { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private ISonarrApi SonarrApi { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Queued(long check)
|
||||
{
|
||||
Log.Trace("This is check no. {0}", check);
|
||||
Log.Trace("Getting the settings");
|
||||
|
||||
var settings = SonarrSettings.GetSettings();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
Log.Trace("Getting all tv series from Sonarr");
|
||||
var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri);
|
||||
Cache.Set(CacheKeys.SonarrQueued, series, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to set here...
|
||||
public int[] QueuedIds()
|
||||
{
|
||||
var series = Cache.Get<List<Series>>(CacheKeys.SonarrQueued);
|
||||
return series?.Select(x => x.tvdbId).ToArray() ?? new int[] { };
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexAvailabilityChecker.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Sonarr;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace PlexRequests.Services.Jobs
|
||||
{
|
||||
public class SonarrCacher : IJob, ISonarrCacher
|
||||
{
|
||||
public SonarrCacher(ISettingsService<SonarrSettings> sonarrSettings, ISonarrApi sonarrApi, ICacheProvider cache, IJobRecord rec)
|
||||
{
|
||||
SonarrSettings = sonarrSettings;
|
||||
SonarrApi = sonarrApi;
|
||||
Job = rec;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private ISettingsService<SonarrSettings> SonarrSettings { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private ISonarrApi SonarrApi { get; }
|
||||
private IJobRecord Job { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Queued()
|
||||
{
|
||||
Log.Trace("Getting the settings");
|
||||
|
||||
var settings = SonarrSettings.GetSettings();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
Log.Trace("Getting all tv series from Sonarr");
|
||||
try
|
||||
{
|
||||
var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri);
|
||||
if (series != null)
|
||||
{
|
||||
Cache.Set(CacheKeys.SonarrQueued, series, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed caching queued items from Sonarr");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Job.Record(JobNames.SonarrCacher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we do not want to set here...
|
||||
public int[] QueuedIds()
|
||||
{
|
||||
var series = Cache.Get<List<Series>>(CacheKeys.SonarrQueued);
|
||||
return series?.Select(x => x.tvdbId).ToArray() ?? new int[] { };
|
||||
}
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
Queued();
|
||||
}
|
||||
}
|
||||
}
|
150
PlexRequests.Services/Jobs/StoreBackup.cs
Normal file
150
PlexRequests.Services/Jobs/StoreBackup.cs
Normal file
|
@ -0,0 +1,150 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: StoreBackup.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.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;
|
||||
|
||||
|
||||
namespace PlexRequests.Services.Jobs
|
||||
{
|
||||
public class StoreBackup : IJob
|
||||
{
|
||||
public StoreBackup(ISqliteConfiguration sql, IJobRecord rec)
|
||||
{
|
||||
Sql = sql;
|
||||
JobRecord = rec;
|
||||
}
|
||||
|
||||
private ISqliteConfiguration Sql { get; }
|
||||
private IJobRecord JobRecord { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
TakeBackup();
|
||||
Cleanup ();
|
||||
}
|
||||
|
||||
private void TakeBackup()
|
||||
{
|
||||
Log.Trace("Starting DB Backup");
|
||||
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"));
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(dbPath))
|
||||
{
|
||||
Log.Warn("Could not find the actual database. We cannot backup.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
Log.Warn("Exception when trying to copy the backup.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
JobRecord.Record(JobNames.StoreBackup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: AvailabilityUpdateService.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.Reactive.Linq;
|
||||
using System.Web.Hosting;
|
||||
|
||||
using FluentScheduler;
|
||||
|
||||
using Mono.Data.Sqlite;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Repository;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class MediaCacheService : ITask, IRegisteredObject, IAvailabilityUpdateService
|
||||
{
|
||||
public MediaCacheService()
|
||||
{
|
||||
var memCache = new MemoryCacheProvider();
|
||||
var dbConfig = new DbConfiguration(new SqliteFactory());
|
||||
var repo = new SettingsJsonRepository(dbConfig, memCache);
|
||||
|
||||
ConfigurationReader = new ConfigurationReader();
|
||||
CpCacher = new CouchPotatoCacher(new SettingsServiceV2<CouchPotatoSettings>(repo), new CouchPotatoApi(), memCache);
|
||||
SonarrCacher = new SonarrCacher(new SettingsServiceV2<SonarrSettings>(repo), new SonarrApi(), memCache);
|
||||
SickRageCacher = new SickRageCacher(new SettingsServiceV2<SickRageSettings>(repo), new SickrageApi(), memCache);
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
}
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private IConfigurationReader ConfigurationReader { get; }
|
||||
private ICouchPotatoCacher CpCacher { get; }
|
||||
private ISonarrCacher SonarrCacher { get; }
|
||||
private ISickRageCacher SickRageCacher { get; }
|
||||
private IDisposable CpSubscription { get; set; }
|
||||
private IDisposable SonarrSubscription { get; set; }
|
||||
private IDisposable SickRageSubscription { get; set; }
|
||||
|
||||
public void Start(Configuration c)
|
||||
{
|
||||
CpSubscription?.Dispose();
|
||||
SonarrSubscription?.Dispose();
|
||||
SickRageSubscription?.Dispose();
|
||||
|
||||
Task.Factory.StartNew(() => CpCacher.Queued(-1));
|
||||
Task.Factory.StartNew(() => SonarrCacher.Queued(-1));
|
||||
Task.Factory.StartNew(() => SickRageCacher.Queued(-1));
|
||||
CpSubscription = Observable.Interval(c.Intervals.Notification).Subscribe(CpCacher.Queued);
|
||||
SonarrSubscription = Observable.Interval(c.Intervals.Notification).Subscribe(SonarrCacher.Queued);
|
||||
SickRageSubscription = Observable.Interval(c.Intervals.Notification).Subscribe(SickRageCacher.Queued);
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Start(ConfigurationReader.Read());
|
||||
}
|
||||
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICouchPotatoCacheService
|
||||
{
|
||||
void Start(Configuration c);
|
||||
}
|
||||
}
|
|
@ -25,15 +25,17 @@
|
|||
// ************************************************************************/
|
||||
#endregion
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using MailKit.Security;
|
||||
|
||||
using MimeKit;
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace PlexRequests.Services.Notification
|
||||
{
|
||||
|
@ -71,8 +73,8 @@ namespace PlexRequests.Services.Notification
|
|||
await EmailIssue(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.RequestAvailable:
|
||||
throw new NotImplementedException();
|
||||
|
||||
await EmailAvailableRequest(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.RequestApproved:
|
||||
throw new NotImplementedException();
|
||||
|
||||
|
@ -111,58 +113,68 @@ namespace PlexRequests.Services.Notification
|
|||
|
||||
private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var message = new MailMessage
|
||||
var message = new MimeMessage
|
||||
{
|
||||
IsBodyHtml = true,
|
||||
To = { new MailAddress(settings.RecipientEmail) },
|
||||
Body = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}",
|
||||
From = new MailAddress(settings.EmailSender),
|
||||
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" },
|
||||
Subject = $"Plex Requests: New request for {model.Title}!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
try
|
||||
{
|
||||
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
|
||||
{
|
||||
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
|
||||
smtp.EnableSsl = settings.Ssl;
|
||||
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (SmtpException smtp)
|
||||
{
|
||||
Log.Error(smtp);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var message = new MailMessage
|
||||
var message = new MimeMessage
|
||||
{
|
||||
IsBodyHtml = true,
|
||||
To = { new MailAddress(settings.RecipientEmail) },
|
||||
Body = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!",
|
||||
From = new MailAddress(settings.RecipientEmail),
|
||||
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" },
|
||||
Subject = $"Plex Requests: New issue for {model.Title}!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
if (!settings.EnableUserEmailNotifications)
|
||||
{
|
||||
await Task.FromResult(false);
|
||||
}
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" },
|
||||
Subject = $"Plex Requests: {model.Title} is now available!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail));
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task Send(MimeMessage message, EmailNotificationSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
|
||||
smtp.EnableSsl = settings.Ssl;
|
||||
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
||||
client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions.
|
||||
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
client.Authenticate(settings.EmailUsername, settings.EmailPassword);
|
||||
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
catch (SmtpException smtp)
|
||||
{
|
||||
Log.Error(smtp);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
|
@ -171,32 +183,15 @@ namespace PlexRequests.Services.Notification
|
|||
|
||||
private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var message = new MailMessage
|
||||
var message = new MimeMessage
|
||||
{
|
||||
IsBodyHtml = true,
|
||||
To = { new MailAddress(settings.RecipientEmail) },
|
||||
Body = "This is just a test! Success!",
|
||||
From = new MailAddress(settings.RecipientEmail),
|
||||
Subject = "Plex Requests: Test Message!"
|
||||
Body = new TextPart("plain") {Text= "This is just a test! Success!"},
|
||||
Subject = "Plex Requests: Test Message!",
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
try
|
||||
{
|
||||
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
|
||||
{
|
||||
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
|
||||
smtp.EnableSsl = settings.Ssl;
|
||||
await smtp.SendMailAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (SmtpException smtp)
|
||||
{
|
||||
Log.Error(smtp);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
await Send(message, settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,5 +35,6 @@ namespace PlexRequests.Services.Notification
|
|||
public DateTime DateTime { get; set; }
|
||||
public NotificationType NotificationType { get; set; }
|
||||
public string User { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
}
|
||||
}
|
|
@ -41,6 +41,11 @@ namespace PlexRequests.Services.Notification
|
|||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
public ConcurrentDictionary<string, INotification> Observers { get; } = new ConcurrentDictionary<string, INotification>();
|
||||
|
||||
/// <summary>
|
||||
/// Sends a notification to the user. This one is used in normal notification scenarios
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Publish(NotificationModel model)
|
||||
{
|
||||
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model));
|
||||
|
@ -48,6 +53,12 @@ namespace PlexRequests.Services.Notification
|
|||
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a notification to the user, this is usually for testing the settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Publish(NotificationModel model, Settings settings)
|
||||
{
|
||||
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings));
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace PlexRequests.Services.Notification
|
|||
RequestAvailable,
|
||||
RequestApproved,
|
||||
AdminNote,
|
||||
Test
|
||||
Test,
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ namespace PlexRequests.Services.Notification
|
|||
}
|
||||
private IPushbulletApi PushbulletApi { get; }
|
||||
private ISettingsService<PushbulletNotificationSettings> SettingsService { get; }
|
||||
private PushbulletNotificationSettings Settings => GetSettings();
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
public string NotificationName => "PushbulletNotification";
|
||||
|
@ -78,7 +77,7 @@ namespace PlexRequests.Services.Notification
|
|||
case NotificationType.AdminNote:
|
||||
break;
|
||||
case NotificationType.Test:
|
||||
await PushTestAsync(model, pushSettings);
|
||||
await PushTestAsync(pushSettings);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
@ -107,45 +106,28 @@ namespace PlexRequests.Services.Notification
|
|||
{
|
||||
var message = $"{model.Title} has been requested by user: {model.User}";
|
||||
var pushTitle = $"Plex Requests: {model.Title} has been requested!";
|
||||
try
|
||||
{
|
||||
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
|
||||
if (result == null)
|
||||
{
|
||||
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||
{
|
||||
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
|
||||
try
|
||||
{
|
||||
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
|
||||
if (result != null)
|
||||
{
|
||||
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task PushTestAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||
private async Task PushTestAsync(PushbulletNotificationSettings settings)
|
||||
{
|
||||
var message = "This is just a test! Success!";
|
||||
var pushTitle = "Plex Requests: Test Message!";
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task Push(PushbulletNotificationSettings settings, string message, string title)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
|
||||
var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier);
|
||||
if (result != null)
|
||||
{
|
||||
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||
|
|
|
@ -45,7 +45,6 @@ namespace PlexRequests.Services.Notification
|
|||
}
|
||||
private IPushoverApi PushoverApi { get; }
|
||||
private ISettingsService<PushoverNotificationSettings> SettingsService { get; }
|
||||
private PushoverNotificationSettings Settings => GetSettings();
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
public string NotificationName => "PushoverNotification";
|
||||
|
@ -106,40 +105,23 @@ namespace PlexRequests.Services.Notification
|
|||
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}";
|
||||
try
|
||||
{
|
||||
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||
if (result?.status != 1)
|
||||
{
|
||||
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||
try
|
||||
{
|
||||
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||
if (result?.status != 1)
|
||||
{
|
||||
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Plex Requests: Test Message!";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task Push(PushoverNotificationSettings settings, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||
|
|
155
PlexRequests.Services/Notification/SlackNotification.cs
Normal file
155
PlexRequests.Services/Notification/SlackNotification.cs
Normal file
|
@ -0,0 +1,155 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: SlackNotification.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.Threading.Tasks;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Api.Models.Notifications;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
|
||||
namespace PlexRequests.Services.Notification
|
||||
{
|
||||
public class SlackNotification : INotification
|
||||
{
|
||||
public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn)
|
||||
{
|
||||
Api = api;
|
||||
Settings = sn;
|
||||
}
|
||||
|
||||
public string NotificationName => "SlackNotification";
|
||||
|
||||
private ISlackApi Api { get; }
|
||||
private ISettingsService<SlackNotificationSettings> Settings { get; }
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model)
|
||||
{
|
||||
var settings = Settings.GetSettings();
|
||||
|
||||
await NotifyAsync(model, settings);
|
||||
}
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||
{
|
||||
if (settings == null) await NotifyAsync(model);
|
||||
|
||||
var pushSettings = (SlackNotificationSettings)settings;
|
||||
if (!ValidateConfiguration(pushSettings))
|
||||
{
|
||||
Log.Error("Settings for Slack was not correct, we cannot push a notification");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (model.NotificationType)
|
||||
{
|
||||
case NotificationType.NewRequest:
|
||||
await PushNewRequestAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.Issue:
|
||||
await PushIssueAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestAvailable:
|
||||
break;
|
||||
case NotificationType.RequestApproved:
|
||||
break;
|
||||
case NotificationType.AdminNote:
|
||||
break;
|
||||
case NotificationType.Test:
|
||||
await PushTest(pushSettings);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PushNewRequestAsync(NotificationModel model, SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"{model.Title} has been requested by user: {model.User}";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushIssueAsync(NotificationModel model, SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushTest(SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"This is a test from Plex Requests, if you can see this then we have successfully pushed a notification!";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task Push(SlackNotificationSettings config, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var notification = new SlackNotificationBody { username = config.Username, channel = config.Channel ?? string.Empty, text = message };
|
||||
|
||||
var result = await Api.PushAsync(config.Team, config.Token, config.Service, notification);
|
||||
if (!result.Equals("ok"))
|
||||
{
|
||||
Log.Error("Slack returned a message that was not 'ok', the notification did not get pushed");
|
||||
Log.Error($"Message that slack returned: {result}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateConfiguration(SlackNotificationSettings settings)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.WebhookUrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var a = settings.Team;
|
||||
var b = settings.Service;
|
||||
var c = settings.Token;
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Services</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Services</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -31,73 +31,59 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BouncyCastle, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MimeKit.1.2.22\lib\net45\BouncyCastle.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentScheduler, Version=3.1.46.0, Culture=neutral, PublicKeyToken=b76503528a14ebd1, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentScheduler.3.1.46\lib\net40\FluentScheduler.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MailKit, Version=1.2.0.0, Culture=neutral, PublicKeyToken=4e064fe7c44a8f1b, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MailKit.1.2.21\lib\net451\MailKit.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="MimeKit, Version=1.2.0.0, Culture=neutral, PublicKeyToken=bede1c8a46c66814, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MimeKit.1.2.22\lib\net45\MimeKit.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="BouncyCastle, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
|
||||
<HintPath>..\packages\MimeKit.1.2.22\lib\net45\BouncyCastle.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Common.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e">
|
||||
<HintPath>..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Common.Logging.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e">
|
||||
<HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MailKit, Version=1.2.0.0, Culture=neutral, PublicKeyToken=4e064fe7c44a8f1b">
|
||||
<HintPath>..\packages\MailKit.1.2.21\lib\net451\MailKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MimeKit, Version=1.2.0.0, Culture=neutral, PublicKeyToken=bede1c8a46c66814">
|
||||
<HintPath>..\packages\MimeKit.1.2.22\lib\net45\MimeKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Mono.Data.Sqlite">
|
||||
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Quartz, Version=2.3.3.0, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4">
|
||||
<HintPath>..\packages\Quartz.2.3.3\lib\net40\Quartz.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Interfaces\IJobRecord.cs" />
|
||||
<Compile Include="Jobs\JobRecord.cs" />
|
||||
<Compile Include="Jobs\JobNames.cs" />
|
||||
<Compile Include="Jobs\StoreBackup.cs" />
|
||||
<Compile Include="Jobs\CouchPotatoCacher.cs" />
|
||||
<Compile Include="Jobs\PlexAvailabilityChecker.cs" />
|
||||
<Compile Include="Jobs\SickRageCacher.cs" />
|
||||
<Compile Include="Jobs\SonarrCacher.cs" />
|
||||
<Compile Include="Models\PlexAlbum.cs" />
|
||||
<Compile Include="Models\PlexMovie.cs" />
|
||||
<Compile Include="Models\PlexTvShow.cs" />
|
||||
<Compile Include="SickRageCacher.cs" />
|
||||
<Compile Include="Interfaces\ITvCacher.cs" />
|
||||
<Compile Include="SonarrCacher.cs" />
|
||||
<Compile Include="Interfaces\ISickRageCacher.cs" />
|
||||
<Compile Include="Interfaces\ISonarrCacher.cs" />
|
||||
<Compile Include="MediaCacheService.cs" />
|
||||
<Compile Include="AvailabilityUpdateService.cs" />
|
||||
<Compile Include="Configuration.cs" />
|
||||
<Compile Include="ConfigurationReader.cs" />
|
||||
<Compile Include="Interfaces\ICouchPotatoCacher.cs" />
|
||||
<Compile Include="Interfaces\IAvailabilityChecker.cs" />
|
||||
<Compile Include="Interfaces\IConfigurationReader.cs" />
|
||||
<Compile Include="Interfaces\IIntervals.cs" />
|
||||
<Compile Include="Interfaces\INotification.cs" />
|
||||
<Compile Include="Interfaces\INotificationService.cs" />
|
||||
|
@ -107,11 +93,8 @@
|
|||
<Compile Include="Notification\NotificationType.cs" />
|
||||
<Compile Include="Notification\PushoverNotification.cs" />
|
||||
<Compile Include="Notification\PushbulletNotification.cs" />
|
||||
<Compile Include="CouchPotatoCacher.cs" />
|
||||
<Compile Include="PlexAvailabilityChecker.cs" />
|
||||
<Compile Include="PlexType.cs" />
|
||||
<Compile Include="Notification\SlackNotification.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UpdateInterval.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
|
@ -119,7 +102,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
|
||||
<Project>{95834072-a675-415d-aa8f-877c91623810}</Project>
|
||||
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
|
||||
<Name>PlexRequests.Api.Interfaces</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
|
||||
|
@ -127,11 +110,11 @@
|
|||
<Name>PlexRequests.Api.Models</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PlexRequests.Api\PlexRequests.Api.csproj">
|
||||
<Project>{8cb8d235-2674-442d-9c6a-35fcaeeb160d}</Project>
|
||||
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
|
||||
<Name>PlexRequests.Api</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
|
||||
<Project>{dd7dc444-d3bf-4027-8ab9-efc71f5ec581}</Project>
|
||||
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
|
||||
<Name>PlexRequests.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
|
||||
|
@ -139,7 +122,7 @@
|
|||
<Name>PlexRequests.Helpers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
|
||||
<Project>{92433867-2b7b-477b-a566-96c382427525}</Project>
|
||||
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
|
||||
<Name>PlexRequests.Store</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?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>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentScheduler" version="3.1.46" targetFramework="net452" />
|
||||
<package id="MailKit" version="1.2.21" targetFramework="net46" />
|
||||
<package id="MimeKit" version="1.2.22" targetFramework="net46" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net452" />
|
||||
<package id="Rx-Core" version="2.2.5" targetFramework="net452" />
|
||||
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net452" />
|
||||
<package id="Rx-Linq" version="2.2.5" targetFramework="net452" />
|
||||
<package id="Rx-Main" version="2.2.5" targetFramework="net452" />
|
||||
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net452" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Common.Logging" version="3.0.0" targetFramework="net45" />
|
||||
<package id="Common.Logging.Core" version="3.0.0" targetFramework="net45" />
|
||||
<package id="MailKit" version="1.2.21" targetFramework="net45" requireReinstallation="True" />
|
||||
<package id="MimeKit" version="1.2.22" targetFramework="net45" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net45" />
|
||||
<package id="Quartz" version="2.3.3" targetFramework="net45" />
|
||||
</packages>
|
|
@ -23,6 +23,9 @@
|
|||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ***********************************************************************
|
||||
using System.Globalization;
|
||||
|
||||
|
||||
#endregion
|
||||
using System;
|
||||
using System.Data;
|
||||
|
@ -44,7 +47,7 @@ namespace PlexRequests.Store
|
|||
}
|
||||
|
||||
private SqliteFactory Factory { get; }
|
||||
private string CurrentPath =>Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, DbFile);
|
||||
public string CurrentPath =>Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, DbFile);
|
||||
|
||||
public virtual bool CheckDb()
|
||||
{
|
||||
|
@ -59,7 +62,7 @@ namespace PlexRequests.Store
|
|||
return false;
|
||||
}
|
||||
|
||||
public string DbFile = "PlexRequests.sqlite";
|
||||
public const string DbFile = "PlexRequests.sqlite";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database connection.
|
||||
|
|
|
@ -45,6 +45,9 @@ namespace PlexRequests.Store
|
|||
/// Creates the database.
|
||||
/// </summary>
|
||||
void CreateDatabase();
|
||||
}
|
||||
|
||||
string CurrentPath { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
16
PlexRequests.Store/Models/Audit.cs
Normal file
16
PlexRequests.Store/Models/Audit.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace PlexRequests.Store
|
||||
{
|
||||
[Table("Audit")]
|
||||
public class Audit : Entity
|
||||
{
|
||||
public string Username{get;set;}
|
||||
public DateTime Date {get;set;}
|
||||
public string ChangeType {get;set;}
|
||||
public string OldValue {get;set;}
|
||||
public string NewValue{get;set;}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,39 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: UpdateInterval.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 PlexRequests.Services.Interfaces;
|
||||
|
||||
namespace PlexRequests.Services
|
||||
{
|
||||
public class UpdateInterval : IIntervals
|
||||
{
|
||||
public TimeSpan Notification => TimeSpan.FromMinutes(10);
|
||||
|
||||
}
|
||||
}
|
||||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: LogEntity.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
using System;
|
||||
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace PlexRequests.Store.Models
|
||||
{
|
||||
[Table("ScheduledJobs")]
|
||||
public class ScheduledJobs : Entity
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public DateTime LastRun { get; set; }
|
||||
}
|
||||
}
|
36
PlexRequests.Store/Models/UsersToNotify.cs
Normal file
36
PlexRequests.Store/Models/UsersToNotify.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: UsersToNotify.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace PlexRequests.Store.Models
|
||||
{
|
||||
[Table("UsersToNotify")]
|
||||
public class UsersToNotify : Entity
|
||||
{
|
||||
public string Username { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PlexRequests.Store</RootNamespace>
|
||||
<AssemblyName>PlexRequests.Store</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -31,25 +31,9 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dapper, Version=1.40.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Dapper.1.42\lib\net45\Dapper.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Dapper.Contrib, Version=1.40.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Dapper.Contrib.1.43\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="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
|
@ -59,10 +43,24 @@
|
|||
<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>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c">
|
||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DbConfiguration.cs" />
|
||||
<Compile Include="Entity.cs" />
|
||||
<Compile Include="Models\ScheduledJobs.cs" />
|
||||
<Compile Include="Models\UsersToNotify.cs" />
|
||||
<Compile Include="Repository\IRequestRepository.cs" />
|
||||
<Compile Include="Repository\ISettingsRepository.cs" />
|
||||
<Compile Include="ISqliteConfiguration.cs" />
|
||||
|
@ -84,6 +82,7 @@
|
|||
<DependentUpon>Sql.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TableCreation.cs" />
|
||||
<Compile Include="Models\Audit.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="sqlite3.dll">
|
||||
|
|
|
@ -61,13 +61,7 @@ namespace PlexRequests.Store
|
|||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool CanApprove
|
||||
{
|
||||
get
|
||||
{
|
||||
return !Approved && !Available;
|
||||
}
|
||||
}
|
||||
public bool CanApprove => !Approved && !Available;
|
||||
|
||||
public bool UserHasRequested(string username)
|
||||
{
|
||||
|
|
|
@ -42,8 +42,34 @@ CREATE TABLE IF NOT EXISTS Logs
|
|||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS Logs_Id ON Logs (Id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Audit
|
||||
(
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Date varchar(100) NOT NULL,
|
||||
Username varchar(100) NOT NULL,
|
||||
ChangeType varchar(100) NOT NULL,
|
||||
OldValue varchar(100),
|
||||
NewValue varchar(100)
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS Audit_Id ON Audit (Id);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS DBInfo
|
||||
(
|
||||
SchemaVersion INTEGER
|
||||
);
|
||||
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS ScheduledJobs
|
||||
(
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Name varchar(100) NOT NULL,
|
||||
LastRun varchar(100) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ScheduledJobs_Id ON ScheduledJobs (Id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS UsersToNotify
|
||||
(
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Username varchar(100) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UsersToNotify_Id ON UsersToNotify (Id);
|
|
@ -44,7 +44,18 @@ namespace PlexRequests.Store
|
|||
connection.Close();
|
||||
}
|
||||
|
||||
public static void AlterTable(IDbConnection connection, string tableName, string alterType, string newColumn, bool isNullable, string dataType)
|
||||
public static void DropTable(IDbConnection con, string tableName)
|
||||
{
|
||||
using (con)
|
||||
{
|
||||
con.Open();
|
||||
var query = $"DROP TABLE IF EXISTS {tableName}";
|
||||
con.Execute(query);
|
||||
con.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddColumn(IDbConnection connection, string tableName, string alterType, string newColumn, bool isNullable, string dataType)
|
||||
{
|
||||
connection.Open();
|
||||
var result = connection.Query<TableInfo>($"PRAGMA table_info({tableName});");
|
||||
|
@ -83,7 +94,7 @@ namespace PlexRequests.Store
|
|||
public static void CreateSchema(this IDbConnection con, int version)
|
||||
{
|
||||
con.Open();
|
||||
con.Query(string.Format("INSERT INTO DBInfo (SchemaVersion) values ({0})", version));
|
||||
con.Query($"INSERT INTO DBInfo (SchemaVersion) values ({version})");
|
||||
con.Close();
|
||||
}
|
||||
|
||||
|
@ -115,7 +126,5 @@ namespace PlexRequests.Store
|
|||
public string dflt_value { get; set; }
|
||||
public int pk { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Dapper" version="1.42" targetFramework="net452" />
|
||||
<package id="Dapper.Contrib" version="1.43" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net46" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net452" />
|
||||
<package id="Dapper" version="1.42" targetFramework="net45" />
|
||||
<package id="Dapper.Contrib" version="1.43" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
|
||||
<package id="NLog" version="4.2.3" targetFramework="net45" />
|
||||
</packages>
|
|
@ -25,6 +25,7 @@
|
|||
// ************************************************************************/
|
||||
#endregion
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Moq;
|
||||
|
||||
|
@ -32,6 +33,7 @@ using Nancy;
|
|||
using Nancy.Testing;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
|
@ -45,11 +47,11 @@ using PlexRequests.Store.Repository;
|
|||
using PlexRequests.UI.Models;
|
||||
using PlexRequests.UI.Modules;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Ignore("Needs rework")]
|
||||
public class AdminModuleTests
|
||||
{
|
||||
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
|
||||
|
@ -70,6 +72,7 @@ namespace PlexRequests.UI.Tests
|
|||
private Mock<IRepository<LogEntity>> LogRepo { get; set; }
|
||||
private Mock<INotificationService> NotificationService { get; set; }
|
||||
private Mock<ICacheProvider> Cache { get; set; }
|
||||
private Mock<ISettingsService<LogSettings>> Log { get; set; }
|
||||
|
||||
private ConfigurableBootstrapper Bootstrapper { get; set; }
|
||||
|
||||
|
@ -82,9 +85,10 @@ namespace PlexRequests.UI.Tests
|
|||
|
||||
PlexMock = new Mock<IPlexApi>();
|
||||
PlexMock.Setup(x => x.SignIn("Username1", "Password1"))
|
||||
.Returns(new PlexAuthentication { user = new User { authentication_token = "abc", username = "Username1" } });
|
||||
.Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } });
|
||||
|
||||
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
|
||||
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
|
||||
CpMock = new Mock<ISettingsService<CouchPotatoSettings>>();
|
||||
PlexSettingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||
SonarrApiMock = new Mock<ISonarrApi>();
|
||||
|
@ -100,6 +104,7 @@ namespace PlexRequests.UI.Tests
|
|||
NotificationService = new Mock<INotificationService>();
|
||||
HeadphonesSettings = new Mock<ISettingsService<HeadphonesSettings>>();
|
||||
Cache = new Mock<ICacheProvider>();
|
||||
Log = new Mock<ISettingsService<LogSettings>>();
|
||||
|
||||
Bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
|
@ -121,11 +126,12 @@ namespace PlexRequests.UI.Tests
|
|||
with.Dependency(PushoverApi.Object);
|
||||
with.Dependency(NotificationService.Object);
|
||||
with.Dependency(HeadphonesSettings.Object);
|
||||
with.Dependencies(Cache.Object);
|
||||
with.Dependency(Cache.Object);
|
||||
with.Dependency(Log.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
with.RequestStartup((container, pipelines, context) =>
|
||||
{
|
||||
context.CurrentUser = new UserIdentity { UserName = "user" };
|
||||
context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List<string> {"Admin"} };
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -233,7 +239,7 @@ namespace PlexRequests.UI.Tests
|
|||
[Test]
|
||||
public void GetUsersSuccessfully()
|
||||
{
|
||||
var users = new PlexFriends { User = new[] { new UserFriends { Username = "abc2" }, } };
|
||||
var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } };
|
||||
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
|
@ -248,9 +254,11 @@ namespace PlexRequests.UI.Tests
|
|||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = result.Body.AsString();
|
||||
|
||||
var body = JsonConvert.DeserializeObject<JObject>(result.Body.AsString());
|
||||
var user = body["users"];
|
||||
Assert.That(body, Is.Not.Null);
|
||||
Assert.That(body, Contains.Substring("abc2"));
|
||||
Assert.That(user.ToString().Contains("abc"), Is.True);
|
||||
|
||||
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
|
||||
AuthMock.Verify(x => x.GetSettings(), Times.Once);
|
||||
|
@ -269,6 +277,7 @@ namespace PlexRequests.UI.Tests
|
|||
with.Header("Accept", "application/json");
|
||||
with.FormValue("username", "Username1");
|
||||
with.FormValue("password", "Password1");
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
@ -324,9 +333,10 @@ namespace PlexRequests.UI.Tests
|
|||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
|
||||
var body = JsonConvert.DeserializeObject<JObject>(result.Body.AsString());
|
||||
var user = (string)body["users"];
|
||||
Assert.That(body, Is.Not.Null);
|
||||
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
|
||||
Assert.That(string.IsNullOrWhiteSpace(user), Is.True);
|
||||
|
||||
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
|
||||
AuthMock.Verify(x => x.GetSettings(), Times.Once);
|
||||
|
|
778
PlexRequests.UI.Tests/ApiModuleTests.cs
Normal file
778
PlexRequests.UI.Tests/ApiModuleTests.cs
Normal file
|
@ -0,0 +1,778 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: ApiModuleTests.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 FluentValidation;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Nancy;
|
||||
using Nancy.Testing;
|
||||
using Nancy.Validation;
|
||||
using Nancy.Validation.FluentValidation;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Constraints;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Models;
|
||||
using PlexRequests.UI.Modules;
|
||||
using PlexRequests.UI.Validators;
|
||||
|
||||
using Ploeh.AutoFixture;
|
||||
|
||||
namespace PlexRequests.UI.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ApiModuleTests
|
||||
{
|
||||
private ConfigurableBootstrapper Bootstrapper { get; set; }
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var requests = fixture.CreateMany<RequestedModel>();
|
||||
var requestMock = new Mock<IRequestService>();
|
||||
var settingsMock = new Mock<ISettingsService<PlexRequestSettings>>();
|
||||
var userRepoMock = new Mock<IRepository<UsersModel>>();
|
||||
var mapperMock = new Mock<ICustomUserMapper>();
|
||||
var authSettingsMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||
var plexSettingsMock = new Mock<ISettingsService<PlexSettings>>();
|
||||
var cpMock = new Mock<ISettingsService<CouchPotatoSettings>>();
|
||||
var sonarrMock = new Mock<ISettingsService<SonarrSettings>>();
|
||||
var sickRageMock = new Mock<ISettingsService<SickRageSettings>>();
|
||||
var headphonesMock = new Mock<ISettingsService<HeadphonesSettings>>();
|
||||
|
||||
var userModels = fixture.CreateMany<UsersModel>().ToList();
|
||||
userModels.Add(new UsersModel
|
||||
{
|
||||
UserName = "user1"
|
||||
});
|
||||
|
||||
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings { ApiKey = "api" });
|
||||
requestMock.Setup(x => x.GetAll()).Returns(requests);
|
||||
requestMock.Setup(x => x.Get(1)).Returns(requests.FirstOrDefault());
|
||||
requestMock.Setup(x => x.Get(99)).Returns(new RequestedModel());
|
||||
requestMock.Setup(x => x.DeleteRequest(It.IsAny<RequestedModel>()));
|
||||
|
||||
userRepoMock.Setup(x => x.GetAll()).Returns(userModels);
|
||||
userRepoMock.Setup(x => x.Update(It.IsAny<UsersModel>())).Returns(true);
|
||||
|
||||
mapperMock.Setup(x => x.ValidateUser("user1", It.IsAny<string>())).Returns(Guid.NewGuid());
|
||||
mapperMock.Setup(x => x.UpdatePassword("user1", "password", "newpassword")).Returns(true);
|
||||
|
||||
authSettingsMock.Setup(x => x.SaveSettings(It.Is<AuthenticationSettings>(c => c.PlexAuthToken.Equals("abc")))).Returns(true);
|
||||
|
||||
plexSettingsMock.Setup(x => x.GetSettings()).Returns(fixture.Create<PlexSettings>());
|
||||
plexSettingsMock.Setup(x => x.SaveSettings(It.Is<PlexSettings>(c => c.Ip.Equals("192")))).Returns(true);
|
||||
|
||||
cpMock.Setup(x => x.GetSettings()).Returns(fixture.Create<CouchPotatoSettings>());
|
||||
cpMock.Setup(x => x.SaveSettings(It.Is<CouchPotatoSettings>(c => c.Ip.Equals("192")))).Returns(true);
|
||||
|
||||
sonarrMock.Setup(x => x.GetSettings()).Returns(fixture.Create<SonarrSettings>());
|
||||
sonarrMock.Setup(x => x.SaveSettings(It.Is<SonarrSettings>(c => c.Ip.Equals("192")))).Returns(true);
|
||||
|
||||
sickRageMock.Setup(x => x.GetSettings()).Returns(fixture.Create<SickRageSettings>());
|
||||
sickRageMock.Setup(x => x.SaveSettings(It.Is<SickRageSettings>(c => c.Ip.Equals("192")))).Returns(true);
|
||||
|
||||
headphonesMock.Setup(x => x.GetSettings()).Returns(fixture.Create<HeadphonesSettings>());
|
||||
headphonesMock.Setup(x => x.SaveSettings(It.Is<HeadphonesSettings>(c => c.Ip.Equals("192")))).Returns(true);
|
||||
|
||||
Bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<ApiRequestModule>();
|
||||
with.Module<ApiUserModule>();
|
||||
with.Module<ApiSettingsModule>();
|
||||
|
||||
with.Dependency(requestMock.Object);
|
||||
with.Dependency(settingsMock.Object);
|
||||
with.Dependency(userRepoMock.Object);
|
||||
with.Dependency(mapperMock.Object);
|
||||
with.Dependency(headphonesMock.Object);
|
||||
with.Dependency(authSettingsMock.Object);
|
||||
with.Dependency(plexSettingsMock.Object);
|
||||
with.Dependency(cpMock.Object);
|
||||
with.Dependency(sonarrMock.Object);
|
||||
with.Dependency(sickRageMock.Object);
|
||||
|
||||
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
with.ModelValidatorLocator(
|
||||
new DefaultValidatorLocator(
|
||||
new List<IModelValidatorFactory>
|
||||
{
|
||||
new FluentValidationValidatorFactory(
|
||||
new DefaultFluentAdapterFactory(new List<IFluentAdapter>()),
|
||||
new List<IValidator> { new RequestedModelValidator(), new UserViewModelValidator(), new PlexValidator() })
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private Action<BrowserContext> GetBrowser()
|
||||
{
|
||||
return with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InvalidApiKey()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Get("/api/requests", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "a");
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<List<RequestedModel>>>(result.Body.AsString());
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllRequests()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Get("/api/requests", GetBrowser());
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<List<RequestedModel>>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Data.Count, Is.GreaterThan(0));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSingleRequest()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Get("/api/requests/1", GetBrowser());
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<List<RequestedModel>>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Data.Count, Is.EqualTo(1));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSingleRequestThatDoesntExist()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Get("/api/requests/99", GetBrowser());
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<List<RequestedModel>>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Data.Count, Is.EqualTo(0));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteARequest()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Delete("/api/requests/1", GetBrowser());
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.True);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteARequestThatDoesNotExist()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Delete("/api/requests/99", GetBrowser());
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.False);
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Null.Or.Empty);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void UpdateUsersPassword()
|
||||
{
|
||||
var model = new UserUpdateViewModel
|
||||
{
|
||||
CurrentPassword = "password",
|
||||
NewPassword = "newpassword"
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Put("/api/credentials/user1", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Not.Null.Or.Empty);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateInvalidUsersPassword()
|
||||
{
|
||||
var model = new UserUpdateViewModel
|
||||
{
|
||||
CurrentPassword = "password",
|
||||
NewPassword = "newpassword"
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Put("/api/credentials/user99", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Null.Or.Empty);
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateUsersInvalidPassword()
|
||||
{
|
||||
var model = new UserUpdateViewModel
|
||||
{
|
||||
CurrentPassword = "password",
|
||||
NewPassword = "password2"
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Put("/api/credentials/user1", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Null.Or.Empty);
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateUsersWithBadModel()
|
||||
{
|
||||
var model = new UserUpdateViewModel
|
||||
{
|
||||
CurrentPassword = null,
|
||||
NewPassword = "password2"
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Put("/api/credentials/user1", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string[]>>(result.Body.AsString());
|
||||
Assert.That(body.Data.Length, Is.GreaterThan(0));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetApiKey()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/apikey", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.Query("username", "user1");
|
||||
with.Query("password", "password");
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Not.Null.Or.Empty);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetApiKeyWithBadCredentials()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/apikey", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.Query("username", "user");
|
||||
with.Query("password", "password");
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Null.Or.Empty);
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.Not.Null.Or.Empty);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void SaveNewAuthSettings()
|
||||
{
|
||||
var model = new AuthenticationSettings
|
||||
{
|
||||
Id = 1,
|
||||
PlexAuthToken = "abc",
|
||||
DeniedUsers = "abc",
|
||||
UsePassword = false,
|
||||
UserAuthentication = true
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("api/settings/authentication", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<string>>(result.Body.AsString());
|
||||
Assert.That(body.Data, Is.Not.Null.Or.Empty);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null.Or.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPlexSettings()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/settings/plex", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<PlexSettings>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SavePlexSettings()
|
||||
{
|
||||
var model = new PlexSettings()
|
||||
{
|
||||
Port = 231,
|
||||
Ip = "192",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/plex", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(true));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
[Test]
|
||||
public void SaveBadPlexSettings()
|
||||
{
|
||||
var model = new PlexSettings
|
||||
{
|
||||
Ip = "q",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/plex", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(false));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.EqualTo("Could not update the settings"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCpSettings()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/settings/couchpotato", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<CouchPotatoSettings>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveCpSettings()
|
||||
{
|
||||
var model = new CouchPotatoSettings
|
||||
{
|
||||
Port = 231,
|
||||
Ip = "192",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/couchpotato", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(true));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
[Test]
|
||||
public void SaveBadCpSettings()
|
||||
{
|
||||
var model = new CouchPotatoSettings
|
||||
{
|
||||
Ip = "q",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/couchpotato", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(false));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.EqualTo("Could not update the settings"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSonarrSettings()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/settings/sonarr", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<SonarrSettings>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveSonarrSettings()
|
||||
{
|
||||
var model = new SonarrSettings
|
||||
{
|
||||
Port = 231,
|
||||
Ip = "192",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/sonarr", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(true));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
[Test]
|
||||
public void SaveBadSonarrSettings()
|
||||
{
|
||||
var model = new SonarrSettings
|
||||
{
|
||||
Ip = "q",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/sonarr", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(false));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.EqualTo("Could not update the settings"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSickRageSettings()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/settings/sickrage", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<SickRageSettings>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveSickRageSettings()
|
||||
{
|
||||
var model = new SickRageSettings
|
||||
{
|
||||
Port = 231,
|
||||
Ip = "192",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/sickrage", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(true));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
[Test]
|
||||
public void SaveBadSickRageSettings()
|
||||
{
|
||||
var model = new SickRageSettings
|
||||
{
|
||||
Ip = "q",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/sickrage", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(false));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.EqualTo("Could not update the settings"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetHeadphonesSettings()
|
||||
{
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/api/settings/headphones", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<HeadphonesSettings>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.Not.Null);
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveHeadphonesSettings()
|
||||
{
|
||||
var model = new HeadphonesSettings
|
||||
{
|
||||
Port = 231,
|
||||
Ip = "192",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/headphones", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(true));
|
||||
Assert.That(body.Error, Is.False);
|
||||
Assert.That(body.ErrorMessage, Is.Null);
|
||||
}
|
||||
[Test]
|
||||
public void SaveBadHeadphonesSettings()
|
||||
{
|
||||
var model = new HeadphonesSettings
|
||||
{
|
||||
Ip = "q",
|
||||
Ssl = true,
|
||||
};
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/api/settings/headphones", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
Assert.That(body.Data, Is.EqualTo(false));
|
||||
Assert.That(body.Error, Is.True);
|
||||
Assert.That(body.ErrorMessage, Is.EqualTo("Could not update the settings"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TestCaseSource(nameof(AuthSettingsData))]
|
||||
public object SaveNewAuthSettings(object model)
|
||||
{
|
||||
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("api/settings/authentication", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
with.Header("Accept", "application/json");
|
||||
with.Query("apikey", "api");
|
||||
with.JsonBody(model);
|
||||
});
|
||||
|
||||
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
|
||||
var body = JsonConvert.DeserializeObject<ApiModel<bool>>(result.Body.AsString());
|
||||
|
||||
var retVal = new List<string> { body.ErrorMessage, body.Error.ToString(), body.Data.ToString() };
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> AuthSettingsData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return
|
||||
new TestCaseData(new AuthenticationSettings { Id = 1, PlexAuthToken = "abc", DeniedUsers = "abc", UsePassword = false, UserAuthentication = true })
|
||||
.Returns(new List<string> { null, false.ToString(), true.ToString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
|
@ -46,6 +46,11 @@
|
|||
<HintPath>..\packages\FluentScheduler.3.1.46\lib\net40\FluentScheduler.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentValidation, Version=6.2.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentValidation.6.2.1.0\lib\Net45\FluentValidation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Moq, Version=4.2.1510.2205, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -58,6 +63,10 @@
|
|||
<HintPath>..\packages\Nancy.Testing.1.4.1\lib\net40\Nancy.Testing.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Nancy.Validation.FluentValidation, Version=1.4.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.Validation.FluentValidation.1.4.1\lib\net40\Nancy.Validation.FluentValidation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Nancy.ViewEngines.Razor, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.Viewengines.Razor.1.4.3\lib\net40\Nancy.ViewEngines.Razor.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -90,6 +99,7 @@
|
|||
<Otherwise />
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="ApiModuleTests.cs" />
|
||||
<Compile Include="BootstrapperExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestRootPathProvider.cs" />
|
||||
|
|
|
@ -30,7 +30,6 @@ using Moq;
|
|||
|
||||
using Nancy;
|
||||
using Nancy.Testing;
|
||||
using Nancy.TinyIoc;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
@ -40,18 +39,18 @@ using PlexRequests.Api.Interfaces;
|
|||
using PlexRequests.Api.Models.Plex;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
using PlexRequests.UI.Modules;
|
||||
|
||||
namespace PlexRequests.UI.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Ignore("Needs some work")]
|
||||
//[Ignore("Needs some work")]
|
||||
public class UserLoginModuleTests
|
||||
{
|
||||
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
|
||||
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
|
||||
private ConfigurableBootstrapper Bootstrapper { get; set; }
|
||||
private Mock<IPlexApi> PlexMock { get; set; }
|
||||
|
||||
[SetUp]
|
||||
|
@ -60,6 +59,15 @@ namespace PlexRequests.UI.Tests
|
|||
AuthMock = new Mock<ISettingsService<AuthenticationSettings>>();
|
||||
PlexMock = new Mock<IPlexApi>();
|
||||
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
|
||||
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
|
||||
Bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(PlexRequestMock.Object);
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -68,18 +76,11 @@ namespace PlexRequests.UI.Tests
|
|||
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
|
||||
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.Dependency(PlexRequestMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -103,17 +104,10 @@ namespace PlexRequests.UI.Tests
|
|||
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
|
||||
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -140,7 +134,7 @@ namespace PlexRequests.UI.Tests
|
|||
{
|
||||
new UserFriends
|
||||
{
|
||||
Username = "abc",
|
||||
Title = "abc",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -149,17 +143,9 @@ namespace PlexRequests.UI.Tests
|
|||
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
|
||||
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -196,17 +182,9 @@ namespace PlexRequests.UI.Tests
|
|||
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
|
||||
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
|
@ -237,7 +215,7 @@ namespace PlexRequests.UI.Tests
|
|||
{
|
||||
new UserFriends
|
||||
{
|
||||
Username = "abc",
|
||||
Title = "abc",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -254,17 +232,9 @@ namespace PlexRequests.UI.Tests
|
|||
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
|
||||
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -307,17 +277,10 @@ namespace PlexRequests.UI.Tests
|
|||
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
|
||||
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -344,17 +307,9 @@ namespace PlexRequests.UI.Tests
|
|||
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" };
|
||||
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -376,17 +331,9 @@ namespace PlexRequests.UI.Tests
|
|||
[Test]
|
||||
public void Logout()
|
||||
{
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Get("/userlogin/logout", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -415,17 +362,9 @@ namespace PlexRequests.UI.Tests
|
|||
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
|
||||
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } });
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
@ -470,17 +409,9 @@ namespace PlexRequests.UI.Tests
|
|||
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
|
||||
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
|
||||
|
||||
var bootstrapper = new ConfigurableBootstrapper(with =>
|
||||
{
|
||||
with.Module<UserLoginModule>();
|
||||
with.Dependency(AuthMock.Object);
|
||||
with.Dependency(PlexMock.Object);
|
||||
with.RootPathProvider<TestRootPathProvider>();
|
||||
});
|
||||
Bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
bootstrapper.WithSession(new Dictionary<string, object>());
|
||||
|
||||
var browser = new Browser(bootstrapper);
|
||||
var browser = new Browser(Bootstrapper);
|
||||
var result = browser.Post("/userlogin", with =>
|
||||
{
|
||||
with.HttpRequest();
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
<package id="AutoFixture" version="3.40.0" targetFramework="net452" />
|
||||
<package id="CsQuery" version="1.3.3" targetFramework="net46" />
|
||||
<package id="FluentScheduler" version="3.1.46" targetFramework="net46" />
|
||||
<package id="FluentValidation" version="6.2.1.0" targetFramework="net46" />
|
||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net46" />
|
||||
<package id="Moq" version="4.2.1510.2205" targetFramework="net452" />
|
||||
<package id="Nancy" version="1.4.3" targetFramework="net46" />
|
||||
<package id="Nancy.Testing" version="1.4.1" targetFramework="net46" />
|
||||
<package id="Nancy.Validation.FluentValidation" version="1.4.1" targetFramework="net46" />
|
||||
<package id="Nancy.Viewengines.Razor" version="1.4.3" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net46" />
|
||||
<package id="NUnit" version="3.2.0" targetFramework="net46" />
|
||||
|
|
|
@ -50,6 +50,12 @@ using PlexRequests.Store;
|
|||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using Nancy.Json;
|
||||
|
||||
using PlexRequests.Services.Jobs;
|
||||
using PlexRequests.UI.Jobs;
|
||||
|
||||
using Quartz.Spi;
|
||||
|
||||
namespace PlexRequests.UI
|
||||
{
|
||||
|
@ -62,8 +68,7 @@ namespace PlexRequests.UI
|
|||
|
||||
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
|
||||
{
|
||||
container.Register<IUserMapper, UserMapper>();
|
||||
container.Register<ISqliteConfiguration, DbConfiguration>(new DbConfiguration(new SqliteFactory()));
|
||||
|
||||
container.Register<ICacheProvider, MemoryCacheProvider>().AsSingleton();
|
||||
|
||||
// Settings
|
||||
|
@ -77,19 +82,23 @@ namespace PlexRequests.UI
|
|||
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
|
||||
container.Register<ISettingsService<PushoverNotificationSettings>, SettingsServiceV2<PushoverNotificationSettings>>();
|
||||
container.Register<ISettingsService<HeadphonesSettings>, SettingsServiceV2<HeadphonesSettings>>();
|
||||
container.Register<ISettingsService<LogSettings>, SettingsServiceV2<LogSettings>>();
|
||||
container.Register<ISettingsService<SlackNotificationSettings>, SettingsServiceV2<SlackNotificationSettings>>();
|
||||
|
||||
// Repo's
|
||||
container.Register<IRepository<LogEntity>, GenericRepository<LogEntity>>();
|
||||
container.Register<IRepository<UsersToNotify>, GenericRepository<UsersToNotify>>();
|
||||
container.Register<IRepository<ScheduledJobs>, GenericRepository<ScheduledJobs>>();
|
||||
container.Register<IRequestService, JsonRequestService>();
|
||||
container.Register<ISettingsRepository, SettingsJsonRepository>();
|
||||
container.Register<IJobRecord, JobRecord>();
|
||||
|
||||
// Services
|
||||
container.Register<IAvailabilityChecker, PlexAvailabilityChecker>();
|
||||
container.Register<ICouchPotatoCacher, CouchPotatoCacher>();
|
||||
container.Register<ISonarrCacher, SonarrCacher>();
|
||||
container.Register<ISickRageCacher, SickRageCacher>();
|
||||
container.Register<IConfigurationReader, ConfigurationReader>();
|
||||
container.Register<IIntervals, UpdateInterval>();
|
||||
container.Register<IJobFactory, CustomJobFactory>();
|
||||
|
||||
// Api's
|
||||
container.Register<ICouchPotatoApi, CouchPotatoApi>();
|
||||
|
@ -100,18 +109,28 @@ namespace PlexRequests.UI
|
|||
container.Register<IPlexApi, PlexApi>();
|
||||
container.Register<IMusicBrainzApi, MusicBrainzApi>();
|
||||
container.Register<IHeadphonesApi, HeadphonesApi>();
|
||||
container.Register<ISlackApi, SlackApi>();
|
||||
|
||||
// NotificationService
|
||||
container.Register<INotificationService, NotificationService>().AsSingleton();
|
||||
|
||||
JsonSettings.MaxJsonLength = int.MaxValue;
|
||||
|
||||
SubscribeAllObservers(container);
|
||||
base.ConfigureRequestContainer(container, context);
|
||||
var loc = ServiceLocator.Instance;
|
||||
loc.SetContainer(container);
|
||||
}
|
||||
|
||||
|
||||
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
|
||||
{
|
||||
container.Register<ISqliteConfiguration, DbConfiguration>(new DbConfiguration(new SqliteFactory()));
|
||||
container.Register<IRepository<UsersModel>, UserRepository<UsersModel>>();
|
||||
container.Register<IUserMapper, UserMapper>();
|
||||
container.Register<ICustomUserMapper, UserMapper>();
|
||||
|
||||
|
||||
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
|
||||
|
||||
StaticConfiguration.DisableErrorTraces = false;
|
||||
|
@ -146,6 +165,8 @@ namespace PlexRequests.UI
|
|||
nancyConventions.StaticContentsConventions.Add(
|
||||
StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content")
|
||||
);
|
||||
|
||||
nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui");
|
||||
}
|
||||
|
||||
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
|
||||
|
@ -174,6 +195,26 @@ namespace PlexRequests.UI
|
|||
{
|
||||
notificationService.Subscribe(new PushoverNotification(container.Resolve<IPushoverApi>(), pushoverService));
|
||||
}
|
||||
|
||||
var slackService = container.Resolve<ISettingsService<SlackNotificationSettings>>();
|
||||
var slackSettings = slackService.GetSettings();
|
||||
if (slackSettings.Enabled)
|
||||
{
|
||||
notificationService.Subscribe(new SlackNotification(container.Resolve<ISlackApi>(), slackService));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
|
||||
{
|
||||
//CORS Enable
|
||||
pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) =>
|
||||
{
|
||||
ctx.Response.WithHeader("Access-Control-Allow-Origin", "*")
|
||||
.WithHeader("Access-Control-Allow-Methods", "POST,GET")
|
||||
.WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type");
|
||||
|
||||
});
|
||||
base.RequestStartup(container, pipelines, context);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -42,11 +42,28 @@ label {
|
|||
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: #4e5d6c; }
|
||||
|
||||
.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%;
|
||||
|
@ -220,3 +237,47 @@ label {
|
|||
border-radius: 0 0.25rem 0.25rem 0 !important;
|
||||
padding: 12px 8px; }
|
||||
|
||||
#updateAvailable {
|
||||
background-color: #ffa400;
|
||||
text-align: center;
|
||||
font-size: 15px; }
|
||||
|
||||
.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; }
|
||||
|
1
PlexRequests.UI/Content/Themes/OriginalBootstrapCustom.min.css
vendored
Normal file
1
PlexRequests.UI/Content/Themes/OriginalBootstrapCustom.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,282 +1,349 @@
|
|||
$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;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: .25rem $i;
|
||||
}
|
||||
|
||||
.multiSelect {
|
||||
background-color: $form-color;
|
||||
}
|
||||
|
||||
.form-control-custom {
|
||||
background-color: $form-color $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.active > a,
|
||||
.nav-tabs > li.active > a:hover,
|
||||
.nav-tabs > li.active > a:focus {
|
||||
background: #4e5d6c;
|
||||
}
|
||||
|
||||
.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: $form-color;
|
||||
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: $form-color-lighter;
|
||||
}
|
||||
|
||||
.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: 13px;
|
||||
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;
|
||||
}
|
||||
$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;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: .25rem $i;
|
||||
}
|
||||
|
||||
.multiSelect {
|
||||
background-color: $form-color;
|
||||
}
|
||||
|
||||
.form-control-custom {
|
||||
background-color: $form-color $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: #4e5d6c;
|
||||
}
|
||||
|
||||
.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: $form-color;
|
||||
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: $form-color-lighter;
|
||||
}
|
||||
|
||||
.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: 13px;
|
||||
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: rgb(255, 164, 0);
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.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; }
|
7007
PlexRequests.UI/Content/Themes/PlexBootstrap.css
Normal file
7007
PlexRequests.UI/Content/Themes/PlexBootstrap.css
Normal file
File diff suppressed because it is too large
Load diff
283
PlexRequests.UI/Content/Themes/PlexBootstrapCustom.css
Normal file
283
PlexRequests.UI/Content/Themes/PlexBootstrapCustom.css
Normal file
|
@ -0,0 +1,283 @@
|
|||
@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; } }
|
||||
|
||||
.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: 13px;
|
||||
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: #ffa400;
|
||||
text-align: center;
|
||||
font-size: 15px; }
|
||||
|
||||
.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; }
|
||||
|
1
PlexRequests.UI/Content/Themes/PlexBootstrapCustom.min.css
vendored
Normal file
1
PlexRequests.UI/Content/Themes/PlexBootstrapCustom.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
350
PlexRequests.UI/Content/Themes/PlexBootstrapCustom.scss
Normal file
350
PlexRequests.UI/Content/Themes/PlexBootstrapCustom.scss
Normal file
|
@ -0,0 +1,350 @@
|
|||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
.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: 13px;
|
||||
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: rgb(255, 164, 0);
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.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; }
|
266
PlexRequests.UI/Content/awesome-bootstrap-checkbox.css
Normal file
266
PlexRequests.UI/Content/awesome-bootstrap-checkbox.css
Normal file
|
@ -0,0 +1,266 @@
|
|||
@charset "UTF-8";
|
||||
.abc-checkbox {
|
||||
padding-left: 20px; }
|
||||
.abc-checkbox label {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
padding-left: 5px; }
|
||||
.abc-checkbox label::before {
|
||||
cursor: pointer;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
left: 0;
|
||||
margin-left: -20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
background-color: #fff; }
|
||||
.abc-checkbox label::after {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin-left: -20px;
|
||||
padding-left: 3px;
|
||||
padding-top: 1px;
|
||||
font-size: 11px;
|
||||
color: #55595c; }
|
||||
.abc-checkbox input[type="checkbox"],
|
||||
.abc-checkbox input[type="radio"] {
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
z-index: 1; }
|
||||
.abc-checkbox input[type="checkbox"]:focus + label::before,
|
||||
.abc-checkbox input[type="radio"]:focus + label::before {
|
||||
outline: thin dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px; }
|
||||
.abc-checkbox input[type="checkbox"]:checked + label::after,
|
||||
.abc-checkbox input[type="radio"]:checked + label::after {
|
||||
font-family: "FontAwesome";
|
||||
content: ""; }
|
||||
.abc-checkbox input[type="checkbox"]:indeterminate + label::after,
|
||||
.abc-checkbox input[type="radio"]:indeterminate + label::after {
|
||||
display: block;
|
||||
content: "";
|
||||
width: 10px;
|
||||
height: 3px;
|
||||
background-color: #555555;
|
||||
border-radius: 2px;
|
||||
margin-left: -16.5px;
|
||||
margin-top: 7px; }
|
||||
.abc-checkbox input[type="checkbox"]:disabled + label,
|
||||
.abc-checkbox input[type="radio"]:disabled + label {
|
||||
opacity: 0.65; }
|
||||
.abc-checkbox input[type="checkbox"]:disabled + label::before,
|
||||
.abc-checkbox input[type="radio"]:disabled + label::before {
|
||||
background-color: #eceeef;
|
||||
cursor: not-allowed; }
|
||||
.abc-checkbox.abc-checkbox-circle label::before {
|
||||
border-radius: 50%; }
|
||||
.abc-checkbox.checkbox-inline {
|
||||
margin-top: 0; }
|
||||
|
||||
.abc-checkbox-primary input[type="checkbox"]:checked + label::before,
|
||||
.abc-checkbox-primary input[type="radio"]:checked + label::before {
|
||||
background-color: #0275d8;
|
||||
border-color: #0275d8; }
|
||||
|
||||
.abc-checkbox-primary input[type="checkbox"]:checked + label::after,
|
||||
.abc-checkbox-primary input[type="radio"]:checked + label::after {
|
||||
color: #fff; }
|
||||
|
||||
.abc-checkbox-danger input[type="checkbox"]:checked + label::before,
|
||||
.abc-checkbox-danger input[type="radio"]:checked + label::before {
|
||||
background-color: #d9534f;
|
||||
border-color: #d9534f; }
|
||||
|
||||
.abc-checkbox-danger input[type="checkbox"]:checked + label::after,
|
||||
.abc-checkbox-danger input[type="radio"]:checked + label::after {
|
||||
color: #fff; }
|
||||
|
||||
.abc-checkbox-info input[type="checkbox"]:checked + label::before,
|
||||
.abc-checkbox-info input[type="radio"]:checked + label::before {
|
||||
background-color: #5bc0de;
|
||||
border-color: #5bc0de; }
|
||||
|
||||
.abc-checkbox-info input[type="checkbox"]:checked + label::after,
|
||||
.abc-checkbox-info input[type="radio"]:checked + label::after {
|
||||
color: #fff; }
|
||||
|
||||
.abc-checkbox-warning input[type="checkbox"]:checked + label::before,
|
||||
.abc-checkbox-warning input[type="radio"]:checked + label::before {
|
||||
background-color: #f0ad4e;
|
||||
border-color: #f0ad4e; }
|
||||
|
||||
.abc-checkbox-warning input[type="checkbox"]:checked + label::after,
|
||||
.abc-checkbox-warning input[type="radio"]:checked + label::after {
|
||||
color: #fff; }
|
||||
|
||||
.abc-checkbox-success input[type="checkbox"]:checked + label::before,
|
||||
.abc-checkbox-success input[type="radio"]:checked + label::before {
|
||||
background-color: #5cb85c;
|
||||
border-color: #5cb85c; }
|
||||
|
||||
.abc-checkbox-success input[type="checkbox"]:checked + label::after,
|
||||
.abc-checkbox-success input[type="radio"]:checked + label::after {
|
||||
color: #fff; }
|
||||
|
||||
.abc-checkbox-primary input[type="checkbox"]:indeterminate + label::before,
|
||||
.abc-checkbox-primary input[type="radio"]:indeterminate + label::before {
|
||||
background-color: #0275d8;
|
||||
border-color: #0275d8; }
|
||||
|
||||
.abc-checkbox-primary input[type="checkbox"]:indeterminate + label::after,
|
||||
.abc-checkbox-primary input[type="radio"]:indeterminate + label::after {
|
||||
background-color: #fff; }
|
||||
|
||||
.abc-checkbox-danger input[type="checkbox"]:indeterminate + label::before,
|
||||
.abc-checkbox-danger input[type="radio"]:indeterminate + label::before {
|
||||
background-color: #d9534f;
|
||||
border-color: #d9534f; }
|
||||
|
||||
.abc-checkbox-danger input[type="checkbox"]:indeterminate + label::after,
|
||||
.abc-checkbox-danger input[type="radio"]:indeterminate + label::after {
|
||||
background-color: #fff; }
|
||||
|
||||
.abc-checkbox-info input[type="checkbox"]:indeterminate + label::before,
|
||||
.abc-checkbox-info input[type="radio"]:indeterminate + label::before {
|
||||
background-color: #5bc0de;
|
||||
border-color: #5bc0de; }
|
||||
|
||||
.abc-checkbox-info input[type="checkbox"]:indeterminate + label::after,
|
||||
.abc-checkbox-info input[type="radio"]:indeterminate + label::after {
|
||||
background-color: #fff; }
|
||||
|
||||
.abc-checkbox-warning input[type="checkbox"]:indeterminate + label::before,
|
||||
.abc-checkbox-warning input[type="radio"]:indeterminate + label::before {
|
||||
background-color: #f0ad4e;
|
||||
border-color: #f0ad4e; }
|
||||
|
||||
.abc-checkbox-warning input[type="checkbox"]:indeterminate + label::after,
|
||||
.abc-checkbox-warning input[type="radio"]:indeterminate + label::after {
|
||||
background-color: #fff; }
|
||||
|
||||
.abc-checkbox-success input[type="checkbox"]:indeterminate + label::before,
|
||||
.abc-checkbox-success input[type="radio"]:indeterminate + label::before {
|
||||
background-color: #5cb85c;
|
||||
border-color: #5cb85c; }
|
||||
|
||||
.abc-checkbox-success input[type="checkbox"]:indeterminate + label::after,
|
||||
.abc-checkbox-success input[type="radio"]:indeterminate + label::after {
|
||||
background-color: #fff; }
|
||||
|
||||
.abc-radio {
|
||||
padding-left: 20px; }
|
||||
.abc-radio label {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
padding-left: 5px; }
|
||||
.abc-radio label::before {
|
||||
content: "";
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
left: 0;
|
||||
margin-left: -20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 50%;
|
||||
background-color: #fff; }
|
||||
.abc-radio label::after {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
content: " ";
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
margin-left: -20px;
|
||||
border-radius: 50%;
|
||||
background-color: #55595c;
|
||||
transform: scale(0, 0);
|
||||
transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33); }
|
||||
.abc-radio input[type="radio"] {
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
z-index: 1; }
|
||||
.abc-radio input[type="radio"]:focus + label::before {
|
||||
outline: thin dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px; }
|
||||
.abc-radio input[type="radio"]:checked + label::after {
|
||||
transform: scale(1, 1); }
|
||||
.abc-radio input[type="radio"]:disabled + label {
|
||||
opacity: 0.65; }
|
||||
.abc-radio input[type="radio"]:disabled + label::before {
|
||||
cursor: not-allowed; }
|
||||
.abc-radio.radio-inline {
|
||||
margin-top: 0; }
|
||||
|
||||
.abc-radio-primary input[type="radio"] + label::after {
|
||||
background-color: #0275d8; }
|
||||
|
||||
.abc-radio-primary input[type="radio"]:checked + label::before {
|
||||
border-color: #0275d8; }
|
||||
|
||||
.abc-radio-primary input[type="radio"]:checked + label::after {
|
||||
background-color: #0275d8; }
|
||||
|
||||
.abc-radio-danger input[type="radio"] + label::after {
|
||||
background-color: #d9534f; }
|
||||
|
||||
.abc-radio-danger input[type="radio"]:checked + label::before {
|
||||
border-color: #d9534f; }
|
||||
|
||||
.abc-radio-danger input[type="radio"]:checked + label::after {
|
||||
background-color: #d9534f; }
|
||||
|
||||
.abc-radio-info input[type="radio"] + label::after {
|
||||
background-color: #5bc0de; }
|
||||
|
||||
.abc-radio-info input[type="radio"]:checked + label::before {
|
||||
border-color: #5bc0de; }
|
||||
|
||||
.abc-radio-info input[type="radio"]:checked + label::after {
|
||||
background-color: #5bc0de; }
|
||||
|
||||
.abc-radio-warning input[type="radio"] + label::after {
|
||||
background-color: #f0ad4e; }
|
||||
|
||||
.abc-radio-warning input[type="radio"]:checked + label::before {
|
||||
border-color: #f0ad4e; }
|
||||
|
||||
.abc-radio-warning input[type="radio"]:checked + label::after {
|
||||
background-color: #f0ad4e; }
|
||||
|
||||
.abc-radio-success input[type="radio"] + label::after {
|
||||
background-color: #5cb85c; }
|
||||
|
||||
.abc-radio-success input[type="radio"]:checked + label::before {
|
||||
border-color: #5cb85c; }
|
||||
|
||||
.abc-radio-success input[type="radio"]:checked + label::after {
|
||||
background-color: #5cb85c; }
|
||||
|
||||
input[type="checkbox"].styled:checked + label:after,
|
||||
input[type="radio"].styled:checked + label:after {
|
||||
font-family: "FontAwesome";
|
||||
content: ""; }
|
||||
|
||||
input[type="checkbox"] .styled:checked + label::before,
|
||||
input[type="radio"] .styled:checked + label::before {
|
||||
color: #fff; }
|
||||
|
||||
input[type="checkbox"] .styled:checked + label::after,
|
||||
input[type="radio"] .styled:checked + label::after {
|
||||
color: #fff; }
|
250
PlexRequests.UI/Content/awesome-bootstrap-checkbox.scss
Normal file
250
PlexRequests.UI/Content/awesome-bootstrap-checkbox.scss
Normal file
|
@ -0,0 +1,250 @@
|
|||
|
||||
//
|
||||
// Checkboxes
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
$font-family-icon: 'FontAwesome' !default;
|
||||
$fa-var-check: "\f00c" !default;
|
||||
$check-icon: $fa-var-check !default;
|
||||
|
||||
@mixin checkbox-variant($parent, $color) {
|
||||
#{$parent} input[type="checkbox"]:checked + label,
|
||||
#{$parent} input[type="radio"]:checked + label {
|
||||
&::before {
|
||||
background-color: $color;
|
||||
border-color: $color;
|
||||
}
|
||||
&::after{
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin checkbox-variant-indeterminate($parent, $color) {
|
||||
#{$parent} input[type="checkbox"]:indeterminate + label,
|
||||
#{$parent} input[type="radio"]:indeterminate + label {
|
||||
&::before {
|
||||
background-color: $color;
|
||||
border-color: $color;
|
||||
}
|
||||
&::after{
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.abc-checkbox{
|
||||
padding-left: 20px;
|
||||
|
||||
label{
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
padding-left: 5px;
|
||||
|
||||
&::before{
|
||||
cursor: pointer;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
left: 0;
|
||||
margin-left: -20px;
|
||||
border: 1px solid $input-border-color;
|
||||
border-radius: 3px;
|
||||
background-color: #fff;
|
||||
@include transition(border 0.15s ease-in-out, color 0.15s ease-in-out);
|
||||
}
|
||||
|
||||
&::after{
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin-left: -20px;
|
||||
padding-left: 3px;
|
||||
padding-top: 1px;
|
||||
font-size: 11px;
|
||||
color: $input-color;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
|
||||
&:focus + label::before{
|
||||
@include tab-focus();
|
||||
}
|
||||
|
||||
&:checked + label::after{
|
||||
font-family: $font-family-icon;
|
||||
content: $check-icon;
|
||||
}
|
||||
|
||||
&:indeterminate + label::after{
|
||||
display: block;
|
||||
content: "";
|
||||
width: 10px;
|
||||
height: 3px;
|
||||
background-color: #555555;
|
||||
border-radius: 2px;
|
||||
margin-left: -16.5px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
&:disabled + label{
|
||||
opacity: 0.65;
|
||||
|
||||
&::before{
|
||||
background-color: $input-bg-disabled;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.abc-checkbox-circle label::before{
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.checkbox-inline{
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include checkbox-variant('.abc-checkbox-primary', $brand-primary);
|
||||
@include checkbox-variant('.abc-checkbox-danger', $brand-danger);
|
||||
@include checkbox-variant('.abc-checkbox-info', $brand-info);
|
||||
@include checkbox-variant('.abc-checkbox-warning', $brand-warning);
|
||||
@include checkbox-variant('.abc-checkbox-success', $brand-success);
|
||||
|
||||
|
||||
@include checkbox-variant-indeterminate('.abc-checkbox-primary', $brand-primary);
|
||||
@include checkbox-variant-indeterminate('.abc-checkbox-danger', $brand-danger);
|
||||
@include checkbox-variant-indeterminate('.abc-checkbox-info', $brand-info);
|
||||
@include checkbox-variant-indeterminate('.abc-checkbox-warning', $brand-warning);
|
||||
@include checkbox-variant-indeterminate('.abc-checkbox-success', $brand-success);
|
||||
|
||||
//
|
||||
// Radios
|
||||
// --------------------------------------------------
|
||||
|
||||
@mixin radio-variant($parent, $color) {
|
||||
#{$parent} input[type="radio"]{
|
||||
+ label{
|
||||
&::after{
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
&:checked + label{
|
||||
&::before {
|
||||
border-color: $color;
|
||||
}
|
||||
&::after{
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.abc-radio{
|
||||
padding-left: 20px;
|
||||
|
||||
label{
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
padding-left: 5px;
|
||||
|
||||
&::before{
|
||||
cursor: pointer;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
left: 0;
|
||||
margin-left: -20px;
|
||||
border: 1px solid $input-border-color;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
@include transition(border 0.15s ease-in-out);
|
||||
}
|
||||
|
||||
&::after{
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
content: " ";
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
margin-left: -20px;
|
||||
border-radius: 50%;
|
||||
background-color: $input-color;
|
||||
transform: scale(0, 0);
|
||||
|
||||
transition: transform .1s cubic-bezier(.8,-0.33,.2,1.33);
|
||||
//curve - http://cubic-bezier.com/#.8,-0.33,.2,1.33
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"]{
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
|
||||
&:focus + label::before{
|
||||
@include tab-focus();
|
||||
}
|
||||
|
||||
&:checked + label::after{
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
&:disabled + label{
|
||||
opacity: 0.65;
|
||||
|
||||
&::before{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.radio-inline{
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include radio-variant('.abc-radio-primary', $brand-primary);
|
||||
@include radio-variant('.abc-radio-danger', $brand-danger);
|
||||
@include radio-variant('.abc-radio-info', $brand-info);
|
||||
@include radio-variant('.abc-radio-warning', $brand-warning);
|
||||
@include radio-variant('.abc-radio-success', $brand-success);
|
||||
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
&.styled:checked + label:after {
|
||||
font-family: $font-family-icon;
|
||||
content: $check-icon;
|
||||
}
|
||||
.styled:checked + label {
|
||||
&::before {
|
||||
color: #fff;
|
||||
}
|
||||
&::after {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
1
PlexRequests.UI/Content/custom.min.css
vendored
1
PlexRequests.UI/Content/custom.min.css
vendored
|
@ -1 +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;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !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:.5rem !important;font-size:16px !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.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:#4e5d6c;color:#eee;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 .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.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:13px;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 !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;}
|
File diff suppressed because it is too large
Load diff
|
@ -1,348 +1,462 @@
|
|||
Handlebars.registerHelper('if_eq', function (a, b, opts) {
|
||||
if (a == b)
|
||||
return opts.fn(this);
|
||||
else
|
||||
return opts.inverse(this);
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(function () {
|
||||
|
||||
var searchSource = $("#search-template").html();
|
||||
var musicSource = $("#music-template").html();
|
||||
var searchTemplate = Handlebars.compile(searchSource);
|
||||
var musicTemplate = Handlebars.compile(musicSource);
|
||||
|
||||
var base = $('#baseUrl').text();
|
||||
|
||||
var searchTimer = 0;
|
||||
|
||||
// fix for selecting a default tab
|
||||
var $tabs = $('#nav-tabs').children('li');
|
||||
if ($tabs.filter(function (li) { return $(li).hasClass('active') }).length <= 0) {
|
||||
$tabs.first().children('a:first-child').tab('show');
|
||||
}
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
focusSearch($($(e.target).attr('href')));
|
||||
});
|
||||
focusSearch($('li.active a', '#nav-tabs').first().attr('href'));
|
||||
|
||||
// Type in movie search
|
||||
$("#movieSearchContent").on("input", function () {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
searchTimer = setTimeout(movieSearch, 400);
|
||||
|
||||
});
|
||||
|
||||
$('#moviesComingSoon').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
moviesComingSoon();
|
||||
});
|
||||
|
||||
$('#moviesInTheaters').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
moviesInTheaters();
|
||||
});
|
||||
|
||||
// Type in TV search
|
||||
$("#tvSearchContent").on("input", function () {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
searchTimer = setTimeout(tvSearch, 400);
|
||||
});
|
||||
|
||||
// Click TV dropdown option
|
||||
$(document).on("click", ".dropdownTv", function (e) {
|
||||
e.preventDefault();
|
||||
var buttonId = e.target.id;
|
||||
if ($("#" + buttonId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + buttonId).prop("disabled", true);
|
||||
loadingButton(buttonId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + buttonId);
|
||||
var data = $form.serialize();
|
||||
var seasons = $(this).attr("season-select");
|
||||
if (seasons === "2") {
|
||||
// Send over the latest
|
||||
data = data + "&seasons=latest";
|
||||
}
|
||||
if (seasons === "1") {
|
||||
// Send over the first season
|
||||
data = data + "&seasons=first";
|
||||
|
||||
}
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
|
||||
sendRequestAjax(data, type, url, buttonId);
|
||||
});
|
||||
|
||||
// Search Music
|
||||
$("#musicSearchContent").on("input", function () {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
searchTimer = setTimeout(musicSearch, 400);
|
||||
|
||||
});
|
||||
|
||||
// Click Request for movie
|
||||
$(document).on("click", ".requestMovie", function (e) {
|
||||
e.preventDefault();
|
||||
var buttonId = e.target.id;
|
||||
if ($("#" + buttonId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + buttonId).prop("disabled", true);
|
||||
loadingButton(buttonId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + buttonId);
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
var data = $form.serialize();
|
||||
|
||||
sendRequestAjax(data, type, url, buttonId);
|
||||
|
||||
});
|
||||
|
||||
// Click Request for album
|
||||
$(document).on("click", ".requestAlbum", function (e) {
|
||||
e.preventDefault();
|
||||
var buttonId = e.target.id;
|
||||
if ($("#" + buttonId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + buttonId).prop("disabled", true);
|
||||
loadingButton(buttonId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + buttonId);
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
var data = $form.serialize();
|
||||
|
||||
sendRequestAjax(data, type, url, buttonId);
|
||||
});
|
||||
|
||||
function focusSearch($content) {
|
||||
if ($content.length > 0) {
|
||||
$('input[type=text].form-control', $content).first().focus();
|
||||
}
|
||||
}
|
||||
|
||||
function sendRequestAjax(data, type, url, buttonId) {
|
||||
$.ajax({
|
||||
type: type,
|
||||
url: url,
|
||||
data: data,
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
console.log(response);
|
||||
if (response.result === true) {
|
||||
generateNotify(response.message || "Success!", "success");
|
||||
|
||||
$('#' + buttonId).html("<i class='fa fa-check'></i> Requested");
|
||||
$('#' + buttonId).removeClass("btn-primary-outline");
|
||||
$('#' + buttonId).removeAttr("data-toggle");
|
||||
$('#' + buttonId).addClass("btn-success-outline");
|
||||
} else {
|
||||
generateNotify(response.message, "warning");
|
||||
$('#' + buttonId).html("<i class='fa fa-plus'></i> Request");
|
||||
$('#' + buttonId).attr("data-toggle", "dropdown");
|
||||
$("#" + buttonId).removeAttr("disabled");
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
console.log(e);
|
||||
generateNotify("Something went wrong!", "danger");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function movieSearch() {
|
||||
var query = $("#movieSearchContent").val();
|
||||
var url = createBaseUrl(base, '/search/movie/');
|
||||
query ? getMovies(url + query) : resetMovies();
|
||||
}
|
||||
|
||||
function moviesComingSoon() {
|
||||
var url = createBaseUrl(base, '/search/movie/upcoming');
|
||||
getMovies(url);
|
||||
}
|
||||
|
||||
function moviesInTheaters() {
|
||||
var url = createBaseUrl(base, '/search/movie/playing');
|
||||
getMovies(url);
|
||||
}
|
||||
|
||||
function getMovies(url) {
|
||||
resetMovies();
|
||||
|
||||
$('#movieSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||
$.ajax(url).success(function (results) {
|
||||
if (results.length > 0) {
|
||||
results.forEach(function (result) {
|
||||
var context = buildMovieContext(result);
|
||||
|
||||
var html = searchTemplate(context);
|
||||
$("#movieList").append(html);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#movieList").html(noResultsHtml);
|
||||
}
|
||||
$('#movieSearchButton').attr("class", "fa fa-search");
|
||||
});
|
||||
};
|
||||
|
||||
function resetMovies() {
|
||||
$("#movieList").html("");
|
||||
}
|
||||
|
||||
function tvSearch() {
|
||||
var query = $("#tvSearchContent").val();
|
||||
|
||||
var url = createBaseUrl(base, '/search/tv/');
|
||||
query ? getTvShows(url + query) : resetTvShows();
|
||||
}
|
||||
|
||||
function getTvShows(url) {
|
||||
resetTvShows();
|
||||
|
||||
$('#tvSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||
$.ajax(url).success(function (results) {
|
||||
if (results.length > 0) {
|
||||
results.forEach(function (result) {
|
||||
var context = buildTvShowContext(result);
|
||||
var html = searchTemplate(context);
|
||||
$("#tvList").append(html);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#tvList").html(noResultsHtml);
|
||||
}
|
||||
$('#tvSearchButton').attr("class", "fa fa-search");
|
||||
});
|
||||
};
|
||||
|
||||
function resetTvShows() {
|
||||
$("#tvList").html("");
|
||||
}
|
||||
|
||||
function musicSearch() {
|
||||
var url = createBaseUrl(base, '/search/music/');
|
||||
var query = $("#musicSearchContent").val();
|
||||
query ? getMusic(url + query) : resetMusic();
|
||||
}
|
||||
|
||||
function getMusic(url) {
|
||||
resetMusic();
|
||||
|
||||
$('#musicSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||
$.ajax(url).success(function (results) {
|
||||
if (results.length > 0) {
|
||||
results.forEach(function (result) {
|
||||
var context = buildMusicContext(result);
|
||||
|
||||
var html = musicTemplate(context);
|
||||
$("#musicList").append(html);
|
||||
getCoverArt(context.id);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#musicList").html(noResultsMusic);
|
||||
}
|
||||
$('#musicSearchButton').attr("class", "fa fa-search");
|
||||
});
|
||||
};
|
||||
|
||||
function resetMusic() {
|
||||
$("#musicList").html("");
|
||||
}
|
||||
|
||||
function getCoverArt(artistId) {
|
||||
|
||||
var url = createBaseUrl(base, '/search/music/coverart/');
|
||||
$.ajax(url + artistId).success(function (result) {
|
||||
if (result) {
|
||||
$('#' + artistId + "imageDiv").html(" <img class='img-responsive' src='" + result + "' width='150' alt='poster'>");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function buildMovieContext(result) {
|
||||
var date = new Date(result.releaseDate);
|
||||
var year = date.getFullYear();
|
||||
var context = {
|
||||
posterPath: result.posterPath,
|
||||
id: result.id,
|
||||
title: result.title,
|
||||
overview: result.overview,
|
||||
voteCount: result.voteCount,
|
||||
voteAverage: result.voteAverage,
|
||||
year: year,
|
||||
type: "movie",
|
||||
imdb: result.imdbId,
|
||||
requested: result.requested,
|
||||
approved: result.approved,
|
||||
available: result.available
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function buildTvShowContext(result) {
|
||||
var date = new Date(result.firstAired);
|
||||
var year = date.getFullYear();
|
||||
var context = {
|
||||
posterPath: result.banner,
|
||||
id: result.id,
|
||||
title: result.seriesName,
|
||||
overview: result.overview,
|
||||
year: year,
|
||||
type: "tv",
|
||||
imdb: result.imdbId,
|
||||
requested: result.requested,
|
||||
approved: result.approved,
|
||||
available: result.available
|
||||
};
|
||||
return context;
|
||||
}
|
||||
|
||||
function buildMusicContext(result) {
|
||||
|
||||
var context = {
|
||||
id: result.id,
|
||||
title: result.title,
|
||||
overview: result.overview,
|
||||
year: result.releaseDate,
|
||||
type: "album",
|
||||
trackCount: result.trackCount,
|
||||
coverArtUrl: result.coverArtUrl,
|
||||
artist: result.artist,
|
||||
releaseType: result.releaseType,
|
||||
country: result.country,
|
||||
requested: result.requested,
|
||||
approved: result.approved,
|
||||
available: result.available
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
});
|
||||
Handlebars.registerHelper('if_eq', function (a, b, opts) {
|
||||
if (a == b)
|
||||
return opts.fn(this);
|
||||
else
|
||||
return opts.inverse(this);
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(function () {
|
||||
|
||||
var searchSource = $("#search-template").html();
|
||||
var seasonsSource = $("#seasons-template").html();
|
||||
var musicSource = $("#music-template").html();
|
||||
var searchTemplate = Handlebars.compile(searchSource);
|
||||
var musicTemplate = Handlebars.compile(musicSource);
|
||||
var seasonsTemplate = Handlebars.compile(seasonsSource);
|
||||
|
||||
var base = $('#baseUrl').text();
|
||||
|
||||
var searchTimer = 0;
|
||||
|
||||
// fix for selecting a default tab
|
||||
var $tabs = $('#nav-tabs').children('li');
|
||||
if ($tabs.filter(function (li) { return $(li).hasClass('active') }).length <= 0) {
|
||||
$tabs.first().children('a:first-child').tab('show');
|
||||
}
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
focusSearch($($(e.target).attr('href')));
|
||||
});
|
||||
focusSearch($('li.active a', '#nav-tabs').first().attr('href'));
|
||||
|
||||
// Get the user notification setting
|
||||
var url = createBaseUrl(base, '/search/notifyuser/');
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: url,
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
$('#notifyUser').prop("checked", response);
|
||||
},
|
||||
error: function (e) {
|
||||
console.log(e);
|
||||
generateNotify("Something went wrong!", "danger");
|
||||
}
|
||||
});
|
||||
|
||||
// Type in movie search
|
||||
$("#movieSearchContent").on("input", function () {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
searchTimer = setTimeout(movieSearch, 400);
|
||||
|
||||
});
|
||||
|
||||
$('#moviesComingSoon').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
moviesComingSoon();
|
||||
});
|
||||
|
||||
$('#moviesInTheaters').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
moviesInTheaters();
|
||||
});
|
||||
|
||||
// Type in TV search
|
||||
$("#tvSearchContent").on("input", function () {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
searchTimer = setTimeout(tvSearch, 400);
|
||||
});
|
||||
|
||||
// Click TV dropdown option
|
||||
$(document).on("click", ".dropdownTv", function (e) {
|
||||
e.preventDefault();
|
||||
var buttonId = e.target.id;
|
||||
if ($("#" + buttonId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + buttonId).prop("disabled", true);
|
||||
loadingButton(buttonId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + buttonId);
|
||||
var data = $form.serialize();
|
||||
var seasons = $(this).attr("season-select");
|
||||
if (seasons === "2") {
|
||||
// Send over the latest
|
||||
data = data + "&seasons=latest";
|
||||
}
|
||||
if (seasons === "1") {
|
||||
// Send over the first season
|
||||
data = data + "&seasons=first";
|
||||
} if (seasons === "0") {
|
||||
// Send over the first season
|
||||
data = data + "&seasons=all";
|
||||
}
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
|
||||
sendRequestAjax(data, type, url, buttonId);
|
||||
});
|
||||
|
||||
// Search Music
|
||||
$("#musicSearchContent").on("input", function () {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
searchTimer = setTimeout(musicSearch, 400);
|
||||
|
||||
});
|
||||
|
||||
// Click Request for movie
|
||||
$(document).on("click", ".requestMovie", function (e) {
|
||||
e.preventDefault();
|
||||
var buttonId = e.target.id;
|
||||
if ($("#" + buttonId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + buttonId).prop("disabled", true);
|
||||
loadingButton(buttonId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + buttonId);
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
var data = $form.serialize();
|
||||
|
||||
sendRequestAjax(data, type, url, buttonId);
|
||||
|
||||
});
|
||||
|
||||
// Click Request for album
|
||||
$(document).on("click", ".requestAlbum", function (e) {
|
||||
e.preventDefault();
|
||||
var buttonId = e.target.id;
|
||||
if ($("#" + buttonId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + buttonId).prop("disabled", true);
|
||||
loadingButton(buttonId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + buttonId);
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
var data = $form.serialize();
|
||||
|
||||
sendRequestAjax(data, type, url, buttonId);
|
||||
});
|
||||
|
||||
// Enable/Disable user notifications
|
||||
$('#saveNotificationSettings')
|
||||
.click(function (e) {
|
||||
e.preventDefault();
|
||||
var url = createBaseUrl(base, '/search/notifyuser/');
|
||||
var checked = $('#notifyUser').prop('checked');
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: url,
|
||||
data: { notify: checked },
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
console.log(response);
|
||||
if (response.result === true) {
|
||||
generateNotify(response.message || "Success!", "success");
|
||||
} else {
|
||||
generateNotify(response.message, "warning");
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
console.log(e);
|
||||
generateNotify("Something went wrong!", "danger");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function focusSearch($content) {
|
||||
if ($content.length > 0) {
|
||||
$('input[type=text].form-control', $content).first().focus();
|
||||
}
|
||||
}
|
||||
|
||||
function sendRequestAjax(data, type, url, buttonId) {
|
||||
$.ajax({
|
||||
type: type,
|
||||
url: url,
|
||||
data: data,
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
console.log(response);
|
||||
if (response.result === true) {
|
||||
generateNotify(response.message || "Success!", "success");
|
||||
|
||||
$('#' + buttonId).html("<i class='fa fa-check'></i> Requested");
|
||||
$('#' + buttonId).removeClass("btn-primary-outline");
|
||||
$('#' + buttonId).removeAttr("data-toggle");
|
||||
$('#' + buttonId).addClass("btn-success-outline");
|
||||
} else {
|
||||
generateNotify(response.message, "warning");
|
||||
$('#' + buttonId).html("<i class='fa fa-plus'></i> Request");
|
||||
$('#' + buttonId).attr("data-toggle", "dropdown");
|
||||
$("#" + buttonId).removeAttr("disabled");
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
console.log(e);
|
||||
generateNotify("Something went wrong!", "danger");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function movieSearch() {
|
||||
var query = $("#movieSearchContent").val();
|
||||
var url = createBaseUrl(base, '/search/movie/');
|
||||
query ? getMovies(url + query) : resetMovies();
|
||||
}
|
||||
|
||||
function moviesComingSoon() {
|
||||
var url = createBaseUrl(base, '/search/movie/upcoming');
|
||||
getMovies(url);
|
||||
}
|
||||
|
||||
function moviesInTheaters() {
|
||||
var url = createBaseUrl(base, '/search/movie/playing');
|
||||
getMovies(url);
|
||||
}
|
||||
|
||||
function getMovies(url) {
|
||||
resetMovies();
|
||||
|
||||
$('#movieSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||
$.ajax(url).success(function (results) {
|
||||
if (results.length > 0) {
|
||||
results.forEach(function (result) {
|
||||
var context = buildMovieContext(result);
|
||||
|
||||
var html = searchTemplate(context);
|
||||
$("#movieList").append(html);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#movieList").html(noResultsHtml);
|
||||
}
|
||||
$('#movieSearchButton').attr("class", "fa fa-search");
|
||||
});
|
||||
};
|
||||
|
||||
function resetMovies() {
|
||||
$("#movieList").html("");
|
||||
}
|
||||
|
||||
function tvSearch() {
|
||||
var query = $("#tvSearchContent").val();
|
||||
|
||||
var url = createBaseUrl(base, '/search/tv/');
|
||||
query ? getTvShows(url + query) : resetTvShows();
|
||||
}
|
||||
|
||||
function getTvShows(url) {
|
||||
resetTvShows();
|
||||
|
||||
$('#tvSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||
$.ajax(url).success(function (results) {
|
||||
if (results.length > 0) {
|
||||
results.forEach(function (result) {
|
||||
var context = buildTvShowContext(result);
|
||||
var html = searchTemplate(context);
|
||||
$("#tvList").append(html);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#tvList").html(noResultsHtml);
|
||||
}
|
||||
$('#tvSearchButton').attr("class", "fa fa-search");
|
||||
});
|
||||
};
|
||||
|
||||
function resetTvShows() {
|
||||
$("#tvList").html("");
|
||||
}
|
||||
|
||||
function musicSearch() {
|
||||
var url = createBaseUrl(base, '/search/music/');
|
||||
var query = $("#musicSearchContent").val();
|
||||
query ? getMusic(url + query) : resetMusic();
|
||||
}
|
||||
|
||||
function getMusic(url) {
|
||||
resetMusic();
|
||||
|
||||
$('#musicSearchButton').attr("class", "fa fa-spinner fa-spin");
|
||||
$.ajax(url).success(function (results) {
|
||||
if (results.length > 0) {
|
||||
results.forEach(function (result) {
|
||||
var context = buildMusicContext(result);
|
||||
|
||||
var html = musicTemplate(context);
|
||||
$("#musicList").append(html);
|
||||
getCoverArt(context.id);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#musicList").html(noResultsMusic);
|
||||
}
|
||||
$('#musicSearchButton').attr("class", "fa fa-search");
|
||||
});
|
||||
};
|
||||
|
||||
function resetMusic() {
|
||||
$("#musicList").html("");
|
||||
}
|
||||
|
||||
function getCoverArt(artistId) {
|
||||
|
||||
var url = createBaseUrl(base, '/search/music/coverart/');
|
||||
$.ajax(url + artistId).success(function (result) {
|
||||
if (result) {
|
||||
$('#' + artistId + "imageDiv").html(" <img class='img-responsive' src='" + result + "' width='150' alt='poster'>");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function buildMovieContext(result) {
|
||||
var date = new Date(result.releaseDate);
|
||||
var year = date.getFullYear();
|
||||
var context = {
|
||||
posterPath: result.posterPath,
|
||||
id: result.id,
|
||||
title: result.title,
|
||||
overview: result.overview,
|
||||
voteCount: result.voteCount,
|
||||
voteAverage: result.voteAverage,
|
||||
year: year,
|
||||
type: "movie",
|
||||
imdb: result.imdbId,
|
||||
requested: result.requested,
|
||||
approved: result.approved,
|
||||
available: result.available
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function buildTvShowContext(result) {
|
||||
var date = new Date(result.firstAired);
|
||||
var year = date.getFullYear();
|
||||
var context = {
|
||||
posterPath: result.banner,
|
||||
id: result.id,
|
||||
title: result.seriesName,
|
||||
overview: result.overview,
|
||||
year: year,
|
||||
type: "tv",
|
||||
imdb: result.imdbId,
|
||||
requested: result.requested,
|
||||
approved: result.approved,
|
||||
available: result.available
|
||||
};
|
||||
return context;
|
||||
}
|
||||
|
||||
function buildMusicContext(result) {
|
||||
|
||||
var context = {
|
||||
id: result.id,
|
||||
title: result.title,
|
||||
overview: result.overview,
|
||||
year: result.releaseDate,
|
||||
type: "album",
|
||||
trackCount: result.trackCount,
|
||||
coverArtUrl: result.coverArtUrl,
|
||||
artist: result.artist,
|
||||
releaseType: result.releaseType,
|
||||
country: result.country,
|
||||
requested: result.requested,
|
||||
approved: result.approved,
|
||||
available: result.available
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
$('#seasonsModal').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget); // Button that triggered the modal
|
||||
var id = button.data('identifier'); // Extract info from data-* attributes
|
||||
var url = createBaseUrl(base, '/search/seasons/');
|
||||
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: url,
|
||||
data: { tvId: id },
|
||||
dataType: "json",
|
||||
success: function (results) {
|
||||
var $content = $("#seasonsBody");
|
||||
$content.html("");
|
||||
$('#selectedSeasonsId').val(id);
|
||||
results.forEach(function(result) {
|
||||
var context = buildSeasonsContext(result);
|
||||
$content.append(seasonsTemplate(context));
|
||||
});
|
||||
},
|
||||
error: function (e) {
|
||||
console.log(e);
|
||||
generateNotify("Something went wrong!", "danger");
|
||||
}
|
||||
});
|
||||
|
||||
function buildSeasonsContext(result) {
|
||||
var context = {
|
||||
id: result
|
||||
};
|
||||
return context;
|
||||
};
|
||||
});
|
||||
|
||||
$('#seasonsRequest').click(function(e) {
|
||||
e.preventDefault();
|
||||
var tvId = $('#selectedSeasonsId').val();
|
||||
var url = createBaseUrl(base, '/search/seasons/');
|
||||
|
||||
if ($("#" + tvId).attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#" + tvId).prop("disabled", true);
|
||||
loadingButton(tvId, "primary");
|
||||
|
||||
|
||||
var $form = $('#form' + tvId);
|
||||
var data = $form.serialize();
|
||||
var seasonsParam = "&seasons=";
|
||||
|
||||
var $checkedSeasons = $('.selectedSeasons:checkbox:checked');
|
||||
$checkedSeasons.each(function (index, element) {
|
||||
if (index < $checkedSeasons.length -1) {
|
||||
seasonsParam = seasonsParam + element.id + ",";
|
||||
} else {
|
||||
seasonsParam = seasonsParam + element.id;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
data = data + seasonsParam;
|
||||
|
||||
var type = $form.prop('method');
|
||||
var url = $form.prop('action');
|
||||
|
||||
sendRequestAjax(data, type, url, tvId);
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,79 +1,79 @@
|
|||
String.prototype.format = String.prototype.f = function () {
|
||||
var s = this,
|
||||
i = arguments.length;
|
||||
|
||||
while (i--) {
|
||||
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function Humanize(date) {
|
||||
var mNow = moment();
|
||||
var mDate = moment(date).local();
|
||||
return moment.duration(mNow - mDate).humanize() + (mNow.isBefore(mDate) ? ' from now' : ' ago');
|
||||
}
|
||||
|
||||
function generateNotify(message, type) {
|
||||
// type = danger, warning, info, successs
|
||||
$.notify({
|
||||
// options
|
||||
message: message
|
||||
}, {
|
||||
// settings
|
||||
type: type,
|
||||
animate: {
|
||||
enter: 'animated bounceInDown',
|
||||
exit: 'animated bounceOutUp'
|
||||
},
|
||||
newest_on_top: true
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function checkJsonResponse(response) {
|
||||
if (response.result === true) {
|
||||
return true;
|
||||
} else {
|
||||
generateNotify(response.message, "warning");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadingButton(elementId, originalCss) {
|
||||
var $element = $('#' + elementId);
|
||||
$element.removeClass("btn-" + originalCss + "-outline").addClass("btn-primary-outline").addClass('disabled').html("<i class='fa fa-spinner fa-spin'></i> Loading...");
|
||||
|
||||
// handle split-buttons
|
||||
var $dropdown = $element.next('.dropdown-toggle')
|
||||
if ($dropdown.length > 0) {
|
||||
$dropdown.removeClass("btn-" + originalCss + "-outline").addClass("btn-primary-outline").addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function finishLoading(elementId, originalCss, html) {
|
||||
var $element = $('#' + elementId);
|
||||
$element.removeClass("btn-primary-outline").removeClass('disabled').addClass("btn-" + originalCss + "-outline").html(html);
|
||||
|
||||
// handle split-buttons
|
||||
var $dropdown = $element.next('.dropdown-toggle')
|
||||
if ($dropdown.length > 0) {
|
||||
$dropdown.removeClass("btn-primary-outline").removeClass('disabled').addClass("btn-" + originalCss + "-outline");
|
||||
}
|
||||
}
|
||||
|
||||
function createBaseUrl(base, url) {
|
||||
if (base) {
|
||||
if (url.charAt(0) === "/") {
|
||||
url = "/" + base + url;
|
||||
} else {
|
||||
url = "/" + base + "/" + url;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
var noResultsHtml = "<div class='no-search-results'>" +
|
||||
"<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div></div>";
|
||||
var noResultsMusic = "<div class='no-search-results'>" +
|
||||
String.prototype.format = String.prototype.f = function () {
|
||||
var s = this,
|
||||
i = arguments.length;
|
||||
|
||||
while (i--) {
|
||||
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function Humanize(date) {
|
||||
var mNow = moment();
|
||||
var mDate = moment(date).local();
|
||||
return moment.duration(mNow - mDate).humanize() + (mNow.isBefore(mDate) ? ' from now' : ' ago');
|
||||
}
|
||||
|
||||
function generateNotify(message, type) {
|
||||
// type = danger, warning, info, successs
|
||||
$.notify({
|
||||
// options
|
||||
message: message
|
||||
}, {
|
||||
// settings
|
||||
type: type,
|
||||
animate: {
|
||||
enter: 'animated bounceInDown',
|
||||
exit: 'animated bounceOutUp'
|
||||
},
|
||||
newest_on_top: true
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function checkJsonResponse(response) {
|
||||
if (response.result === true) {
|
||||
return true;
|
||||
} else {
|
||||
generateNotify(response.message, "warning");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadingButton(elementId, originalCss) {
|
||||
var $element = $('#' + elementId);
|
||||
$element.removeClass("btn-" + originalCss + "-outline").addClass("btn-primary-outline").addClass('disabled').html("<i class='fa fa-spinner fa-spin'></i> Loading...");
|
||||
|
||||
// handle split-buttons
|
||||
var $dropdown = $element.next('.dropdown-toggle')
|
||||
if ($dropdown.length > 0) {
|
||||
$dropdown.removeClass("btn-" + originalCss + "-outline").addClass("btn-primary-outline").addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function finishLoading(elementId, originalCss, html) {
|
||||
var $element = $('#' + elementId);
|
||||
$element.removeClass("btn-primary-outline").removeClass('disabled').addClass("btn-" + originalCss + "-outline").html(html);
|
||||
|
||||
// handle split-buttons
|
||||
var $dropdown = $element.next('.dropdown-toggle')
|
||||
if ($dropdown.length > 0) {
|
||||
$dropdown.removeClass("btn-primary-outline").removeClass('disabled').addClass("btn-" + originalCss + "-outline");
|
||||
}
|
||||
}
|
||||
|
||||
function createBaseUrl(base, url) {
|
||||
if (base) {
|
||||
if (url.charAt(0) === "/") {
|
||||
url = "/" + base + url;
|
||||
} else {
|
||||
url = "/" + base + "/" + url;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
var noResultsHtml = "<div class='no-search-results'>" +
|
||||
"<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div></div>";
|
||||
var noResultsMusic = "<div class='no-search-results'>" +
|
||||
"<i class='fa fa-headphones no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div></div>";
|
15
PlexRequests.UI/Content/swagger/backbone-min.js
vendored
Normal file
15
PlexRequests.UI/Content/swagger/backbone-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue