Merge pull request #385 from tidusjar/dev

Dev to master
pull/391/head v1.8.3
Jamie 9 years ago committed by GitHub
commit ec8315ba2e

@ -1,59 +1,116 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AuthenticationSettingsTests.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 NUnit.Framework;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core.Tests
{
[TestFixture]
public class AuthenticationSettingsTests
{
[Test, TestCaseSource(nameof(UserData))]
public void DeniedUserListTest(string users, string[] expected)
{
var model = new AuthenticationSettings { DeniedUsers = users };
var result = model.DeniedUserList;
Assert.That(result.Count, Is.EqualTo(expected.Length));
for (var i = 0; i < expected.Length; i++)
{
Assert.That(result[i], Is.EqualTo(expected[i]));
}
}
static readonly object[] UserData =
{
new object[] { "john", new [] {"john"} },
new object[] { "john , abc ,", new [] {"john", "abc"} },
new object[] { "john,, cde", new [] {"john", "cde"} },
new object[] { "john,,, aaa , baaa , ", new [] {"john","aaa","baaa"} },
new object[] { "john, aaa , baaa , maaa, caaa", new [] {"john","aaa","baaa", "maaa", "caaa"} },
};
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AuthenticationSettingsTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using NUnit.Framework;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core.Tests
{
[TestFixture]
public class NotificationMessageResolverTests
{
[TestCaseSource(nameof(MessageResolver))]
public string Resolve(Dictionary<NotificationType, string> message, Dictionary<string,string> param)
{
var n = new NotificationMessageResolver();
var s = new NotificationSettings
{
Message = message,
CustomParamaters = param
};
return n.ParseMessage(s, NotificationType.NewRequest);
}
private static IEnumerable<TestCaseData> MessageResolver
{
get
{
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "There has been a new request from {Username}, Title: {Title} for {Type}" } },
new Dictionary<string, string>{{"Username", "Jamie" },{"Title", "Finding Dory" },{"Type", "Movie" }})
.Returns("There has been a new request from Jamie, Title: Finding Dory for Movie")
.SetName("FindingDory");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, string.Empty } },
new Dictionary<string, string>())
.Returns(string.Empty)
.SetName("Empty Message");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "{{Wowwzer}} Damn}{{son}}}}" } },
new Dictionary<string, string> { {"son","HEY!"} })
.Returns("{{Wowwzer}} Damn}{HEY!}}}")
.SetName("Multiple Curlys");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "This is a message with no curlys" } },
new Dictionary<string, string> { { "son", "HEY!" } })
.Returns("This is a message with no curlys")
.SetName("No Curlys");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, new string(')', 5000)} },
new Dictionary<string, string> { { "son", "HEY!" } })
.Returns(new string(')', 5000))
.SetName("Long String");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "This is a {Username} and {Username} Because {Curly}{Curly}" } },
new Dictionary<string, string> { { "Username", "HEY!" }, {"Curly","Bob"} })
.Returns("This is a HEY! and HEY! Because BobBob")
.SetName("Double Curly");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "This is a {Username} and {Username} Because {Curly}{Curly}" } },
new Dictionary<string, string> { { "username", "HEY!" }, { "Curly", "Bob" } })
.Returns("This is a {Username} and {Username} Because BobBob")
.SetName("Case sensitive");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, "{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}{a}" } },
new Dictionary<string, string> { { "a", "b" } })
.Returns("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
.SetName("Lots of curlys");
yield return new TestCaseData(
new Dictionary<NotificationType, string> { { NotificationType.NewRequest, $"{{{new string('b', 10000)}}}" } },
new Dictionary<string, string> { { new string('b', 10000), "Hello" } })
.Returns("Hello")
.SetName("Very long Curly");
}
}
}
}

@ -0,0 +1,59 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AuthenticationSettingsTests.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 NUnit.Framework;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core.Tests
{
[TestFixture]
public class AuthenticationSettingsTests
{
[Test, TestCaseSource(nameof(UserData))]
public void DeniedUserListTest(string users, string[] expected)
{
var model = new AuthenticationSettings { DeniedUsers = users };
var result = model.DeniedUserList;
Assert.That(result.Count, Is.EqualTo(expected.Length));
for (var i = 0; i < expected.Length; i++)
{
Assert.That(result[i], Is.EqualTo(expected[i]));
}
}
static readonly object[] UserData =
{
new object[] { "john", new [] {"john"} },
new object[] { "john , abc ,", new [] {"john", "abc"} },
new object[] { "john,, cde", new [] {"john", "cde"} },
new object[] { "john,,, aaa , baaa , ", new [] {"john","aaa","baaa"} },
new object[] { "john, aaa , baaa , maaa, caaa", new [] {"john","aaa","baaa", "maaa", "caaa"} },
};
}
}

@ -1,107 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
<ProjectGuid>{FCFECD5D-47F6-454D-8692-E27A921BE655}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Core.Tests</RootNamespace>
<AssemblyName>PlexRequests.Core.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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>
</Reference>
<Reference Include="nunit.framework, Version=3.0.5813.39031, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ploeh.AutoFixture, Version=3.40.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL">
<HintPath>..\packages\AutoFixture.3.40.0\lib\net40\Ploeh.AutoFixture.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="StatusCheckerTests.cs" />
<Compile Include="AuthenticationSettingsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<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>
<ProjectGuid>{FCFECD5D-47F6-454D-8692-E27A921BE655}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Core.Tests</RootNamespace>
<AssemblyName>PlexRequests.Core.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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>
</Reference>
<Reference Include="nunit.framework, Version=3.0.5813.39031, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ploeh.AutoFixture, Version=3.40.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL">
<HintPath>..\packages\AutoFixture.3.40.0\lib\net40\Ploeh.AutoFixture.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="NotificationMessageResolverTests.cs" />
<Compile Include="StatusCheckerTests.cs" />
<Compile Include="AuthenticationSettingsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -1,11 +1,11 @@
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>

@ -1,39 +1,39 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationType.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.Notification
{
public enum NotificationType
{
NewRequest,
Issue,
RequestAvailable,
RequestApproved,
AdminNote,
Test,
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationType.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.Models
{
public enum NotificationType
{
NewRequest,
Issue,
RequestAvailable,
RequestApproved,
AdminNote,
Test,
}
}

@ -0,0 +1,104 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CustomNotificationService.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 PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Core
{
public class NotificationMessageResolver
{
private const char StartChar = (char)123;
private const char EndChar = (char)125;
public string ParseMessage<T>(T notification, NotificationType type) where T : NotificationSettings
{
var notificationToParse = notification.Message.FirstOrDefault(x => x.Key == type).Value;
if (string.IsNullOrEmpty(notificationToParse))
return string.Empty;
return Resolve(notificationToParse, notification.CustomParamaters);
}
private string Resolve(string message, Dictionary<string,string> paramaters)
{
var fields = FindCurlyFields(message);
foreach (var f in fields)
{
string outString;
if (paramaters.TryGetValue(f, out outString))
{
message = message.Replace($"{{{f}}}", outString);
}
}
return message;
}
private IEnumerable<string> FindCurlyFields(string message)
{
var insideCurly = false;
var fields = new List<string>();
var currentWord = string.Empty;
var chars = message.ToCharArray();
foreach (var c in chars)
{
if (char.IsWhiteSpace(c))
{
currentWord = string.Empty;
continue;
}
if (c == StartChar) // Start of curly '{'
{
insideCurly = true;
continue;
}
if (c == EndChar) // End of curly '}'
{
fields.Add(currentWord); // We have finished the curly, add the word into the list
currentWord = string.Empty;
insideCurly = false;
continue;
}
if (insideCurly)
{
currentWord += c.ToString(); // Add the character onto the word.
}
}
return fields;
}
}
}

@ -1,134 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Core</RootNamespace>
<AssemblyName>PlexRequests.Core</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="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="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" />
<Compile Include="IIssueService.cs" />
<Compile Include="IRequestService.cs" />
<Compile Include="ISettingsService.cs" />
<Compile Include="JsonIssuesModelRequestService.cs" />
<Compile Include="JsonRequestModelRequestService.cs" />
<Compile Include="Models\IssuesModel.cs" />
<Compile Include="Models\StatusModel.cs" />
<Compile Include="Models\UserProperties.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\HeadphonesSettings.cs" />
<Compile Include="SettingModels\LandingPageSettings.cs" />
<Compile Include="SettingModels\ScheduledJobsSettings.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" />
<Compile Include="SettingModels\PlexRequestSettings.cs" />
<Compile Include="SettingModels\Settings.cs" />
<Compile Include="SettingsServiceV2.cs" />
<Compile Include="Setup.cs" />
<Compile Include="StatusChecker.cs" />
<Compile Include="UserIdentity.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UserMapper.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api\PlexRequests.Api.csproj">
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
<Name>PlexRequests.Api</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup>
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Core</RootNamespace>
<AssemblyName>PlexRequests.Core</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="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="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" />
<Compile Include="NotificationMessageResolver.cs" />
<Compile Include="IIssueService.cs" />
<Compile Include="IRequestService.cs" />
<Compile Include="ISettingsService.cs" />
<Compile Include="JsonIssuesModelRequestService.cs" />
<Compile Include="JsonRequestModelRequestService.cs" />
<Compile Include="Models\IssuesModel.cs" />
<Compile Include="Models\NotificationType.cs" />
<Compile Include="Models\StatusModel.cs" />
<Compile Include="Models\UserProperties.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\HeadphonesSettings.cs" />
<Compile Include="SettingModels\LandingPageSettings.cs" />
<Compile Include="SettingModels\NotificationSettings.cs" />
<Compile Include="SettingModels\ScheduledJobsSettings.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" />
<Compile Include="SettingModels\PlexRequestSettings.cs" />
<Compile Include="SettingModels\Settings.cs" />
<Compile Include="SettingsServiceV2.cs" />
<Compile Include="Setup.cs" />
<Compile Include="StatusChecker.cs" />
<Compile Include="UserIdentity.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UserMapper.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api\PlexRequests.Api.csproj">
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
<Name>PlexRequests.Api</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup>
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -1,40 +1,40 @@
#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 string EmailSender { get; set; }
public string EmailUsername { get; set; }
public bool Enabled { get; set; }
public bool EnableUserEmailNotifications { get; set; }
public string RecipientEmail { get; set; }
}
#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 : NotificationSettings
{
public string EmailHost { get; set; }
public string EmailPassword { get; set; }
public int EmailPort { get; set; }
public string EmailSender { get; set; }
public string EmailUsername { get; set; }
public bool Enabled { get; set; }
public bool EnableUserEmailNotifications { get; set; }
public string RecipientEmail { get; set; }
}
}

@ -0,0 +1,38 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationSettings.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 PlexRequests.Core.Models;
namespace PlexRequests.Core.SettingModels
{
public class NotificationSettings : Settings
{
public Dictionary<NotificationType, string> Message { get; set; }
public Dictionary<string,string> CustomParamaters { get; set; }
}
}

@ -32,6 +32,13 @@ namespace PlexRequests.Core.SettingModels
{
public class PlexRequestSettings : Settings
{
public PlexRequestSettings()
{
TvWeeklyRequestLimit = 0;
MovieWeeklyRequestLimit = 0;
AlbumWeeklyRequestLimit = 0;
}
public int Port { get; set; }
public string BaseUrl { get; set; }
public bool SearchForMovies { get; set; }
@ -42,7 +49,9 @@ namespace PlexRequests.Core.SettingModels
public bool RequireMusicApproval { get; set; }
public bool UsersCanViewOnlyOwnRequests { get; set; }
public bool UsersCanViewOnlyOwnIssues { get; set; }
public int WeeklyRequestLimit { get; set; }
public int MovieWeeklyRequestLimit { get; set; }
public int TvWeeklyRequestLimit { get; set; }
public int AlbumWeeklyRequestLimit { get; set; }
public string NoApprovalUsers { get; set; }
public bool CollectAnalyticData { get; set; }
public bool IgnoreNotifyForAutoApprovedRequests { get; set; }

@ -1,9 +1,9 @@
namespace PlexRequests.Core.SettingModels
{
public class PushbulletNotificationSettings : Settings
{
public bool Enabled { get; set; }
public string AccessToken { get; set; }
public string DeviceIdentifier { get; set; }
}
namespace PlexRequests.Core.SettingModels
{
public class PushbulletNotificationSettings : NotificationSettings
{
public bool Enabled { get; set; }
public string AccessToken { get; set; }
public string DeviceIdentifier { get; set; }
}
}

@ -1,9 +1,9 @@
namespace PlexRequests.Core.SettingModels
{
public class PushoverNotificationSettings : Settings
{
public bool Enabled { get; set; }
public string AccessToken { get; set; }
public string UserToken { get; set; }
}
namespace PlexRequests.Core.SettingModels
{
public class PushoverNotificationSettings : NotificationSettings
{
public bool Enabled { get; set; }
public string AccessToken { get; set; }
public string UserToken { get; set; }
}
}

@ -36,6 +36,7 @@ namespace PlexRequests.Core.SettingModels
CouchPotatoCacher = 10;
StoreBackup = 24;
StoreCleanup = 24;
UserRequestLimitResetter = 12;
}
public int PlexAvailabilityChecker { get; set; }
public int SickRageCacher { get; set; }
@ -43,5 +44,6 @@ namespace PlexRequests.Core.SettingModels
public int CouchPotatoCacher { get; set; }
public int StoreBackup { get; set; }
public int StoreCleanup { get; set; }
public int UserRequestLimitResetter { get; set; }
}
}

@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace PlexRequests.Core.SettingModels
{
public class SlackNotificationSettings : Settings
public class SlackNotificationSettings : NotificationSettings
{
public bool Enabled { get; set; }
public string WebhookUrl { get; set; }

@ -100,7 +100,6 @@ namespace PlexRequests.Core
RequireMovieApproval = true,
SearchForMovies = true,
SearchForTvShows = true,
WeeklyRequestLimit = 0,
BaseUrl = baseUrl ?? string.Empty,
CollectAnalyticData = true,
};

@ -1,11 +1,11 @@
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<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="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.3.4" targetFramework="net45" />
<package id="Octokit" version="0.19.0" targetFramework="net45" />
<package id="valueinjecter" version="3.1.1.2" targetFramework="net45" />
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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.3.4" targetFramework="net45" />
<package id="Octokit" version="0.19.0" targetFramework="net45" />
<package id="valueinjecter" version="3.1.1.2" targetFramework="net45" />
</packages>

@ -43,9 +43,9 @@ namespace PlexRequests.Helpers.Tests
{
get
{
yield return new TestCaseData(new Dictionary<string, string>()).Returns(string.Empty);
yield return new TestCaseData(new Dictionary<string, string> { { "_ga", "GA1.1.306549087.1464005217" } }).Returns("306549087.1464005217");
yield return new TestCaseData(new Dictionary<string,string> { {"_ga", "GA1.1.306549087" } }).Returns(string.Empty);
yield return new TestCaseData(new Dictionary<string, string>()).Returns(string.Empty).SetName("Empty");
yield return new TestCaseData(new Dictionary<string, string> { { "_ga", "GA1.1.306549087.1464005217" } }).Returns("306549087.1464005217").SetName("Returns correctly");
yield return new TestCaseData(new Dictionary<string,string> { {"_ga", "GA1.1.306549087" } }).Returns(string.Empty).SetName("Invalid Cookie");
}
}
}

@ -35,6 +35,11 @@ namespace PlexRequests.Helpers.Analytics
Save,
Update,
Start,
View
View,
Movie,
TvShow,
Album,
Request,
Language
}
}

@ -37,5 +37,6 @@ namespace PlexRequests.Helpers.Analytics
Issues,
UserLogin,
Services,
Navbar
}
}

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9C266462-BE82-461A-87A2-9EDCFB95D732}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Resources</RootNamespace>
<AssemblyName>PlexRequests.Resources</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<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" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("PlexRequests.Resources")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PlexRequests.Resources")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9c266462-be82-461a-87a2-9edcfb95d732")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

@ -1,137 +1,137 @@
#region Copyright
/************************************************************************
Copyright (c) 2016 Jamie Rees
File: NotificationServiceTests.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 Moq;
using NUnit.Framework;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
namespace PlexRequests.Services.Tests
{
[TestFixture]
public class NotificationServiceTests
{
public NotificationService NotificationService { get; set; }
[SetUp]
public void Setup()
{
NotificationService = new NotificationService();
}
[Test]
public void SubscribeNewNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
public void SubscribeExistingNotifier()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock1.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
public void UnSubscribeMissingNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
public void UnSubscribeNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
public void PublishWithNoObservers()
{
Assert.DoesNotThrowAsync(
async() =>
{ await NotificationService.Publish(new NotificationModel()); });
}
[Test]
public async Task PublishAllNotifiers()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification2");
NotificationService.Subscribe(notificationMock1.Object);
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(2));
var model = new NotificationModel { Title = "abc", Body = "test" };
await NotificationService.Publish(model);
notificationMock1.Verify(x => x.NotifyAsync(model), Times.Once);
notificationMock2.Verify(x => x.NotifyAsync(model), Times.Once);
}
[Test]
public async Task PublishWithException()
{
var notificationMock = new Mock<INotification>();
notificationMock.Setup(x => x.NotifyAsync(It.IsAny<NotificationModel>())).Throws<Exception>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
await NotificationService.Publish(new NotificationModel());
}
}
#region Copyright
/************************************************************************
Copyright (c) 2016 Jamie Rees
File: NotificationServiceTests.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 Moq;
using NUnit.Framework;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
namespace PlexRequests.Services.Tests
{
[TestFixture]
public class NotificationServiceTests
{
public NotificationService NotificationService { get; set; }
[SetUp]
public void Setup()
{
NotificationService = new NotificationService();
}
[Test]
public void SubscribeNewNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
public void SubscribeExistingNotifier()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock1.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
public void UnSubscribeMissingNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
public void UnSubscribeNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
public void PublishWithNoObservers()
{
Assert.DoesNotThrowAsync(
async() =>
{ await NotificationService.Publish(new NotificationModel()); });
}
[Test]
public async Task PublishAllNotifiers()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification2");
NotificationService.Subscribe(notificationMock1.Object);
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(2));
var model = new NotificationModel { Title = "abc", Body = "test" };
await NotificationService.Publish(model);
notificationMock1.Verify(x => x.NotifyAsync(model), Times.Once);
notificationMock2.Verify(x => x.NotifyAsync(model), Times.Once);
}
[Test]
public async Task PublishWithException()
{
var notificationMock = new Mock<INotification>();
notificationMock.Setup(x => x.NotifyAsync(It.IsAny<NotificationModel>())).Throws<Exception>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
await NotificationService.Publish(new NotificationModel());
}
}
}

@ -1,136 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
<ProjectGuid>{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Services.Tests</RootNamespace>
<AssemblyName>PlexRequests.Services.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Common.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<HintPath>..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Common.Logging.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<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>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Quartz, Version=2.3.3.0, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4, processorArchitecture=MSIL">
<HintPath>..\packages\Quartz.2.3.3\lib\net40\Quartz.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="NotificationServiceTests.cs" />
<Compile Include="PlexAvailabilityCheckerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config">
<SubType>Designer</SubType>
</None>
<None Include="job_scheduling_data_2_0.xsd">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Services\PlexRequests.Services.csproj">
<Project>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</Project>
<Name>PlexRequests.Services</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<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>
<ProjectGuid>{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Services.Tests</RootNamespace>
<AssemblyName>PlexRequests.Services.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Common.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<HintPath>..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Common.Logging.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<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>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ploeh.AutoFixture, Version=3.40.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL">
<HintPath>..\packages\AutoFixture.3.40.0\lib\net40\Ploeh.AutoFixture.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Quartz, Version=2.3.3.0, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4, processorArchitecture=MSIL">
<HintPath>..\packages\Quartz.2.3.3\lib\net40\Quartz.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="UserRequestLimitResetterTests.cs" />
<Compile Include="NotificationServiceTests.cs" />
<Compile Include="PlexAvailabilityCheckerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config">
<SubType>Designer</SubType>
</None>
<None Include="job_scheduling_data_2_0.xsd">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Services\PlexRequests.Services.csproj">
<Project>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</Project>
<Name>PlexRequests.Services</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -0,0 +1,145 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserRequestLimitResetterTests.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 Moq;
using NUnit.Framework;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Jobs;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using Ploeh.AutoFixture;
using Quartz;
namespace PlexRequests.Services.Tests
{
[TestFixture]
public class UserRequestLimitResetterTests
{
[SetUp]
public void Setup()
{
F = new Fixture();
JobMock = new Mock<IJobRecord>();
RepoMock = new Mock<IRepository<RequestLimit>>();
SettingsMock = new Mock<ISettingsService<PlexRequestSettings>>();
ContextMock = new Mock<IJobExecutionContext>();
SettingsMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
Resetter = new UserRequestLimitResetter(JobMock.Object, RepoMock.Object, SettingsMock.Object);
}
[TearDown]
public void Teardown()
{
SettingsMock.Verify(x => x.GetSettings(), Times.Once);
JobMock.Verify(x => x.Record(It.IsAny<string>()), Times.Once());
}
public UserRequestLimitResetter Resetter { get; set; }
private Mock<IJobRecord> JobMock { get; set; }
private Mock<IRepository<RequestLimit>> RepoMock { get; set; }
private Mock<ISettingsService<PlexRequestSettings>> SettingsMock { get; set; }
private Mock<IJobExecutionContext> ContextMock { get; set; }
private Fixture F { get; set; }
[TestCaseSource(nameof(ResetData))]
public void Reset(int movie, int tv, int album, RequestType type)
{
SetupSettings(movie, tv, album);
RepoMock.Setup(x => x.GetAll())
.Returns(F.Build<RequestLimit>().With(x => x.FirstRequestDate, DateTime.Now.AddDays(-8)).With(x => x.RequestType, type).CreateMany());
Resetter = new UserRequestLimitResetter(JobMock.Object, RepoMock.Object, SettingsMock.Object);
Resetter.Execute(ContextMock.Object);
RepoMock.Verify(x => x.Delete(It.IsAny<RequestLimit>()), Times.Exactly(3));
}
[TestCaseSource(nameof(DoNotResetData))]
public void DoNotReset(int days, RequestType type)
{
SetupSettings(1, 1, 1);
RepoMock.Setup(x => x.GetAll())
.Returns(F.Build<RequestLimit>().With(x => x.FirstRequestDate, DateTime.Now.AddDays(days)).With(x => x.RequestType, type).CreateMany());
Resetter = new UserRequestLimitResetter(JobMock.Object, RepoMock.Object, SettingsMock.Object);
Resetter.Execute(ContextMock.Object);
RepoMock.Verify(x => x.Delete(It.IsAny<RequestLimit>()), Times.Never);
}
static readonly IEnumerable<TestCaseData> ResetData = new List<TestCaseData>
{
new TestCaseData(1, 0, 0, RequestType.Movie).SetName("Reset Movies"),
new TestCaseData(0, 1, 0, RequestType.TvShow).SetName("Reset TV Shows"),
new TestCaseData(0, 0, 1, RequestType.Album).SetName("Reset Albums"),
new TestCaseData(1, 1, 1, RequestType.Album).SetName("Reset Albums with all enabled")
};
private static readonly IEnumerable<TestCaseData> DoNotResetData = new List<TestCaseData>
{
new TestCaseData(1, RequestType.Movie).SetName("1 Day(s)"),
new TestCaseData(100, RequestType.Movie).SetName("100 Day(s)"),
new TestCaseData(-6, RequestType.TvShow).SetName("-6 Day(s)"),
new TestCaseData(-1, RequestType.TvShow).SetName("-1 Day(s)"),
new TestCaseData(-2, RequestType.Album).SetName("-2 Day(s)"),
new TestCaseData(-3, RequestType.TvShow).SetName("-3 Day(s)"),
new TestCaseData(-4, RequestType.Movie).SetName("-4 Day(s)"),
new TestCaseData(-5, RequestType.TvShow).SetName("-5 Day(s)"),
new TestCaseData(0, RequestType.TvShow).SetName("0 Day(s)")
};
private void SetupSettings(int movie, int tv, int album)
{
SettingsMock.Setup(x => x.GetSettings())
.Returns(new PlexRequestSettings { MovieWeeklyRequestLimit = movie, TvWeeklyRequestLimit = tv, AlbumWeeklyRequestLimit = album });
}
[Test]
public void ResetTurnedOff()
{
SetupSettings(0, 0, 0);
Resetter = new UserRequestLimitResetter(JobMock.Object, RepoMock.Object, SettingsMock.Object);
Resetter.Execute(ContextMock.Object);
RepoMock.Verify(x => x.Delete(It.IsAny<RequestLimit>()), Times.Never);
}
}
}

@ -1,11 +1,11 @@
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Common.Logging" version="3.0.0" targetFramework="net452" />
<package id="Common.Logging.Core" version="3.0.0" targetFramework="net452" />
<package id="Moq" version="4.2.1510.2205" targetFramework="net46" />
<package id="NUnit" version="3.2.0" targetFramework="net46" />
<package id="Quartz" version="2.3.3" targetFramework="net452" />
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AutoFixture" version="3.40.0" targetFramework="net452" />
<package id="Common.Logging" version="3.0.0" targetFramework="net452" />
<package id="Common.Logging.Core" version="3.0.0" targetFramework="net452" />
<package id="Moq" version="4.2.1510.2205" targetFramework="net46" />
<package id="NUnit" version="3.2.0" targetFramework="net46" />
<package id="Quartz" version="2.3.3" targetFramework="net452" />
</packages>

@ -24,6 +24,8 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using NLog;
@ -86,8 +88,18 @@ namespace PlexRequests.Services.Jobs
// 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[] { };
try
{
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
var items = movies?.movies?.Select(x => x.info?.tmdb_id).Cast<int>().ToArray();
return items ?? new int[] { };
}
catch (Exception e)
{
Log.Error(e);
return new int[] {};
}
}
public void Execute(IJobExecutionContext context)

@ -34,5 +34,6 @@ namespace PlexRequests.Services.Jobs
public const string SrCacher = "SickRage Cacher";
public const string PlexChecker = "Plex Availability Cacher";
public const string StoreCleanup = "Database Cleanup";
public const string RequestLimitReset = "Request Limit Reset";
}
}

@ -33,6 +33,7 @@ using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;

@ -0,0 +1,121 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserRequestLimitResetter.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.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using Quartz;
namespace PlexRequests.Services.Jobs
{
public class UserRequestLimitResetter : IJob
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public UserRequestLimitResetter(IJobRecord record, IRepository<RequestLimit> repo, ISettingsService<PlexRequestSettings> settings)
{
Record = record;
Repo = repo;
Settings = settings;
}
private IJobRecord Record { get; }
private IRepository<RequestLimit> Repo { get; }
private ISettingsService<PlexRequestSettings> Settings { get; }
public void AlbumLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.AlbumWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Album);
}
public void MovieLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.MovieWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Movie);
}
public void TvLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.TvWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.TvShow);
}
private void CheckAndDelete(IEnumerable<RequestLimit> allUsers, RequestType type)
{
var users = allUsers.Where(x => x.RequestType == type);
foreach (var u in users)
{
var daysDiff = (u.FirstRequestDate - DateTime.UtcNow.AddDays(-7)).TotalDays;
if (daysDiff <= 0)
{
Repo.Delete(u);
}
}
}
public void Execute(IJobExecutionContext context)
{
try
{
var settings = Settings.GetSettings();
var users = Repo.GetAll();
var requestLimits = users as RequestLimit[] ?? users.ToArray();
MovieLimit(settings, requestLimits);
TvLimit(settings, requestLimits);
AlbumLimit(settings, requestLimits);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Record.Record(JobNames.RequestLimitReset);
}
}
}
}

@ -33,6 +33,7 @@ using MimeKit;
using NLog;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
@ -120,8 +121,8 @@ namespace PlexRequests.Services.Notification
{
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested the {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" },
Subject = $"Plex Requests: New {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} request for {model.Title}!"
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" },
Subject = $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!"
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));

@ -27,6 +27,8 @@
using PlexRequests.Store;
using System;
using PlexRequests.Core.Models;
namespace PlexRequests.Services.Notification
{
public class NotificationModel

@ -31,6 +31,7 @@ using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
@ -105,8 +106,8 @@ namespace PlexRequests.Services.Notification
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = $"The {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
var pushTitle = $"Plex Requests: The {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} {model.Title} has been requested!";
var message = $"The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
var pushTitle = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} {model.Title} has been requested!";
await Push(settings, message, pushTitle);
}

@ -31,6 +31,7 @@ using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
@ -105,7 +106,7 @@ namespace PlexRequests.Services.Notification
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: The {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
var message = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
await Push(settings, message);
}

@ -32,6 +32,7 @@ using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Notifications;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;

@ -1,139 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Services</RootNamespace>
<AssemblyName>PlexRequests.Services</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="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\StoreCleanup.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="Interfaces\ISickRageCacher.cs" />
<Compile Include="Interfaces\ISonarrCacher.cs" />
<Compile Include="Interfaces\ICouchPotatoCacher.cs" />
<Compile Include="Interfaces\IAvailabilityChecker.cs" />
<Compile Include="Interfaces\IIntervals.cs" />
<Compile Include="Interfaces\INotification.cs" />
<Compile Include="Interfaces\INotificationService.cs" />
<Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationModel.cs" />
<Compile Include="Notification\NotificationService.cs" />
<Compile Include="Notification\NotificationType.cs" />
<Compile Include="Notification\PushoverNotification.cs" />
<Compile Include="Notification\PushbulletNotification.cs" />
<Compile Include="Notification\SlackNotification.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api\PlexRequests.Api.csproj">
<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>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup>
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Services</RootNamespace>
<AssemblyName>PlexRequests.Services</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="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\StoreCleanup.cs" />
<Compile Include="Jobs\CouchPotatoCacher.cs" />
<Compile Include="Jobs\PlexAvailabilityChecker.cs" />
<Compile Include="Jobs\SickRageCacher.cs" />
<Compile Include="Jobs\SonarrCacher.cs" />
<Compile Include="Jobs\UserRequestLimitResetter.cs" />
<Compile Include="Models\PlexAlbum.cs" />
<Compile Include="Models\PlexMovie.cs" />
<Compile Include="Models\PlexTvShow.cs" />
<Compile Include="Interfaces\ISickRageCacher.cs" />
<Compile Include="Interfaces\ISonarrCacher.cs" />
<Compile Include="Interfaces\ICouchPotatoCacher.cs" />
<Compile Include="Interfaces\IAvailabilityChecker.cs" />
<Compile Include="Interfaces\IIntervals.cs" />
<Compile Include="Interfaces\INotification.cs" />
<Compile Include="Interfaces\INotificationService.cs" />
<Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationModel.cs" />
<Compile Include="Notification\NotificationService.cs" />
<Compile Include="Notification\PushoverNotification.cs" />
<Compile Include="Notification\PushbulletNotification.cs" />
<Compile Include="Notification\SlackNotification.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api\PlexRequests.Api.csproj">
<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>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
<Name>PlexRequests.Store</Name>
</ProjectReference>
</ItemGroup>
<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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -1,11 +1,11 @@
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
<?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" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>

@ -0,0 +1,34 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexUsers.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.Store.Models
{
public class PlexUsers : Entity
{
public int PlexUserId { get; set; }
public string UserAlias { get; set; }
}
}

@ -0,0 +1,41 @@
#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 System;
using Dapper.Contrib.Extensions;
namespace PlexRequests.Store.Models
{
[Table("RequestLimit")]
public class RequestLimit : Entity
{
public string Username { get; set; }
public DateTime FirstRequestDate { get; set; }
public int RequestCount { get; set; }
public RequestType RequestType { get; set; }
}
}

@ -1,125 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{92433867-2B7B-477B-A566-96C382427525}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Store</RootNamespace>
<AssemblyName>PlexRequests.Store</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dapper, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Dapper.Contrib, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.Contrib.1.50.0-beta8\lib\net45\Dapper.Contrib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Data.Sqlite">
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Linq" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<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="DbConfiguration.cs" />
<Compile Include="Entity.cs" />
<Compile Include="Models\IssueBlobs.cs" />
<Compile Include="Models\ScheduledJobs.cs" />
<Compile Include="Models\UsersToNotify.cs" />
<Compile Include="Repository\BaseGenericRepository.cs" />
<Compile Include="Repository\IRequestRepository.cs" />
<Compile Include="Repository\ISettingsRepository.cs" />
<Compile Include="ISqliteConfiguration.cs" />
<Compile Include="Repository\IRepository.cs" />
<Compile Include="Models\GlobalSettings.cs" />
<Compile Include="Models\LogEntity.cs" />
<Compile Include="Models\RequestBlobs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Repository\SettingsJsonRepository.cs" />
<Compile Include="Repository\RequestJsonRepository.cs" />
<Compile Include="Repository\GenericRepository.cs" />
<Compile Include="RequestedModel.cs" />
<Compile Include="UserEntity.cs" />
<Compile Include="UsersModel.cs" />
<Compile Include="UserRepository.cs" />
<Compile Include="Sql.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Sql.resx</DependentUpon>
</Compile>
<Compile Include="TableCreation.cs" />
<Compile Include="Models\Audit.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="sqlite3.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="SqlTables.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Sql.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Sql.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
</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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{92433867-2B7B-477B-A566-96C382427525}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Store</RootNamespace>
<AssemblyName>PlexRequests.Store</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dapper, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Dapper.Contrib, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.Contrib.1.50.0-beta8\lib\net45\Dapper.Contrib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Data.Sqlite">
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Linq" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<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="DbConfiguration.cs" />
<Compile Include="Entity.cs" />
<Compile Include="Models\IssueBlobs.cs" />
<Compile Include="Models\PlexUsers.cs" />
<Compile Include="Models\ScheduledJobs.cs" />
<Compile Include="Models\RequestLimit.cs" />
<Compile Include="Models\UsersToNotify.cs" />
<Compile Include="Repository\BaseGenericRepository.cs" />
<Compile Include="Repository\IRequestRepository.cs" />
<Compile Include="Repository\ISettingsRepository.cs" />
<Compile Include="ISqliteConfiguration.cs" />
<Compile Include="Repository\IRepository.cs" />
<Compile Include="Models\GlobalSettings.cs" />
<Compile Include="Models\LogEntity.cs" />
<Compile Include="Models\RequestBlobs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Repository\SettingsJsonRepository.cs" />
<Compile Include="Repository\RequestJsonRepository.cs" />
<Compile Include="Repository\GenericRepository.cs" />
<Compile Include="RequestedModel.cs" />
<Compile Include="UserEntity.cs" />
<Compile Include="UsersModel.cs" />
<Compile Include="UserRepository.cs" />
<Compile Include="Sql.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Sql.resx</DependentUpon>
</Compile>
<Compile Include="TableCreation.cs" />
<Compile Include="Models\Audit.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="sqlite3.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="SqlTables.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Sql.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Sql.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
</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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -80,7 +80,7 @@ namespace PlexRequests.Store
public static class RequestTypeDisplay
{
public static string Get(RequestType type)
public static string GetString(this RequestType type)
{
switch (type)
{

@ -1,84 +1,102 @@
--Any DB changes need to be made in this file.
CREATE TABLE IF NOT EXISTS Users
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
UserGuid varchar(50) NOT NULL ,
UserName varchar(50) NOT NULL,
Salt BLOB NOT NULL,
Hash BLOB NOT NULL,
Claims BLOB NOT NULL,
UserProperties BLOB
);
CREATE TABLE IF NOT EXISTS GlobalSettings
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
SettingsName varchar(50) NOT NULL,
Content varchar(100) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS GlobalSettings_Id ON GlobalSettings (Id);
CREATE TABLE IF NOT EXISTS RequestBlobs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ProviderId INTEGER NOT NULL,
Type INTEGER NOT NULL,
Content BLOB NOT NULL,
MusicId TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS RequestBlobs_Id ON RequestBlobs (Id);
CREATE TABLE IF NOT EXISTS Logs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Date varchar(100) NOT NULL,
Level varchar(100) NOT NULL,
Logger varchar(100) NOT NULL,
Message varchar(100) NOT NULL,
CallSite varchar(100) NOT NULL,
Exception varchar(100) NOT NULL
);
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);
CREATE TABLE IF NOT EXISTS IssueBlobs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
RequestId INTEGER,
Type INTEGER NOT NULL,
Content BLOB NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS IssueBlobs_Id ON IssueBlobs (Id);
--Any DB changes need to be made in this file.
CREATE TABLE IF NOT EXISTS Users
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
UserGuid varchar(50) NOT NULL ,
UserName varchar(50) NOT NULL,
Salt BLOB NOT NULL,
Hash BLOB NOT NULL,
Claims BLOB NOT NULL,
UserProperties BLOB
);
CREATE TABLE IF NOT EXISTS GlobalSettings
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
SettingsName varchar(50) NOT NULL,
Content varchar(100) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS GlobalSettings_Id ON GlobalSettings (Id);
CREATE TABLE IF NOT EXISTS RequestBlobs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ProviderId INTEGER NOT NULL,
Type INTEGER NOT NULL,
Content BLOB NOT NULL,
MusicId TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS RequestBlobs_Id ON RequestBlobs (Id);
CREATE TABLE IF NOT EXISTS Logs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Date varchar(100) NOT NULL,
Level varchar(100) NOT NULL,
Logger varchar(100) NOT NULL,
Message varchar(100) NOT NULL,
CallSite varchar(100) NOT NULL,
Exception varchar(100) NOT NULL
);
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);
CREATE TABLE IF NOT EXISTS IssueBlobs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
RequestId INTEGER,
Type INTEGER NOT NULL,
Content BLOB NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS IssueBlobs_Id ON IssueBlobs (Id);
CREATE TABLE IF NOT EXISTS RequestLimit
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Username varchar(100) NOT NULL,
FirstRequestDate varchar(100) NOT NULL,
RequestCount INTEGER NOT NULL,
RequestType INTEGER NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS RequestLimit_Id ON RequestLimit (Id);
CREATE TABLE IF NOT EXISTS PlexUsers
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
PlexUserId INTEGER NOT NULL,
UserAlias varchar(100) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON RequestLimit (Id);

@ -52,9 +52,9 @@ namespace PlexRequests.UI.Tests
{
get
{
yield return new TestCaseData("abcCba").Returns("AbcCba");
yield return new TestCaseData("").Returns("");
yield return new TestCaseData("12DSAda").Returns("12DSAda");
yield return new TestCaseData("abcCba").Returns("AbcCba").SetName("pascalCase");
yield return new TestCaseData("").Returns("").SetName("Empty");
yield return new TestCaseData("12DSAda").Returns("12DSAda").SetName("With numbers");
}
}
@ -62,14 +62,13 @@ namespace PlexRequests.UI.Tests
{
get
{
yield return new TestCaseData("abcCba").Returns("Abc Cba");
yield return new TestCaseData("").Returns("");
yield return new TestCaseData("JamieRees").Returns("Jamie Rees");
yield return new TestCaseData("Jamierees").Returns("Jamierees");
yield return new TestCaseData("ThisIsANewString").Returns("This Is A New String");
yield return new TestCaseData("").Returns("");
yield return new TestCaseData(IssueStatus.PendingIssue.ToString()).Returns("Pending Issue");
yield return new TestCaseData(IssueStatus.ResolvedIssue.ToString()).Returns("Resolved Issue");
yield return new TestCaseData("abcCba").Returns("Abc Cba").SetName("spaces");
yield return new TestCaseData("").Returns("").SetName("empty");
yield return new TestCaseData("JamieRees").Returns("Jamie Rees").SetName("Name");
yield return new TestCaseData("Jamierees").Returns("Jamierees").SetName("single word");
yield return new TestCaseData("ThisIsANewString").Returns("This Is A New String").SetName("longer string");
yield return new TestCaseData(IssueStatus.PendingIssue.ToString()).Returns("Pending Issue").SetName("enum pending");
yield return new TestCaseData(IssueStatus.ResolvedIssue.ToString()).Returns("Resolved Issue").SetName("enum resolved");
}
}
}

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<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" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="webPages:Enabled" value="false" />
</appSettings><system.web.webPages.razor>
<pages pageBaseType="Nancy.ViewEngines.Razor.NancyRazorViewBase">
<namespaces>
<add namespace="Nancy.ViewEngines.Razor" />
</namespaces>
</pages>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<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" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="webPages:Enabled" value="false" />
</appSettings><system.web.webPages.razor>
<pages pageBaseType="Nancy.ViewEngines.Razor.NancyRazorViewBase">
<namespaces>
<add namespace="Nancy.ViewEngines.Razor" />
</namespaces>
</pages>
</system.web.webPages.razor></configuration>

@ -1,228 +1,230 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Bootstrapper.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.Net;
using Mono.Data.Sqlite;
using Nancy;
using Nancy.Authentication.Forms;
using Nancy.Bootstrapper;
using Nancy.Conventions;
using Nancy.Cryptography;
using Nancy.Diagnostics;
using Nancy.Session;
using Nancy.TinyIoc;
using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Helpers;
using Nancy.Json;
using PlexRequests.Helpers.Analytics;
using PlexRequests.Services.Jobs;
using PlexRequests.UI.Jobs;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
namespace PlexRequests.UI
{
public class Bootstrapper : DefaultNancyBootstrapper
{
// The bootstrapper enables you to reconfigure the composition of the framework,
// by overriding the various methods and properties.
// For more information https://github.com/NancyFx/Nancy/wiki/Bootstrapper
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
ConfigureContainer(container);
JsonSettings.MaxJsonLength = int.MaxValue;
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
StaticConfiguration.DisableErrorTraces = false;
base.ApplicationStartup(container, pipelines);
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var baseUrl = settings.GetSettings().BaseUrl;
var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login";
// Enable forms auth
var formsAuthConfiguration = new FormsAuthenticationConfiguration
{
RedirectUrl = redirect,
UserMapper = container.Resolve<IUserMapper>()
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => true;
SubscribeAllObservers(container);
}
protected override void ConfigureConventions(NancyConventions nancyConventions)
{
base.ConfigureConventions(nancyConventions);
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var assetLocation = settings.GetSettings().BaseUrl;
nancyConventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content")
);
nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui");
}
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
private void SubscribeAllObservers(TinyIoCContainer container)
{
var notificationService = container.Resolve<INotificationService>();
var emailSettingsService = container.Resolve<ISettingsService<EmailNotificationSettings>>();
var emailSettings = emailSettingsService.GetSettings();
if (emailSettings.Enabled)
{
notificationService.Subscribe(new EmailMessageNotification(emailSettingsService));
}
var pushbulletService = container.Resolve<ISettingsService<PushbulletNotificationSettings>>();
var pushbulletSettings = pushbulletService.GetSettings();
if (pushbulletSettings.Enabled)
{
notificationService.Subscribe(new PushbulletNotification(container.Resolve<IPushbulletApi>(), pushbulletService));
}
var pushoverService = container.Resolve<ISettingsService<PushoverNotificationSettings>>();
var pushoverSettings = pushoverService.GetSettings();
if (pushoverSettings.Enabled)
{
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);
}
private void ConfigureContainer(TinyIoCContainer container)
{
container.Register<ICacheProvider, MemoryCacheProvider>().AsSingleton();
container.Register<ISqliteConfiguration, DbConfiguration>(new DbConfiguration(new SqliteFactory()));
container.Register<IRepository<UsersModel>, UserRepository<UsersModel>>();
container.Register<IUserMapper, UserMapper>();
container.Register<ICustomUserMapper, UserMapper>();
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
container.Register<ISettingsService<PushoverNotificationSettings>, SettingsServiceV2<PushoverNotificationSettings>>();
container.Register<ISettingsService<SlackNotificationSettings>, SettingsServiceV2<SlackNotificationSettings>>();
container.Register<ISettingsService<ScheduledJobsSettings>, SettingsServiceV2<ScheduledJobsSettings>>();
// Notification Service
container.Register<INotificationService, NotificationService>().AsSingleton();
// Settings
container.Register<ISettingsService<PlexRequestSettings>, SettingsServiceV2<PlexRequestSettings>>();
container.Register<ISettingsService<CouchPotatoSettings>, SettingsServiceV2<CouchPotatoSettings>>();
container.Register<ISettingsService<AuthenticationSettings>, SettingsServiceV2<AuthenticationSettings>>();
container.Register<ISettingsService<PlexSettings>, SettingsServiceV2<PlexSettings>>();
container.Register<ISettingsService<SonarrSettings>, SettingsServiceV2<SonarrSettings>>();
container.Register<ISettingsService<SickRageSettings>, SettingsServiceV2<SickRageSettings>>();
container.Register<ISettingsService<HeadphonesSettings>, SettingsServiceV2<HeadphonesSettings>>();
container.Register<ISettingsService<LogSettings>, SettingsServiceV2<LogSettings>>();
container.Register<ISettingsService<LandingPageSettings>, SettingsServiceV2<LandingPageSettings>>();
// Repo's
container.Register<IRepository<LogEntity>, GenericRepository<LogEntity>>();
container.Register<IRepository<UsersToNotify>, GenericRepository<UsersToNotify>>();
container.Register<IRepository<ScheduledJobs>, GenericRepository<ScheduledJobs>>();
container.Register<IRepository<IssueBlobs>, GenericRepository<IssueBlobs>>();
container.Register<IRequestService, JsonRequestModelRequestService>();
container.Register<IIssueService, IssueJsonService>();
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<IJobFactory, CustomJobFactory>();
container.Register<IAnalytics, Analytics>();
container.Register<ISchedulerFactory, StdSchedulerFactory>();
container.Register<IJobScheduler, Scheduler>();
// Api
container.Register<ICouchPotatoApi, CouchPotatoApi>();
container.Register<IPushbulletApi, PushbulletApi>();
container.Register<IPushoverApi, PushoverApi>();
container.Register<ISickRageApi, SickrageApi>();
container.Register<ISonarrApi, SonarrApi>();
container.Register<IPlexApi, PlexApi>();
container.Register<IMusicBrainzApi, MusicBrainzApi>();
container.Register<IHeadphonesApi, HeadphonesApi>();
container.Register<ISlackApi, SlackApi>();
var loc = ServiceLocator.Instance;
loc.SetContainer(container);
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Bootstrapper.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.Net;
using Mono.Data.Sqlite;
using Nancy;
using Nancy.Authentication.Forms;
using Nancy.Bootstrapper;
using Nancy.Conventions;
using Nancy.Cryptography;
using Nancy.Diagnostics;
using Nancy.Session;
using Nancy.TinyIoc;
using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Helpers;
using Nancy.Json;
using PlexRequests.Helpers.Analytics;
using PlexRequests.Services.Jobs;
using PlexRequests.UI.Jobs;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
namespace PlexRequests.UI
{
public class Bootstrapper : DefaultNancyBootstrapper
{
// The bootstrapper enables you to reconfigure the composition of the framework,
// by overriding the various methods and properties.
// For more information https://github.com/NancyFx/Nancy/wiki/Bootstrapper
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
ConfigureContainer(container);
JsonSettings.MaxJsonLength = int.MaxValue;
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
StaticConfiguration.DisableErrorTraces = false;
base.ApplicationStartup(container, pipelines);
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var baseUrl = settings.GetSettings().BaseUrl;
var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login";
// Enable forms auth
var formsAuthConfiguration = new FormsAuthenticationConfiguration
{
RedirectUrl = redirect,
UserMapper = container.Resolve<IUserMapper>()
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => true;
SubscribeAllObservers(container);
}
protected override void ConfigureConventions(NancyConventions nancyConventions)
{
base.ConfigureConventions(nancyConventions);
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var assetLocation = settings.GetSettings().BaseUrl;
nancyConventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content")
);
nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui");
}
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
private void SubscribeAllObservers(TinyIoCContainer container)
{
var notificationService = container.Resolve<INotificationService>();
var emailSettingsService = container.Resolve<ISettingsService<EmailNotificationSettings>>();
var emailSettings = emailSettingsService.GetSettings();
if (emailSettings.Enabled)
{
notificationService.Subscribe(new EmailMessageNotification(emailSettingsService));
}
var pushbulletService = container.Resolve<ISettingsService<PushbulletNotificationSettings>>();
var pushbulletSettings = pushbulletService.GetSettings();
if (pushbulletSettings.Enabled)
{
notificationService.Subscribe(new PushbulletNotification(container.Resolve<IPushbulletApi>(), pushbulletService));
}
var pushoverService = container.Resolve<ISettingsService<PushoverNotificationSettings>>();
var pushoverSettings = pushoverService.GetSettings();
if (pushoverSettings.Enabled)
{
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);
}
private void ConfigureContainer(TinyIoCContainer container)
{
container.Register<ICacheProvider, MemoryCacheProvider>().AsSingleton();
container.Register<ISqliteConfiguration, DbConfiguration>(new DbConfiguration(new SqliteFactory()));
container.Register<IRepository<UsersModel>, UserRepository<UsersModel>>();
container.Register<IUserMapper, UserMapper>();
container.Register<ICustomUserMapper, UserMapper>();
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
container.Register<ISettingsService<PushoverNotificationSettings>, SettingsServiceV2<PushoverNotificationSettings>>();
container.Register<ISettingsService<SlackNotificationSettings>, SettingsServiceV2<SlackNotificationSettings>>();
container.Register<ISettingsService<ScheduledJobsSettings>, SettingsServiceV2<ScheduledJobsSettings>>();
// Notification Service
container.Register<INotificationService, NotificationService>().AsSingleton();
// Settings
container.Register<ISettingsService<PlexRequestSettings>, SettingsServiceV2<PlexRequestSettings>>();
container.Register<ISettingsService<CouchPotatoSettings>, SettingsServiceV2<CouchPotatoSettings>>();
container.Register<ISettingsService<AuthenticationSettings>, SettingsServiceV2<AuthenticationSettings>>();
container.Register<ISettingsService<PlexSettings>, SettingsServiceV2<PlexSettings>>();
container.Register<ISettingsService<SonarrSettings>, SettingsServiceV2<SonarrSettings>>();
container.Register<ISettingsService<SickRageSettings>, SettingsServiceV2<SickRageSettings>>();
container.Register<ISettingsService<HeadphonesSettings>, SettingsServiceV2<HeadphonesSettings>>();
container.Register<ISettingsService<LogSettings>, SettingsServiceV2<LogSettings>>();
container.Register<ISettingsService<LandingPageSettings>, SettingsServiceV2<LandingPageSettings>>();
// Repo's
container.Register<IRepository<LogEntity>, GenericRepository<LogEntity>>();
container.Register<IRepository<UsersToNotify>, GenericRepository<UsersToNotify>>();
container.Register<IRepository<ScheduledJobs>, GenericRepository<ScheduledJobs>>();
container.Register<IRepository<IssueBlobs>, GenericRepository<IssueBlobs>>();
container.Register<IRepository<RequestLimit>, GenericRepository<RequestLimit>>();
container.Register<IRepository<PlexUsers>, GenericRepository<PlexUsers>>();
container.Register<IRequestService, JsonRequestModelRequestService>();
container.Register<IIssueService, IssueJsonService>();
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<IJobFactory, CustomJobFactory>();
container.Register<IAnalytics, Analytics>();
container.Register<ISchedulerFactory, StdSchedulerFactory>();
container.Register<IJobScheduler, Scheduler>();
// Api
container.Register<ICouchPotatoApi, CouchPotatoApi>();
container.Register<IPushbulletApi, PushbulletApi>();
container.Register<IPushoverApi, PushoverApi>();
container.Register<ISickRageApi, SickrageApi>();
container.Register<ISonarrApi, SonarrApi>();
container.Register<IPlexApi, PlexApi>();
container.Register<IMusicBrainzApi, MusicBrainzApi>();
container.Register<IHeadphonesApi, HeadphonesApi>();
container.Register<ISlackApi, SlackApi>();
var loc = ServiceLocator.Instance;
loc.SetContainer(container);
}
}
}

@ -264,8 +264,9 @@ function buildIssueContext(result) {
requestId: result.requestId,
type: result.type,
title: result.title,
issues: result.issues
};
issues: result.issues,
admin: result.admin
};
return context;
}

@ -159,7 +159,7 @@ $('#approveTVShows').click(function (e) {
$('#deleteMovies').click(function (e) {
e.preventDefault();
if (!confirm("Are you sure you want to delete all TV show requests?")) return;
if (!confirm("Are you sure you want to delete all Movie requests?")) return;
var buttonId = e.target.id;
var origHtml = $(this).html();

@ -0,0 +1,117 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CultureHelper.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace PlexRequests.UI.Helpers
{
public class CultureHelper
{
private static readonly List<string> ValidCultures = new List<string> { "en-US", "de-DE", "fr-FR", "es-ES", "de", "en", "fr", "es","da","sv","it","nl" };
private static readonly List<string> ImplimentedCultures = new List<string> {
"en-US",
"en",
"de",
"fr",
"es",
"da",
"sv",
"it",
"nl"
};
/// <summary>
/// Returns true if the language is a right-to-left language.
/// </summary>
public static bool IsRighToLeft()
{
return Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft;
}
/// <summary>
/// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
/// </summary>
/// <param name='name'> Culture's name (e.g. en-US) </param>
public static string GetImplementedCulture(string name)
{
if (string.IsNullOrEmpty(name))
{
return GetDefaultCulture();
}
// Validate the culture
if (!ValidCultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
{
return GetDefaultCulture();
}
if (ImplimentedCultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
{
return name; // We do have the culture, lets go with it
}
// Find a close match.
var match = GetNeutralCulture(name);
foreach (var c in ImplimentedCultures.Where(c => c.StartsWith(match, StringComparison.InvariantCultureIgnoreCase)))
{
return c;
}
return GetDefaultCulture(); // return Default culture since we cannot find anything
}
/// <summary>
/// Returns default culture name which is the first name declared (e.g. en-US)
/// </summary>
/// <returns> Culture string e.g. en-US </returns>
public static string GetDefaultCulture()
{
return ImplimentedCultures[0]; // return first culture
}
public static string GetCurrentCulture()
{
return Thread.CurrentThread.CurrentCulture.Name;
}
public static string GetCurrentNeutralCulture()
{
return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name);
}
public static string GetNeutralCulture(string name)
{
if (!name.Contains("-")) return name;
return name.Split('-')[0]; // Read first part only
}
}
}

@ -62,6 +62,7 @@ namespace PlexRequests.UI.Jobs
var cp = JobBuilder.Create<CouchPotatoCacher>().WithIdentity("CouchPotatoCacher", "Cache").Build();
var store = JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build();
var storeClean = JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build();
var userRequestLimitReset = JobBuilder.Create<UserRequestLimitResetter>().WithIdentity("UserRequestLimiter", "Request").Build();
jobs.Add(plex);
jobs.Add(sickrage);
@ -69,6 +70,7 @@ namespace PlexRequests.UI.Jobs
jobs.Add(cp);
jobs.Add(store);
jobs.Add(storeClean);
jobs.Add(userRequestLimitReset);
return jobs;
}
@ -150,6 +152,13 @@ namespace PlexRequests.UI.Jobs
.WithSimpleSchedule(x => x.WithIntervalInHours(s.StoreCleanup).RepeatForever())
.Build();
var userRequestLimiter =
TriggerBuilder.Create()
.WithIdentity("UserRequestLimiter", "Request")
.StartAt(DateTimeOffset.Now.AddMinutes(5)) // Everything has started on application start, lets wait 5 minutes
.WithSimpleSchedule(x => x.WithIntervalInHours(s.UserRequestLimitResetter).RepeatForever())
.Build();
triggers.Add(plexAvailabilityChecker);
triggers.Add(srCacher);
@ -157,6 +166,7 @@ namespace PlexRequests.UI.Jobs
triggers.Add(cpCacher);
triggers.Add(storeBackup);
triggers.Add(storeCleanup);
triggers.Add(userRequestLimiter);
return triggers;
}

@ -36,6 +36,7 @@ namespace PlexRequests.UI.Models
public string Title { get; set; }
public string Issues { get; set; }
public string Type { get; set; }
public bool Admin { get; set; }
}
}

@ -46,9 +46,12 @@ using NLog;
using MarkdownSharp;
using Nancy.Responses;
using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
@ -204,6 +207,8 @@ namespace PlexRequests.UI.Modules
Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs();
Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs();
Post["/clearlogs", true] = async (x, ct) => await ClearLogs();
}
private async Task<Negotiator> Authentication()
@ -256,9 +261,13 @@ namespace PlexRequests.UI.Modules
model.BaseUrl = model.BaseUrl.Remove(0, 1);
}
}
if (!model.CollectAnalyticData)
{
Analytics.TrackEventAsync(Category.Admin, Action.Save, "CollectAnalyticData turned off", Username, CookieHelper.GetAnalyticClientId(Cookies));
}
var result = PrService.SaveSettings(model);
await Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies));
Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies));
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" });
@ -710,7 +719,7 @@ namespace PlexRequests.UI.Modules
private Response UpdateLogLevels(int level)
{
var settings = LogService.GetSettings();
Analytics.TrackEventAsync(Category.Admin, Action.Update, "Updated Log Levels", Username, CookieHelper.GetAnalyticClientId(Cookies), level);
// apply the level
var newLevel = LogLevel.FromOrdinal(level);
LoggingHelper.ReconfigureLogLevel(newLevel);
@ -751,6 +760,7 @@ namespace PlexRequests.UI.Modules
private Response CreateApiKey()
{
this.RequiresClaims(UserClaims.Admin);
Analytics.TrackEventAsync(Category.Admin, Action.Create, "Created API Key", Username, CookieHelper.GetAnalyticClientId(Cookies));
var apiKey = Guid.NewGuid().ToString("N");
var settings = PrService.GetSettings();
@ -838,6 +848,7 @@ namespace PlexRequests.UI.Modules
{
var settings = this.Bind<LandingPageSettings>();
Analytics.TrackEventAsync(Category.Admin, Action.Update, "Update Landing Page", Username, CookieHelper.GetAnalyticClientId(Cookies));
var plexSettings = await PlexService.GetSettingsAsync();
if (string.IsNullOrEmpty(plexSettings.Ip))
{
@ -876,6 +887,8 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SaveScheduledJobs()
{
Analytics.TrackEventAsync(Category.Admin, Action.Update, "Update ScheduledJobs", Username, CookieHelper.GetAnalyticClientId(Cookies));
var settings = this.Bind<ScheduledJobsSettings>();
var result = await ScheduledJobSettings.SaveSettingsAsync(settings);
@ -884,5 +897,24 @@ namespace PlexRequests.UI.Modules
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" });
}
private async Task<Response> ClearLogs()
{
try
{
Analytics.TrackEventAsync(Category.Admin, Action.Delete, "Clear Logs", Username, CookieHelper.GetAnalyticClientId(Cookies));
var allLogs = await LogsRepo.GetAllAsync();
foreach (var logEntity in allLogs)
{
await LogsRepo.DeleteAsync(logEntity);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully."});
}
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
}
}
}
}

@ -27,12 +27,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Nancy;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
@ -40,9 +42,11 @@ namespace PlexRequests.UI.Modules
public abstract class BaseModule : NancyModule
{
protected string BaseUrl { get; set; }
protected BaseModule(ISettingsService<PlexRequestSettings> settingsService)
{
var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl;
@ -50,10 +54,13 @@ namespace PlexRequests.UI.Modules
var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl;
ModulePath = modulePath;
Before += (ctx) => SetCookie();
}
protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService)
{
var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl;
@ -61,6 +68,8 @@ namespace PlexRequests.UI.Modules
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
ModulePath = settingModulePath;
Before += (ctx) => SetCookie();
}
private int _dateTimeOffset = -1;
@ -96,7 +105,7 @@ namespace PlexRequests.UI.Modules
}
}
protected IDictionary<string, string> Cookies => Request.Cookies;
protected IDictionary<string, string> Cookies => Request?.Cookies;
protected bool IsAdmin
{
@ -110,6 +119,41 @@ namespace PlexRequests.UI.Modules
return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser);
}
}
protected string Culture { get; set; }
protected const string CultureCookieName = "_culture";
protected Response SetCookie()
{
try
{
string cultureName;
// Attempt to read the culture cookie from Request
var outCookie = string.Empty;
if (Cookies.TryGetValue(CultureCookieName, out outCookie))
{
cultureName = outCookie;
}
else
{
cultureName = Request.Headers?.AcceptLanguage?.FirstOrDefault()?.Item1;
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Culture = Thread.CurrentThread.CurrentCulture.Name;
}
catch (Exception)
{
// Couldn't Set the culture
}
return null;
}
}
}

@ -0,0 +1,79 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CultureModule.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 Nancy;
using Nancy.Extensions;
using Nancy.Responses;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Modules
{
public class CultureModule : BaseModule
{
public CultureModule(ISettingsService<PlexRequestSettings> pr, IAnalytics a) : base("culture",pr)
{
Analytics = a;
Get["/", true] = async(x,c) => await SetCulture();
}
private IAnalytics Analytics { get; }
public async Task<RedirectResponse> SetCulture()
{
var culture = (string)Request.Query["l"];
var returnUrl = (string)Request.Query["u"];
// Validate
culture = CultureHelper.GetImplementedCulture(culture);
var outCookie = string.Empty;
if (Cookies.TryGetValue(CultureCookieName, out outCookie))
{
Cookies[CultureCookieName] = culture;
}
else
{
Cookies.Add(CultureCookieName, culture);
}
var cookie = Cookies[CultureCookieName];
var response = Context.GetRedirect(returnUrl);
response.WithCookie(CultureCookieName, cookie ?? culture, DateTime.Now.AddYears(1));
Analytics.TrackEventAsync(Category.Navbar, PlexRequests.Helpers.Analytics.Action.Language, culture, Username, CookieHelper.GetAnalyticClientId(Cookies));
return response;
}
}
}

@ -78,7 +78,7 @@ namespace PlexRequests.UI.Modules
foreach (var i in issuesModels)
{
var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), };
var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), Admin = IsAdmin };
// Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues
var state = i.Issues.Select(x => x.Issue).ToArray();

@ -58,6 +58,7 @@ namespace PlexRequests.UI.Modules
private async Task<Negotiator> Index()
{
var s = await LandingSettings.GetSettingsAsync();
var model = new LandingPageViewModel
{

@ -0,0 +1,98 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexUsersModule.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 System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Music;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class PlexUsersModule : BaseAuthModule
{
public PlexUsersModule(ISettingsService<PlexRequestSettings> pr, IPlexApi plexApi, ISettingsService<AuthenticationSettings> auth,
IRepository<PlexUsers> repo) : base("plexusers", pr)
{
PlexApi = plexApi;
AuthSettings = auth;
Repo = repo;
Get["/"] = x => Index();
Get["/users", true] = async (x, ct) => await GetPlexUsers();
Post["/alias", true] = async (x, ct) => await Alias();
}
private IPlexApi PlexApi { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private IRepository<PlexUsers> Repo { get; }
private Negotiator Index()
{
return View["Index"];
}
private async Task<Response> GetPlexUsers()
{
var authSettings = await AuthSettings.GetSettingsAsync();
var users = PlexApi.GetUsers(authSettings.PlexAuthToken);
return Response.AsJson(users.User);
}
private async Task<Response> Alias()
{
var plexUserId = (int)Request.Form["plexid"];
var alias = (string)Request.Form["alias"];
var allUsers = await Repo.GetAllAsync();
var existingUser = allUsers.FirstOrDefault(x => x.PlexUserId == plexUserId);
if (existingUser == null)
{
// Create a new mapping
existingUser = new PlexUsers { PlexUserId = plexUserId, UserAlias = alias };
}
else
{
// Modify existing alias
existingUser.UserAlias = alias;
}
await Repo.InsertAsync(existingUser);
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
}

@ -1,377 +1,387 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RequestsModule.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 Nancy;
using Nancy.Responses.Negotiation;
using Nancy.Security;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.UI.Models;
using PlexRequests.Helpers;
using PlexRequests.UI.Helpers;
using System.Collections.Generic;
using PlexRequests.Api.Interfaces;
using System.Threading.Tasks;
using NLog;
namespace PlexRequests.UI.Modules
{
public class RequestsModule : BaseAuthModule
{
public RequestsModule(
IRequestService service,
ISettingsService<PlexRequestSettings> prSettings,
ISettingsService<PlexSettings> plex,
INotificationService notify,
ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<SickRageSettings> sickRageSettings,
ISettingsService<CouchPotatoSettings> cpSettings,
ICouchPotatoApi cpApi,
ISonarrApi sonarrApi,
ISickRageApi sickRageApi,
ICacheProvider cache) : base("requests", prSettings)
{
Service = service;
PrSettings = prSettings;
PlexSettings = plex;
NotificationService = notify;
SonarrSettings = sonarrSettings;
SickRageSettings = sickRageSettings;
CpSettings = cpSettings;
SonarrApi = sonarrApi;
SickRageApi = sickRageApi;
CpApi = cpApi;
Cache = cache;
Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies();
Get["/tvshows", true] = async (c, ct) => await GetTvShows();
Get["/albums", true] = async (x, ct) => await GetAlbumRequests();
Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id);
Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id);
Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private IRequestService Service { get; }
private INotificationService NotificationService { get; }
private ISettingsService<PlexRequestSettings> PrSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; }
private async Task<Negotiator> LoadRequests()
{
var settings = await PrSettings.GetSettingsAsync();
return View["Index", settings];
}
private async Task<Response> GetMovies()
{
var settings = PrSettings.GetSettings();
var allRequests = await Service.GetAllAsync();
allRequests = allRequests.Where(x => x.Type == RequestType.Movie);
var dbMovies = allRequests.ToList();
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList();
}
List<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
{
var cpSettings = CpSettings.GetSettings();
if (cpSettings.Enabled)
{
try
{
var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
{
return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false);
});
qualities = result.list.Select(x => new QualityModel() { Id = x._id, Name = x.label }).ToList();
}
catch (Exception e)
{
Log.Info(e);
}
}
}
var viewModel = dbMovies.Select(movie => new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
ReleaseDateTicks = movie.ReleaseDate.Ticks,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { },
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = IsAdmin,
IssueId = movie.IssueId,
Qualities = qualities.ToArray()
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetTvShows()
{
var settings = PrSettings.GetSettings();
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.TvShow);
var dbTv = requests;
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
}
IEnumerable<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
{
try
{
var sonarrSettings = SonarrSettings.GetSettings();
if (sonarrSettings.Enabled)
{
var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () =>
{
return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
qualities = result.Result.Select(x => new QualityModel() { Id = x.id.ToString(), Name = x.name }).ToList();
}
else
{
var sickRageSettings = SickRageSettings.GetSettings();
if (sickRageSettings.Enabled)
{
qualities = sickRageSettings.Qualities.Select(x => new QualityModel() { Id = x.Key, Name = x.Value }).ToList();
}
}
}
catch (Exception e)
{
Log.Info(e);
}
}
var viewModel = dbTv.Select(tv =>
{
return new RequestViewModel
{
ProviderId = tv.ProviderId,
Type = tv.Type,
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath,
ReleaseDate = tv.ReleaseDate,
ReleaseDateTicks = tv.ReleaseDate.Ticks,
RequestedDate = tv.RequestedDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks,
Released = DateTime.Now > tv.ReleaseDate,
Approved = tv.Available || tv.Approved,
Title = tv.Title,
Overview = tv.Overview,
RequestedUsers = IsAdmin ? tv.AllUsers.ToArray() : new string[] { },
ReleaseYear = tv.ReleaseDate.Year.ToString(),
Available = tv.Available,
Admin = IsAdmin,
IssueId = tv.IssueId,
TvSeriesRequestType = tv.SeasonsRequested,
Qualities = qualities.ToArray()
};
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetAlbumRequests()
{
var settings = PrSettings.GetSettings();
var dbAlbum = await Service.GetAllAsync();
dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album);
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
}
var viewModel = dbAlbum.Select(album =>
{
return new RequestViewModel
{
ProviderId = album.ProviderId,
Type = album.Type,
Status = album.Status,
ImdbId = album.ImdbId,
Id = album.Id,
PosterPath = album.PosterPath,
ReleaseDate = album.ReleaseDate,
ReleaseDateTicks = album.ReleaseDate.Ticks,
RequestedDate = album.RequestedDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks,
Released = DateTime.Now > album.ReleaseDate,
Approved = album.Available || album.Approved,
Title = album.Title,
Overview = album.Overview,
RequestedUsers = IsAdmin ? album.AllUsers.ToArray() : new string[] { },
ReleaseYear = album.ReleaseDate.Year.ToString(),
Available = album.Available,
Admin = IsAdmin,
IssueId = album.IssueId,
TvSeriesRequestType = album.SeasonsRequested,
MusicBrainzId = album.MusicBrainzId,
ArtistName = album.ArtistName
};
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> DeleteRequest(int requestid)
{
this.RequiresClaims(UserClaims.Admin);
var currentEntity = await Service.GetAsync(requestid);
await Service.DeleteRequestAsync(currentEntity);
return Response.AsJson(new JsonResponseModel { Result = true });
}
/// <summary>
/// Reports the issue.
/// Comment can be null if the <c>IssueState != Other</c>
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <param name="issue">The issue.</param>
/// <param name="comment">The comment.</param>
/// <returns></returns>
private async Task<Response> ReportIssue(int requestId, IssueState issue, string comment)
{
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
originalRequest.Issues = issue;
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
? $"{Username} - {comment}"
: string.Empty;
var result = await Service.UpdateRequestAsync(originalRequest);
var model = new NotificationModel
{
User = Username,
NotificationType = NotificationType.Issue,
Title = originalRequest.Title,
DateTime = DateTime.Now,
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
};
await NotificationService.Publish(model);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
private async Task<Response> ClearIssue(int requestId)
{
this.RequiresClaims(UserClaims.Admin);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" });
}
originalRequest.Issues = IssueState.None;
originalRequest.OtherMessage = string.Empty;
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
}
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
{
this.RequiresClaims(UserClaims.Admin);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" });
}
originalRequest.Available = available;
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new { Result = true, Available = available, Message = string.Empty }
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RequestsModule.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 Nancy;
using Nancy.Responses.Negotiation;
using Nancy.Security;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.UI.Models;
using PlexRequests.Helpers;
using PlexRequests.UI.Helpers;
using System.Collections.Generic;
using PlexRequests.Api.Interfaces;
using System.Threading.Tasks;
using NLog;
using PlexRequests.Core.Models;
using PlexRequests.Helpers.Analytics;
using Action = PlexRequests.Helpers.Analytics.Action;
namespace PlexRequests.UI.Modules
{
public class RequestsModule : BaseAuthModule
{
public RequestsModule(
IRequestService service,
ISettingsService<PlexRequestSettings> prSettings,
ISettingsService<PlexSettings> plex,
INotificationService notify,
ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<SickRageSettings> sickRageSettings,
ISettingsService<CouchPotatoSettings> cpSettings,
ICouchPotatoApi cpApi,
ISonarrApi sonarrApi,
ISickRageApi sickRageApi,
ICacheProvider cache,
IAnalytics an) : base("requests", prSettings)
{
Service = service;
PrSettings = prSettings;
PlexSettings = plex;
NotificationService = notify;
SonarrSettings = sonarrSettings;
SickRageSettings = sickRageSettings;
CpSettings = cpSettings;
SonarrApi = sonarrApi;
SickRageApi = sickRageApi;
CpApi = cpApi;
Cache = cache;
Analytics = an;
Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies();
Get["/tvshows", true] = async (c, ct) => await GetTvShows();
Get["/albums", true] = async (x, ct) => await GetAlbumRequests();
Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id);
Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id);
Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private IRequestService Service { get; }
private IAnalytics Analytics { get; }
private INotificationService NotificationService { get; }
private ISettingsService<PlexRequestSettings> PrSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; }
private async Task<Negotiator> LoadRequests()
{
var settings = await PrSettings.GetSettingsAsync();
return View["Index", settings];
}
private async Task<Response> GetMovies()
{
var settings = PrSettings.GetSettings();
var allRequests = await Service.GetAllAsync();
allRequests = allRequests.Where(x => x.Type == RequestType.Movie);
var dbMovies = allRequests.ToList();
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList();
}
List<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
{
var cpSettings = CpSettings.GetSettings();
if (cpSettings.Enabled)
{
try
{
var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
{
return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false);
});
qualities = result.list.Select(x => new QualityModel() { Id = x._id, Name = x.label }).ToList();
}
catch (Exception e)
{
Log.Info(e);
}
}
}
var viewModel = dbMovies.Select(movie => new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
ReleaseDateTicks = movie.ReleaseDate.Ticks,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { },
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = IsAdmin,
IssueId = movie.IssueId,
Qualities = qualities.ToArray()
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetTvShows()
{
var settings = PrSettings.GetSettings();
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.TvShow);
var dbTv = requests;
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
}
IEnumerable<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
{
try
{
var sonarrSettings = SonarrSettings.GetSettings();
if (sonarrSettings.Enabled)
{
var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () =>
{
return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
qualities = result.Result.Select(x => new QualityModel() { Id = x.id.ToString(), Name = x.name }).ToList();
}
else
{
var sickRageSettings = SickRageSettings.GetSettings();
if (sickRageSettings.Enabled)
{
qualities = sickRageSettings.Qualities.Select(x => new QualityModel() { Id = x.Key, Name = x.Value }).ToList();
}
}
}
catch (Exception e)
{
Log.Info(e);
}
}
var viewModel = dbTv.Select(tv =>
{
return new RequestViewModel
{
ProviderId = tv.ProviderId,
Type = tv.Type,
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath,
ReleaseDate = tv.ReleaseDate,
ReleaseDateTicks = tv.ReleaseDate.Ticks,
RequestedDate = tv.RequestedDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks,
Released = DateTime.Now > tv.ReleaseDate,
Approved = tv.Available || tv.Approved,
Title = tv.Title,
Overview = tv.Overview,
RequestedUsers = IsAdmin ? tv.AllUsers.ToArray() : new string[] { },
ReleaseYear = tv.ReleaseDate.Year.ToString(),
Available = tv.Available,
Admin = IsAdmin,
IssueId = tv.IssueId,
TvSeriesRequestType = tv.SeasonsRequested,
Qualities = qualities.ToArray()
};
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetAlbumRequests()
{
var settings = PrSettings.GetSettings();
var dbAlbum = await Service.GetAllAsync();
dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album);
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
{
dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
}
var viewModel = dbAlbum.Select(album =>
{
return new RequestViewModel
{
ProviderId = album.ProviderId,
Type = album.Type,
Status = album.Status,
ImdbId = album.ImdbId,
Id = album.Id,
PosterPath = album.PosterPath,
ReleaseDate = album.ReleaseDate,
ReleaseDateTicks = album.ReleaseDate.Ticks,
RequestedDate = album.RequestedDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks,
Released = DateTime.Now > album.ReleaseDate,
Approved = album.Available || album.Approved,
Title = album.Title,
Overview = album.Overview,
RequestedUsers = IsAdmin ? album.AllUsers.ToArray() : new string[] { },
ReleaseYear = album.ReleaseDate.Year.ToString(),
Available = album.Available,
Admin = IsAdmin,
IssueId = album.IssueId,
TvSeriesRequestType = album.SeasonsRequested,
MusicBrainzId = album.MusicBrainzId,
ArtistName = album.ArtistName
};
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> DeleteRequest(int requestid)
{
this.RequiresClaims(UserClaims.Admin);
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
var currentEntity = await Service.GetAsync(requestid);
await Service.DeleteRequestAsync(currentEntity);
return Response.AsJson(new JsonResponseModel { Result = true });
}
/// <summary>
/// Reports the issue.
/// Comment can be null if the <c>IssueState != Other</c>
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <param name="issue">The issue.</param>
/// <param name="comment">The comment.</param>
/// <returns></returns>
private async Task<Response> ReportIssue(int requestId, IssueState issue, string comment)
{
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
originalRequest.Issues = issue;
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
? $"{Username} - {comment}"
: string.Empty;
var result = await Service.UpdateRequestAsync(originalRequest);
var model = new NotificationModel
{
User = Username,
NotificationType = NotificationType.Issue,
Title = originalRequest.Title,
DateTime = DateTime.Now,
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
};
await NotificationService.Publish(model);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
private async Task<Response> ClearIssue(int requestId)
{
this.RequiresClaims(UserClaims.Admin);
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" });
}
originalRequest.Issues = IssueState.None;
originalRequest.OtherMessage = string.Empty;
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
}
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
{
this.RequiresClaims(UserClaims.Admin);
Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" });
}
originalRequest.Available = available;
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new { Result = true, Available = available, Message = string.Empty }
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
}
}
}

@ -53,11 +53,14 @@ using Nancy.Responses;
using PlexRequests.Api.Models.Tv;
using PlexRequests.Core.Models;
using PlexRequests.Helpers.Analytics;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using TMDbLib.Objects.General;
using Action = PlexRequests.Helpers.Analytics.Action;
namespace PlexRequests.UI.Modules
{
public class SearchModule : BaseAuthModule
@ -69,7 +72,7 @@ namespace PlexRequests.UI.Modules
INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService<HeadphonesSettings> hpService,
ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi,
ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth, IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email,
IIssueService issue) : base("search", prSettings)
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl) : base("search", prSettings)
{
Auth = auth;
PlexService = plexService;
@ -95,6 +98,8 @@ namespace PlexRequests.UI.Modules
UsersToNotifyRepo = u;
EmailNotificationSettings = email;
IssueService = issue;
Analytics = a;
RequestLimitRepo = rl;
Get["/", true] = async (x, ct) => await RequestLoad();
@ -140,31 +145,32 @@ namespace PlexRequests.UI.Modules
private IHeadphonesApi HeadphonesApi { get; }
private IRepository<UsersToNotify> UsersToNotifyRepo { get; }
private IIssueService IssueService { get; }
private IAnalytics Analytics { get; }
private IRepository<RequestLimit> RequestLimitRepo { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private async Task<Negotiator> RequestLoad()
{
var settings = await PrService.GetSettingsAsync();
Log.Trace("Loading Index");
return View["Search/Index", settings];
}
private async Task<Response> UpcomingMovies()
{
Log.Trace("Loading upcoming movies");
Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, CookieHelper.GetAnalyticClientId(Cookies));
return await ProcessMovies(MovieSearchType.Upcoming, string.Empty);
}
private async Task<Response> CurrentlyPlayingMovies()
{
Log.Trace("Loading currently playing movies");
Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, CookieHelper.GetAnalyticClientId(Cookies));
return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty);
}
private async Task<Response> SearchMovie(string searchTerm)
{
Log.Trace("Searching for Movie {0}", searchTerm);
Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
return await ProcessMovies(MovieSearchType.Search, searchTerm);
}
@ -279,8 +285,8 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SearchTvShow(string searchTerm)
{
Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
var plexSettings = await PlexService.GetSettingsAsync();
Log.Trace("Searching for TV Show {0}", searchTerm);
var apiTv = new List<TvMazeSearch>();
await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) =>
@ -295,7 +301,6 @@ namespace PlexRequests.UI.Modules
if (!apiTv.Any())
{
Log.Trace("TV Show data is null");
return Response.AsJson("");
}
@ -366,6 +371,7 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SearchMusic(string searchTerm)
{
Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
var apiAlbums = new List<Release>();
await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) =>
{
@ -417,14 +423,16 @@ namespace PlexRequests.UI.Modules
private async Task<Response> RequestMovie(int movieId)
{
var movieInfo = MovieApi.GetMovieInformation(movieId).Result;
var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}";
Log.Trace("Getting movie info from TheMovieDb");
var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.Movie))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Movies! Please contact your admin." });
}
// check if the movie has already been requested
Log.Info("Requesting movie with id {0}", movieId);
Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, CookieHelper.GetAnalyticClientId(Cookies));
var movieInfo = MovieApi.GetMovieInformation(movieId).Result;
var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}";
var existingRequest = await RequestService.CheckRequestAsync(movieId);
if (existingRequest != null)
{
@ -435,11 +443,9 @@ namespace PlexRequests.UI.Modules
await RequestService.UpdateRequestAsync(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" });
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" });
}
Log.Debug("movie with id {0} doesnt exists", movieId);
try
{
var movies = Checker.GetPlexMovies();
@ -451,7 +457,7 @@ namespace PlexRequests.UI.Modules
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) });
}
//#endif
@ -484,70 +490,28 @@ namespace PlexRequests.UI.Modules
Log.Debug("Adding movie to CP result {0}", result);
if (result)
{
model.Approved = true;
Log.Info("Adding movie to database (No approval required)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.Movie, settings))
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.Movie
};
await NotificationService.Publish(notificationModel);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message =
"Something went wrong adding the movie to CouchPotato! Please check your settings."
});
}
else
{
model.Approved = true;
Log.Info("Adding movie to database (No approval required)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.Movie, settings))
return Response.AsJson(new JsonResponseModel
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.Movie
};
await NotificationService.Publish(notificationModel);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
Result = false,
Message = Resources.UI.Search_CouchPotatoError
});
}
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
try
{
Log.Info("Adding movie to database");
var id = await RequestService.AddRequestAsync(model);
var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest, RequestType = RequestType.Movie };
await NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." });
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_CouchPotatoError });
}
}
@ -559,6 +523,12 @@ namespace PlexRequests.UI.Modules
/// <returns></returns>
private async Task<Response> RequestTvShow(int showId, string seasons)
{
var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.TvShow))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitTVShow });
}
Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies));
var tvApi = new TvMazeApi();
var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
@ -567,10 +537,8 @@ namespace PlexRequests.UI.Modules
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
//#if !DEBUG
var settings = await PrService.GetSettingsAsync();
// check if the show has already been requested
Log.Info("Requesting tv show with id {0}", showId);
var existingRequest = await RequestService.CheckRequestAsync(showId);
if (existingRequest != null)
{
@ -580,7 +548,7 @@ namespace PlexRequests.UI.Modules
existingRequest.RequestedUsers.Add(Username);
await RequestService.UpdateRequestAsync(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" });
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" : $"{fullShowName} {Resources.UI.Search_AlreadyRequested}" });
}
try
@ -594,12 +562,12 @@ namespace PlexRequests.UI.Modules
}
if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" });
}
}
catch (Exception)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) });
}
//#endif
@ -661,28 +629,10 @@ namespace PlexRequests.UI.Modules
var result = sender.SendToSonarr(sonarrSettings, model);
if (!string.IsNullOrEmpty(result?.title))
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & Sonarr)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.TvShow, settings))
{
var notify1 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.TvShow
};
await NotificationService.Publish(notify1);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}");
}
return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages));
}
var srSettings = SickRageService.GetSettings();
@ -691,57 +641,20 @@ namespace PlexRequests.UI.Modules
var result = sender.SendToSickRage(srSettings, model);
if (result?.result == "success")
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required & SickRage)");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.TvShow, settings))
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.TvShow
};
await NotificationService.Publish(notify2);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}");
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "<b>Message From SickRage: </b>" + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." });
return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message ?? Resources.UI.Search_SickrageError });
}
if (!srSettings.Enabled && !sonarrSettings.Enabled)
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup");
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.TvShow, settings))
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.TvShow
};
await NotificationService.Publish(notify2);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}");
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." });
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp });
}
await RequestService.AddRequestAsync(model);
var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest, RequestType = RequestType.TvShow };
await NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" });
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}");
}
private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings)
@ -763,24 +676,23 @@ namespace PlexRequests.UI.Modules
private async Task<Response> RequestAlbum(string releaseId)
{
var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.Album))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitAlbums });
}
Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, CookieHelper.GetAnalyticClientId(Cookies));
var existingRequest = await RequestService.CheckRequestAsync(releaseId);
Log.Debug("Checking for an existing request");
if (existingRequest != null)
{
Log.Debug("We do have an existing album request");
if (!existingRequest.UserHasRequested(Username))
{
Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username);
existingRequest.RequestedUsers.Add(Username);
await RequestService.UpdateRequestAsync(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" });
return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" });
}
Log.Debug("This is a new request");
var albumInfo = MusicBrainzApi.GetAlbum(releaseId);
DateTime release;
DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release);
@ -788,7 +700,7 @@ namespace PlexRequests.UI.Modules
var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist;
if (artist == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_MusicBrainzError });
}
var albums = Checker.GetPlexAlbums();
@ -799,7 +711,7 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{albumInfo.title} is already in Plex!"
Message = $"{albumInfo.title} {Resources.UI.Search_AlreadyInPlex}"
});
}
@ -824,7 +736,6 @@ namespace PlexRequests.UI.Modules
if (ShouldAutoApprove(RequestType.Album, settings))
{
Log.Debug("We don't require approval OR the user is in the whitelist");
var hpSettings = HeadphonesService.GetSettings();
if (!hpSettings.Enabled)
@ -834,54 +745,16 @@ namespace PlexRequests.UI.Modules
Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"{model.Title} was successfully added!"
Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"
});
}
var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService);
await sender.AddAlbum(model);
model.Approved = true;
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.Album, settings))
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.Album
};
await NotificationService.Publish(notify2);
}
return
Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"{model.Title} was successfully added!"
});
return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}");
}
if (ShouldSendNotification(RequestType.Album, settings))
{
var notify2 = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.Album
};
await NotificationService.Publish(notify2);
}
await RequestService.AddRequestAsync(model);
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"{model.Title} was successfully added!"
});
return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}");
}
private string GetMusicBrainzCoverArt(string id)
@ -919,17 +792,18 @@ namespace PlexRequests.UI.Modules
private async Task<Response> NotifyUser(bool notify)
{
Analytics.TrackEventAsync(Category.Search, Action.Save, "NotifyUser", Username, CookieHelper.GetAnalyticClientId(Cookies), notify ? 1 : 0);
var authSettings = await Auth.GetSettingsAsync();
var auth = authSettings.UserAuthentication;
var emailSettings = await EmailNotificationSettings.GetSettingsAsync();
var email = emailSettings.EnableUserEmailNotifications;
if (!auth)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_ErrorPlexAccountOnly });
}
if (!email)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." });
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_ErrorNotEnabled });
}
var username = Username;
var originalList = await UsersToNotifyRepo.GetAllAsync();
@ -937,7 +811,7 @@ namespace PlexRequests.UI.Modules
{
if (originalList == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_NotificationError });
}
var userToRemove = originalList.FirstOrDefault(x => x.Username == username);
if (userToRemove != null)
@ -952,7 +826,7 @@ namespace PlexRequests.UI.Modules
{
var userModel = new UsersToNotify { Username = username };
var insertResult = await UsersToNotifyRepo.InsertAsync(userModel);
return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" });
return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = Resources.UI.Common_CouldNotSave });
}
var existingUser = originalList.FirstOrDefault(x => x.Username == username);
@ -964,7 +838,7 @@ namespace PlexRequests.UI.Modules
{
var userModel = new UsersToNotify { Username = username };
var insertResult = await UsersToNotifyRepo.InsertAsync(userModel);
return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" });
return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = Resources.UI.Common_CouldNotSave });
}
}
@ -985,6 +859,89 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(model);
}
private async Task<bool> CheckRequestLimit(PlexRequestSettings s, RequestType type)
{
if (IsAdmin)
return true;
if (s.ApprovalWhiteList.Contains(Username))
return true;
var requestLimit = GetRequestLimitForType(type, s);
if (requestLimit == 0)
{
return true;
}
var limit = await RequestLimitRepo.GetAllAsync();
var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == type);
if (usersLimit == null)
{
// Have not set a requestLimit yet
return true;
}
return usersLimit.RequestCount >= requestLimit;
}
private int GetRequestLimitForType(RequestType type, PlexRequestSettings s)
{
int requestLimit;
switch (type)
{
case RequestType.Movie:
requestLimit = s.MovieWeeklyRequestLimit;
break;
case RequestType.TvShow:
requestLimit = s.TvWeeklyRequestLimit;
break;
case RequestType.Album:
requestLimit = s.AlbumWeeklyRequestLimit;
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
return requestLimit;
}
private async Task<Response> AddRequest(RequestedModel model, PlexRequestSettings settings, string message)
{
model.Approved = true;
await RequestService.AddRequestAsync(model);
if (ShouldSendNotification(RequestType.Movie, settings))
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = RequestType.Movie
};
await NotificationService.Publish(notificationModel);
}
var limit = await RequestLimitRepo.GetAllAsync();
var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
if (usersLimit == null)
{
await RequestLimitRepo.InsertAsync(new RequestLimit
{
Username = Username,
RequestType = model.Type,
FirstRequestDate = DateTime.UtcNow,
RequestCount = 1
});
}
else
{
usersLimit.RequestCount++;
await RequestLimitRepo.UpdateAsync(usersLimit);
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = message });
}
}
}

@ -80,13 +80,14 @@ namespace PlexRequests.UI.Modules
if (landingSettings.BeforeLogin)
{
await
Analytics.TrackEventAsync(
Category.LandingPage,
Action.View,
"Going To LandingPage before login",
Username,
CookieHelper.GetAnalyticClientId(Cookies));
#pragma warning disable 4014
Analytics.TrackEventAsync(
#pragma warning restore 4014
Category.LandingPage,
Action.View,
"Going To LandingPage before login",
Username,
CookieHelper.GetAnalyticClientId(Cookies));
var model = new LandingPageViewModel
{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Log ind</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Ønsker du at se en film eller tv-show, men det er i øjeblikket ikke på Plex? Log nedenfor med dit Plex.tv brugernavn og password !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Dine login-oplysninger bruges kun til at godkende din Plex konto.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Brugernavn</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Brugernavn</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Adgangskode</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>log på</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Noget gik galt</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Fuldført</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Søg</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Anmodninger</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Issues</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>STØT</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Administrator</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Indstillinger</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Skift adgangskode</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Log ud</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Der er en ny opdatering tilgængelig! Klik</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Dansk</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spansk</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Tysk</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Dansk</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugisisk</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Swedish</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiensk</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>her</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Hollandsk</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Film</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>TV-shows</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Album</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Ønsker at se noget, der ikke i øjeblikket på Plex ?! Intet problem! Bare søge efter det nedenfor og anmode den !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Søg</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Forslag</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Kommer snart</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Teatre</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Send mig en meddelelse, når emner, jeg har anmodet er blevet tilføjet</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Gem</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Tilgængelig</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Forespørgsel sendt</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Forespørgsel</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Alle sæsoner</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Første sæson</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Sidste sæson</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Vælg</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Rapport Issue</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Forkert lyd</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Undertekster</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Forkert indhold</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Playback Issues</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Andet</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Track Count</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Årstid</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Luk</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Tilføj et problem!</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Gem ændringer</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Sæson</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Velkommen</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Anmodninger</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Nedenfor kan du se dine og alle andre anmodninger, samt deres download og godkendelse status.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Film</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>TV-shows</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Album</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Slet Film</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Godkend film</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Slet tv-udsendelser</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Godkend tv-udsendelser</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Slet Music</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Godkend Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Godkendt</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Godkendt</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Tilgængelig</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Ikke tilgængelig</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Udgivet</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Udgivet:</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Ordrer</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Seneste anmodninger</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Ældste anmodninger</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Nyeste udgivelser</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Ældste udgivelser</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Frigivelsesdato</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons Anmodet</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Anmodet Af</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Ønskede dato</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Godkend</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Fjern</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>ikke tilgængelig</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Mark tilgængelig</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Godkendt</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Tilgængelig</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Aktieemission</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Möchten Sie einen Film oder eine TV-Show zu sehen, aber es ist derzeit nicht auf Plex? Loggen Sie sich unten mit Ihrem Plex.tv Benutzernamen und Passwort !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Ihre Login-Daten werden verwendet, nur Ihr Plex Konto zu authentifizieren.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Benutzername</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Benutzername</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Passwort</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Irgendetwas ist falsch gelaufen</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Erfolg</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Suche</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Anfragen</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Probleme</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Spenden</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Verwaltung</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Einstellungen</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Passwort ändern</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Ausloggen</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Es gibt ein neues Update verfügbar! Klicken</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Englisch</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanisch</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Deutsch</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Dänisch</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugiesisch</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Schwedisch</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italienisch</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>hier</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Niederländisch</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Filme</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>SERIEN</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Alben</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Möchten Sie etwas zu sehen, die derzeit nicht auf Plex ist ?! Kein Problem! Suchen Sie einfach nach unten und es es wünschen !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Suche</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Vorschläge</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Demnächst</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Theatern</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Senden Sie mir eine Benachrichtigung, wenn Gegenstände, die ich angefordert wurden hinzugefügt</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Speichern</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>angefragt</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Angefordert</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>alle Saisonen</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Erste Saison</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Neueste Saison</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Auswählen</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Report Ausgabe</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Falsche Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Keine Untertitel</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Falscher Inhaltstyp.</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Wiedergabe-Probleme</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Sonstige</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Track-Count</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Jahreszeiten</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Schließen</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Fügen Sie ein Problem</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Änderungen speichern</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Staffel</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Herzlich willkommen</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Anfragen</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Im Folgenden finden Sie Ihre und alle anderen Anfragen, sowie deren Download und Genehmigungsstatus zu sehen.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Filme</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>SERIEN</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Alben</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Löschen von Filmen</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Genehmigen-Filme</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Löschen TV Shows</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Genehmigen TV Shows</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Löschen Music</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Genehmigen Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Genehmigt</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Nicht bestätigt</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Nicht verfügbar</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Frei</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Frei</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Bestellung</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter!</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Aktuelle Anfragen</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Älteste Anfragen</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Neueste Veröffentlichungen</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Die ältesten Releases</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Veröffentlichung</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Jahreszeiten heraus</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Beantragt von</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>angefragt</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Genehmigen</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Entfernen</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Nicht verfügbar</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Genehmigt</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>V ERFÜGBAR</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Problemstellung</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>INICIAR SESIÓN</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>¿Quieres ver una película o programa de televisión, pero no es actualmente en Plex? Ingresa abajo con su nombre de usuario y contraseña Plex.tv !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Sus datos de acceso sólo se utilizan para autenticar su cuenta Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv nombre de usuario</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Username</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Contraseña</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Iniciar sesión</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Algo salió mal</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>¡Éxito!</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Peticiones</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Problemas</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Dona</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Administración</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Ajustes</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Cambiar contraseña</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Desconectarse</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Hay una nueva actualización disponible! Hacer clic</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Inglés</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>German</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danés</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugués</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Sueco</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiano</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>aquí</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Holandés</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Películas</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Serie de TV</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Álbumes</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>¿Quieres ver algo que no se encuentra actualmente en Plex ?! ¡No hay problema! Sólo la búsqueda de abajo y que solicitarlo !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Sugerencias</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Muy Pronto</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>En los cines</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Envíame una notificación cuando se han añadido elementos que he solicitado</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Ahorra</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Pedido</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Solicitud</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Todas las temporadas</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Primera Temporada</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Última estación</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Seleccionar</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Informe del problema</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Audio mal</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Subtitles</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Contenido incorrecto</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemas de reproducción</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Otro</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>El número de pistas</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>País</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Temporadas</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Cerrar</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Añadir un problema</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Guardar cambios</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Temporada</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Bienvenido</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Peticiones</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>A continuación se puede ver la suya y todas las demás solicitudes, así como su descarga y aprobación de estado.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Películas</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Serie de TV</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Álbumes</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Eliminar películas</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Aprobar películas</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Eliminar programas de televisión</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Aprobar programas de televisión</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Eliminar la música</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Aprobar la música</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Todo</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Aprobado</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>No Aprovado</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>No disponible</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Publicado</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Publicado</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Pedido</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtra</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Las últimas peticiones</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Las solicitudes más antiguas</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Últimos Lanzamientos</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Estrenos más antiguas</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Fecha de lanzamiento</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Estaciones solicitado</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>SOLICITADO POR</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>¡Fecha solicitada</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Desplegable</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Trabajo aprobado</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Eliminar</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Marcar como no disponible</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Aprobado</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>disponible</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>emitir</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Se connecter</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vous voulez regarder un film ou la télévision, mais il est pas actuellement sur Plex? Connectez-vous ci-dessous avec votre nom d'utilisateur et mot de passe Plex.tv!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Vos informations de connexion sont uniquement utilisées pour authentifier votre compte Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Nom d'utilisateur</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nom dutilisateur</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Mot de passe</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Se connecter</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Quelque-chose s'est mal passé</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Succès</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Chercher</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Requêtes</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Sortie</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Faire Un Don</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>d'Administration</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Paramètres</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Modifier le mot de passe</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Déconnexion</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Il y a une nouvelle mise à jour disponible! Cliquez</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Anglais</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Espagnol</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Allemand</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danois</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugais</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Suédois (homonymie)</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italien</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>ici</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Néerlandais</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Films</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Émissions de télévision</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Vous voulez regarder quelque chose qui est pas actuellement sur Plex ?! Pas de problème! Il suffit de chercher ci-dessous et demander !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Chercher</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggestions</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Bientôt disponible </value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Théâtre</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Envoyez-moi une notification lorsque des éléments que j'ai demandés ont été ajoutés</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Enregistrer</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Disponible</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Demandé</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Requête</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Saisons</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Première saison</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Dernière saison</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Sélectionner</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Formulaire de rapport d'incident</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Mauvais Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Pas de sous-titres</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Contenu erroné</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problèmes de lecture</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Autre</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Nombre de pistes</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Pays</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Saisons</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Fermer</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Ajouter une question</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Sauvegarder les modifications</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>SAISON</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Bienvenue</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Requêtes</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Ci-dessous vous pouvez voir la vôtre et toutes les autres demandes, ainsi que le téléchargement et l'état d'approbation</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Films</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Émissions de télévision</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Supprimer Films</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Approuver Films</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Supprimer Séries TV</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Approuver Séries TV</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Supprimer la musique</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Approuver la musique</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Tous</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Approuvé</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Par voie orale pas approuvée</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Disponible</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Non disponible</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Publié</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Publié</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Commandez </value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtrer</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Dernières demandes</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Le plus ancien demandes</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Dernières informations</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Le plus ancien de presse</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Date de commercialisation</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons Demandés</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Demandé par</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Date demandée</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Basculer Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Approuver</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Supprimer</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Marquer comme indisponible</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Marquer comme disponible</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Approuvé</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Disponible</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Question en litige</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Accesso</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vuoi guardare un film o tv ma non è attualmente in Plex? Effettua il login con il tuo username e la password Plex.tv !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>I dati di accesso vengono utilizzati solo per autenticare l&amp;#39;account Plex.!</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Nome utente</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nome utente</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Parola d'ordine</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Accedi</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Errore</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Successo</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>C'è un nuovo aggiornamento disponibile! Clic</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Cerca!</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>problemi quantificati</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Donazione</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Impostazioni</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Modifica password</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Disconnettersi</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Inglese</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spagnolo</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Tedesco</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danese</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portoghese</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Svedese</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiano</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Qui!</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Olandese</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Cerca</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Film</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Spettacoli TV</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Album</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Voglia di guardare qualcosa che non è attualmente il Plex?! Non c'è problema! Basta cercare per esso qui sotto e richiederla!</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggerimenti</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Novita</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Nei Cinema</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Inviami una notifica quando sono stati aggiunti elementi che ho chiesto!</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Salva</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>disponibili</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Richiesto</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Richiedi</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Tutte le Stagioni</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Prima stagione</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Ultima stagione</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>seleziona</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Segnala il problema</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Audio sbagliato</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Nessun sottotitolo</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Contenuto sbagliato</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemi di riproduzione</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Altre</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Conta di pista</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Nazione</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Stagioni</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Chiudi</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Aggiungere un problema</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Salva Cambia</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Stagione</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Benvenuta</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Qui sotto potete vedere il vostro e tutte le altre richieste, così come il loro download e l'approvazione dello stato</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Film</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Spettacoli TV</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Album</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Cancellare i filmati</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Approva Film</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Cancellare programmi TV</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Approvare programmi TV</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Eliminare la musica</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Approva Musica</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Tutto ciò</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Approvato</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Approvato</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>disponibili</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Non disponibile</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Rilasciato</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Non rilasciato</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Ordina</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtro</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Ultimi Richieste</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>I più vecchi richieste</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Ultime uscite</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>I più vecchi uscite</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Data di disponibilità</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Stagioni obbligatorio</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Richiesto</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Richiesto</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Imposta/rimuovi a discesa</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Approva</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Togliere</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Segna non disponibile</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Segna disponibile</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Approvato</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>disponibili</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Problema</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Inloggen</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Wilt u een film of tv-show te kijken, maar het is op het moment niet op Plex? Log hieronder in met uw gebruikersnaam en wachtwoord Plex.tv !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Uw login gegevens worden alleen gebruikt om uw account te verifiëren Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Gebruikersnaam</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Gebruikersnaam</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Wachtwoord</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Aanmelden!</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Er is iets fout gegaan</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Succes</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Zoeken</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Verzoeken</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>UItgaves</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Doneren</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Instellingen</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Wachtwoord wijzigen</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Uitloggen</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Er is een nieuwe update beschikbaar is! Klik</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>English</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>German</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Koffiebroodje</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portuguese</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Zweeds</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italian</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Hier!</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Dutch</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Zoeken</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Aangevraagd</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>films</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>TV programma's</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Wilt u kijken naar iets dat is momenteel niet op de Plex?! Geen probleem! Onderzoek enkel naar het hieronder en vraag het!</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggesties</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Binnenkort verwacht</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>In de Theaters</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Stuur me een bericht wanneer objecten die ik heb gevraagd zijn toegevoegd</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Opslaan</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Beschikbaar</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Verzoek</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Alle seizoenen</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Eerste seizoen</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Laatste seizoen</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Selecteer</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Probleem melden</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Verkeerde Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Geen ondertiteling</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Verkeerde inhoud</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemen met het afspelen</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Overig</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Spoor de graaf</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Seasons</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Dichtbij</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Een actie-item toevoegen</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Wijzigingen opslaan</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Seizoen</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Welkom</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Verzoeken</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Hieronder vindt u de jouwe en alle andere verzoeken kan zien, evenals hun download en goedkeuring-status.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>films</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>TV programma's</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>albums</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Verwijder Movies</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Goedkeuren Movies</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Verwijder TV Shows</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Goedkeuren TV Shows</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Verwijderen Muziek</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Goedkeuren Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Goedgekeurd</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Nog niet gestart</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Beschikbaar</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Niet beschikbaar</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Released</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Niet vrijgegeven</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Bestel</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter!</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Laatste aanvragen</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Oudste aanvragen</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Nieuwste releases</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Oudste Releases</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Uitgiftedatum</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons gevraagd</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Verzocht door</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Aanvraag datum</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Goedkeuren</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>verwijder</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Niet beschikbaar</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Mark beschikbaar</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Goedgekeurd</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Beschikbaar</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Uitgave</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Entrar</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Quer assistir a um filme ou programa de TV, mas não está atualmente em Plex? Entre abaixo com seu nome de usuário e senha Plex.tv !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Seus dados de login são apenas usados para autenticar sua conta Plex.!</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv usuário</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nome de usuário</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Senha</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Assinar em!</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Alguma coisa saiu errada.</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Sucesso</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Pedidos</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Issues</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Doar</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Administrativo</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Configurações</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Alterar Senha</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Sair</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Há uma nova atualização disponível! Clique</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Inglês</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Espanhol</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Alemão</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Dinamarquês</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Português</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Sueco</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italiano</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>aqui</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Holandês</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Filmes</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Todas as Series de TV</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Álbuns</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Quer assistir algo que não está atualmente em Plex ?! Sem problemas! Basta procurá-lo abaixo e solicitá-lo !!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Buscar</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Sugestões</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Em breve!</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>somente nos cinemas</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Envie-me uma notificação quando os itens I solicitados foram adicionados!</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Salvar</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Disponível</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Requeridos</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Pedido</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Todas as temporadas</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Primeira Temporada</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Últimas estação</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Selecione</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>relatório do problema</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Áudio errado</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Sem legendas</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Conteúdo errado</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Problemas de reprodução</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Outra</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Contagem pista</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>País</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Temporadas</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Fechar</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Adicionar um problema</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Salvar alterações</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Temporada</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Bem vinda</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Pedidos</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Abaixo você pode ver o seu e todos os outros pedidos, bem como o seu estado de download e aprovação.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Filmes</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Todas as Series de TV</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Álbuns</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Apagar filmes</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Aprovar filmes</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Excluir programas televisivo</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Aprovar programas televisivo</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Excluir música</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Aprovar a música</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>tudo</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Aprovado</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Aprovado</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Disponível</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Não disponível</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Liberada</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Liberada</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>de Fabricação</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filtro!</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Últimos pedidos</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Mais antigos pedidos</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Últimos Lançamentos</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Mais antigos Lançamentos</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Data de liberação</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Estações pedida</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>solicitado por</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>data solicitada</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Alternar Menu Suspenso</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Aprovar</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>REMOVER</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Marcar como indisponível</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Marcar como disponível</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Aprovado</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Disponível</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Questão</value>
</data>
</root>

@ -0,0 +1,431 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Login</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Want to watch a movie or tv show but it's not currently on Plex?
Login below with your Plex.tv username and password!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Your login details are only used to authenticate your Plex account.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Username </value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Username</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Password</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Sign In</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Something went wrong!</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Success!</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Search</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Issues</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Donate</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Change Password</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>There is a new update available! Click</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>English</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>German</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danish</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portuguese</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Swedish</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italian</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Here!</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>Dutch</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Movies</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>TV Shows</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Search</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggestions</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Coming Soon</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>In Theaters</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Send me a notification when items I have requested have been added</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Save</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Available</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Requested</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Request</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>All Seasons</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>First Season</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Latest Season</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Select </value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Report Issue</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Wrong Audio</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>No Subtitles</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Wrong Content</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Playback Issues</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Other</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Track Count</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Country</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Seasons</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Add an issue</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Save Changes</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Season</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Welcome</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Requests</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Below you can see yours and all other requests, as well as their download and approval status.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Movies</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>TV Shows</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Delete Movies</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Approve Movies</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Delete TV Shows</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Approve TV Shows</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Delete Music</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Approve Music</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>All</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Approved</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Not Approved</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Available</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Not Available</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Released</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Not Released</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Order</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Latest Requests</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Oldest Requests</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Latest Releases</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Oldest Releases</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Release Date</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Seasons Requested</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Requested By</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Requested Date</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Toggle Dropdown</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Approve</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Mark Unavailable</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Mark Available</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Approved</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Available</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Issue</value>
</data>
<data name="Search_SuccessfullyAdded" xml:space="preserve">
<value>was successfully added!</value>
</data>
<data name="Search_AlreadyRequested" xml:space="preserve">
<value>has already been requested!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>We could not check if {0} is in Plex, are you sure it's correctly setup?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Something went wrong adding the movie to CouchPotato! Please check your settings.</value>
</data>
<data name="Search_WeeklyRequestLimitMovie" xml:space="preserve">
<value>You have reached your weekly request limit for Movies! Please contact your admin.</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>is already in Plex!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Something went wrong adding the movie to SickRage! Please check your settings.</value>
</data>
<data name="Search_TvNotSetUp" xml:space="preserve">
<value>The request of TV Shows is not correctly set up. Please contact your admin.</value>
</data>
<data name="Search_WeeklyRequestLimitAlbums" xml:space="preserve">
<value>You have reached your weekly request limit for Albums! Please contact your admin.</value>
</data>
<data name="Search_MusicBrainzError" xml:space="preserve">
<value>We could not find the artist on MusicBrainz. Please try again later or contact your admin</value>
</data>
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>You have reached your weekly request limit for TV Shows! Please contact your admin.</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Sorry, but this functionality is currently only for users with Plex accounts</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Sorry, but your administrator has not yet enabled this functionality.</value>
</data>
<data name="Search_NotificationError" xml:space="preserve">
<value>We could not remove this notification because you never had it!</value>
</data>
<data name="Common_CouldNotSave" xml:space="preserve">
<value>Could not save, please try again</value>
</data>
</root>

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserLogin_Title" xml:space="preserve">
<value>Logga in</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vill du titta på en film eller TV-show, men det är inte närvarande på Plex? Logga in nedan med Plex.tv användarnamn och lösenord !!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Dina inloggningsuppgifter används endast för att autentisera ditt Plex-konto.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv användarnamn</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Användarnamn</value>
</data>
<data name="UserLogin_Password" xml:space="preserve">
<value>Lösenord</value>
</data>
<data name="UserLogin_SignIn" xml:space="preserve">
<value>Logga in</value>
</data>
<data name="Javascript_SomethingWentWrong" xml:space="preserve">
<value>Något gick fel</value>
</data>
<data name="Javascript_Success" xml:space="preserve">
<value>Lyckades</value>
</data>
<data name="Layout_Title" xml:space="preserve">
<value>Plex Requests</value>
</data>
<data name="Layout_Search" xml:space="preserve">
<value>Sök</value>
</data>
<data name="Layout_Requests" xml:space="preserve">
<value>Begäran</value>
</data>
<data name="Layout_Issues" xml:space="preserve">
<value>Frågor</value>
</data>
<data name="Layout_Donate" xml:space="preserve">
<value>Donera</value>
</data>
<data name="Layout_Admin" xml:space="preserve">
<value>admin</value>
</data>
<data name="Layout_Settings" xml:space="preserve">
<value>Inställningar</value>
</data>
<data name="Layout_ChangePassword" xml:space="preserve">
<value>Byt lösenord</value>
</data>
<data name="Layout_Logout" xml:space="preserve">
<value>Logga ut</value>
</data>
<data name="Layout_UpdateAvailablePart1" xml:space="preserve">
<value>Det finns en ny uppdatering tillgänglig! Klick</value>
</data>
<data name="Layout_English" xml:space="preserve">
<value>Svenska</value>
</data>
<data name="Layout_Spanish" xml:space="preserve">
<value>Spanska</value>
</data>
<data name="Layout_German" xml:space="preserve">
<value>Tyska</value>
</data>
<data name="Layout_Danish" xml:space="preserve">
<value>Danska</value>
</data>
<data name="Layout_Portuguese" xml:space="preserve">
<value>Portugisiska</value>
</data>
<data name="Layout_Swedish" xml:space="preserve">
<value>Svenska</value>
</data>
<data name="Layout_Italian" xml:space="preserve">
<value>Italienska</value>
</data>
<data name="Layout_UpdateAvailablePart2" xml:space="preserve">
<value>Här</value>
</data>
<data name="Layout_Dutch" xml:space="preserve">
<value>dutch</value>
</data>
<data name="Search_Request" xml:space="preserve">
<value>Fråga</value>
</data>
<data name="Search_Movies" xml:space="preserve">
<value>Filmer</value>
</data>
<data name="Search_TvShows" xml:space="preserve">
<value>Tv program</value>
</data>
<data name="Search_Albums" xml:space="preserve">
<value>Album</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Vill titta på något som inte är närvarande på Plex ?! Inga problem! Bara söka efter den nedan och begär det !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Sök</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Förslag</value>
</data>
<data name="Search_ComingSoon" xml:space="preserve">
<value>Kommer snart</value>
</data>
<data name="Search_InTheaters" xml:space="preserve">
<value>Teater</value>
</data>
<data name="Search_SendNotificationText" xml:space="preserve">
<value>Skicka mig ett meddelande när objekt jag har begärt har lagts till!</value>
</data>
<data name="Common_Save" xml:space="preserve">
<value>Spara</value>
</data>
<data name="Search_Available" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Search_Requested" xml:space="preserve">
<value>Begärd</value>
</data>
<data name="Search_AllSeasons" xml:space="preserve">
<value>Alla säsonger</value>
</data>
<data name="Search_FirstSeason" xml:space="preserve">
<value>Första säsongen</value>
</data>
<data name="Search_LatestSeason" xml:space="preserve">
<value>Senaste säsongen</value>
</data>
<data name="Search_SelectSeason" xml:space="preserve">
<value>Välj</value>
</data>
<data name="Search_ReportIssue" xml:space="preserve">
<value>Rapporten fråga</value>
</data>
<data name="Issues_WrongAudio" xml:space="preserve">
<value>Fel ljud</value>
</data>
<data name="Issues_NoSubs" xml:space="preserve">
<value>Inga undertexter</value>
</data>
<data name="Issues_WrongContent" xml:space="preserve">
<value>Fel innehåll</value>
</data>
<data name="Issues_Playback" xml:space="preserve">
<value>Uppspelningsproblem</value>
</data>
<data name="Issues_Other" xml:space="preserve">
<value>Annat</value>
</data>
<data name="Search_TrackCount" xml:space="preserve">
<value>Spår räknas</value>
</data>
<data name="Search_Country" xml:space="preserve">
<value>Land</value>
</data>
<data name="Search_Modal_SeasonsTitle" xml:space="preserve">
<value>Säsonger</value>
</data>
<data name="Common_Close" xml:space="preserve">
<value>Stäng</value>
</data>
<data name="Issues_Modal_Title" xml:space="preserve">
<value>Lägg till en fråga</value>
</data>
<data name="Issues_Modal_Save" xml:space="preserve">
<value>Spara Ändringar</value>
</data>
<data name="Search_Season" xml:space="preserve">
<value>Årstid</value>
</data>
<data name="Layout_Welcome" xml:space="preserve">
<value>Välkommen</value>
</data>
<data name="Requests_Title" xml:space="preserve">
<value>Begäran</value>
</data>
<data name="Requests_Paragraph" xml:space="preserve">
<value>Nedan kan du se din och alla andra förfrågningar, liksom deras nedladdning och godkännandestatus.</value>
</data>
<data name="Requests_MoviesTabTitle" xml:space="preserve">
<value>Filmer</value>
</data>
<data name="Requests_TvShowTabTitle" xml:space="preserve">
<value>Tv program</value>
</data>
<data name="Requests_AlbumsTabTitle" xml:space="preserve">
<value>Album</value>
</data>
<data name="Requests_DeleteMovies" xml:space="preserve">
<value>Radera filmer</value>
</data>
<data name="Requests_ApproveMovies" xml:space="preserve">
<value>Godkänn filmer</value>
</data>
<data name="Requests_DeleteTVShows" xml:space="preserve">
<value>Radera TV-program</value>
</data>
<data name="Requests_ApproveTvShows" xml:space="preserve">
<value>Godkänna TV-program</value>
</data>
<data name="Requests_DeleteMusic" xml:space="preserve">
<value>Radera musik</value>
</data>
<data name="Requests_ApproveMusic" xml:space="preserve">
<value>Godkänn musik</value>
</data>
<data name="Requests_Filter_All" xml:space="preserve">
<value>Alla</value>
</data>
<data name="Requests_Filter_Approved" xml:space="preserve">
<value>Godkänd</value>
</data>
<data name="Requests_Filter_NotApproved" xml:space="preserve">
<value>Ej Godkänd</value>
</data>
<data name="Requests_Filter_Available" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Requests_Filter_NotAvailable" xml:space="preserve">
<value>Inte tillgängligt</value>
</data>
<data name="Requests_Filter_Released" xml:space="preserve">
<value>Släppte ut</value>
</data>
<data name="Requests_Filter_NotReleased" xml:space="preserve">
<value>Inte släppt</value>
</data>
<data name="Requests_Order" xml:space="preserve">
<value>Beställning</value>
</data>
<data name="Requests_Filter" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Requests_Order_LatestRequests" xml:space="preserve">
<value>Senaste förfrågningar</value>
</data>
<data name="Requests_Order_OldestRequests" xml:space="preserve">
<value>Äldsta önskemål</value>
</data>
<data name="Requests_Order_LatestReleases" xml:space="preserve">
<value>Senaste versionerna</value>
</data>
<data name="Requests_Order_OldestReleases" xml:space="preserve">
<value>Äldsta meddelanden</value>
</data>
<data name="Requests_ReleaseDate" xml:space="preserve">
<value>Släpptes</value>
</data>
<data name="Requests_SeasonsRequested" xml:space="preserve">
<value>Säsonger Requested</value>
</data>
<data name="Requests_RequestedBy" xml:space="preserve">
<value>Begärd av</value>
</data>
<data name="Requests_RequestedDate" xml:space="preserve">
<value>Önskat datum</value>
</data>
<data name="Requests_ToggleDropdown" xml:space="preserve">
<value>Växla rullgardinslista</value>
</data>
<data name="Common_Approve" xml:space="preserve">
<value>Godkänn</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>ta bort</value>
</data>
<data name="Requests_MarkUnavailable" xml:space="preserve">
<value>Ej tillgänglig</value>
</data>
<data name="Requests_MarkAvailable" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Common_Approved" xml:space="preserve">
<value>Godkänd</value>
</data>
<data name="Requests_Available" xml:space="preserve">
<value>Tillgänglig</value>
</data>
<data name="Issues_Issue" xml:space="preserve">
<value>Problem</value>
</data>
</root>

@ -1,59 +1,59 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Startup.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 Nancy.TinyIoc;
using NLog;
using Owin;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Jobs;
namespace PlexRequests.UI
{
public class Startup
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public void Configuration(IAppBuilder app)
{
try
{
app.UseNancy();
var scheduler = new Scheduler();
scheduler.StartScheduler();
}
catch (Exception exception)
{
Log.Fatal(exception);
throw;
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Startup.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 Nancy.TinyIoc;
using NLog;
using Owin;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Jobs;
namespace PlexRequests.UI
{
public class Startup
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public void Configuration(IAppBuilder app)
{
try
{
app.UseNancy();
var scheduler = new Scheduler();
scheduler.StartScheduler();
}
catch (Exception exception)
{
Log.Fatal(exception);
throw;
}
}
}
}

@ -1,123 +1,151 @@
@using PlexRequests.UI.Helpers
@Html.Partial("_Sidebar")
@Html.LoadTableAssets()
@{
var baseUrl = Html.GetBaseUrl();
var formAction = "/admin/loglevel";
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString() + formAction;
}
}
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Logs</legend>
<form method="post" id="mainForm" action="@formAction">
<div class="form-group">
<label for="logLevel" class="control-label">Log Level</label>
<div id="logLevel">
<select class="form-control" id="selected">
<option id="Trace" value="0">Trace</option>
<option id="Debug" value="1">Debug</option>
<option id="Info" value="2">Info</option>
<option id="Warn" value="3">Warn</option>
<option id="Error" value="4">Error</option>
<option id="Fatal" value="5">Fatal</option>
</select>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline ">Submit</button>
</div>
</div>
</form>
<table id="example" class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>Message</th>
<th>Area</th>
<th>Log Level</th>
<th>Date</th>
</tr>
</thead>
</table>
</fieldset>
</div>
<script>
$(function () {
var baseUrl = '@Html.GetBaseUrl()';
var logsUrl = "/admin/loadlogs";
var url = createBaseUrl(baseUrl, logsUrl);
$('#example').DataTable({
"ajax": url,
"columns": [
{ "data": "message" },
{ "data": "logger" },
{ "data": "level" },
{ "data": "dateString" }
],
"order": [[3, "desc"]]
});
var logUrl = "/admin/loglevel";
logUrl = createBaseUrl(baseUrl, logUrl);
$.ajax({
type: "get",
url: logUrl,
dataType: "json",
success: function (response) {
if (response && response.length > 0) {
$("#selected > option").each(function (level) {
var $opt = $(this);
if (response[0].ordinal == level) {
$opt.prop("selected", "selected");
}
});
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
$('#save').click(function (e) {
e.preventDefault();
var logLevel = $("#logLevel option:selected").val();
var $form = $("#mainForm");
var data = "level=" + logLevel;
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
@using PlexRequests.UI.Helpers
@Html.Partial("_Sidebar")
@Html.LoadTableAssets()
@{
var baseUrl = Html.GetBaseUrl();
var formAction = "/admin/loglevel";
var clearAction = "/admin/clearlogs";
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString() + formAction;
clearAction = "/" + baseUrl.ToHtmlString() + clearAction;
}
}
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Logs</legend>
<form method="post" id="mainForm" action="@formAction">
<div class="form-group">
<label for="logLevel" class="control-label">Log Level</label>
<div id="logLevel">
<select class="form-control" id="selected">
<option id="Trace" value="0">Trace</option>
<option id="Debug" value="1">Debug</option>
<option id="Info" value="2">Info</option>
<option id="Warn" value="3">Warn</option>
<option id="Error" value="4">Error</option>
<option id="Fatal" value="5">Fatal</option>
</select>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline ">Submit</button>
</div>
</div>
</form>
<form method="post" id="clearForm" action="@clearAction">
<button id="clearLogs" type="submit" class="btn btn-danger-outline ">Clear Logs</button>
</form>
<table id="example" class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>Message</th>
<th>Area</th>
<th>Log Level</th>
<th>Date</th>
</tr>
</thead>
</table>
</fieldset>
</div>
<script>
$(function () {
var baseUrl = '@Html.GetBaseUrl()';
var logsUrl = "/admin/loadlogs";
var url = createBaseUrl(baseUrl, logsUrl);
$('#example').DataTable({
"ajax": url,
"columns": [
{ "data": "message" },
{ "data": "logger" },
{ "data": "level" },
{ "data": "dateString" }
],
"order": [[3, "desc"]]
});
var logUrl = "/admin/loglevel";
logUrl = createBaseUrl(baseUrl, logUrl);
$.ajax({
type: "get",
url: logUrl,
dataType: "json",
success: function (response) {
if (response && response.length > 0) {
$("#selected > option").each(function (level) {
var $opt = $(this);
if (response[0].ordinal == level) {
$opt.prop("selected", "selected");
}
});
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
$('#save').click(function (e) {
e.preventDefault();
var logLevel = $("#logLevel option:selected").val();
var $form = $("#mainForm");
var data = "level=" + logLevel;
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$('#clearLogs').click(function(e) {
e.preventDefault();
var $form = $("#clearForm");
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -61,6 +61,15 @@
<input type="text" class="form-control form-control-custom " id="StoreCleanup" name="StoreCleanup" value="@Model.StoreCleanup">
</div>
</div>
<small>Please note, this will not reset the users request limit, it will just check every X hours to see if it needs to be reset.</small>
<div class="form-group">
<label for="UserRequestLimitResetter" class="control-label">User Request Limit Reset (hour)</label>
<div>
<input type="text" class="form-control form-control-custom " id="UserRequestLimitResetter" name="UserRequestLimitResetter" value="@Model.UserRequestLimitResetter">
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline ">Submit</button>

@ -77,87 +77,87 @@
<div class="form-group">
<div class="checkbox">
@if (Model.SearchForMovies)
{
<input type="checkbox" id="SearchForMovies" name="SearchForMovies" checked="checked"><label for="SearchForMovies">Search for Movies</label>
}
else
{
<input type="checkbox" id="SearchForMovies" name="SearchForMovies"><label for="SearchForMovies">Search for Movies</label>
}
@if (Model.SearchForMovies)
{
<input type="checkbox" id="SearchForMovies" name="SearchForMovies" checked="checked"><label for="SearchForMovies">Search for Movies</label>
}
else
{
<input type="checkbox" id="SearchForMovies" name="SearchForMovies"><label for="SearchForMovies">Search for Movies</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.SearchForTvShows)
{
<input type="checkbox" id="SearchForTvShows" name="SearchForTvShows" checked="checked"><label for="SearchForTvShows">Search for TV Shows</label>
}
else
{
<input type="checkbox" id="SearchForTvShows" name="SearchForTvShows"><label for="SearchForTvShows">Search for TV Shows</label>
}
@if (Model.SearchForTvShows)
{
<input type="checkbox" id="SearchForTvShows" name="SearchForTvShows" checked="checked"><label for="SearchForTvShows">Search for TV Shows</label>
}
else
{
<input type="checkbox" id="SearchForTvShows" name="SearchForTvShows"><label for="SearchForTvShows">Search for TV Shows</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.SearchForMusic)
{
<input type="checkbox" id="SearchForMusic" name="SearchForMusic" checked="checked"><label for="SearchForMusic">Search for Music</label>
}
else
{
<input type="checkbox" id="SearchForMusic" name="SearchForMusic"><label for="SearchForMusic">Search for Music</label>
}
@if (Model.SearchForMusic)
{
<input type="checkbox" id="SearchForMusic" name="SearchForMusic" checked="checked"><label for="SearchForMusic">Search for Music</label>
}
else
{
<input type="checkbox" id="SearchForMusic" name="SearchForMusic"><label for="SearchForMusic">Search for Music</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.RequireMovieApproval)
{
<input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval" checked="checked"><label for="RequireMovieApproval">Require approval of Movie requests</label>
}
else
{
<input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval"><label for="RequireMovieApproval">Require approval of Movie requests</label>
}
</div>
@if (Model.RequireMovieApproval)
{
<input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval" checked="checked"><label for="RequireMovieApproval">Require approval of Movie requests</label>
}
else
{
<input type="checkbox" id="RequireMovieApproval" name="RequireMovieApproval"><label for="RequireMovieApproval">Require approval of Movie requests</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.RequireTvShowApproval)
{
<input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval" checked="checked"><label for="RequireTvShowApproval">Require approval of TV show requests</label>
}
else
{
<input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval"><label for="RequireTvShowApproval">Require approval of TV show requests</label>
}
@if (Model.RequireTvShowApproval)
{
<input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval" checked="checked"><label for="RequireTvShowApproval">Require approval of TV show requests</label>
}
else
{
<input type="checkbox" id="RequireTvShowApproval" name="RequireTvShowApproval"><label for="RequireTvShowApproval">Require approval of TV show requests</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.RequireMusicApproval)
{
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval" checked="checked"><label for="RequireMusicApproval">Require approval of Music requests</label>
}
else
{
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval"><label for="RequireMusicApproval">Require approval of Music requests</label>
}
@if (Model.RequireMusicApproval)
{
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval" checked="checked"><label for="RequireMusicApproval">Require approval of Music requests</label>
}
else
{
<input type="checkbox" id="RequireMusicApproval" name="RequireMusicApproval"><label for="RequireMusicApproval">Require approval of Music requests</label>
}
</div>
</div>
@ -226,7 +226,7 @@
<p class="form-group">A comma separated list of users whose requests do not require approval.</p>
<p class="form-group">A comma separated list of users whose requests do not require approval (These users also do not have a request limit).</p>
<div class="form-group">
<label for="NoApprovalUsers" class="control-label">Approval White listed Users</label>
<div>
@ -234,16 +234,36 @@
</div>
</div>
@*<div class="form-group">
<label for="WeeklyRequestLimit" class="control-label">Weekly Request Limit</label>
<p class="form-group">If the request limits are set to 0 then no request limit is applied.</p>
<div class="form-group">
<label for="MovieWeeklyRequestLimit" class="control-label">Movie Weekly Request Limit</label>
<div>
<label>
<input type="number" id="MovieWeeklyRequestLimit" name="MovieWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.MovieWeeklyRequestLimit">
</label>
</div>
</div>
<div class="form-group">
<label for="TvWeeklyRequestLimit" class="control-label">TV Show Weekly Request Limit</label>
<div>
<label>
<input type="number" id="TvWeeklyRequestLimit" name="TvWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.TvWeeklyRequestLimit">
</label>
</div>
</div>
<div class="form-group">
<label for="AlbumWeeklyRequestLimit" class="control-label">Album Weekly Request Limit</label>
<div>
<label>
<input type="number" id="WeeklyRequestLimit" name="WeeklyRequestLimit" class="form-control form-control-custom " value="@Model.WeeklyRequestLimit">
<input type="number" id="AlbumWeeklyRequestLimit" name="AlbumWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.AlbumWeeklyRequestLimit">
</label>
</div>
</div> //TODO: Need to implement this*@
</div>
<div>
<div>
</div>
<div class="form-group">
<div>
@ -255,9 +275,9 @@
</div>
<script>
$(function() {
$('#save').click(function (e) {
e.preventDefault();
$(function () {
$('#save').click(function (e) {
e.preventDefault();
var theme = $("#themes option:selected").val();
var $form = $("#mainForm");
@ -285,11 +305,11 @@ $(function() {
});
$('#refreshKey').click(function (e) {
e.preventDefault();
var base = '@Html.GetBaseUrl()';
var url = createBaseUrl(base, '/admin/createapikey');
$.ajax({
e.preventDefault();
var base = '@Html.GetBaseUrl()';
var url = createBaseUrl(base, '/admin/createapikey');
$.ajax({
type: "post",
url: url,
dataType: "json",
@ -297,7 +317,7 @@ $(function() {
if (response) {
generateNotify("Success!", "success");
$('#apiKey').val(response);
}
}
},
error: function (e) {
console.log(e);
@ -305,5 +325,5 @@ $(function() {
}
});
});
});
});
</script>

@ -1,71 +1,71 @@
@using PlexRequests.UI.Helpers
@Html.Partial("_Sidebar")
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Status</legend>
<div class="form-group">
<label class="control-label">Version: </label>
<label class="control-label">@Model.Version</label>
</div>
<div class="form-group">
<label class="control-label">Update Available: </label>
@if (Model.UpdateAvailable)
{
<label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i></a></label>
<br />
<button id="autoUpdate" class="btn btn-success-outline">Automatic Update <i class="fa fa-download"></i></button>
}
else
{
<label class="control-label"><i class="fa fa-times"></i></label>
}
</div>
@if (Model.UpdateAvailable)
{
<h2>
<a href="@Model.DownloadUri">@Model.ReleaseTitle</a>
</h2>
<hr />
<label>Release Notes:</label>
@Html.Raw(Model.ReleaseNotes)
}
</fieldset>
</div>
<script>
var base = '@Html.GetBaseUrl()';
$('#autoUpdate')
.click(function (e) {
e.preventDefault();
$('body').append("<i class=\"fa fa-spinner fa-spin fa-5x fa-fw\" style=\"position: absolute; top: 20%; left: 50%;\"></i>");
$('#autoUpdate').prop("disabled", "disabled");
var count = 0;
setInterval(function () {
count++;
var dots = new Array(count % 10).join('.');
document.getElementById('autoUpdate').innerHTML = "Updating" + dots;
}, 1000);
$.ajax({
type: "Post",
url: "autoupdate",
data: { url: "@Model.DownloadUri" },
dataType: "json",
error: function () {
setTimeout(
function () {
location.reload();
}, 30000);
}
});
});
@using PlexRequests.UI.Helpers
@Html.Partial("_Sidebar")
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Status</legend>
<div class="form-group">
<label class="control-label">Version: </label>
<label class="control-label">@Model.Version</label>
</div>
<div class="form-group">
<label class="control-label">Update Available: </label>
@if (Model.UpdateAvailable)
{
<label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i></a></label>
<br />
@*<button id="autoUpdate" class="btn btn-success-outline">Automatic Update <i class="fa fa-download"></i></button>*@ //TODO
}
else
{
<label class="control-label"><i class="fa fa-times"></i></label>
}
</div>
@if (Model.UpdateAvailable)
{
<h2>
<a href="@Model.DownloadUri">@Model.ReleaseTitle</a>
</h2>
<hr />
<label>Release Notes:</label>
@Html.Raw(Model.ReleaseNotes)
}
</fieldset>
</div>
<script>
var base = '@Html.GetBaseUrl()';
$('#autoUpdate')
.click(function (e) {
e.preventDefault();
$('body').append("<i class=\"fa fa-spinner fa-spin fa-5x fa-fw\" style=\"position: absolute; top: 20%; left: 50%;\"></i>");
$('#autoUpdate').prop("disabled", "disabled");
var count = 0;
setInterval(function () {
count++;
var dots = new Array(count % 10).join('.');
document.getElementById('autoUpdate').innerHTML = "Updating" + dots;
}, 1000);
$.ajax({
type: "Post",
url: "autoupdate",
data: { url: "@Model.DownloadUri" },
dataType: "json",
error: function () {
setTimeout(
function () {
location.reload();
}, 30000);
}
});
});
</script>

@ -26,7 +26,7 @@
<h4>Type</h4>
</div>
<div class="col-md-4">
<h4>Issue's</h4>
<h4>Issues</h4>
</div>
<div class="col-md-2">
@ -83,10 +83,12 @@
<div class="col-sm-1">
<a href="" id="{{id}}link" class="btn btn-sm btn-info-outline approve"><i class="fa fa-info"></i> Details</a>
<br />
{{#if admin}}
<form action="@formAction/issues/remove" method="post" id="delete{{id}}">
<input id="issueId" name="issueId" value="{{id}}" hidden="hidden" />
<button type="submit" id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle delete">Remove</button>
</form>
{{/if}}
</div>
</div>
</div>

@ -19,7 +19,7 @@
</div>
<div class="media-body">
<h4 class="media-heading landing-title">Notice</h4>
@Model.NoticeMessage<br />
@Html.Raw(Model.NoticeMessage)<br />
@if (Model.EnabledNoticeTime)
{
<strong><span id="startDate"></span> <span id="endDate"></span></strong>

@ -1,5 +1,6 @@
@using Nancy.Security
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
@{
var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty;
@ -9,23 +10,23 @@
}
}
<div>
<h1>Requests</h1>
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
<h1>@UI.Requests_Title</h1>
<h4>@UI.Requests_Paragraph</h4>
<br />
<!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies)
{
<li role="presentation" class="active"><a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i>Movies</a></li>
<li role="presentation" class="active"><a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> @UI.Requests_MoviesTabTitle</a></li>
}
@if (Model.SearchForTvShows)
{
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i>TV Shows</a></li>
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i >@UI.Requests_TvShowTabTitle</a></li>
}
@if (Model.SearchForMusic)
{
<li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i>Albums</a></li>
<li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i> @UI.Requests_AlbumsTabTitle</a></li>
}
</ul>
<br />
@ -40,46 +41,46 @@
{
@if (Model.SearchForMovies)
{
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> Delete Movies</button>
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> Approve Movies</button>
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> @UI.Requests_DeleteMovies</button>
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> @UI.Requests_ApproveMovies</button>
}
@if (Model.SearchForTvShows)
{
<button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete TV Shows</button>
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve TV Shows</button>
<button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteTVShows</button>
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveTvShows</button>
}
@if (Model.SearchForMusic)
{
<button id="deleteMusic" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete Music</button>
<button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve Music</button>
<button id="deleteMusic" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteMusic</button>
<button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveMusic</button>
}
}
</div>
<div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Filter
@UI.Requests_Filter
<i class="fa fa-filter"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> All</a></li>
<li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> Approved</a></li>
<li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> Not Approved</a></li>
<li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> Not Available</a></li>
<li><a href="#" class="filter" data-filter=".released-true"><i class="fa fa-square-o"></i> Released</a></li>
<li><a href="#" class="filter" data-filter=".released-false"><i class="fa fa-square-o"></i> Not Released</a></li>
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> @UI.Requests_Filter_All</a></li>
<li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Approved</a></li>
<li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotApproved</a></li>
<li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotAvailable</a></li>
<li><a href="#" class="filter" data-filter=".released-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Released</a></li>
<li><a href="#" class="filter" data-filter=".released-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotReleased</a></li>
</ul>
</div>
<div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Order
@UI.Requests_Order
<i class="fa fa-sort"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="sort" data-sort="requestorder:desc"><i class="fa fa-check-square"></i> Latest Requests</a></li>
<li><a href="#" class="sort" data-sort="requestorder:asc"><i class="fa fa-square-o"></i> Oldest Requests</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:desc"><i class="fa fa-square-o"></i> Latest Releases</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:asc"><i class="fa fa-square-o"></i> Oldest Releases</a></li>
<li><a href="#" class="sort" data-sort="requestorder:desc"><i class="fa fa-check-square"></i> @UI.Requests_Order_LatestRequests</a></li>
<li><a href="#" class="sort" data-sort="requestorder:asc"><i class="fa fa-square-o"></i> @UI.Requests_Order_OldestRequests</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:desc"><i class="fa fa-square-o"></i> @UI.Requests_Order_LatestReleases</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:asc"><i class="fa fa-square-o"></i> @UI.Requests_Order_OldestReleases</a></li>
</ul>
</div>
</div>
@ -152,9 +153,9 @@
<span class="label label-success">{{status}}</span>
</div>
<br />
<div>Release Date: {{releaseDate}}</div>
<div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
<div>
Approved:
@UI.Common_Approved:
{{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}}
@ -163,7 +164,7 @@
{{/if_eq}}
</div>
<div>
Available
@UI.Requests_Available
{{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}}
@ -172,14 +173,14 @@
{{/if_eq}}
</div>
{{#if_eq type "tv"}}
<div>Seasons Requested: {{seriesRequested}}</div>
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
{{/if_eq}}
{{#if requestedUsers}}
<div>Requested By: {{requestedUsers}}</div>
<div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}}
<div>Requested Date: {{requestedDate}}</div>
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
<div>
Issue:
@UI.Issues_Issue:
{{#if_eq issueId 0}}
<i class="fa fa-times"></i>
{{else}}
@ -194,10 +195,10 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq hasQualities true}}
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline approve" id="{{requestId}}" custom-button="{{requestId}}"><i class="fa fa-plus"></i> Approve</button>
<button type="button" class="btn btn-sm btn-success-outline approve" id="{{requestId}}" custom-button="{{requestId}}"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
{{#each qualities}}
@ -206,21 +207,21 @@
</ul>
</div>
{{else}}
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
{{/if_eq}}
</form>
{{/if_eq}}
<form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> Remove</button>
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> @UI.Common_Remove</button>
</form>
<form method="POST" action="@formAction/requests/changeavailability" id="change{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq available true}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> Mark Unavailable</button>
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> @UI.Requests_MarkUnavailable</button>
{{else}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> Mark Available</button>
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> @UI.Requests_MarkAvailable</button>
{{/if_eq}}
</form>
@ -230,15 +231,15 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<div class="dropdown">
<button id="{{requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue
<i class="fa fa-plus"></i> @UI.Search_ReportIssue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">Wrong Audio</a></li>
<li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">No Subtitles</a></li>
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li>
<li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
</ul>
</div>
</form>
@ -270,9 +271,9 @@
<span class="label label-success">{{status}}</span>
</div>
<br />
<div>Release Date: {{releaseDate}}</div>
<div>@UI.Requests_ReleaseDate {{releaseDate}}</div>
<div>
Approved:
@UI.Common_Approved:
{{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}}
@ -281,7 +282,7 @@
{{/if_eq}}
</div>
<div>
Available
@UI.Requests_Available
{{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}}
@ -290,41 +291,29 @@
{{/if_eq}}
</div>
{{#if requestedUsers}}
<div>Requested By: {{requestedUsers}}</div>
<div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}}
<div>Requested Date: {{requestedDate}}</div>
<div id="issueArea{{requestId}}">
{{#if otherMessage}}
<div>Message: {{otherMessage}}</div>
{{else}}
<div>Issue: {{issues}}</div>
{{/if}}
</div>
<div id="adminNotesArea{{requestId}}">
{{#if adminNote}}
<div>Note from Admin: {{adminNote}}</div>
{{/if}}
</div>
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
</div>
<div class="col-sm-2 col-sm-push-3">
{{#if_eq admin true}}
{{#if_eq approved false}}
<form method="POST" action="@formAction/approval/approve" id="approve{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
</form>
{{/if_eq}}
<form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> Remove</button>
<button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-minus"></i> @UI.Common_Remove</button>
</form>
<form method="POST" action="@formAction/requests/changeavailability" id="change{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
{{#if_eq available true}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> Mark Unavailable</button>
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change" type="submit"><i class="fa fa-minus"></i> @UI.Requests_MarkUnavailable</button>
{{else}}
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> Mark Available</button>
<button id="{{requestId}}" custom-availibility="{{requestId}}" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change" type="submit"><i class="fa fa-plus"></i> @UI.Requests_MarkAvailable</button>
{{/if_eq}}
</form>
@ -334,15 +323,15 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<div class="dropdown">
<button id="{{requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue
<i class="fa fa-plus"></i> @UI.Search_ReportIssue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">Wrong Audio</a></li>
<li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">No Subtitles</a></li>
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li>
<li><a id="{{requestId}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a id="{{requestId}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
</ul>
</div>
</form>
@ -358,7 +347,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Add issue/comment</h4>
<h4 class="modal-title">@UI.Issues_Modal_Title</h4>
</div>
<form method="POST" action="@formAction/issues/issuecomment" id="commentForm">
<div class="modal-body">
@ -366,8 +355,8 @@
<textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">Save changes</button>
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">@UI.Common_Save</button>
</div>
</form>
</div>

@ -1,326 +1,327 @@
@using PlexRequests.UI.Helpers
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<div>
<h1>Search</h1>
<h4>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</h4>
<br />
<!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies)
{
<li role="presentation" class="active">
<a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> Movies</a>
</li>
}
@if (Model.SearchForTvShows)
{
<li role="presentation">
<a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i> TV Shows</a>
</li>
}
@if (Model.SearchForMusic)
{
<li role="presentation">
<a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i>Albums</a>
</li>
}
<li role="presentation" class="nav-tab-right nav-tab-icononly">
<a href="#NotificationsTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-bell"></i></a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
@if (Model.SearchForMovies)
{
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group">
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a id="moviesComingSoon" href="#">Coming Soon</a></li>
<li><a id="moviesInTheaters" href="#">In Theaters</a></li>
</ul>
</div>
<i id="movieSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
</div>
}
@if (Model.SearchForTvShows)
{
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<div class="input-group">
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search">
<div class="input-group-addon">
<i id="tvSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
</div>
}
@if (Model.SearchForMusic)
{
<!-- Music tab -->
<div role="tabpanel" class="tab-pane" id="MusicTab">
<div class="input-group">
<input id="musicSearchContent" type="text" class="form-control form-control-custom form-control-search">
<div class="input-group-addon">
<i id="musicSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Music content -->
<div id="musicList">
</div>
</div>
}
<!-- Notification tab -->
<div role="tabpanel" class="tab-pane" id="NotificationsTab">
<div class="input-group">
<div class="input-group-addon input-group-sm"></div>
</div>
<br />
<!-- Notifications content -->
<form class="form-horizontal" method="POST" id="notificationsForm">
<fieldset>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="notifyUser" name="Notify">
<label for="notifyUser">Send me a notification when items I have requested have been added</label>
</div>
</div>
<div class="form-group">
<div>
<button id="saveNotificationSettings" class="btn btn-primary-outline">Save</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<!-- Movie and TV Results template -->
<script id="search-template" type="text/x-handlebars-template">
<div class="row">
<div class="col-sm-2">
{{#if_eq type "movie"}}
{{#if posterPath}}
<img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
{{#if_eq type "tv"}}
{{#if posterPath}}
<img class="img-responsive" width="150" src="{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
</div>
<div class="col-sm-5 ">
<div>
{{#if_eq type "movie"}}
<a href="https://www.themoviedb.org/movie/{{id}}/" target="_blank">
<h4>{{title}} ({{year}})</h4>
</a>
{{else}}
<a href="http://www.imdb.com/title/{{imdb}}/" target="_blank">
<h4>{{title}} ({{year}})</h4>
</a>
{{/if_eq}}
</div>
<p>{{overview}}</p>
</div>
<div class="col-sm-2 col-sm-push-3">
<form method="POST" action="@url/search/request/{{type}}" id="form{{id}}">
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
{{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
{{else}}
{{#if_eq requested true}}
<button style="text-align: right" class="btn btn-primary-outline disabled" disabled><i class="fa fa-check"></i> Requested</button>
{{else}}
{{#if_eq type "movie"}}
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestMovie" type="submit"><i class="fa fa-plus"></i> Request</button>
{{/if_eq}}
{{#if_eq type "tv"}}
<div class="dropdown">
<button id="{{id}}" class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Request
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">All Seasons</a></li>
<li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">First Season</a></li>
<li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">Latest Season</a></li>
<li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">Select...</a></li>
</ul>
</div>
{{/if_eq}}
{{/if_eq}}
{{/if_eq}}
<br />
</form>
{{#if_eq available true}}
<form method="POST" action="@url/issues/nonrequestissue/" id="report{{id}}">
<input name="providerId" type="text" value="{{id}}" hidden="hidden" />
<input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown">
<button id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-exclamation"></i> Report Issue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" issue-select="0" class="dropdownIssue" href="#">Wrong Audio</a></li>
<li><a id="{{id}}" issue-select="1" class="dropdownIssue" href="#">No Subtitles</a></li>
<li><a id="{{id}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li>
<li><a id="{{id}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li>
<li><a id="{{id}}" issue-select="4" class="dropdownIssue" data-identifier="{{id}}" data-type="{{type}}" href="#" data-toggle="modal" data-target="#issuesModal">Other</a></li>
</ul>
</div>
</form>
{{/if_eq}}
</div>
</div>
<hr />
</script>
<!-- Music Results template -->
<script id="music-template" type="text/x-handlebars-template">
<div class="row">
<div id="{{id}}imageDiv" class="col-sm-2">
{{#if coverArtUrl}}
<img id="{{id}}cover" class="img-responsive" src="{{coverArtUrl}}" width="150" alt="poster">
{{/if}}
</div>
<div class="col-sm-5 ">
<div>
<a href="https://musicbrainz.org/release/{{id}}" target="_blank">
<h4>
{{artist}} - {{title}}
{{#if year}}
({{year}})
{{/if}}
</h4>
</a>
</div>
<p>{{overview}}</p>
</div>
<div class="col-sm-2 col-sm-push-3">
<form method="POST" action="@url/search/request/{{type}}" id="form{{id}}">
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
{{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
{{else}}
{{#if_eq requested true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Requested</button>
{{else}}
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> Request</button>
{{/if_eq}}
{{/if_eq}}
<br />
<small class="row">Track Count: {{trackCount}}</small>
<small class="row">Country: {{country}}</small>
</form>
</div>
</div>
<hr />
</script>
<div class="modal fade" id="seasonsModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Seasons</h4>
</div>
<div class="modal-body" id="seasonsBody">
</div>
<div hidden="hidden" id="selectedSeasonsId"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" id="seasonsRequest" class="btn btn-primary">Request</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="issuesModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Add an issue</h4>
</div>
<form method="POST" action="@url/issues/nonrequestissuecomment" id="commentForm">
<div class="modal-body">
<input id="providerIdModal" name="providerId" class="providerId" type="text" hidden="hidden" value="" />
<input name="issue" class="issue" type="text" hidden="hidden" value="" />
<input id="typeModal" name="type" class="type" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">Save changes</button>
</div>
</form>
</div>
</div>
</div>
<script id="seasons-template" type="text/x-handlebars-template">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" class="selectedSeasons" id="{{id}}" name="{{id}}"><label for="{{id}}">Season {{id}}</label>
</div>
</div>
</script>
@Html.LoadSearchAssets()
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<div>
<h1>@UI.Search_Title</h1>
<h4>@UI.Search_Paragraph</h4>
<br />
<!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies)
{
<li role="presentation" class="active">
<a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> @UI.Search_Movies</a>
</li>
}
@if (Model.SearchForTvShows)
{
<li role="presentation">
<a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i> @UI.Search_TvShows</a>
</li>
}
@if (Model.SearchForMusic)
{
<li role="presentation">
<a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i>@UI.Search_Albums</a>
</li>
}
<li role="presentation" class="nav-tab-right nav-tab-icononly">
<a href="#NotificationsTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-bell"></i></a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
@if (Model.SearchForMovies)
{
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group">
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
@UI.Search_Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a id="moviesComingSoon" href="#">@UI.Search_ComingSoon</a></li>
<li><a id="moviesInTheaters" href="#">@UI.Search_InTheaters</a></li>
</ul>
</div>
<i id="movieSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
</div>
}
@if (Model.SearchForTvShows)
{
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<div class="input-group">
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search">
<div class="input-group-addon">
<i id="tvSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
</div>
}
@if (Model.SearchForMusic)
{
<!-- Music tab -->
<div role="tabpanel" class="tab-pane" id="MusicTab">
<div class="input-group">
<input id="musicSearchContent" type="text" class="form-control form-control-custom form-control-search">
<div class="input-group-addon">
<i id="musicSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Music content -->
<div id="musicList">
</div>
</div>
}
<!-- Notification tab -->
<div role="tabpanel" class="tab-pane" id="NotificationsTab">
<div class="input-group">
<div class="input-group-addon input-group-sm"></div>
</div>
<br />
<!-- Notifications content -->
<form class="form-horizontal" method="POST" id="notificationsForm">
<fieldset>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="notifyUser" name="Notify">
<label for="notifyUser">@UI.Search_SendNotificationText</label>
</div>
</div>
<div class="form-group">
<div>
<button id="saveNotificationSettings" class="btn btn-primary-outline">@UI.Common_Save</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<!-- Movie and TV Results template -->
<script id="search-template" type="text/x-handlebars-template">
<div class="row">
<div class="col-sm-2">
{{#if_eq type "movie"}}
{{#if posterPath}}
<img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
{{#if_eq type "tv"}}
{{#if posterPath}}
<img class="img-responsive" width="150" src="{{posterPath}}" alt="poster">
{{/if}}
{{/if_eq}}
</div>
<div class="col-sm-5 ">
<div>
{{#if_eq type "movie"}}
<a href="https://www.themoviedb.org/movie/{{id}}/" target="_blank">
<h4>{{title}} ({{year}})</h4>
</a>
{{else}}
<a href="http://www.imdb.com/title/{{imdb}}/" target="_blank">
<h4>{{title}} ({{year}})</h4>
</a>
{{/if_eq}}
</div>
<p>{{overview}}</p>
</div>
<div class="col-sm-2 col-sm-push-3">
<form method="POST" action="@url/search/request/{{type}}" id="form{{id}}">
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
{{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button>
{{else}}
{{#if_eq requested true}}
<button style="text-align: right" class="btn btn-primary-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button>
{{else}}
{{#if_eq type "movie"}}
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestMovie" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button>
{{/if_eq}}
{{#if_eq type "tv"}}
<div class="dropdown">
<button id="{{id}}" class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> @UI.Search_Request
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">@UI.Search_AllSeasons</a></li>
<li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">@UI.Search_FirstSeason</a></li>
<li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">@UI.Search_LatestSeason</a></li>
<li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">@UI.Search_SelectSeason...</a></li>
</ul>
</div>
{{/if_eq}}
{{/if_eq}}
{{/if_eq}}
<br />
</form>
{{#if_eq available true}}
<form method="POST" action="@url/issues/nonrequestissue/" id="report{{id}}">
<input name="providerId" type="text" value="{{id}}" hidden="hidden" />
<input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown">
<button id="{{id}}" class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-exclamation"></i> @UI.Search_ReportIssue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a id="{{id}}" issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a id="{{id}}" issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a id="{{id}}" issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a id="{{id}}" issue-select="4" class="dropdownIssue" data-identifier="{{id}}" data-type="{{type}}" href="#" data-toggle="modal" data-target="#issuesModal">@UI.Issues_Other</a></li>
</ul>
</div>
</form>
{{/if_eq}}
</div>
</div>
<hr />
</script>
<!-- Music Results template -->
<script id="music-template" type="text/x-handlebars-template">
<div class="row">
<div id="{{id}}imageDiv" class="col-sm-2">
{{#if coverArtUrl}}
<img id="{{id}}cover" class="img-responsive" src="{{coverArtUrl}}" width="150" alt="poster">
{{/if}}
</div>
<div class="col-sm-5 ">
<div>
<a href="https://musicbrainz.org/release/{{id}}" target="_blank">
<h4>
{{artist}} - {{title}}
{{#if year}}
({{year}})
{{/if}}
</h4>
</a>
</div>
<p>{{overview}}</p>
</div>
<div class="col-sm-2 col-sm-push-3">
<form method="POST" action="@url/search/request/{{type}}" id="form{{id}}">
<input name="{{type}}Id" type="text" value="{{id}}" hidden="hidden" />
{{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button>
{{else}}
{{#if_eq requested true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Requested</button>
{{else}}
<button id="{{id}}" style="text-align: right" class="btn btn-primary-outline requestAlbum" type="submit"><i class="fa fa-plus"></i> @UI.Search_Request</button>
{{/if_eq}}
{{/if_eq}}
<br />
<small class="row">@UI.Search_TrackCount: {{trackCount}}</small>
<small class="row">@UI.Search_Country: {{country}}</small>
</form>
</div>
</div>
<hr />
</script>
<div class="modal fade" id="seasonsModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">@UI.Search_Modal_SeasonsTitle</h4>
</div>
<div class="modal-body" id="seasonsBody">
</div>
<div hidden="hidden" id="selectedSeasonsId"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" id="seasonsRequest" class="btn btn-primary">@UI.Search_Request</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="issuesModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">@UI.Issues_Modal_Title</h4>
</div>
<form method="POST" action="@url/issues/nonrequestissuecomment" id="commentForm">
<div class="modal-body">
<input id="providerIdModal" name="providerId" class="providerId" type="text" hidden="hidden" value="" />
<input name="issue" class="issue" type="text" hidden="hidden" value="" />
<input id="typeModal" name="type" class="type" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">@UI.Issues_Modal_Save</button>
</div>
</form>
</div>
</div>
</div>
<script id="seasons-template" type="text/x-handlebars-template">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" class="selectedSeasons" id="{{id}}" name="{{id}}"><label for="{{id}}">@UI.Search_Season {{id}}</label>
</div>
</div>
</script>
@Html.LoadSearchAssets()

@ -1,157 +1,191 @@
@using Nancy.Security
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<html>
<div hidden="hidden" id="baseUrl">@baseUrl.ToHtmlString()</div>
<head>
<title>Plex Requests</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
@Html.LoadAnalytics()
@Html.LoadAssets()
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@url/search">Plex Requests</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
@Html.GetNavbarUrl(Context, "/search", "Search", "search")
@Html.GetNavbarUrl(Context, "/requests", "Requests", "plus-circle")
@Html.GetNavbarUrl(Context, "/issues", "Issues", "exclamation", "<span id=\"issueCount\"></span>")
</ul>
<ul class="nav navbar-nav navbar-right">
@if (!Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> Admin</a></li>
}
else
{
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> Admin <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/admin"><i class="fa fa-cog"></i> Settings</a></li>
<li><a href="@url/changepassword"><i class="fa fa-key"></i> Change password</a></li>
<li class="divider"></li>
<li><a href="https://www.paypal.me/PlexRequestsNet" target="_blank"><i class="fa fa-heart" style="color:red"></i> Donate!</a></li>
<li class="divider"></li>
<li><a href="@url/logout"><i class="fa fa-sign-out"></i> Logout</a></li>
</ul>
</li>
}
@if (Context.Request.Session[SessionKeys.UsernameKey] != null)
{
<li><a href="@url/userlogin/logout"><i class="fa fa-sign-out"></i> Logout</a></li>
}
</ul>
</div>
</div>
<div id="updateAvailable" hidden="hidden"></div>
</nav>
<div class="container">
@RenderBody()
</div>
<div class="scroll-top-wrapper ">
<span class="scroll-top-inner">
<i class="fa fa-2x fa-arrow-circle-up"></i>
</span>
</div>
</body>
</html>
<script>
$(function () {
var urlBase = '@Html.GetBaseUrl()';
// Check for update
var url = createBaseUrl(urlBase, '/updatechecker');
$.ajax({
type: "GET",
url: url,
dataType: "json",
success: function (response) {
if (response.updateAvailable) {
var status = createBaseUrl(urlBase, '/admin/status');
$('#updateAvailable').html("<i class='fa fa-cloud-download' aria-hidden='true'></i> There is a new update available! Click <a style='color: white' href='" + status + "'>Here!</a>");
$('#updateAvailable').removeAttr("hidden");
$('body').addClass('update-available');
}
},
error: function (e) {
console.log(e);
}
});
// End Check for update
// Scroller
$(document).on('scroll', function () {
if ($(window).scrollTop() > 100) {
$('.scroll-top-wrapper').addClass('show');
} else {
$('.scroll-top-wrapper').removeClass('show');
}
});
$('.scroll-top-wrapper').on('click', scrollToTop);
// End Scroller
// Get Issue count
var issueUrl = createBaseUrl(urlBase, '/issues/issuecount');
$.ajax({
type: "GET",
url: issueUrl,
dataType: "json",
success: function (response) {
if (response) {
if(response > 0)
$('#issueCount').addClass("badge");
$('#issueCount').html(+response);
}
},
error: function (e) {
console.log(e);
}
});
// End issue count
});
function scrollToTop() {
verticalOffset = typeof (verticalOffset) != 'undefined' ? verticalOffset : 0;
element = $('body');
offset = element.offset();
offsetTop = offset.top;
$('html, body').animate({ scrollTop: offsetTop }, 500, 'linear');
}
@using Nancy.Security
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<html>
<div hidden="hidden" id="baseUrl">@baseUrl.ToHtmlString()</div>
<head>
<title>@UI.Layout_Title</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
@Html.LoadAnalytics()
@Html.LoadAssets()
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@url/search">@UI.Layout_Title</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
@Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search")
@Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle")
@Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "<span id=\"issueCount\"></span>")
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a id="donate" onclick="donateClick('https://www.paypal.me/PlexRequestsNet');"><i class="fa fa-heart" style="color: red"></i> @UI.Layout_Donate</a></li>
}
</ul>
<ul class="nav navbar-nav navbar-right">
@if (!Context.CurrentUser.IsAuthenticated() && Context.Request.Session[SessionKeys.UsernameKey] == null) // TODO replace with IsAdmin
{
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
}
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a>@UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey]</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Admin <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/admin"><i class="fa fa-cog"></i> @UI.Layout_Settings</a></li>
<li><a href="@url/changepassword"><i class="fa fa-key"></i> @UI.Layout_ChangePassword</a></li>
<li class="divider"></li>
<li><a href="@url/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
}
@if (Context.Request.Session[SessionKeys.UsernameKey] != null && !Context.CurrentUser.IsAuthenticated())
{
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey] <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
<li class="divider"></li>
<li><a href="@url/userlogin/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-language" aria-hidden="true"><span class="caret"></span></i></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/culture?l=en&u=@Context.Request.Path">@UI.Layout_English</a></li>
<li><a href="@url/culture?l=nl&u=@Context.Request.Path">@UI.Layout_Dutch</a></li>
<li><a href="@url/culture?l=es&u=@Context.Request.Path">@UI.Layout_Spanish</a></li>
<li><a href="@url/culture?l=de&u=@Context.Request.Path">@UI.Layout_German</a></li>
<li><a href="@url/culture?l=da&u=@Context.Request.Path">@UI.Layout_Danish</a></li>
<li><a href="@url/culture?l=pt&u=@Context.Request.Path">@UI.Layout_Portuguese</a></li>
<li><a href="@url/culture?l=sv&u=@Context.Request.Path">@UI.Layout_Swedish</a></li>
<li><a href="@url/culture?l=it&u=@Context.Request.Path">@UI.Layout_Italian</a></li>
</ul>
<li />
</ul>
</div>
</div>
<div id="updateAvailable" hidden="hidden"></div>
</nav>
<div class="container">
@RenderBody()
</div>
<div class="scroll-top-wrapper ">
<span class="scroll-top-inner">
<i class="fa fa-2x fa-arrow-circle-up"></i>
</span>
</div>
</body>
</html>
<script>
function donateClick(url) {
ga('send', 'event', 'Navbar', 'Donate', 'Donate Clicked');
var redirectWindow = window.open(url, '_blank');
redirectWindow.location;
}
$(function () {
var urlBase = '@Html.GetBaseUrl()';
// Check for update
var url = createBaseUrl(urlBase, '/updatechecker');
$.ajax({
type: "GET",
url: url,
dataType: "json",
success: function (response) {
if (response.updateAvailable) {
var status = createBaseUrl(urlBase, '/admin/status');
$('#updateAvailable').html("<i class='fa fa-cloud-download' aria-hidden='true'></i> @UI.Layout_UpdateAvailablePart1 <a style='color: white' href='" + status + "'>@UI.Layout_UpdateAvailablePart2</a>");
$('#updateAvailable').removeAttr("hidden");
$('body').addClass('update-available');
}
},
error: function (e) {
console.log(e);
}
});
// End Check for update
// Scroller
$(document).on('scroll', function () {
if ($(window).scrollTop() > 100) {
$('.scroll-top-wrapper').addClass('show');
} else {
$('.scroll-top-wrapper').removeClass('show');
}
});
$('.scroll-top-wrapper').on('click', scrollToTop);
// End Scroller
// Get Issue count
var issueUrl = createBaseUrl(urlBase, '/issues/issuecount');
$.ajax({
type: "GET",
url: issueUrl,
dataType: "json",
success: function (response) {
if (response) {
if (response > 0)
$('#issueCount').addClass("badge");
$('#issueCount').html(+response);
}
},
error: function (e) {
console.log(e);
}
});
// End issue count
});
function scrollToTop() {
verticalOffset = typeof (verticalOffset) != 'undefined' ? verticalOffset : 0;
element = $('body');
offset = element.offset();
offsetTop = offset.top;
$('html, body').animate({ scrollTop: offsetTop }, 500, 'linear');
}
</script>

@ -1,70 +1,70 @@
@using PlexRequests.UI.Helpers
<div class="home">
<h1>Login</h1>
<div>
<p>
Want to watch a movie or tv show but it's not currently on Plex?
Login below with your Plex.tv username and password! <span title="Your login details are only used to authenticate your Plex account."><i class="fa fa-question-circle"></i></span>
</p>
</div>
<form method="POST" id="loginForm">
<div>
<div>
<label>Plex.tv Username </label>
</div>
<div>
<input class="form-control form-control-custom" type="text" name="Username" placeholder="Username" />
</div>
</div>
<br />
@if (Model.UsePassword)
{
<div>
<div>
<label> Password </label>
</div>
<div>
<input class="form-control form-control-custom" name="Password" type="password" placeholder="Password"/>
</div>
</div>
<br />
}
<button id="loginBtn" class="btn btn-success-outline" type="submit"><i class="fa fa-user fa-fw"></i> Sign In</button>
</form>
</div>
<script>
$(function () {
var base = '@Html.GetBaseUrl()';
$('#loginBtn').click(function (e) {
e.preventDefault();
var $form = $("#loginForm");
var formData = $form.serialize();
var dtOffset = new Date().getTimezoneOffset();
formData += ('&DateTimeOffset=' + dtOffset);
var url = createBaseUrl(base, '/search');
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
data: formData,
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
location.replace(response.message);
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
<div class="home">
<h1>@UI.UserLogin_Title</h1>
<div>
<p>
@UI.UserLogin_Paragraph <span title="@UI.UserLogin_Paragraph_SpanHover"><i class="fa fa-question-circle"></i></span>
</p>
</div>
<form method="POST" id="loginForm">
<div>
<div>
<label>@UI.UserLogin_Username</label>
</div>
<div>
<input class="form-control form-control-custom" type="text" name="Username" placeholder="@UI.UserLogin_Username_Placeholder" />
</div>
</div>
<br />
@if (Model.UsePassword)
{
<div>
<div>
<label> @UI.UserLogin_Password </label>
</div>
<div>
<input class="form-control form-control-custom" name="Password" type="password" placeholder="@UI.UserLogin_Password"/>
</div>
</div>
<br />
}
<button id="loginBtn" class="btn btn-success-outline" type="submit"><i class="fa fa-user fa-fw"></i> @UI.UserLogin_SignIn</button>
</form>
</div>
<script>
$(function () {
var base = '@Html.GetBaseUrl()';
$('#loginBtn').click(function (e) {
e.preventDefault();
var $form = $("#loginForm");
var formData = $form.serialize();
var dtOffset = new Date().getTimezoneOffset();
formData += ('&DateTimeOffset=' + dtOffset);
var url = createBaseUrl(base, '/search');
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
data: formData,
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
location.replace(response.message);
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("@UI.Javascript_SomethingWentWrong", "danger");
}
});
});
});
</script>

@ -1,50 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<appSettings>
<add key="ClientSettingsProvider.ServiceUri" value="" />
<add key="webPages:Enabled" value="false" /></appSettings>
<connectionStrings>
<add name="Sqlite" connectionString="Data Source=RequestPlex.sqlite" providerName="Mono.Data.Sqlite" />
</connectionStrings>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
<providers>
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
</providers>
</roleManager>
</system.web>
<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" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.99.0" newVersion="1.0.99.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.web.webPages.razor>
<pages pageBaseType="Nancy.ViewEngines.Razor.NancyRazorViewBase">
<namespaces>
<add namespace="Nancy.ViewEngines.Razor" />
</namespaces>
</pages>
</system.web.webPages.razor><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<appSettings>
<add key="ClientSettingsProvider.ServiceUri" value="" />
<add key="webPages:Enabled" value="false" /></appSettings>
<connectionStrings>
<add name="Sqlite" connectionString="Data Source=RequestPlex.sqlite" providerName="Mono.Data.Sqlite" />
</connectionStrings>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
<providers>
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
</providers>
</roleManager>
</system.web>
<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" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.99.0" newVersion="1.0.99.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.web.webPages.razor>
<pages pageBaseType="Nancy.ViewEngines.Razor.NancyRazorViewBase">
<namespaces>
<add namespace="Nancy.ViewEngines.Razor" />
</namespaces>
</pages>
</system.web.webPages.razor><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>

@ -1,102 +1,105 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI", "PlexRequests.UI\PlexRequests.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api", "PlexRequests.Api\PlexRequests.Api.csproj", "{8CB8D235-2674-442D-9C6A-35FCAEEB160D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Interfaces", "PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj", "{95834072-A675-415D-AA8F-877C91623810}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core", "PlexRequests.Core\PlexRequests.Core.csproj", "{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Store", "PlexRequests.Store\PlexRequests.Store.csproj", "{92433867-2B7B-477B-A566-96C382427525}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4BC839C-B8FF-48BE-B22E-536A0A0A81A5}"
ProjectSection(SolutionItems) = preProject
.travis.yml = .travis.yml
appveyor.yml = appveyor.yml
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers", "PlexRequests.Helpers\PlexRequests.Helpers.csproj", "{1252336D-42A3-482A-804C-836E60173DFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI.Tests", "PlexRequests.UI.Tests\PlexRequests.UI.Tests.csproj", "{A930E2CF-79E2-45F9-B06A-9A719A254CE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core.Tests", "PlexRequests.Core.Tests\PlexRequests.Core.Tests.csproj", "{FCFECD5D-47F6-454D-8692-E27A921BE655}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services", "PlexRequests.Services\PlexRequests.Services.csproj", "{566EFA49-68F8-4716-9693-A6B3F2624DEA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Models", "PlexRequests.Api.Models\PlexRequests.Api.Models.csproj", "{CB37A5F8-6DFC-4554-99D3-A42B502E4591}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services.Tests", "PlexRequests.Services.Tests\PlexRequests.Services.Tests.csproj", "{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Updater", "PlexRequests.Updater\PlexRequests.Updater.csproj", "{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.Build.0 = Release|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.Build.0 = Release|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.Build.0 = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.Build.0 = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.Build.0 = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.Build.0 = Release|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.Build.0 = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.Build.0 = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.Build.0 = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.Build.0 = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.Build.0 = Release|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.Build.0 = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI", "PlexRequests.UI\PlexRequests.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api", "PlexRequests.Api\PlexRequests.Api.csproj", "{8CB8D235-2674-442D-9C6A-35FCAEEB160D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Interfaces", "PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj", "{95834072-A675-415D-AA8F-877C91623810}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core", "PlexRequests.Core\PlexRequests.Core.csproj", "{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Store", "PlexRequests.Store\PlexRequests.Store.csproj", "{92433867-2B7B-477B-A566-96C382427525}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4BC839C-B8FF-48BE-B22E-536A0A0A81A5}"
ProjectSection(SolutionItems) = preProject
.travis.yml = .travis.yml
appveyor.yml = appveyor.yml
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers", "PlexRequests.Helpers\PlexRequests.Helpers.csproj", "{1252336D-42A3-482A-804C-836E60173DFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI.Tests", "PlexRequests.UI.Tests\PlexRequests.UI.Tests.csproj", "{A930E2CF-79E2-45F9-B06A-9A719A254CE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core.Tests", "PlexRequests.Core.Tests\PlexRequests.Core.Tests.csproj", "{FCFECD5D-47F6-454D-8692-E27A921BE655}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services", "PlexRequests.Services\PlexRequests.Services.csproj", "{566EFA49-68F8-4716-9693-A6B3F2624DEA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Models", "PlexRequests.Api.Models\PlexRequests.Api.Models.csproj", "{CB37A5F8-6DFC-4554-99D3-A42B502E4591}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services.Tests", "PlexRequests.Services.Tests\PlexRequests.Services.Tests.csproj", "{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Updater", "PlexRequests.Updater\PlexRequests.Updater.csproj", "{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.Build.0 = Release|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.Build.0 = Release|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.Build.0 = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.Build.0 = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.Build.0 = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.Build.0 = Release|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.Build.0 = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.Build.0 = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.Build.0 = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.Build.0 = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.Build.0 = Release|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.Build.0 = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_PrefixTranslations = False
EndGlobalSection
EndGlobal

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

Loading…
Cancel
Save