Merge pull request #1 from tidusjar/dev

Dev
This commit is contained in:
halali 2016-06-28 18:48:17 +02:00 committed by GitHub
commit 18bc5e8b42
97 changed files with 14104 additions and 8429 deletions

71
.gitattributes vendored Normal file
View file

@ -0,0 +1,71 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
PlexRequests.UI/Content/* linguist-vendored
PlexRequests.UI/Content/* linguist-vendored
base.scss linguist-vendored=false
requests-1.7.js linguist-vendored=false
search-1.7.js linguist-vendored=false
site-1.7.js linguist-vendored=false

View file

@ -1,100 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlexRequests.Api.Models.Movie
{
public class CouchPotatoAdd
{
public Movie movie { get; set; }
public bool success { get; set; }
}
public class Rating
{
public List<string> imdb { get; set; }
}
public class Images
{
public List<object> disc_art { get; set; }
public List<string> poster { get; set; }
public List<string> backdrop { get; set; }
public List<object> extra_thumbs { get; set; }
public List<string> poster_original { get; set; }
public List<string> actors { get; set; }
public List<string> backdrop_original { get; set; }
public List<object> clear_art { get; set; }
public List<object> logo { get; set; }
public List<object> banner { get; set; }
public List<object> landscape { get; set; }
public List<object> extra_fanart { get; set; }
}
public class Info
{
public Rating rating { get; set; }
public List<string> genres { get; set; }
public int tmdb_id { get; set; }
public string plot { get; set; }
public string tagline { get; set; }
public Release_Date release_date { get; set; }
public int year { get; set; }
public string original_title { get; set; }
public List<string> actor_roles { get; set; }
public bool via_imdb { get; set; }
public Images images { get; set; }
public List<string> directors { get; set; }
public List<string> titles { get; set; }
public string imdb { get; set; }
public string mpaa { get; set; }
public bool via_tmdb { get; set; }
public List<string> actors { get; set; }
public List<string> writers { get; set; }
public int runtime { get; set; }
public string type { get; set; }
public string released { get; set; }
}
public class Release_Date
{
public int dvd { get; set; }
public int expires { get; set; }
public int theater { get; set; }
public bool bluray { get; set; }
}
public class Files
{
public List<string> image_poster { get; set; }
}
public class Identifiers
{
public string imdb { get; set; }
}
public class Movie
{
public string status { get; set; }
public Info info { get; set; }
public string _t { get; set; }
public List<object> releases { get; set; }
public string title { get; set; }
public string _rev { get; set; }
public string profile_id { get; set; }
public string _id { get; set; }
public List<object> tags { get; set; }
public int last_edit { get; set; }
public object category_id { get; set; }
public string type { get; set; }
public Files files { get; set; }
public Identifiers identifiers { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlexRequests.Api.Models.Movie
{
public class CouchPotatoAdd
{
public Movie movie { get; set; }
public bool success { get; set; }
}
public class Rating
{
public List<string> imdb { get; set; }
}
public class Images
{
public List<object> disc_art { get; set; }
public List<string> poster { get; set; }
public List<string> backdrop { get; set; }
public List<object> extra_thumbs { get; set; }
public List<string> poster_original { get; set; }
public List<string> actors { get; set; }
public List<string> backdrop_original { get; set; }
public List<object> clear_art { get; set; }
public List<object> logo { get; set; }
public List<object> banner { get; set; }
public List<object> landscape { get; set; }
public List<object> extra_fanart { get; set; }
}
public class Info
{
public Rating rating { get; set; }
public List<string> genres { get; set; }
public int tmdb_id { get; set; }
public string plot { get; set; }
public string tagline { get; set; }
public Release_Date release_date { get; set; }
public int year { get; set; }
public string original_title { get; set; }
public List<string> actor_roles { get; set; }
public bool via_imdb { get; set; }
public Images images { get; set; }
public List<string> directors { get; set; }
public List<string> titles { get; set; }
public string imdb { get; set; }
public string mpaa { get; set; }
public bool via_tmdb { get; set; }
public List<string> actors { get; set; }
public List<string> writers { get; set; }
public int runtime { get; set; }
public string type { get; set; }
public string released { get; set; }
}
public class Release_Date
{
public int dvd { get; set; }
public int expires { get; set; }
public int theater { get; set; }
public bool bluray { get; set; }
}
public class Files
{
public List<string> image_poster { get; set; }
}
public class Identifiers
{
public string imdb { get; set; }
}
public class Movie
{
public string status { get; set; }
public Info info { get; set; }
public string _t { get; set; }
public List<object> releases { get; set; }
public string title { get; set; }
public string _rev { get; set; }
public string profile_id { get; set; }
public string _id { get; set; }
public List<object> tags { get; set; }
public int last_edit { get; set; }
public object category_id { get; set; }
public string type { get; set; }
public Files files { get; set; }
public Identifiers identifiers { get; set; }
}
}

View file

@ -1,164 +1,168 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CouchPotatoApi.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 Newtonsoft.Json.Linq;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Movie;
using PlexRequests.Helpers.Exceptions;
using RestSharp;
namespace PlexRequests.Api
{
public class CouchPotatoApi : ICouchPotatoApi
{
public CouchPotatoApi()
{
Api = new ApiRequest();
}
private ApiRequest Api { get; set; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string))
{
RestRequest request;
request = string.IsNullOrEmpty(profileId)
? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"}
: new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" };
if (!string.IsNullOrEmpty(profileId))
{
request.AddUrlSegment("profileId", profileId);
}
request.AddUrlSegment("apikey", apiKey);
request.AddUrlSegment("imdbid", imdbid);
request.AddUrlSegment("title", title);
var obj = RetryHandler.Execute(() => Api.ExecuteJson<JObject> (request, baseUrl),new[] {
TimeSpan.FromSeconds (2),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)},
(exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan));
Log.Trace("CP movie Add result count {0}", obj.Count);
if (obj.Count > 0)
{
try
{
Log.Trace("CP movie obj[\"success\"] = {0}", obj["success"]);
var result = (bool)obj["success"];
Log.Trace("CP movie Add result {0}", result);
return result;
}
catch (Exception e)
{
Log.Fatal(e);
return false;
}
}
return false;
}
/// <summary>
/// Gets the status.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="apiKey">The API key.</param>
/// <returns></returns>
public CouchPotatoStatus GetStatus(Uri url, string apiKey)
{
Log.Trace("Getting CP Status, ApiKey = {0}", apiKey);
var request = new RestRequest
{
Resource = "api/{apikey}/app.available/",
Method = Method.GET
};
request.AddUrlSegment("apikey", apiKey);
var obj = RetryHandler.Execute<CouchPotatoStatus>(() => Api.Execute<CouchPotatoStatus> (request, url),new TimeSpan[] {
TimeSpan.FromSeconds (2),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)},
(exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan));
return obj;
}
public CouchPotatoProfiles GetProfiles(Uri url, string apiKey)
{
Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey);
var request = new RestRequest
{
Resource = "api/{apikey}/profile.list/",
Method = Method.GET
};
request.AddUrlSegment("apikey", apiKey);
var obj = RetryHandler.Execute<CouchPotatoProfiles>(() => Api.Execute<CouchPotatoProfiles> (request, url),null,
(exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan));
return obj;
}
public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status)
{
var request = new RestRequest
{
Resource = "/api/{apikey}/movie.list?status={status}"
};
request.AddUrlSegment("apikey", apiKey);
request.AddUrlSegment("status", string.Join(",", status));
try
{
var obj = RetryHandler.Execute(() => Api.Execute<CouchPotatoMovies> (request, baseUrl),
new[] {
TimeSpan.FromSeconds (5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30)
},
(exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan));
return obj;
}
catch (Exception e) // Request error is already logged in the ApiRequest class
{
Log.Error("Error when attempting to GetMovies.");
Log.Error (e);
return new CouchPotatoMovies();
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CouchPotatoApi.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 Newtonsoft.Json.Linq;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Movie;
using PlexRequests.Helpers.Exceptions;
using RestSharp;
namespace PlexRequests.Api
{
public class CouchPotatoApi : ICouchPotatoApi
{
public CouchPotatoApi()
{
Api = new ApiRequest();
}
private ApiRequest Api { get; set; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string))
{
RestRequest request;
request = string.IsNullOrEmpty(profileId)
? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"}
: new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" };
if (!string.IsNullOrEmpty(profileId))
{
request.AddUrlSegment("profileId", profileId);
}
request.AddUrlSegment("apikey", apiKey);
request.AddUrlSegment("imdbid", imdbid);
request.AddUrlSegment("title", title);
var obj = RetryHandler.Execute(() => Api.ExecuteJson<JObject> (request, baseUrl),new[] {
TimeSpan.FromSeconds (2),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)},
(exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan));
Log.Trace("CP movie Add result count {0}", obj.Count);
if (obj.Count > 0)
{
try
{
Log.Trace("CP movie obj[\"success\"] = {0}", obj["success"]);
var result = (bool)obj["success"];
Log.Trace("CP movie Add result {0}", result);
return result;
}
catch (Exception e)
{
Log.Fatal(e);
return false;
}
}
return false;
}
/// <summary>
/// Gets the status.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="apiKey">The API key.</param>
/// <returns></returns>
public CouchPotatoStatus GetStatus(Uri url, string apiKey)
{
Log.Trace("Getting CP Status, ApiKey = {0}", apiKey);
var request = new RestRequest
{
Resource = "api/{apikey}/app.available/",
Method = Method.GET
};
request.AddUrlSegment("apikey", apiKey);
var obj = RetryHandler.Execute<CouchPotatoStatus>(() => Api.Execute<CouchPotatoStatus> (request, url),new TimeSpan[] {
TimeSpan.FromSeconds (2),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)},
(exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan));
return obj;
}
public CouchPotatoProfiles GetProfiles(Uri url, string apiKey)
{
Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey);
var request = new RestRequest
{
Resource = "api/{apikey}/profile.list/",
Method = Method.GET
};
request.AddUrlSegment("apikey", apiKey);
var obj = RetryHandler.Execute<CouchPotatoProfiles>(() => Api.Execute<CouchPotatoProfiles> (request, url),null,
(exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan));
return obj;
}
public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status)
{
var request = new RestRequest
{
Resource = "/api/{apikey}/movie.list?status={status}",
OnBeforeDeserialization = (x =>
{
x.Content = x.Content.Replace("[]", "{}");
})
};
request.AddUrlSegment("apikey", apiKey);
request.AddUrlSegment("status", string.Join(",", status));
try
{
var obj = RetryHandler.Execute(() => Api.Execute<CouchPotatoMovies> (request, baseUrl),
new[] {
TimeSpan.FromSeconds (5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30)
},
(exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan));
return obj;
}
catch (Exception e) // Request error is already logged in the ApiRequest class
{
Log.Error("Error when attempting to GetMovies.");
Log.Error (e);
return new CouchPotatoMovies();
}
}
}
}

View file

@ -1,59 +1,91 @@
#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");
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,102 @@
#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
{
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 == 123) // Start of curly '{'
{
insideCurly = true;
continue;
}
if (c == 125) // 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;
}
}
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ContainerHelper.cs
// File: NotificationSettings.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
@ -24,25 +24,15 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy.TinyIoc;
using System.Collections.Generic;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
using PlexRequests.Core.Models;
namespace PlexRequests.UI.Helpers
namespace PlexRequests.Core.SettingModels
{
public static class ContainerHelper
public class NotificationSettings : Settings
{
public static void RegisterSetting<T>(this TinyIoCContainer container) where T : Settings, new()
{
container.Register<ISettingsService<T>, SettingsServiceV2<T>>();
}
public static void RegisterRepo<T>(this TinyIoCContainer container) where T : Entity, new()
{
container.Register<IRepository<T>, GenericRepository<T>>();
}
public Dictionary<NotificationType, string> Message { get; set; }
public Dictionary<string,string> CustomParamaters { get; set; }
}
}

View file

@ -1,77 +1,87 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexRequestSettings.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace PlexRequests.Core.SettingModels
{
public class PlexRequestSettings : Settings
{
public int Port { get; set; }
public string BaseUrl { get; set; }
public bool SearchForMovies { get; set; }
public bool SearchForTvShows { get; set; }
public bool SearchForMusic { get; set; }
public bool RequireMovieApproval { get; set; }
public bool RequireTvShowApproval { get; set; }
public bool RequireMusicApproval { get; set; }
public bool UsersCanViewOnlyOwnRequests { get; set; }
public bool UsersCanViewOnlyOwnIssues { get; set; }
public int WeeklyRequestLimit { get; set; }
public string NoApprovalUsers { get; set; }
public bool CollectAnalyticData { get; set; }
/// <summary>
/// The CSS name of the theme we want
/// </summary>
public string ThemeName { get; set; }
public string ApiKey { get; set; }
[JsonIgnore]
public List<string> ApprovalWhiteList
{
get
{
var users = new List<string>();
if (string.IsNullOrEmpty(NoApprovalUsers))
{
return users;
}
var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var user in splitUsers)
{
if (!string.IsNullOrWhiteSpace(user))
users.Add(user.Trim());
}
return users;
}
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexRequestSettings.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
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; }
public bool SearchForTvShows { get; set; }
public bool SearchForMusic { get; set; }
public bool RequireMovieApproval { get; set; }
public bool RequireTvShowApproval { get; set; }
public bool RequireMusicApproval { get; set; }
public bool UsersCanViewOnlyOwnRequests { get; set; }
public bool UsersCanViewOnlyOwnIssues { 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; }
/// <summary>
/// The CSS name of the theme we want
/// </summary>
public string ThemeName { get; set; }
public string ApiKey { get; set; }
[JsonIgnore]
public List<string> ApprovalWhiteList
{
get
{
var users = new List<string>();
if (string.IsNullOrEmpty(NoApprovalUsers))
{
return users;
}
var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var user in splitUsers)
{
if (!string.IsNullOrWhiteSpace(user))
users.Add(user.Trim());
}
return users;
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,244 +1,240 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Setup.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.Text.RegularExpressions;
using Mono.Data.Sqlite;
using NLog;
using PlexRequests.Api;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
using System.Threading.Tasks;
namespace PlexRequests.Core
{
public class Setup
{
private static Logger Log = LogManager.GetCurrentClassLogger();
private static DbConfiguration Db { get; set; }
public string SetupDb(string urlBase)
{
Db = new DbConfiguration(new SqliteFactory());
var created = Db.CheckDb();
TableCreation.CreateTables(Db.DbConnection());
if (created)
{
CreateDefaultSettingsPage(urlBase);
}
var version = CheckSchema();
if (version > 0)
{
if (version > 1700 && version <= 1799)
{
MigrateToVersion1700();
}
if (version > 1799 && version <= 1800)
{
MigrateToVersion1800();
}
}
return Db.DbConnection().ConnectionString;
}
public static string ConnectionString => Db.DbConnection().ConnectionString;
private int CheckSchema()
{
var productVersion = AssemblyHelper.GetProductVersion();
var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0');
var version = int.Parse(trimStatus);
var connection = Db.DbConnection();
var schema = connection.GetSchemaVersion();
if (schema == null)
{
connection.CreateSchema(version); // Set the default.
schema = connection.GetSchemaVersion();
}
if (version > schema.SchemaVersion)
{
Db.DbConnection().UpdateSchemaVersion(version);
schema = connection.GetSchemaVersion();
}
version = schema.SchemaVersion;
return version;
}
private void CreateDefaultSettingsPage(string baseUrl)
{
var defaultSettings = new PlexRequestSettings
{
RequireTvShowApproval = true,
RequireMovieApproval = true,
SearchForMovies = true,
SearchForTvShows = true,
WeeklyRequestLimit = 0,
BaseUrl = baseUrl ?? string.Empty
};
var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
s.SaveSettings(defaultSettings);
}
public void CacheQualityProfiles()
{
var mc = new MemoryCacheProvider();
try
{
Task.Run(() => { CacheSonarrQualityProfiles(mc); });
Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); });
// we don't need to cache sickrage profiles, those are static
// TODO: cache headphones profiles?
}
catch (Exception)
{
Log.Error("Failed to cache quality profiles on startup!");
}
}
private void CacheSonarrQualityProfiles(MemoryCacheProvider cacheProvider)
{
try
{
Log.Info("Executing GetSettings call to Sonarr for quality profiles");
var sonarrSettingsService = new SettingsServiceV2<SonarrSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var sonarrSettings = sonarrSettingsService.GetSettings();
if (sonarrSettings.Enabled)
{
Log.Info("Begin executing GetProfiles call to Sonarr for quality profiles");
SonarrApi sonarrApi = new SonarrApi();
var profiles = sonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri);
cacheProvider.Set(CacheKeys.SonarrQualityProfiles, profiles);
Log.Info("Finished executing GetProfiles call to Sonarr for quality profiles");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to cache Sonarr quality profiles!");
}
}
private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider cacheProvider)
{
try
{
Log.Info("Executing GetSettings call to CouchPotato for quality profiles");
var cpSettingsService = new SettingsServiceV2<CouchPotatoSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var cpSettings = cpSettingsService.GetSettings();
if (cpSettings.Enabled)
{
Log.Info("Begin executing GetProfiles call to CouchPotato for quality profiles");
CouchPotatoApi cpApi = new CouchPotatoApi();
var profiles = cpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey);
cacheProvider.Set(CacheKeys.CouchPotatoQualityProfiles, profiles);
Log.Info("Finished executing GetProfiles call to CouchPotato for quality profiles");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to cache CouchPotato quality profiles!");
}
}
public void MigrateToVersion1700()
{
// Drop old tables
TableCreation.DropTable(Db.DbConnection(), "User");
TableCreation.DropTable(Db.DbConnection(), "Log");
}
/// <summary>
/// Migrates to version 1.8.
/// <para>This includes updating the admin account to have all roles.</para>
/// <para>Set the log level to Error</para>
/// <para>Enable Analytics by default</para>
/// </summary>
private void MigrateToVersion1800()
{
// Give admin all roles/claims
try
{
var userMapper = new UserMapper(new UserRepository<UsersModel>(Db, new MemoryCacheProvider()));
var users = userMapper.GetUsers();
foreach (var u in users)
{
var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser };
u.Claims = ByteConverterHelper.ReturnBytes(claims);
userMapper.EditUser(u);
}
}
catch (Exception e)
{
Log.Error(e);
}
// Set log level
try
{
var settingsService = new SettingsServiceV2<LogSettings>(new SettingsJsonRepository(Db, new MemoryCacheProvider()));
var logSettings = settingsService.GetSettings();
logSettings.Level = LogLevel.Error.Ordinal;
settingsService.SaveSettings(logSettings);
LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level));
}
catch (Exception e)
{
Log.Error(e);
}
// Enable analytics;
try
{
var prSettings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(Db, new MemoryCacheProvider()));
var settings = prSettings.GetSettings();
settings.CollectAnalyticData = true;
var updated = prSettings.SaveSettings(settings);
}
catch (Exception e)
{
Log.Error(e);
}
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Setup.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.Text.RegularExpressions;
using Mono.Data.Sqlite;
using NLog;
using PlexRequests.Api;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
using System.Threading.Tasks;
namespace PlexRequests.Core
{
public class Setup
{
private static Logger Log = LogManager.GetCurrentClassLogger();
private static DbConfiguration Db { get; set; }
public string SetupDb(string urlBase)
{
Db = new DbConfiguration(new SqliteFactory());
var created = Db.CheckDb();
TableCreation.CreateTables(Db.DbConnection());
if (created)
{
CreateDefaultSettingsPage(urlBase);
}
var version = CheckSchema();
if (version > 0)
{
if (version > 1799 && version <= 1800)
{
MigrateToVersion1800();
}
}
return Db.DbConnection().ConnectionString;
}
public static string ConnectionString => Db.DbConnection().ConnectionString;
private int CheckSchema()
{
var productVersion = AssemblyHelper.GetProductVersion();
var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0');
var version = int.Parse(trimStatus);
var connection = Db.DbConnection();
var schema = connection.GetSchemaVersion();
if (schema == null)
{
connection.CreateSchema(version); // Set the default.
schema = connection.GetSchemaVersion();
}
if (version > schema.SchemaVersion)
{
Db.DbConnection().UpdateSchemaVersion(version);
schema = connection.GetSchemaVersion();
}
version = schema.SchemaVersion;
return version;
}
private void CreateDefaultSettingsPage(string baseUrl)
{
var defaultSettings = new PlexRequestSettings
{
RequireTvShowApproval = true,
RequireMovieApproval = true,
SearchForMovies = true,
SearchForTvShows = true,
BaseUrl = baseUrl ?? string.Empty,
CollectAnalyticData = true,
};
var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
s.SaveSettings(defaultSettings);
}
public void CacheQualityProfiles()
{
var mc = new MemoryCacheProvider();
try
{
Task.Run(() => { CacheSonarrQualityProfiles(mc); });
Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); });
// we don't need to cache sickrage profiles, those are static
// TODO: cache headphones profiles?
}
catch (Exception)
{
Log.Error("Failed to cache quality profiles on startup!");
}
}
private void CacheSonarrQualityProfiles(MemoryCacheProvider cacheProvider)
{
try
{
Log.Info("Executing GetSettings call to Sonarr for quality profiles");
var sonarrSettingsService = new SettingsServiceV2<SonarrSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var sonarrSettings = sonarrSettingsService.GetSettings();
if (sonarrSettings.Enabled)
{
Log.Info("Begin executing GetProfiles call to Sonarr for quality profiles");
SonarrApi sonarrApi = new SonarrApi();
var profiles = sonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri);
cacheProvider.Set(CacheKeys.SonarrQualityProfiles, profiles);
Log.Info("Finished executing GetProfiles call to Sonarr for quality profiles");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to cache Sonarr quality profiles!");
}
}
private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider cacheProvider)
{
try
{
Log.Info("Executing GetSettings call to CouchPotato for quality profiles");
var cpSettingsService = new SettingsServiceV2<CouchPotatoSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var cpSettings = cpSettingsService.GetSettings();
if (cpSettings.Enabled)
{
Log.Info("Begin executing GetProfiles call to CouchPotato for quality profiles");
CouchPotatoApi cpApi = new CouchPotatoApi();
var profiles = cpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey);
cacheProvider.Set(CacheKeys.CouchPotatoQualityProfiles, profiles);
Log.Info("Finished executing GetProfiles call to CouchPotato for quality profiles");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to cache CouchPotato quality profiles!");
}
}
public void MigrateToVersion1700()
{
// Drop old tables
TableCreation.DropTable(Db.DbConnection(), "User");
TableCreation.DropTable(Db.DbConnection(), "Log");
}
/// <summary>
/// Migrates to version 1.8.
/// <para>This includes updating the admin account to have all roles.</para>
/// <para>Set the log level to Error</para>
/// <para>Enable Analytics by default</para>
/// </summary>
private void MigrateToVersion1800()
{
// Give admin all roles/claims
try
{
var userMapper = new UserMapper(new UserRepository<UsersModel>(Db, new MemoryCacheProvider()));
var users = userMapper.GetUsers();
foreach (var u in users)
{
var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser };
u.Claims = ByteConverterHelper.ReturnBytes(claims);
userMapper.EditUser(u);
}
}
catch (Exception e)
{
Log.Error(e);
}
// Set log level
try
{
var settingsService = new SettingsServiceV2<LogSettings>(new SettingsJsonRepository(Db, new MemoryCacheProvider()));
var logSettings = settingsService.GetSettings();
logSettings.Level = LogLevel.Error.Ordinal;
settingsService.SaveSettings(logSettings);
LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level));
}
catch (Exception e)
{
Log.Error(e);
}
// Enable analytics;
try
{
var prSettings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(Db, new MemoryCacheProvider()));
var settings = prSettings.GetSettings();
settings.CollectAnalyticData = true;
var updated = prSettings.SaveSettings(settings);
}
catch (Exception e)
{
Log.Error(e);
}
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,52 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: DateTimeHelperTests.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 NUnit.Framework;
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class CookieHelperTests
{
[TestCaseSource(nameof(GetAnalyticsClientId))]
public string TestGetAnalyticsClientId(Dictionary<string,string> cookies)
{
return CookieHelper.GetAnalyticClientId(cookies);
}
private static IEnumerable<TestCaseData> GetAnalyticsClientId
{
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);
}
}
}
}

View file

@ -1,119 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0E6395D3-B074-49E8-898D-0EB99E507E0E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Helpers.Tests</RootNamespace>
<AssemblyName>PlexRequests.Helpers.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<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="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="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="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.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>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="DateTimeHelperTests.cs" />
<Compile Include="PasswordHasherTests.cs" />
<Compile Include="HtmlRemoverTests.cs" />
<Compile Include="AssemblyHelperTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PlexHelperTests.cs" />
<Compile Include="UriHelperTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<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>
<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="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0E6395D3-B074-49E8-898D-0EB99E507E0E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Helpers.Tests</RootNamespace>
<AssemblyName>PlexRequests.Helpers.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<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="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="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="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.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>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="CookieHelperTests.cs" />
<Compile Include="DateTimeHelperTests.cs" />
<Compile Include="PasswordHasherTests.cs" />
<Compile Include="HtmlRemoverTests.cs" />
<Compile Include="AssemblyHelperTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PlexHelperTests.cs" />
<Compile Include="UriHelperTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<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>
<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>

View file

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

View file

@ -47,52 +47,51 @@ namespace PlexRequests.Helpers.Analytics
private static Logger Log = LogManager.GetCurrentClassLogger();
public void TrackEvent(Category category, Action action, string label, string username, int? value = null)
public void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null)
{
var cat = category.ToString();
var act = action.ToString();
Track(HitType.@event, username, cat, act, label, value);
Track(HitType.@event, username, cat, act, label, clientId, value);
}
public async Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null)
public async Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null)
{
var cat = category.ToString();
var act = action.ToString();
await TrackAsync(HitType.@event, username, cat, act, label, value);
await TrackAsync(HitType.@event, username, cat, act, clientId, label, value);
}
public void TrackPageview(Category category, Action action, string label, string username, int? value = null)
public void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null)
{
var cat = category.ToString();
var act = action.ToString();
Track(HitType.@pageview, username, cat, act, label, value);
Track(HitType.@pageview, username, cat, act, clientId, label, value);
}
public async Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null)
public async Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null)
{
var cat = category.ToString();
var act = action.ToString();
await TrackAsync(HitType.@pageview, username, cat, act, label, value);
await TrackAsync(HitType.@pageview, username, cat, act, clientId, label, value);
}
public void TrackException(string message, string username, bool fatal)
public void TrackException(string message, string username, string clientId, bool fatal)
{
var fatalInt = fatal ? 1 : 0;
Track(HitType.exception, message, fatalInt, username);
Track(HitType.exception, message, fatalInt, username, clientId);
}
public async Task TrackExceptionAsync(string message, string username, bool fatal)
public async Task TrackExceptionAsync(string message, string username, string clientId, bool fatal)
{
var fatalInt = fatal ? 1 : 0;
await TrackAsync(HitType.exception, message, fatalInt, username);
await TrackAsync(HitType.exception, message, fatalInt, username, clientId);
}
private void Track(HitType type, string username, string category, string action, string label, int? value = null)
private void Track(HitType type, string username, string category, string action, string clientId, string label, int? value = null)
{
if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category));
if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, category, action, label, value, null, null);
var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null);
var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -101,13 +100,12 @@ namespace PlexRequests.Helpers.Analytics
SendRequest(postDataString);
}
private async Task TrackAsync(HitType type, string username, string category, string action, string label, int? value = null)
private async Task TrackAsync(HitType type, string username, string category, string action, string clientId, string label, int? value = null)
{
if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category));
if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, category, action, label, value, null, null);
var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null);
var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -115,12 +113,11 @@ namespace PlexRequests.Helpers.Analytics
await SendRequestAsync(postDataString);
}
private async Task TrackAsync(HitType type, string message, int fatal, string username)
private async Task TrackAsync(HitType type, string message, int fatal, string username, string clientId)
{
if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, null, null, null, null, message, fatal);
var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal);
var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -129,12 +126,12 @@ namespace PlexRequests.Helpers.Analytics
await SendRequestAsync(postDataString);
}
private void Track(HitType type, string message, int fatal, string username)
private void Track(HitType type, string message, int fatal, string username, string clientId)
{
if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));
if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
var postData = BuildRequestData(type, username, null, null, null, null, message, fatal);
var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal);
var postDataString = postData
.Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}"))
@ -196,20 +193,24 @@ namespace PlexRequests.Helpers.Analytics
}
}
private Dictionary<string, string> BuildRequestData(HitType type, string username, string category, string action, string label, int? value, string exceptionDescription, int? fatal)
private Dictionary<string, string> BuildRequestData(HitType type, string username, string category, string action, string clientId, string label, int? value, string exceptionDescription, int? fatal)
{
var postData = new Dictionary<string, string>
{
{ "v", "1" },
{ "tid", TrackingId },
{ "t", type.ToString() },
{"cid", Guid.NewGuid().ToString() }
{ "t", type.ToString() }
};
if (!string.IsNullOrEmpty(username))
{
postData.Add("uid", username);
}
postData.Add("cid", !string.IsNullOrEmpty(clientId)
? clientId
: Guid.NewGuid().ToString());
if (!string.IsNullOrEmpty(label))
{
postData.Add("el", label);

View file

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

View file

@ -37,8 +37,9 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param>
/// <param name="label">The label.</param>
/// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param>
void TrackEvent(Category category, Action action, string label, string username, int? value = null);
void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary>
/// Tracks the event asynchronous.
@ -47,9 +48,10 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param>
/// <param name="label">The label.</param>
/// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null);
Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary>
/// Tracks the page view.
@ -58,8 +60,9 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param>
/// <param name="label">The label.</param>
/// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param>
void TrackPageview(Category category, Action action, string label, string username, int? value = null);
void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary>
/// Tracks the page view asynchronous.
@ -68,25 +71,28 @@ namespace PlexRequests.Helpers.Analytics
/// <param name="action">The action.</param>
/// <param name="label">The label.</param>
/// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null);
Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null);
/// <summary>
/// Tracks the exception.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="fatal">if set to <c>true</c> [fatal].</param>
void TrackException(string message, string username, bool fatal);
void TrackException(string message, string username, string clientId, bool fatal);
/// <summary>
/// Tracks the exception asynchronous.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="username">The username.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="fatal">if set to <c>true</c> [fatal].</param>
/// <returns></returns>
Task TrackExceptionAsync(string message, string username, bool fatal);
Task TrackExceptionAsync(string message, string username, string clientId, bool fatal);
}
}

View file

@ -0,0 +1,57 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CookieHelper.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;
namespace PlexRequests.Helpers
{
public static class CookieHelper
{
private const string GaCookie = "_ga";
/// <summary>
/// Gets the analytic client identifier.
/// <para>Example: Value = "GA1.1.306549087.1464005217"</para>
/// </summary>
/// <param name="cookies">The cookies.</param>
/// <returns></returns>
public static string GetAnalyticClientId(IDictionary<string, string> cookies)
{
var outString = string.Empty;
if (cookies.TryGetValue(GaCookie, out outString))
{
var split = outString.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
return split.Length < 4
? string.Empty
: $"{split[2]}.{split[3]}";
}
return string.Empty;
}
}
}

View file

@ -1,102 +1,103 @@
<?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>{1252336D-42A3-482A-804C-836E60173DFA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Helpers</RootNamespace>
<AssemblyName>PlexRequests.Helpers</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="Hangfire.Core, Version=1.5.7.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Hangfire.Core.1.5.7\lib\net45\Hangfire.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="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="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Web" />
<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="Analytics\Action.cs" />
<Compile Include="Analytics\Analytics.cs" />
<Compile Include="Analytics\Category.cs" />
<Compile Include="Analytics\HitType.cs" />
<Compile Include="Analytics\IAnalytics.cs" />
<Compile Include="AssemblyHelper.cs" />
<Compile Include="ByteConverterHelper.cs" />
<Compile Include="DateTimeHelper.cs" />
<Compile Include="Exceptions\ApiRequestException.cs" />
<Compile Include="Exceptions\ApplicationSettingsException.cs" />
<Compile Include="HtmlRemover.cs" />
<Compile Include="ICacheProvider.cs" />
<Compile Include="JsonConvertHelper.cs" />
<Compile Include="LoggingHelper.cs" />
<Compile Include="MemoryCacheProvider.cs" />
<Compile Include="ObjectCopier.cs" />
<Compile Include="PasswordHasher.cs" />
<Compile Include="PlexHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerializerSettings.cs" />
<Compile Include="StringCipher.cs" />
<Compile Include="UriHelper.cs" />
<Compile Include="UserClaims.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<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>{1252336D-42A3-482A-804C-836E60173DFA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Helpers</RootNamespace>
<AssemblyName>PlexRequests.Helpers</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="Hangfire.Core, Version=1.5.7.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Hangfire.Core.1.5.7\lib\net45\Hangfire.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="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="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Web" />
<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="Analytics\Action.cs" />
<Compile Include="Analytics\Analytics.cs" />
<Compile Include="Analytics\Category.cs" />
<Compile Include="Analytics\HitType.cs" />
<Compile Include="Analytics\IAnalytics.cs" />
<Compile Include="AssemblyHelper.cs" />
<Compile Include="ByteConverterHelper.cs" />
<Compile Include="CookieHelper.cs" />
<Compile Include="DateTimeHelper.cs" />
<Compile Include="Exceptions\ApiRequestException.cs" />
<Compile Include="Exceptions\ApplicationSettingsException.cs" />
<Compile Include="HtmlRemover.cs" />
<Compile Include="ICacheProvider.cs" />
<Compile Include="JsonConvertHelper.cs" />
<Compile Include="LoggingHelper.cs" />
<Compile Include="MemoryCacheProvider.cs" />
<Compile Include="ObjectCopier.cs" />
<Compile Include="PasswordHasher.cs" />
<Compile Include="PlexHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerializerSettings.cs" />
<Compile Include="StringCipher.cs" />
<Compile Include="UriHelper.cs" />
<Compile Include="UserClaims.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<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>

View file

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

View file

@ -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")]

View file

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

View file

@ -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)

View file

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

View file

@ -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;
@ -179,7 +180,8 @@ namespace PlexRequests.Services.Jobs
{
if (advanced)
{
if (movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
if (!string.IsNullOrEmpty(movie.ProviderId) &&
movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
@ -225,7 +227,8 @@ namespace PlexRequests.Services.Jobs
{
if (advanced)
{
if (show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
if (!string.IsNullOrEmpty(show.ProviderId) &&
show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}

View file

@ -0,0 +1,119 @@
#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
{
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; }
private static Logger Log = LogManager.GetCurrentClassLogger();
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);
}
}
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);
}
public void AlbumLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.AlbumWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Album);
}
private void CheckAndDelete(IEnumerable<RequestLimit> allUsers, RequestType type)
{
var users = allUsers.Where(x => x.RequestType == type);
foreach (var u in users)
{
if (u.FirstRequestDate > DateTime.UtcNow.AddDays(-7))
{
Repo.Delete(u);
}
}
}
}
}

View file

@ -1,201 +1,203 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EmailMessageNotification.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 MailKit.Security;
using MimeKit;
using NLog;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
namespace PlexRequests.Services.Notification
{
public class EmailMessageNotification : INotification
{
public EmailMessageNotification(ISettingsService<EmailNotificationSettings> settings)
{
EmailNotificationSettings = settings;
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
public string NotificationName => "EmailMessageNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetConfiguration();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var emailSettings = (EmailNotificationSettings)settings;
if (!ValidateConfiguration(emailSettings))
{
return;
}
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await EmailNewRequest(model, emailSettings);
break;
case NotificationType.Issue:
await EmailIssue(model, emailSettings);
break;
case NotificationType.RequestAvailable:
await EmailAvailableRequest(model, emailSettings);
break;
case NotificationType.RequestApproved:
throw new NotImplementedException();
case NotificationType.AdminNote:
throw new NotImplementedException();
case NotificationType.Test:
await EmailTest(model, emailSettings);
break;
}
}
private EmailNotificationSettings GetConfiguration()
{
var settings = EmailNotificationSettings.GetSettings();
return settings;
}
private bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
{
return false;
}
if (!settings.EnableUserEmailNotifications)
{
if (!settings.Enabled)
{
return false;
}
}
return true;
}
private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" },
Subject = $"Plex Requests: New request for {model.Title}!"
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
await Send(message, settings);
}
private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" },
Subject = $"Plex Requests: New issue for {model.Title}!"
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
await Send(message, settings);
}
private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings)
{
if (!settings.EnableUserEmailNotifications)
{
await Task.FromResult(false);
}
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" },
Subject = $"Plex Requests: {model.Title} is now available!"
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail));
await Send(message, settings);
}
private async Task Send(MimeMessage message, EmailNotificationSettings settings)
{
try
{
using (var client = new SmtpClient())
{
client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions.
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(settings.EmailUsername, settings.EmailPassword);
await client.SendAsync(message);
await client.DisconnectAsync(true);
}
}
catch (Exception e)
{
Log.Error(e);
}
}
private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = "This is just a test! Success!" },
Subject = "Plex Requests: Test Message!",
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
await Send(message, settings);
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EmailMessageNotification.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 MailKit.Security;
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;
using PlexRequests.Store;
namespace PlexRequests.Services.Notification
{
public class EmailMessageNotification : INotification
{
public EmailMessageNotification(ISettingsService<EmailNotificationSettings> settings)
{
EmailNotificationSettings = settings;
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
public string NotificationName => "EmailMessageNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetConfiguration();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var emailSettings = (EmailNotificationSettings)settings;
if (!ValidateConfiguration(emailSettings))
{
return;
}
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await EmailNewRequest(model, emailSettings);
break;
case NotificationType.Issue:
await EmailIssue(model, emailSettings);
break;
case NotificationType.RequestAvailable:
await EmailAvailableRequest(model, emailSettings);
break;
case NotificationType.RequestApproved:
throw new NotImplementedException();
case NotificationType.AdminNote:
throw new NotImplementedException();
case NotificationType.Test:
await EmailTest(model, emailSettings);
break;
}
}
private EmailNotificationSettings GetConfiguration()
{
var settings = EmailNotificationSettings.GetSettings();
return settings;
}
private bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
{
return false;
}
if (!settings.EnableUserEmailNotifications)
{
if (!settings.Enabled)
{
return false;
}
}
return true;
}
private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MimeMessage
{
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));
await Send(message, settings);
}
private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" },
Subject = $"Plex Requests: New issue for {model.Title}!"
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
await Send(message, settings);
}
private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings)
{
if (!settings.EnableUserEmailNotifications)
{
await Task.FromResult(false);
}
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" },
Subject = $"Plex Requests: {model.Title} is now available!"
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail));
await Send(message, settings);
}
private async Task Send(MimeMessage message, EmailNotificationSettings settings)
{
try
{
using (var client = new SmtpClient())
{
client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions.
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(settings.EmailUsername, settings.EmailPassword);
await client.SendAsync(message);
await client.DisconnectAsync(true);
}
}
catch (Exception e)
{
Log.Error(e);
}
}
private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MimeMessage
{
Body = new TextPart("plain") { Text = "This is just a test! Success!" },
Subject = "Plex Requests: Test Message!",
};
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
await Send(message, settings);
}
}
}

View file

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

View file

@ -1,142 +1,144 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
namespace PlexRequests.Services.Notification
{
public class PushbulletNotification : INotification
{
public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService<PushbulletNotificationSettings> settings)
{
PushbulletApi = pushbulletApi;
SettingsService = settings;
}
private IPushbulletApi PushbulletApi { get; }
private ISettingsService<PushbulletNotificationSettings> SettingsService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public string NotificationName => "PushbulletNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetSettings();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var pushSettings = (PushbulletNotificationSettings)settings;
if (!ValidateConfiguration(pushSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await PushNewRequestAsync(model, pushSettings);
break;
case NotificationType.Issue:
await PushIssueAsync(model, pushSettings);
break;
case NotificationType.RequestAvailable:
break;
case NotificationType.RequestApproved:
break;
case NotificationType.AdminNote:
break;
case NotificationType.Test:
await PushTestAsync(pushSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ValidateConfiguration(PushbulletNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.AccessToken))
{
return false;
}
return true;
}
private PushbulletNotificationSettings GetSettings()
{
return SettingsService.GetSettings();
}
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = $"{model.Title} has been requested by user: {model.User}";
var pushTitle = $"Plex Requests: {model.Title} has been requested!";
await Push(settings, message, pushTitle);
}
private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
await Push(settings, message, pushTitle);
}
private async Task PushTestAsync(PushbulletNotificationSettings settings)
{
var message = "This is just a test! Success!";
var pushTitle = "Plex Requests: Test Message!";
await Push(settings, message, pushTitle);
}
private async Task Push(PushbulletNotificationSettings settings, string message, string title)
{
try
{
var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier);
if (result != null)
{
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
namespace PlexRequests.Services.Notification
{
public class PushbulletNotification : INotification
{
public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService<PushbulletNotificationSettings> settings)
{
PushbulletApi = pushbulletApi;
SettingsService = settings;
}
private IPushbulletApi PushbulletApi { get; }
private ISettingsService<PushbulletNotificationSettings> SettingsService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public string NotificationName => "PushbulletNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetSettings();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var pushSettings = (PushbulletNotificationSettings)settings;
if (!ValidateConfiguration(pushSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await PushNewRequestAsync(model, pushSettings);
break;
case NotificationType.Issue:
await PushIssueAsync(model, pushSettings);
break;
case NotificationType.RequestAvailable:
break;
case NotificationType.RequestApproved:
break;
case NotificationType.AdminNote:
break;
case NotificationType.Test:
await PushTestAsync(pushSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ValidateConfiguration(PushbulletNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.AccessToken))
{
return false;
}
return true;
}
private PushbulletNotificationSettings GetSettings()
{
return SettingsService.GetSettings();
}
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
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);
}
private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
await Push(settings, message, pushTitle);
}
private async Task PushTestAsync(PushbulletNotificationSettings settings)
{
var message = "This is just a test! Success!";
var pushTitle = "Plex Requests: Test Message!";
await Push(settings, message, pushTitle);
}
private async Task Push(PushbulletNotificationSettings settings, string message, string title)
{
try
{
var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier);
if (result != null)
{
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
}
}

View file

@ -1,139 +1,141 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
namespace PlexRequests.Services.Notification
{
public class PushoverNotification : INotification
{
public PushoverNotification(IPushoverApi pushoverApi, ISettingsService<PushoverNotificationSettings> settings)
{
PushoverApi = pushoverApi;
SettingsService = settings;
}
private IPushoverApi PushoverApi { get; }
private ISettingsService<PushoverNotificationSettings> SettingsService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public string NotificationName => "PushoverNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetSettings();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var pushSettings = (PushoverNotificationSettings)settings;
if (!ValidateConfiguration(pushSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await PushNewRequestAsync(model, pushSettings);
break;
case NotificationType.Issue:
await PushIssueAsync(model, pushSettings);
break;
case NotificationType.RequestAvailable:
break;
case NotificationType.RequestApproved:
break;
case NotificationType.AdminNote:
break;
case NotificationType.Test:
await PushTestAsync(model, pushSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ValidateConfiguration(PushoverNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken))
{
return false;
}
return true;
}
private PushoverNotificationSettings GetSettings()
{
return SettingsService.GetSettings();
}
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}";
await Push(settings, message);
}
private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
await Push(settings, message);
}
private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: Test Message!";
await Push(settings, message);
}
private async Task Push(PushoverNotificationSettings settings, string message)
{
try
{
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
if (result?.status != 1)
{
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store;
namespace PlexRequests.Services.Notification
{
public class PushoverNotification : INotification
{
public PushoverNotification(IPushoverApi pushoverApi, ISettingsService<PushoverNotificationSettings> settings)
{
PushoverApi = pushoverApi;
SettingsService = settings;
}
private IPushoverApi PushoverApi { get; }
private ISettingsService<PushoverNotificationSettings> SettingsService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public string NotificationName => "PushoverNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetSettings();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var pushSettings = (PushoverNotificationSettings)settings;
if (!ValidateConfiguration(pushSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await PushNewRequestAsync(model, pushSettings);
break;
case NotificationType.Issue:
await PushIssueAsync(model, pushSettings);
break;
case NotificationType.RequestAvailable:
break;
case NotificationType.RequestApproved:
break;
case NotificationType.AdminNote:
break;
case NotificationType.Test:
await PushTestAsync(model, pushSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ValidateConfiguration(PushoverNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken))
{
return false;
}
return true;
}
private PushoverNotificationSettings GetSettings()
{
return SettingsService.GetSettings();
}
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
await Push(settings, message);
}
private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
await Push(settings, message);
}
private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: Test Message!";
await Push(settings, message);
}
private async Task Push(PushoverNotificationSettings settings, string message)
{
try
{
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
if (result?.status != 1)
{
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,90 +1,108 @@
using System;
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace PlexRequests.Store
{
[Table("Requested")]
public class RequestedModel : Entity
{
public RequestedModel()
{
RequestedUsers = new List<string>();
}
// ReSharper disable once IdentifierTypo
public int ProviderId { get; set; }
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public string Overview { get; set; }
public string Title { get; set; }
public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
[Obsolete("Use RequestedUsers")]
public string RequestedBy { get; set; }
public DateTime RequestedDate { get; set; }
public bool Available { get; set; }
public IssueState Issues { get; set; }
public string OtherMessage { get; set; }
public string AdminNote { get; set; }
public int[] SeasonList { get; set; }
public int SeasonCount { get; set; }
public string SeasonsRequested { get; set; }
public string MusicBrainzId { get; set; }
public List<string> RequestedUsers { get; set; }
public string ArtistName { get; set; }
public string ArtistId { get; set; }
public int IssueId { get; set; }
[JsonIgnore]
public List<string> AllUsers
{
get
{
var u = new List<string>();
if (!string.IsNullOrEmpty(RequestedBy))
{
u.Add(RequestedBy);
}
if (RequestedUsers.Any())
{
u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy));
}
return u;
}
}
[JsonIgnore]
public bool CanApprove => !Approved && !Available;
public bool UserHasRequested(string username)
{
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));
}
}
public enum RequestType
{
Movie,
TvShow,
Album
}
public enum IssueState
{
None = 99,
WrongAudio = 0,
NoSubtitles = 1,
WrongContent = 2,
PlaybackIssues = 3,
Other = 4, // Provide a message
}
}
using System;
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace PlexRequests.Store
{
[Table("Requested")]
public class RequestedModel : Entity
{
public RequestedModel()
{
RequestedUsers = new List<string>();
}
// ReSharper disable once IdentifierTypo
public int ProviderId { get; set; }
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public string Overview { get; set; }
public string Title { get; set; }
public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
[Obsolete("Use RequestedUsers")]
public string RequestedBy { get; set; }
public DateTime RequestedDate { get; set; }
public bool Available { get; set; }
public IssueState Issues { get; set; }
public string OtherMessage { get; set; }
public string AdminNote { get; set; }
public int[] SeasonList { get; set; }
public int SeasonCount { get; set; }
public string SeasonsRequested { get; set; }
public string MusicBrainzId { get; set; }
public List<string> RequestedUsers { get; set; }
public string ArtistName { get; set; }
public string ArtistId { get; set; }
public int IssueId { get; set; }
[JsonIgnore]
public List<string> AllUsers
{
get
{
var u = new List<string>();
if (!string.IsNullOrEmpty(RequestedBy))
{
u.Add(RequestedBy);
}
if (RequestedUsers.Any())
{
u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy));
}
return u;
}
}
[JsonIgnore]
public bool CanApprove => !Approved && !Available;
public bool UserHasRequested(string username)
{
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));
}
}
public enum RequestType
{
Movie,
TvShow,
Album
}
public static class RequestTypeDisplay
{
public static string GetString(this RequestType type)
{
switch (type)
{
case RequestType.Movie:
return "Movie";
case RequestType.TvShow:
return "TV Show";
case RequestType.Album:
return "Album";
default:
return string.Empty;
}
}
}
public enum IssueState
{
None = 99,
WrongAudio = 0,
NoSubtitles = 1,
WrongContent = 2,
PlaybackIssues = 3,
Other = 4, // Provide a message
}
}

View file

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

View file

@ -1,361 +1,365 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModuleTests.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 Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
using PlexRequests.Helpers;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class AdminModuleTests
{
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<ISettingsService<CouchPotatoSettings>> CpMock { get; set; }
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
private Mock<ISettingsService<PlexSettings>> PlexSettingsMock { get; set; }
private Mock<ISettingsService<SonarrSettings>> SonarrSettingsMock { get; set; }
private Mock<ISettingsService<SickRageSettings>> SickRageSettingsMock { get; set; }
private Mock<ISettingsService<ScheduledJobsSettings>> ScheduledJobsSettingsMock { get; set; }
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
private Mock<ISettingsService<PushbulletNotificationSettings>> PushbulletSettings { get; set; }
private Mock<ISettingsService<PushoverNotificationSettings>> PushoverSettings { get; set; }
private Mock<ISettingsService<HeadphonesSettings>> HeadphonesSettings { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
private Mock<ISonarrApi> SonarrApiMock { get; set; }
private Mock<IPushbulletApi> PushbulletApi { get; set; }
private Mock<IPushoverApi> PushoverApi { get; set; }
private Mock<ICouchPotatoApi> CpApi { get; set; }
private Mock<IJobRecord> RecorderMock { get; set; }
private Mock<IRepository<LogEntity>> LogRepo { get; set; }
private Mock<INotificationService> NotificationService { get; set; }
private Mock<ICacheProvider> Cache { get; set; }
private Mock<ISettingsService<LogSettings>> Log { get; set; }
private Mock<ISettingsService<SlackNotificationSettings>> SlackSettings { get; set; }
private Mock<ISettingsService<LandingPageSettings>> LandingPageSettings { get; set; }
private Mock<ISlackApi> SlackApi { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
[SetUp]
public void Setup()
{
AuthMock = new Mock<ISettingsService<AuthenticationSettings>>();
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock = new Mock<IPlexApi>();
PlexMock.Setup(x => x.SignIn("Username1", "Password1"))
.Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } });
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
CpMock = new Mock<ISettingsService<CouchPotatoSettings>>();
PlexSettingsMock = new Mock<ISettingsService<PlexSettings>>();
SonarrApiMock = new Mock<ISonarrApi>();
SonarrSettingsMock = new Mock<ISettingsService<SonarrSettings>>();
EmailMock = new Mock<ISettingsService<EmailNotificationSettings>>();
PushbulletApi = new Mock<IPushbulletApi>();
PushbulletSettings = new Mock<ISettingsService<PushbulletNotificationSettings>>();
CpApi = new Mock<ICouchPotatoApi>();
SickRageSettingsMock = new Mock<ISettingsService<SickRageSettings>>();
LogRepo = new Mock<IRepository<LogEntity>>();
PushoverSettings = new Mock<ISettingsService<PushoverNotificationSettings>>();
PushoverApi = new Mock<IPushoverApi>();
NotificationService = new Mock<INotificationService>();
HeadphonesSettings = new Mock<ISettingsService<HeadphonesSettings>>();
Cache = new Mock<ICacheProvider>();
Log = new Mock<ISettingsService<LogSettings>>();
SlackApi = new Mock<ISlackApi>();
SlackSettings = new Mock<ISettingsService<SlackNotificationSettings>>();
LandingPageSettings = new Mock<ISettingsService<LandingPageSettings>>();
ScheduledJobsSettingsMock = new Mock<ISettingsService<ScheduledJobsSettings>>();
RecorderMock = new Mock<IJobRecord>();
Bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<AdminModule>();
with.Dependency(AuthMock.Object);
with.Dependency(PlexRequestMock.Object);
with.Dependency(CpMock.Object);
with.Dependency(PlexSettingsMock.Object);
with.Dependency(SonarrApiMock.Object);
with.Dependency(SonarrSettingsMock.Object);
with.Dependency(PlexMock.Object);
with.Dependency(EmailMock.Object);
with.Dependency(PushbulletApi.Object);
with.Dependency(PushbulletSettings.Object);
with.Dependency(CpApi.Object);
with.Dependency(SickRageSettingsMock.Object);
with.Dependency(LogRepo.Object);
with.Dependency(PushoverSettings.Object);
with.Dependency(PushoverApi.Object);
with.Dependency(NotificationService.Object);
with.Dependency(HeadphonesSettings.Object);
with.Dependency(Cache.Object);
with.Dependency(Log.Object);
with.Dependency(SlackApi.Object);
with.Dependency(LandingPageSettings.Object);
with.Dependency(SlackSettings.Object);
with.Dependency(ScheduledJobsSettingsMock.Object);
with.Dependency(RecorderMock.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup((container, pipelines, context) =>
{
context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List<string> {"Admin"} };
});
});
Bootstrapper.WithSession(new Dictionary<string, object>());
}
[Test]
public void RequestAuthTokenTestNewSettings()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Once);
}
[Test]
public void RequestAuthTokenTestEmptyCredentials()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", string.Empty);
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Never);
AuthMock.Verify(x => x.GetSettings(), Times.Never);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Never);
}
[Test]
public void RequestAuthTokenTesPlexSignInFail()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Badusername");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
PlexMock.Verify(x => x.SignIn("Badusername", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Never);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Never);
}
[Test]
public void RequestAuthTokenTestExistingSettings()
{
AuthMock.Setup(x => x.GetSettings()).Returns(() => null);
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Once);
}
[Test]
public void GetUsersSuccessfully()
{
var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } };
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JObject>(result.Body.AsString());
var user = body["users"];
Assert.That(body, Is.Not.Null);
Assert.That(user.ToString().Contains("abc"), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersReturnsNoUsers()
{
var users = new PlexFriends();
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersReturnsNull()
{
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(() => null);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersTokenIsNull()
{
AuthMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings());
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JObject>(result.Body.AsString());
var user = (string)body["users"];
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(user), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModuleTests.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 Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Services.Interfaces;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class AdminModuleTests
{
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<ISettingsService<CouchPotatoSettings>> CpMock { get; set; }
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
private Mock<ISettingsService<PlexSettings>> PlexSettingsMock { get; set; }
private Mock<ISettingsService<SonarrSettings>> SonarrSettingsMock { get; set; }
private Mock<ISettingsService<SickRageSettings>> SickRageSettingsMock { get; set; }
private Mock<ISettingsService<ScheduledJobsSettings>> ScheduledJobsSettingsMock { get; set; }
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
private Mock<ISettingsService<PushbulletNotificationSettings>> PushbulletSettings { get; set; }
private Mock<ISettingsService<PushoverNotificationSettings>> PushoverSettings { get; set; }
private Mock<ISettingsService<HeadphonesSettings>> HeadphonesSettings { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
private Mock<ISonarrApi> SonarrApiMock { get; set; }
private Mock<IPushbulletApi> PushbulletApi { get; set; }
private Mock<IPushoverApi> PushoverApi { get; set; }
private Mock<ICouchPotatoApi> CpApi { get; set; }
private Mock<IJobRecord> RecorderMock { get; set; }
private Mock<IRepository<LogEntity>> LogRepo { get; set; }
private Mock<INotificationService> NotificationService { get; set; }
private Mock<ICacheProvider> Cache { get; set; }
private Mock<ISettingsService<LogSettings>> Log { get; set; }
private Mock<ISettingsService<SlackNotificationSettings>> SlackSettings { get; set; }
private Mock<ISettingsService<LandingPageSettings>> LandingPageSettings { get; set; }
private Mock<ISlackApi> SlackApi { get; set; }
private Mock<IAnalytics> IAnalytics { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
[SetUp]
public void Setup()
{
AuthMock = new Mock<ISettingsService<AuthenticationSettings>>();
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock = new Mock<IPlexApi>();
PlexMock.Setup(x => x.SignIn("Username1", "Password1"))
.Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } });
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
CpMock = new Mock<ISettingsService<CouchPotatoSettings>>();
PlexSettingsMock = new Mock<ISettingsService<PlexSettings>>();
SonarrApiMock = new Mock<ISonarrApi>();
SonarrSettingsMock = new Mock<ISettingsService<SonarrSettings>>();
EmailMock = new Mock<ISettingsService<EmailNotificationSettings>>();
PushbulletApi = new Mock<IPushbulletApi>();
PushbulletSettings = new Mock<ISettingsService<PushbulletNotificationSettings>>();
CpApi = new Mock<ICouchPotatoApi>();
SickRageSettingsMock = new Mock<ISettingsService<SickRageSettings>>();
LogRepo = new Mock<IRepository<LogEntity>>();
PushoverSettings = new Mock<ISettingsService<PushoverNotificationSettings>>();
PushoverApi = new Mock<IPushoverApi>();
NotificationService = new Mock<INotificationService>();
HeadphonesSettings = new Mock<ISettingsService<HeadphonesSettings>>();
Cache = new Mock<ICacheProvider>();
Log = new Mock<ISettingsService<LogSettings>>();
SlackApi = new Mock<ISlackApi>();
SlackSettings = new Mock<ISettingsService<SlackNotificationSettings>>();
LandingPageSettings = new Mock<ISettingsService<LandingPageSettings>>();
ScheduledJobsSettingsMock = new Mock<ISettingsService<ScheduledJobsSettings>>();
RecorderMock = new Mock<IJobRecord>();
IAnalytics = new Mock<IAnalytics>();
Bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<AdminModule>();
with.Dependency(AuthMock.Object);
with.Dependency(PlexRequestMock.Object);
with.Dependency(CpMock.Object);
with.Dependency(PlexSettingsMock.Object);
with.Dependency(SonarrApiMock.Object);
with.Dependency(SonarrSettingsMock.Object);
with.Dependency(PlexMock.Object);
with.Dependency(EmailMock.Object);
with.Dependency(PushbulletApi.Object);
with.Dependency(PushbulletSettings.Object);
with.Dependency(CpApi.Object);
with.Dependency(SickRageSettingsMock.Object);
with.Dependency(LogRepo.Object);
with.Dependency(PushoverSettings.Object);
with.Dependency(PushoverApi.Object);
with.Dependency(NotificationService.Object);
with.Dependency(IAnalytics.Object);
with.Dependency(HeadphonesSettings.Object);
with.Dependency(Cache.Object);
with.Dependency(Log.Object);
with.Dependency(SlackApi.Object);
with.Dependency(LandingPageSettings.Object);
with.Dependency(SlackSettings.Object);
with.Dependency(ScheduledJobsSettingsMock.Object);
with.Dependency(RecorderMock.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup((container, pipelines, context) =>
{
context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List<string> {"Admin"} };
});
});
Bootstrapper.WithSession(new Dictionary<string, object>());
}
[Test]
public void RequestAuthTokenTestNewSettings()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Once);
}
[Test]
public void RequestAuthTokenTestEmptyCredentials()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", string.Empty);
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Never);
AuthMock.Verify(x => x.GetSettings(), Times.Never);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Never);
}
[Test]
public void RequestAuthTokenTesPlexSignInFail()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Badusername");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
PlexMock.Verify(x => x.SignIn("Badusername", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Never);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Never);
}
[Test]
public void RequestAuthTokenTestExistingSettings()
{
AuthMock.Setup(x => x.GetSettings()).Returns(() => null);
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Once);
}
[Test]
public void GetUsersSuccessfully()
{
var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } };
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JObject>(result.Body.AsString());
var user = body["users"];
Assert.That(body, Is.Not.Null);
Assert.That(user.ToString().Contains("abc"), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersReturnsNoUsers()
{
var users = new PlexFriends();
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersReturnsNull()
{
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(() => null);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersTokenIsNull()
{
AuthMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings());
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JObject>(result.Body.AsString());
var user = (string)body["users"];
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(user), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
}
}

View file

@ -1,149 +0,0 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: IssuesModuleTests.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 System.Threading.Tasks;
using Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using NUnit.Framework;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
using Ploeh.AutoFixture;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class IssuesModuleTests
{
[SetUp]
public void Setup()
{
var f = new Fixture();
ModelList = f.CreateMany<IssuesModel>();
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings()));
IssueServiceMock = new Mock<IIssueService>();
RequestServiceMock = new Mock<IRequestService>();
NotificationServiceMock = new Mock<INotificationService>();
IssueServiceMock.Setup(x => x.GetAllAsync()).Returns(Task.FromResult(ModelList));
Bootstrapper = new ConfigurableBootstrapper(
with =>
{
with.Module<IssuesModule>();
with.Dependency(PlexRequestMock.Object);
with.Dependency(IssueServiceMock.Object);
with.Dependency(RequestServiceMock.Object);
with.Dependency(NotificationServiceMock.Object);
with.RootPathProvider<TestRootPathProvider>();
});
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
}
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<IIssueService> IssueServiceMock { get; set; }
private Mock<IRequestService> RequestServiceMock { get; set; }
private Mock<INotificationService> NotificationServiceMock { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
private IEnumerable<IssuesModel> ModelList { get; set; }
private IEnumerable<IssuesModel> NonResolvedModel => ModelList.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
[Test]
public void GetIssuesNonAdminButAllCanSee()
{
var browser = new Browser(Bootstrapper);
var result = browser.Get(
"/issues/pending",
with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<List<IssuesViewModel>>(result.Body.AsString());
Assert.That(body.Count, Is.EqualTo(NonResolvedModel.Count()));
Assert.That(body[0].Title, Is.Not.Empty);
}
[Test]
public void GetIssuesForAdmin()
{
Bootstrapper = new ConfigurableBootstrapper(
with =>
{
with.Module<IssuesModule>();
with.Dependency(PlexRequestMock.Object);
with.Dependency(IssueServiceMock.Object);
with.Dependency(RequestServiceMock.Object);
with.Dependency(NotificationServiceMock.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup(
(container, pipelines, context) =>
{
context.CurrentUser = new UserIdentity() { Claims = new[] { UserClaims.Admin } };
});
});
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
var browser = new Browser(Bootstrapper);
var result = browser.Get(
"/issues/pending",
with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<List<IssuesViewModel>>(result.Body.AsString());
Assert.That(body.Count, Is.EqualTo(NonResolvedModel.Count()));
Assert.That(body[0].Title, Is.Not.Empty);
}
}
}

View file

@ -105,7 +105,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringHelperTests.cs" />
<Compile Include="TestRootPathProvider.cs" />
<Compile Include="IssuesModuleTests.cs" />
<Compile Include="UserLoginModuleTests.cs" />
<Compile Include="AdminModuleTests.cs" />
</ItemGroup>

View file

@ -1,437 +1,441 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModuleTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
using Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using NUnit.Framework;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class UserLoginModuleTests
{
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<ISettingsService<LandingPageSettings>> LandingPageMock { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
[SetUp]
public void Setup()
{
AuthMock = new Mock<ISettingsService<AuthenticationSettings>>();
PlexMock = new Mock<IPlexApi>();
LandingPageMock = new Mock<ISettingsService<LandingPageSettings>>();
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings()));
LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings());
Bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<UserLoginModule>();
with.Dependency(PlexRequestMock.Object);
with.Dependency(AuthMock.Object);
with.Dependency(PlexMock.Object);
with.Dependency(LandingPageMock.Object);
with.RootPathProvider<TestRootPathProvider>();
});
}
[Test]
public void LoginWithoutAuthentication()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void LoginWithoutAuthenticationWithEmptyUsername()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", string.Empty);
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
AuthMock.Verify(x => x.GetSettings(), Times.Never);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void LoginWithUsernameSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Title = "abc",
},
}
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithUsernameUnSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Username = "aaaa",
},
}
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithUsernameAndPasswordSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Title = "abc",
}
}
};
var plexAuth = new PlexAuthentication
{
user = new User
{
authentication_token = "abc"
}
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithUsernameAndPasswordUnSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Username = "abc",
},
}
};
var plexAuth = new PlexAuthentication
{
user = null
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void AttemptToLoginAsDeniedUser()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void Logout()
{
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
var browser = new Browser(Bootstrapper);
var result = browser.Get("/userlogin/logout", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
});
Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
}
[Test]
public void LoginWithOwnerUsernameSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends()
}
};
var account = new PlexAccount { Username = "Jamie" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } });
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "Jamie");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithOwnerUsernameAndPasswordSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends()
}
};
var plexAuth = new PlexAuthentication
{
user = new User
{
authentication_token = "abc",
username = "Jamie"
}
};
var account = new PlexAccount { Username = "Jamie" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "jamie");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModuleTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
using Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using NUnit.Framework;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class UserLoginModuleTests
{
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<ISettingsService<LandingPageSettings>> LandingPageMock { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
private Mock<IAnalytics> IAnalytics { get; set; }
[SetUp]
public void Setup()
{
AuthMock = new Mock<ISettingsService<AuthenticationSettings>>();
PlexMock = new Mock<IPlexApi>();
LandingPageMock = new Mock<ISettingsService<LandingPageSettings>>();
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings());
PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings()));
LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings());
IAnalytics = new Mock<IAnalytics>();
Bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<UserLoginModule>();
with.Dependency(PlexRequestMock.Object);
with.Dependency(AuthMock.Object);
with.Dependency(PlexMock.Object);
with.Dependency(LandingPageMock.Object);
with.Dependency(IAnalytics.Object);
with.RootPathProvider<TestRootPathProvider>();
});
}
[Test]
public void LoginWithoutAuthentication()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void LoginWithoutAuthenticationWithEmptyUsername()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", string.Empty);
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
AuthMock.Verify(x => x.GetSettings(), Times.Never);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void LoginWithUsernameSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Title = "abc",
},
}
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithUsernameUnSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Username = "aaaa",
},
}
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithUsernameAndPasswordSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Title = "abc",
}
}
};
var plexAuth = new PlexAuthentication
{
user = new User
{
authentication_token = "abc"
}
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(new PlexAccount());
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithUsernameAndPasswordUnSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends
{
Username = "abc",
},
}
};
var plexAuth = new PlexAuthentication
{
user = null
};
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void AttemptToLoginAsDeniedUser()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test]
public void Logout()
{
Bootstrapper.WithSession(new Dictionary<string, object> { { SessionKeys.UsernameKey, "abc" } });
var browser = new Browser(Bootstrapper);
var result = browser.Get("/userlogin/logout", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
});
Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null);
}
[Test]
public void LoginWithOwnerUsernameSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends()
}
};
var account = new PlexAccount { Username = "Jamie" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } });
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "Jamie");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
}
[Test]
public void LoginWithOwnerUsernameAndPasswordSuccessfully()
{
var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" };
var plexFriends = new PlexFriends
{
User = new[]
{
new UserFriends()
}
};
var plexAuth = new PlexAuthentication
{
user = new User
{
authentication_token = "abc",
username = "Jamie"
}
};
var account = new PlexAccount { Username = "Jamie" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(plexFriends);
PlexMock.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(plexAuth);
PlexMock.Setup(x => x.GetAccount(It.IsAny<string>())).Returns(account);
Bootstrapper.WithSession(new Dictionary<string, object>());
var browser = new Browser(Bootstrapper);
var result = browser.Post("/userlogin", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("Username", "jamie");
with.FormValue("Password", "abc");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie"));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
AuthMock.Verify(x => x.GetSettings(), Times.Once);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
}
}

View file

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

View file

@ -1,229 +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.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>();
// Settings
container.RegisterSetting<EmailNotificationSettings>();
container.RegisterSetting<PushbulletNotificationSettings>();
container.RegisterSetting<PushoverNotificationSettings>();
container.RegisterSetting<SlackNotificationSettings>();
container.RegisterSetting<ScheduledJobsSettings>();
container.RegisterSetting<PlexRequestSettings>();
container.RegisterSetting<CouchPotatoSettings>();
container.RegisterSetting<AuthenticationSettings>();
container.RegisterSetting<PlexSettings>();
container.RegisterSetting<SonarrSettings>();
container.RegisterSetting<SickRageSettings>();
container.RegisterSetting<HeadphonesSettings>();
container.RegisterSetting<LogSettings>();
container.RegisterSetting<LandingPageSettings>();
// Repository
container.RegisterRepo<LogEntity>();
container.RegisterRepo<UsersToNotify>();
container.RegisterRepo<ScheduledJobs>();
container.RegisterRepo<IssueBlobs>();
// Notification Service
container.Register<INotificationService, NotificationService>().AsSingleton();
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);
}
}
}

View file

@ -27,19 +27,43 @@ $(".theNoteSaveButton").click(function (e) {
});
});
// Update the note modal
$('#noteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var id = button.data('identifier'); // Extract info from data-* attributes
$('#noteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var id = button.data('identifier'); // Extract info from data-* attributes
var issue = button.data('issue');
var modal = $(this);
modal.find('.theNoteSaveButton').val(id); // Add ID to the button
var requestField = modal.find('.noteId');
requestField.val(id); // Add ID to the hidden field
var modal = $(this);
modal.find('.theNoteSaveButton').val(id); // Add ID to the button
var requestField = modal.find('.noteId');
requestField.val(id); // Add ID to the hidden field
var noteType = modal.find('.issue');
noteType.val(issue);
});
$('.delete').click(function(e) {
e.preventDefault();
var url = createBaseUrl(base, "/issues");
var $form = $("#removeForm");
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
data: $form.serialize(),
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) { window.location.replace(url); }
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});

View file

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

View file

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

View file

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

View file

@ -1,99 +1,100 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: TvSender.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.SickRage;
using PlexRequests.Api.Models.Sonarr;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
using System.Linq;
namespace PlexRequests.UI.Helpers
{
public class TvSender
{
public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi)
{
SonarrApi = sonarrApi;
SickrageApi = srApi;
}
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickrageApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model)
{
return SendToSonarr(sonarrSettings, model, string.Empty);
}
public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId)
{
var qualityProfile = 0;
if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
{
if (!int.TryParse(qualityId, out qualityProfile))
{
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
}
}
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey,
sonarrSettings.FullUri);
Log.Trace("Sonarr Add Result: ");
Log.Trace(result.DumpJson());
return result;
}
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model)
{
return SendToSickRage(sickRageSettings, model, sickRageSettings.QualityProfile);
}
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model, string qualityId)
{
Log.Info("Sending to SickRage {0}", model.Title);
if (!sickRageSettings.Qualities.Any(x => x.Key == qualityId))
{
qualityId = sickRageSettings.QualityProfile;
}
var apiResult = SickrageApi.AddSeries(model.ProviderId, model.SeasonCount, model.SeasonList, qualityId,
sickRageSettings.ApiKey, sickRageSettings.FullUri);
var result = apiResult.Result;
return result;
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: TvSender.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.SickRage;
using PlexRequests.Api.Models.Sonarr;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Store;
using System.Linq;
namespace PlexRequests.UI.Helpers
{
public class TvSender
{
public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi)
{
SonarrApi = sonarrApi;
SickrageApi = srApi;
}
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickrageApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model)
{
return SendToSonarr(sonarrSettings, model, string.Empty);
}
public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId)
{
var qualityProfile = 0;
if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
{
int.TryParse(qualityId, out qualityProfile);
}
if (qualityProfile <= 0)
{
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
}
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey,
sonarrSettings.FullUri);
Log.Trace("Sonarr Add Result: ");
Log.Trace(result.DumpJson());
return result;
}
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model)
{
return SendToSickRage(sickRageSettings, model, sickRageSettings.QualityProfile);
}
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model, string qualityId)
{
Log.Info("Sending to SickRage {0}", model.Title);
if (!sickRageSettings.Qualities.Any(x => x.Key == qualityId))
{
qualityId = sickRageSettings.QualityProfile;
}
var apiResult = SickrageApi.AddSeries(model.ProviderId, model.SeasonCount, model.SeasonList, qualityId,
sickRageSettings.ApiKey, sickRageSettings.FullUri);
var result = apiResult.Result;
return result;
}
}
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,68 +1,78 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseModule.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 PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.UI.Models;
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;
var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl;
ModulePath = modulePath;
}
protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService)
{
var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl;
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
ModulePath = settingModulePath;
}
private int _dateTimeOffset = -1;
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseModule.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;
using Nancy;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
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;
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;
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
ModulePath = settingModulePath;
Before += (ctx) => SetCookie();
}
private int _dateTimeOffset = -1;
protected int DateTimeOffset
{
get
@ -73,7 +83,7 @@ namespace PlexRequests.UI.Modules
}
return _dateTimeOffset;
}
}
}
private string _username;
protected string Username
@ -82,12 +92,21 @@ namespace PlexRequests.UI.Modules
{
if (string.IsNullOrEmpty(_username))
{
_username = Session[SessionKeys.UsernameKey].ToString();
try
{
_username = Session[SessionKeys.UsernameKey].ToString();
}
catch (Exception)
{
return string.Empty;
}
}
return _username;
}
}
protected IDictionary<string, string> Cookies => Request?.Cookies;
protected bool IsAdmin
{
get
@ -100,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;
}
}
}

View file

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

View file

@ -48,7 +48,7 @@ namespace PlexRequests.UI.Modules
Get["/issuecount", true] = async (x, ct) => await IssueCount();
Get["/tabCount", true] = async (x, ct) => await TabCount();
Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.provierId, IssueState.Other, (string)Request.Form.commentArea);
Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea);
Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null);
@ -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();
@ -317,8 +317,6 @@ namespace PlexRequests.UI.Modules
/// Filters the issues. Checks to see if we have set <c>UsersCanViewOnlyOwnIssues</c> in the database and filters upon the user logged in and that setting.
/// </summary>
/// <param name="issues">The issues.</param>
/// <param name="showResolved">if set to <c>true</c> [show resolved].</param>
/// <returns></returns>
private async Task<IEnumerable<IssuesModel>> FilterIssuesAsync(IEnumerable<IssuesModel> issues, bool showResolved = false)
{
var settings = await PlexRequestSettings.GetSettingsAsync();
@ -364,29 +362,35 @@ namespace PlexRequests.UI.Modules
return myIssues;
}
private async Task<Negotiator> RemoveIssue(int issueId)
private async Task<Response> RemoveIssue(int issueId)
{
try
{
this.RequiresClaims(UserClaims.Admin);
var issue = await IssuesService.GetAsync(issueId);
var request = await RequestService.GetAsync(issue.RequestId);
if (request.Id > 0)
{
request.IssueId = 0; // No issue;
request.IssueId = 0; // No issue;
var result = await RequestService.UpdateRequestAsync(request);
if (result)
var result = await RequestService.UpdateRequestAsync(request);
if (result)
{
await IssuesService.DeleteIssueAsync(issueId);
}
}
else
{
await IssuesService.DeleteIssueAsync(issueId);
}
return View["Index"];
return Response.AsJson(new JsonResponseModel { Result = true });
}
catch (Exception e)
{
Log.Error(e);
return View["Index"];
return Response.AsJson(new JsonResponseModel() { Result = false, Message = "Could not delete issue! Check the logs."});
}
}

View file

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

View file

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

View file

@ -1,377 +1,379 @@
#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;
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" });
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,220 +1,236 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModule.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 System.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.Responses.Negotiation;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class UserLoginModule : BaseModule
{
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> lp) : base("userlogin", pr)
{
AuthService = auth;
LandingPageSettings = lp;
Api = api;
Get["/", true] = async (x, ct) => await Index();
Post["/"] = x => LoginUser();
Get["/logout"] = x => Logout();
}
private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<LandingPageSettings> LandingPageSettings { get; }
private IPlexApi Api { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public async Task<Negotiator> Index()
{
var query = Request.Query["landing"];
var landingCheck = (bool?)query ?? true;
if (landingCheck)
{
var landingSettings = await LandingPageSettings.GetSettingsAsync();
if (landingSettings.Enabled)
{
if (landingSettings.BeforeLogin)
{
var model = new LandingPageViewModel
{
Enabled = landingSettings.Enabled,
Id = landingSettings.Id,
EnabledNoticeTime = landingSettings.EnabledNoticeTime,
NoticeEnable = landingSettings.NoticeEnable,
NoticeEnd = landingSettings.NoticeEnd,
NoticeMessage = landingSettings.NoticeMessage,
NoticeStart = landingSettings.NoticeStart,
ContinueUrl = landingSettings.BeforeLogin ? $"userlogin" : $"search"
};
return View["Landing/Index", model];
}
}
}
var settings = await AuthService.GetSettingsAsync();
return View["Index", settings];
}
private Response LoginUser()
{
var dateTimeOffset = Request.Form.DateTimeOffset;
var username = Request.Form.username.Value;
Log.Debug("Username \"{0}\" attempting to login", username);
if (string.IsNullOrWhiteSpace(username))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
}
var authenticated = false;
var settings = AuthService.GetSettings();
if (IsUserInDeniedList(username, settings))
{
Log.Debug("User is in denied list, not allowing them to authenticate");
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
}
var password = string.Empty;
if (settings.UsePassword)
{
Log.Debug("Using password");
password = Request.Form.password.Value;
}
if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex
{
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication)Api.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
}
}
else if (settings.UserAuthentication) // Check against the users in Plex
{
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
if (CheckIfUserIsOwner(settings.PlexAuthToken, username))
{
Log.Debug("User is the account owner");
authenticated = true;
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if (!settings.UserAuthentication) // No auth, let them pass!
{
Log.Debug("No need to auth");
authenticated = true;
}
if (authenticated)
{
Log.Debug("We are authenticated! Setting session.");
// Add to the session (Used in the BaseModules)
Session[SessionKeys.UsernameKey] = (string)username;
}
Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset;
if (!authenticated)
{
return Response.AsJson(new JsonResponseModel {Result = false, Message = "Incorrect User or Password"});
}
var landingSettings = LandingPageSettings.GetSettings();
if (landingSettings.Enabled)
{
if (!landingSettings.BeforeLogin)
return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" });
}
return Response.AsJson(new JsonResponseModel {Result = true, Message = "search" });
}
private Response Logout()
{
Log.Debug("Logging Out");
if (Session[SessionKeys.UsernameKey] != null)
{
Session.Delete(SessionKeys.UsernameKey);
}
return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/userlogin"
: "~/userlogin");
}
private bool CheckIfUserIsOwner(string authToken, string userName)
{
var userAccount = Api.GetAccount(authToken);
if (userAccount == null)
{
return false;
}
return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase);
}
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{
var users = Api.GetUsers(authToken);
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title));
return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
private bool IsUserInDeniedList(string username, AuthenticationSettings settings)
{
return settings.DeniedUserList.Any(x => x.Equals(username));
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModule.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 System.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.Responses.Negotiation;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Helpers.Analytics;
using PlexRequests.UI.Models;
using Action = PlexRequests.Helpers.Analytics.Action;
namespace PlexRequests.UI.Modules
{
public class UserLoginModule : BaseModule
{
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> lp, IAnalytics a) : base("userlogin", pr)
{
AuthService = auth;
LandingPageSettings = lp;
Analytics = a;
Api = api;
Get["/", true] = async (x, ct) => await Index();
Post["/"] = x => LoginUser();
Get["/logout"] = x => Logout();
}
private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<LandingPageSettings> LandingPageSettings { get; }
private IPlexApi Api { get; }
private IAnalytics Analytics { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public async Task<Negotiator> Index()
{
var query = Request.Query["landing"];
var landingCheck = (bool?)query ?? true;
if (landingCheck)
{
var landingSettings = await LandingPageSettings.GetSettingsAsync();
if (landingSettings.Enabled)
{
if (landingSettings.BeforeLogin)
{
#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
{
Enabled = landingSettings.Enabled,
Id = landingSettings.Id,
EnabledNoticeTime = landingSettings.EnabledNoticeTime,
NoticeEnable = landingSettings.NoticeEnable,
NoticeEnd = landingSettings.NoticeEnd,
NoticeMessage = landingSettings.NoticeMessage,
NoticeStart = landingSettings.NoticeStart,
ContinueUrl = landingSettings.BeforeLogin ? $"userlogin" : $"search"
};
return View["Landing/Index", model];
}
}
}
var settings = await AuthService.GetSettingsAsync();
return View["Index", settings];
}
private Response LoginUser()
{
var dateTimeOffset = Request.Form.DateTimeOffset;
var username = Request.Form.username.Value;
Log.Debug("Username \"{0}\" attempting to login", username);
if (string.IsNullOrWhiteSpace(username))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
}
var authenticated = false;
var settings = AuthService.GetSettings();
if (IsUserInDeniedList(username, settings))
{
Log.Debug("User is in denied list, not allowing them to authenticate");
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
}
var password = string.Empty;
if (settings.UsePassword)
{
Log.Debug("Using password");
password = Request.Form.password.Value;
}
if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex
{
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication)Api.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
}
}
else if (settings.UserAuthentication) // Check against the users in Plex
{
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
if (CheckIfUserIsOwner(settings.PlexAuthToken, username))
{
Log.Debug("User is the account owner");
authenticated = true;
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if (!settings.UserAuthentication) // No auth, let them pass!
{
Log.Debug("No need to auth");
authenticated = true;
}
if (authenticated)
{
Log.Debug("We are authenticated! Setting session.");
// Add to the session (Used in the BaseModules)
Session[SessionKeys.UsernameKey] = (string)username;
}
Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset;
if (!authenticated)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
}
var landingSettings = LandingPageSettings.GetSettings();
if (landingSettings.Enabled)
{
if (!landingSettings.BeforeLogin)
return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" });
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "search" });
}
private Response Logout()
{
Log.Debug("Logging Out");
if (Session[SessionKeys.UsernameKey] != null)
{
Session.Delete(SessionKeys.UsernameKey);
}
return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/userlogin"
: "~/userlogin");
}
private bool CheckIfUserIsOwner(string authToken, string userName)
{
var userAccount = Api.GetAccount(authToken);
if (userAccount == null)
{
return false;
}
return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase);
}
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{
var users = Api.GetUsers(authToken);
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title));
return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
private bool IsUserInDeniedList(string username, AuthenticationSettings settings)
{
return settings.DeniedUserList.Any(x => x.Equals(username));
}
}
}

File diff suppressed because it is too large Load diff

919
PlexRequests.UI/Resources/UI.Designer.cs generated Normal file
View file

@ -0,0 +1,919 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PlexRequests.UI.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class UI {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal UI() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PlexRequests.UI.Resources.UI", typeof(UI).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Approve.
/// </summary>
public static string Common_Approve {
get {
return ResourceManager.GetString("Common_Approve", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approved.
/// </summary>
public static string Common_Approved {
get {
return ResourceManager.GetString("Common_Approved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close.
/// </summary>
public static string Common_Close {
get {
return ResourceManager.GetString("Common_Close", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove.
/// </summary>
public static string Common_Remove {
get {
return ResourceManager.GetString("Common_Remove", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save.
/// </summary>
public static string Common_Save {
get {
return ResourceManager.GetString("Common_Save", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Issue.
/// </summary>
public static string Issues_Issue {
get {
return ResourceManager.GetString("Issues_Issue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save Changes.
/// </summary>
public static string Issues_Modal_Save {
get {
return ResourceManager.GetString("Issues_Modal_Save", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add an issue.
/// </summary>
public static string Issues_Modal_Title {
get {
return ResourceManager.GetString("Issues_Modal_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No Subtitles.
/// </summary>
public static string Issues_NoSubs {
get {
return ResourceManager.GetString("Issues_NoSubs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Other.
/// </summary>
public static string Issues_Other {
get {
return ResourceManager.GetString("Issues_Other", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Playback Issues.
/// </summary>
public static string Issues_Playback {
get {
return ResourceManager.GetString("Issues_Playback", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Wrong Audio.
/// </summary>
public static string Issues_WrongAudio {
get {
return ResourceManager.GetString("Issues_WrongAudio", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Wrong Content.
/// </summary>
public static string Issues_WrongContent {
get {
return ResourceManager.GetString("Issues_WrongContent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Something went wrong!.
/// </summary>
public static string Javascript_SomethingWentWrong {
get {
return ResourceManager.GetString("Javascript_SomethingWentWrong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Success!.
/// </summary>
public static string Javascript_Success {
get {
return ResourceManager.GetString("Javascript_Success", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Admin.
/// </summary>
public static string Layout_Admin {
get {
return ResourceManager.GetString("Layout_Admin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Change Password.
/// </summary>
public static string Layout_ChangePassword {
get {
return ResourceManager.GetString("Layout_ChangePassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Danish.
/// </summary>
public static string Layout_Danish {
get {
return ResourceManager.GetString("Layout_Danish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Donate.
/// </summary>
public static string Layout_Donate {
get {
return ResourceManager.GetString("Layout_Donate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Dutch.
/// </summary>
public static string Layout_Dutch {
get {
return ResourceManager.GetString("Layout_Dutch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to English.
/// </summary>
public static string Layout_English {
get {
return ResourceManager.GetString("Layout_English", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to German.
/// </summary>
public static string Layout_German {
get {
return ResourceManager.GetString("Layout_German", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Issues.
/// </summary>
public static string Layout_Issues {
get {
return ResourceManager.GetString("Layout_Issues", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Italian.
/// </summary>
public static string Layout_Italian {
get {
return ResourceManager.GetString("Layout_Italian", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logout.
/// </summary>
public static string Layout_Logout {
get {
return ResourceManager.GetString("Layout_Logout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Portuguese.
/// </summary>
public static string Layout_Portuguese {
get {
return ResourceManager.GetString("Layout_Portuguese", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requests.
/// </summary>
public static string Layout_Requests {
get {
return ResourceManager.GetString("Layout_Requests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search.
/// </summary>
public static string Layout_Search {
get {
return ResourceManager.GetString("Layout_Search", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Settings.
/// </summary>
public static string Layout_Settings {
get {
return ResourceManager.GetString("Layout_Settings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Spanish.
/// </summary>
public static string Layout_Spanish {
get {
return ResourceManager.GetString("Layout_Spanish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Swedish.
/// </summary>
public static string Layout_Swedish {
get {
return ResourceManager.GetString("Layout_Swedish", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Plex Requests.
/// </summary>
public static string Layout_Title {
get {
return ResourceManager.GetString("Layout_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There is a new update available! Click.
/// </summary>
public static string Layout_UpdateAvailablePart1 {
get {
return ResourceManager.GetString("Layout_UpdateAvailablePart1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Here!.
/// </summary>
public static string Layout_UpdateAvailablePart2 {
get {
return ResourceManager.GetString("Layout_UpdateAvailablePart2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Welcome.
/// </summary>
public static string Layout_Welcome {
get {
return ResourceManager.GetString("Layout_Welcome", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Albums.
/// </summary>
public static string Requests_AlbumsTabTitle {
get {
return ResourceManager.GetString("Requests_AlbumsTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve Movies.
/// </summary>
public static string Requests_ApproveMovies {
get {
return ResourceManager.GetString("Requests_ApproveMovies", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve Music.
/// </summary>
public static string Requests_ApproveMusic {
get {
return ResourceManager.GetString("Requests_ApproveMusic", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve TV Shows.
/// </summary>
public static string Requests_ApproveTvShows {
get {
return ResourceManager.GetString("Requests_ApproveTvShows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Available.
/// </summary>
public static string Requests_Available {
get {
return ResourceManager.GetString("Requests_Available", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete Movies.
/// </summary>
public static string Requests_DeleteMovies {
get {
return ResourceManager.GetString("Requests_DeleteMovies", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete Music.
/// </summary>
public static string Requests_DeleteMusic {
get {
return ResourceManager.GetString("Requests_DeleteMusic", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete TV Shows.
/// </summary>
public static string Requests_DeleteTVShows {
get {
return ResourceManager.GetString("Requests_DeleteTVShows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Filter.
/// </summary>
public static string Requests_Filter {
get {
return ResourceManager.GetString("Requests_Filter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to All.
/// </summary>
public static string Requests_Filter_All {
get {
return ResourceManager.GetString("Requests_Filter_All", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approved.
/// </summary>
public static string Requests_Filter_Approved {
get {
return ResourceManager.GetString("Requests_Filter_Approved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Available.
/// </summary>
public static string Requests_Filter_Available {
get {
return ResourceManager.GetString("Requests_Filter_Available", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Approved.
/// </summary>
public static string Requests_Filter_NotApproved {
get {
return ResourceManager.GetString("Requests_Filter_NotApproved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Available.
/// </summary>
public static string Requests_Filter_NotAvailable {
get {
return ResourceManager.GetString("Requests_Filter_NotAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Released.
/// </summary>
public static string Requests_Filter_NotReleased {
get {
return ResourceManager.GetString("Requests_Filter_NotReleased", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Released.
/// </summary>
public static string Requests_Filter_Released {
get {
return ResourceManager.GetString("Requests_Filter_Released", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Mark Available.
/// </summary>
public static string Requests_MarkAvailable {
get {
return ResourceManager.GetString("Requests_MarkAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Mark Unavailable.
/// </summary>
public static string Requests_MarkUnavailable {
get {
return ResourceManager.GetString("Requests_MarkUnavailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Movies.
/// </summary>
public static string Requests_MoviesTabTitle {
get {
return ResourceManager.GetString("Requests_MoviesTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Order.
/// </summary>
public static string Requests_Order {
get {
return ResourceManager.GetString("Requests_Order", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Latest Releases.
/// </summary>
public static string Requests_Order_LatestReleases {
get {
return ResourceManager.GetString("Requests_Order_LatestReleases", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Latest Requests.
/// </summary>
public static string Requests_Order_LatestRequests {
get {
return ResourceManager.GetString("Requests_Order_LatestRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Oldest Releases.
/// </summary>
public static string Requests_Order_OldestReleases {
get {
return ResourceManager.GetString("Requests_Order_OldestReleases", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Oldest Requests.
/// </summary>
public static string Requests_Order_OldestRequests {
get {
return ResourceManager.GetString("Requests_Order_OldestRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Below you can see yours and all other requests, as well as their download and approval status..
/// </summary>
public static string Requests_Paragraph {
get {
return ResourceManager.GetString("Requests_Paragraph", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Release Date.
/// </summary>
public static string Requests_ReleaseDate {
get {
return ResourceManager.GetString("Requests_ReleaseDate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requested By.
/// </summary>
public static string Requests_RequestedBy {
get {
return ResourceManager.GetString("Requests_RequestedBy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requested Date.
/// </summary>
public static string Requests_RequestedDate {
get {
return ResourceManager.GetString("Requests_RequestedDate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Seasons Requested.
/// </summary>
public static string Requests_SeasonsRequested {
get {
return ResourceManager.GetString("Requests_SeasonsRequested", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requests.
/// </summary>
public static string Requests_Title {
get {
return ResourceManager.GetString("Requests_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Toggle Dropdown.
/// </summary>
public static string Requests_ToggleDropdown {
get {
return ResourceManager.GetString("Requests_ToggleDropdown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to TV Shows.
/// </summary>
public static string Requests_TvShowTabTitle {
get {
return ResourceManager.GetString("Requests_TvShowTabTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Albums.
/// </summary>
public static string Search_Albums {
get {
return ResourceManager.GetString("Search_Albums", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to All Seasons.
/// </summary>
public static string Search_AllSeasons {
get {
return ResourceManager.GetString("Search_AllSeasons", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Available.
/// </summary>
public static string Search_Available {
get {
return ResourceManager.GetString("Search_Available", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Coming Soon.
/// </summary>
public static string Search_ComingSoon {
get {
return ResourceManager.GetString("Search_ComingSoon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Country.
/// </summary>
public static string Search_Country {
get {
return ResourceManager.GetString("Search_Country", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to First Season.
/// </summary>
public static string Search_FirstSeason {
get {
return ResourceManager.GetString("Search_FirstSeason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to In Theaters.
/// </summary>
public static string Search_InTheaters {
get {
return ResourceManager.GetString("Search_InTheaters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Latest Season.
/// </summary>
public static string Search_LatestSeason {
get {
return ResourceManager.GetString("Search_LatestSeason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Seasons.
/// </summary>
public static string Search_Modal_SeasonsTitle {
get {
return ResourceManager.GetString("Search_Modal_SeasonsTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Movies.
/// </summary>
public static string Search_Movies {
get {
return ResourceManager.GetString("Search_Movies", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!.
/// </summary>
public static string Search_Paragraph {
get {
return ResourceManager.GetString("Search_Paragraph", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Report Issue.
/// </summary>
public static string Search_ReportIssue {
get {
return ResourceManager.GetString("Search_ReportIssue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Request.
/// </summary>
public static string Search_Request {
get {
return ResourceManager.GetString("Search_Request", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Requested.
/// </summary>
public static string Search_Requested {
get {
return ResourceManager.GetString("Search_Requested", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Season.
/// </summary>
public static string Search_Season {
get {
return ResourceManager.GetString("Search_Season", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select .
/// </summary>
public static string Search_SelectSeason {
get {
return ResourceManager.GetString("Search_SelectSeason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Send me a notification when items I have requested have been added.
/// </summary>
public static string Search_SendNotificationText {
get {
return ResourceManager.GetString("Search_SendNotificationText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Suggestions.
/// </summary>
public static string Search_Suggestions {
get {
return ResourceManager.GetString("Search_Suggestions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search.
/// </summary>
public static string Search_Title {
get {
return ResourceManager.GetString("Search_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Track Count.
/// </summary>
public static string Search_TrackCount {
get {
return ResourceManager.GetString("Search_TrackCount", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to TV Shows.
/// </summary>
public static string Search_TvShows {
get {
return ResourceManager.GetString("Search_TvShows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Want to watch a movie or tv show but it&apos;s not currently on Plex?
/// Login below with your Plex.tv username and password!.
/// </summary>
public static string UserLogin_Paragraph {
get {
return ResourceManager.GetString("UserLogin_Paragraph", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your login details are only used to authenticate your Plex account..
/// </summary>
public static string UserLogin_Paragraph_SpanHover {
get {
return ResourceManager.GetString("UserLogin_Paragraph_SpanHover", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password.
/// </summary>
public static string UserLogin_Password {
get {
return ResourceManager.GetString("UserLogin_Password", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sign In.
/// </summary>
public static string UserLogin_SignIn {
get {
return ResourceManager.GetString("UserLogin_SignIn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
public static string UserLogin_Title {
get {
return ResourceManager.GetString("UserLogin_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Plex.tv Username .
/// </summary>
public static string UserLogin_Username {
get {
return ResourceManager.GetString("UserLogin_Username", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username.
/// </summary>
public static string UserLogin_Username_Placeholder {
get {
return ResourceManager.GetString("UserLogin_Username_Placeholder", resourceCulture);
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,386 @@
<?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>
</root>

View file

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

View file

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

View file

@ -45,6 +45,7 @@ namespace PlexRequests.UI.Validators
RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application.");
RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application.");
RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application.");
RuleFor(x => x.BaseUrl).NotEqual("landing").WithMessage("You cannot use 'landing' as this is reserved by the application.");
}
}
}

View file

@ -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.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>

View file

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

View file

@ -1,294 +1,329 @@
@using PlexRequests.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<PlexRequests.Core.SettingModels.PlexRequestSettings>
@Html.Partial("_Sidebar")
@{
int port;
if (Model.Port == 0)
{
port = 3579;
}
else
{
port = Model.Port;
}
var baseUrl = Html.GetBaseUrl();
var formAction = "/admin";
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString() + formAction;
}
var plexTheme = string.Empty;
var originalTheme = string.Empty;
if (!string.IsNullOrEmpty(Model.ThemeName))
{
plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty;
originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty;
}
else
{
plexTheme = "selected=\"selected\"";
}
}
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Plex Request Settings</legend>
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div>
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div>
</div>
<small class="control-label">You will have to restart after changing the port.</small>
<div class="form-group">
<label for="BaseUrl" class="control-label">Base Url</label>
<div>
<input type="text" class="form-control form-control-custom " id="BaseUrl" name="BaseUrl" placeholder="Base Url" value="@Model.BaseUrl">
</div>
</div>
<small class="control-label">You will have to restart after changing the url base.</small>
<div class="form-group">
<label for="ApiKey" class="control-label">Api Key</label>
<div class="input-group">
<input type="text" disabled="disabled" class="form-control form-control-custom" id="apiKey" name="ApiKey" value="@Model.ApiKey">
<div class="input-group-addon">
<div id="refreshKey" class="fa fa-refresh" title="Reset API Key"></div>
</div>
</div>
</div>
<div class="form-group">
<label for="select" class="control-label">Theme</label>
<div id="themes">
<select class="form-control form-control-custom" id="select">
<option @plexTheme class="form-control form-control-custom" value="@Themes.PlexTheme">Plex</option>
<option @originalTheme class="form-control form-control-custom" value="@Themes.OriginalTheme">Original Blue</option>
</select>
</div>
</div>
<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>
}
</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>
}
</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>
}
</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>
</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>
}
</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>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.UsersCanViewOnlyOwnRequests)
{
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests" checked="checked">
<label for="UsersCanViewOnlyOwnRequests">Users can view their own requests only</label>
}
else
{
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests"><label for="UsersCanViewOnlyOwnRequests">Users can view their own requests only</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.UsersCanViewOnlyOwnIssues)
{
<input type="checkbox" id="UsersCanViewOnlyOwnIssues" name="UsersCanViewOnlyOwnIssues" checked="checked">
<label for="UsersCanViewOnlyOwnIssues">Users can view their own issues only</label>
}
else
{
<input type="checkbox" id="UsersCanViewOnlyOwnIssues" name="UsersCanViewOnlyOwnIssues"><label for="UsersCanViewOnlyOwnIssues">Users can view their own issues only</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.CollectAnalyticData)
{
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData" checked="checked">
<label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser used</label>
}
else
{
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData"><label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser</label>
}
</div>
</div>
<p class="form-group">A comma separated list of users whose requests do not require approval.</p>
<div class="form-group">
<label for="noApprovalUsers" class="control-label">Approval White listed Users</label>
<div>
<input type="text" class="form-control-custom form-control " id="NoApprovalUsers" name="NoApprovalUsers" placeholder="e.g. John, Bobby" value="@Model.NoApprovalUsers">
</div>
</div>
@*<div class="form-group">
<label for="WeeklyRequestLimit" class="control-label">Weekly Request Limit</label>
<div>
<label>
<input type="number" id="WeeklyRequestLimit" name="WeeklyRequestLimit" class="form-control form-control-custom " value="@Model.WeeklyRequestLimit">
</label>
</div>
</div> //TODO: Need to implement this*@
<div>
</div>
<div class="form-group">
<div>
<button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function() {
$('#save').click(function (e) {
e.preventDefault();
var theme = $("#themes option:selected").val();
var $form = $("#mainForm");
var data = $form.serialize();
data = data + "&themeName=" + theme;
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify("Success!", "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$('#refreshKey').click(function (e) {
e.preventDefault();
var base = '@Html.GetBaseUrl()';
var url = createBaseUrl(base, '/admin/createapikey');
$.ajax({
type: "post",
url: url,
dataType: "json",
success: function (response) {
if (response) {
generateNotify("Success!", "success");
$('#apiKey').val(response);
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
@using PlexRequests.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<PlexRequests.Core.SettingModels.PlexRequestSettings>
@Html.Partial("_Sidebar")
@{
int port;
if (Model.Port == 0)
{
port = 3579;
}
else
{
port = Model.Port;
}
var baseUrl = Html.GetBaseUrl();
var formAction = "/admin";
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString() + formAction;
}
var plexTheme = string.Empty;
var originalTheme = string.Empty;
if (!string.IsNullOrEmpty(Model.ThemeName))
{
plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty;
originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty;
}
else
{
plexTheme = "selected=\"selected\"";
}
}
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Plex Request Settings</legend>
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div>
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div>
</div>
<small class="control-label">You will have to restart after changing the port.</small>
<div class="form-group">
<label for="BaseUrl" class="control-label">Base Url</label>
<div>
<input type="text" class="form-control form-control-custom " id="BaseUrl" name="BaseUrl" placeholder="Base Url" value="@Model.BaseUrl">
</div>
</div>
<small class="control-label">You will have to restart after changing the url base.</small>
<div class="form-group">
<label for="ApiKey" class="control-label">Api Key</label>
<div class="input-group">
<input type="text" disabled="disabled" class="form-control form-control-custom" id="apiKey" name="ApiKey" value="@Model.ApiKey">
<div class="input-group-addon">
<div id="refreshKey" class="fa fa-refresh" title="Reset API Key"></div>
</div>
</div>
</div>
<div class="form-group">
<label for="select" class="control-label">Theme</label>
<div id="themes">
<select class="form-control form-control-custom" id="select">
<option @plexTheme class="form-control form-control-custom" value="@Themes.PlexTheme">Plex</option>
<option @originalTheme class="form-control form-control-custom" value="@Themes.OriginalTheme">Original Blue</option>
</select>
</div>
</div>
<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>
}
</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>
}
</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>
}
</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>
</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>
}
</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>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.UsersCanViewOnlyOwnRequests)
{
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests" checked="checked">
<label for="UsersCanViewOnlyOwnRequests">Users can view their own requests only</label>
}
else
{
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests"><label for="UsersCanViewOnlyOwnRequests">Users can view their own requests only</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.UsersCanViewOnlyOwnIssues)
{
<input type="checkbox" id="UsersCanViewOnlyOwnIssues" name="UsersCanViewOnlyOwnIssues" checked="checked">
<label for="UsersCanViewOnlyOwnIssues">Users can view their own issues only</label>
}
else
{
<input type="checkbox" id="UsersCanViewOnlyOwnIssues" name="UsersCanViewOnlyOwnIssues"><label for="UsersCanViewOnlyOwnIssues">Users can view their own issues only</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.IgnoreNotifyForAutoApprovedRequests)
{
<input type="checkbox" id="IgnoreNotifyForAutoApprovedRequests" name="IgnoreNotifyForAutoApprovedRequests" checked="checked">
<label for="IgnoreNotifyForAutoApprovedRequests">Do not send notifications for requests that don't require approval</label>
}
else
{
<input type="checkbox" id="IgnoreNotifyForAutoApprovedRequests" name="IgnoreNotifyForAutoApprovedRequests"><label for="IgnoreNotifyForAutoApprovedRequests">Do not send notifications for requests that don't require approval</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.CollectAnalyticData)
{
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData" checked="checked">
<label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser used</label>
}
else
{
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData"><label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser</label>
}
</div>
</div>
<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>
<input type="text" class="form-control-custom form-control " id="NoApprovalUsers" name="NoApprovalUsers" placeholder="e.g. John, Bobby" value="@Model.NoApprovalUsers">
</div>
</div>
<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="AlbumWeeklyRequestLimit" name="AlbumWeeklyRequestLimit" class="form-control form-control-custom " value="@Model.AlbumWeeklyRequestLimit">
</label>
</div>
</div>
<div>
</div>
<div class="form-group">
<div>
<button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function () {
$('#save').click(function (e) {
e.preventDefault();
var theme = $("#themes option:selected").val();
var $form = $("#mainForm");
var data = $form.serialize();
data = data + "&themeName=" + theme;
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify("Success!", "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$('#refreshKey').click(function (e) {
e.preventDefault();
var base = '@Html.GetBaseUrl()';
var url = createBaseUrl(base, '/admin/createapikey');
$.ajax({
type: "post",
url: url,
dataType: "json",
success: function (response) {
if (response) {
generateNotify("Success!", "success");
$('#apiKey').val(response);
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

View file

@ -42,7 +42,7 @@
}
@if (Model.IssueStatus == IssueStatus.ResolvedIssue)
{
<form action="@formAction/issues/remove" method="post">
<form action="@formAction/issues/remove" method="post" id="removeForm">
<input id="issueId" name="issueId" value="@Model.Id" hidden="hidden" />
<button type="submit" id="@Model.Id" class="btn btn-sm btn-danger-outline dropdown-toggle delete">Remove</button>
</form>

View file

@ -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">
@ -61,7 +61,7 @@
</div>
<script id="issue-template" type="text/x-handlebars-template">
<div>
<div id="{{id}}Template">
<div class="row">
<div class="col-md-3">
<div id="title">{{title}}</div>
@ -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 />
<form action="@formAction/issues/remove" method="post">
{{#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>

View file

@ -1,8 +1,15 @@

@using PlexRequests.UI.Helpers
@inherits PlexRequests.UI.Helpers.EmptyViewBase<PlexRequests.UI.Models.LandingPageViewModel>
<img class="landing-header" src="~/Content/images/logo.png" width="300" />
@{
var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString();
}
}
<img class="landing-header" src="@formAction/Content/images/logo.png" width="300" />
<div id="area" class="landing-block">
@if (Model.NoticeEnable && (!Model.EnabledNoticeTime || Model.NoticeActive))
{

View file

@ -1,408 +1,368 @@
@using Nancy.Security
@using PlexRequests.UI.Helpers
@{
var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString();
}
}
<div>
<h1>Requests</h1>
<h4>Below you can see yours and all other requests, as well as their download and approval status.</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>
}
</ul>
<br />
<!-- Tab panes -->
<div class="tab-content contentList">
<div class="row">
<div class="col-sm-12">
<div class="pull-right">
<div class="btn-group btn-group-separated">
@if (Context.CurrentUser.IsAuthenticated()) //TODO replace with IsAdmin
{
@if (Model.SearchForMovies)
{
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> Delete Movies</button>
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> Approve Movies</button>
}
@if (Model.SearchForTvShows)
{
<button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> Delete TV Shows</button>
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve TV Shows</button>
}
@if (Model.SearchForMusic)
{
<button id="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>
}
}
</div>
<div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
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>
</ul>
</div>
<div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
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>
</ul>
</div>
</div>
</div>
</div>
@if (Model.SearchForMovies)
{
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
</div>
}
@if (Model.SearchForTvShows)
{
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
</div>
}
@if (Model.SearchForMusic)
{
<!-- Music tab -->
<div role="tabpanel" class="tab-pane" id="MusicTab">
<br />
<br />
<!-- TV content -->
<div id="musicList">
</div>
</div>
}
</div>
</div>
<script id="search-template" type="text/x-handlebars-template">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}} released-{{released}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
<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>
<a href="http://www.imdb.com/title/{{imdb}}/" target="_blank">
<h4 class="request-title">{{title}} ({{year}})</h4>
</a>
<span class="label label-success">{{status}}</span>
</div>
<br />
<div>Release Date: {{releaseDate}}</div>
<div>
Approved:
{{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq approved true}}
<i class="fa fa-check"></i>
{{/if_eq}}
</div>
<div>
Available
{{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq available true}}
<i id="availableIcon{{requestId}}" class="fa fa-check"></i>
{{/if_eq}}
</div>
{{#if_eq type "tv"}}
<div>Seasons Requested: {{seriesRequested}}</div>
{{/if_eq}}
{{#if requestedUsers}}
<div>Requested By: {{requestedUsers}}</div>
{{/if}}
<div>Requested Date: {{requestedDate}}</div>
<div>
Issue:
{{#if_eq issueId 0}}
<i class="fa fa-times"></i>
{{else}}
<a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
{{/if_eq}}
</div>
</div>
<div class="col-sm-3 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" />
{{#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-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
{{#each qualities}}
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
{{/each}}
</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>
{{/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>
</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>
{{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>
{{/if_eq}}
</form>
{{/if_eq}}
<form method="POST" action="@formAction/issues/issue/" id="report{{requestId}}">
<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
<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>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul>
</div>
</form>
</div>
</div>
<hr />
</div>
</script>
<script id="album-template" type="text/x-handlebars-template">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
<div class="row">
<div class="col-sm-2">
{{#if posterPath}}
<img class="img-responsive" src="{{posterPath}}" width="150" alt="poster">
{{/if}}
</div>
<div class="col-sm-5 ">
<div>
<a href="https://musicbrainz.org/release/{{musicBrainzId}}" target="_blank">
<h4>
{{artist}} - {{title}}
{{#if year}}
({{year}})
{{/if}}
</h4>
</a>
<span class="label label-success">{{status}}</span>
</div>
<br />
<div>Release Date: {{releaseDate}}</div>
<div>
Approved:
{{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq approved true}}
<i class="fa fa-check"></i>
{{/if_eq}}
</div>
<div>
Available
{{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq available true}}
<i id="availableIcon{{requestId}}" class="fa fa-check"></i>
{{/if_eq}}
</div>
{{#if requestedUsers}}
<div>Requested By: {{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>
<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>
</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>
</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>
{{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>
{{/if_eq}}
</form>
{{/if_eq}}
<form method="POST" action="@formAction/issues/issue/" id="report{{requestId}}">
<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
<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>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul>
</div>
</form>
</div>
</div>
<hr />
</div>
</script>
<div class="modal fade" id="myModal">
<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 issue/comment</h4>
</div>
<form method="POST" action="@formAction/issues/issuecomment" id="commentForm">
<div class="modal-body">
<input name="requestId" class="requestId" 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>
<div class="modal fade" id="noteModal">
<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 a note</h4>
</div>
<form method="POST" action="@formAction/requests/addnote" id="noteForm">
<div class="modal-body">
<input name="requestId" class="noteId" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="noteArea" name="noteArea"></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 theNoteSaveButton" data-dismiss="modal">Save changes</button>
</div>
</form>
</div>
</div>
</div>
@Html.LoadRequestAssets()
@using Nancy.Security
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources
@{
var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
formAction = "/" + baseUrl.ToHtmlString();
}
}
<div>
<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> @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 >@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> @UI.Requests_AlbumsTabTitle</a></li>
}
</ul>
<br />
<!-- Tab panes -->
<div class="tab-content contentList">
<div class="row">
<div class="col-sm-12">
<div class="pull-right">
<div class="btn-group btn-group-separated">
@if (Context.CurrentUser.IsAuthenticated()) //TODO replace with IsAdmin
{
@if (Model.SearchForMovies)
{
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> @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> @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> @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">
@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> @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">
@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> @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>
</div>
</div>
@if (Model.SearchForMovies)
{
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
</div>
}
@if (Model.SearchForTvShows)
{
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
</div>
}
@if (Model.SearchForMusic)
{
<!-- Music tab -->
<div role="tabpanel" class="tab-pane" id="MusicTab">
<br />
<br />
<!-- TV content -->
<div id="musicList">
</div>
</div>
}
</div>
</div>
<script id="search-template" type="text/x-handlebars-template">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}} released-{{released}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
<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>
<a href="http://www.imdb.com/title/{{imdb}}/" target="_blank">
<h4 class="request-title">{{title}} ({{year}})</h4>
</a>
<span class="label label-success">{{status}}</span>
</div>
<br />
<div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
<div>
@UI.Common_Approved:
{{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq approved true}}
<i class="fa fa-check"></i>
{{/if_eq}}
</div>
<div>
@UI.Requests_Available
{{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq available true}}
<i id="availableIcon{{requestId}}" class="fa fa-check"></i>
{{/if_eq}}
</div>
{{#if_eq type "tv"}}
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
{{/if_eq}}
{{#if requestedUsers}}
<div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}}
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
<div>
@UI.Issues_Issue:
{{#if_eq issueId 0}}
<i class="fa fa-times"></i>
{{else}}
<a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
{{/if_eq}}
</div>
</div>
<div class="col-sm-3 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" />
{{#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> @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">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
{{#each qualities}}
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
{{/each}}
</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> @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> @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> @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> @UI.Requests_MarkAvailable</button>
{{/if_eq}}
</form>
{{/if_eq}}
<form method="POST" action="@formAction/issues/issue/" id="report{{requestId}}">
<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> @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="#">@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>
</div>
</div>
<hr />
</div>
</script>
<script id="album-template" type="text/x-handlebars-template">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
<div class="row">
<div class="col-sm-2">
{{#if posterPath}}
<img class="img-responsive" src="{{posterPath}}" width="150" alt="poster">
{{/if}}
</div>
<div class="col-sm-5 ">
<div>
<a href="https://musicbrainz.org/release/{{musicBrainzId}}" target="_blank">
<h4>
{{artist}} - {{title}}
{{#if year}}
({{year}})
{{/if}}
</h4>
</a>
<span class="label label-success">{{status}}</span>
</div>
<br />
<div>@UI.Requests_ReleaseDate {{releaseDate}}</div>
<div>
@UI.Common_Approved:
{{#if_eq approved false}}
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq approved true}}
<i class="fa fa-check"></i>
{{/if_eq}}
</div>
<div>
@UI.Requests_Available
{{#if_eq available false}}
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
{{/if_eq}}
{{#if_eq available true}}
<i id="availableIcon{{requestId}}" class="fa fa-check"></i>
{{/if_eq}}
</div>
{{#if requestedUsers}}
<div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}}
<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> @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> @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> @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> @UI.Requests_MarkAvailable</button>
{{/if_eq}}
</form>
{{/if_eq}}
<form method="POST" action="@formAction/issues/issue/" id="report{{requestId}}">
<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> @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="#">@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>
</div>
</div>
<hr />
</div>
</script>
<div class="modal fade" id="myModal">
<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="@formAction/issues/issuecomment" id="commentForm">
<div class="modal-body">
<input name="providerId" class="providerId" 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.Common_Save</button>
</div>
</form>
</div>
</div>
</div>
@Html.LoadRequestAssets()

View file

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

View file

@ -17,7 +17,7 @@
<title>Plex Requests</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
@Html.LoadAnalytics()
@Html.LoadAssets()
</head>
<body>

View file

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

View file

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

View file

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

View file

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

115
README.md
View file

@ -1,57 +1,58 @@
# Plex Requests .NET!
[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex)
[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open")
[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net)
[![Stories in Progress](https://badge.waffle.io/tidusjar/PlexRequests.Net.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/PlexRequests.Net)
This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy!
I wanted to write a similar application in .Net!
# Features
* Movie and TV Show searching, can't find something on Plex? Just request it!
* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports!
* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)!
* Secure authentication so you don't have to worry about those script kiddies
* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?!
* We have allowed the ability for a user to add a custom note on a request
* It automatically update the status of requests when they are available on Plex
* Slick, responsive and mobile friendly UI
* Headphones integration!
* Ability to run with a reverse proxy!
# Preview ()
![Preview](http://i.imgur.com/DgwkIsW.gif)
#Installation
Download the latest [Release](https://github.com/tidusjar/PlexRequests.Net/releases).
Extract the .zip file (Unblock if on Windows! Right Click > Properties > Unblock).
Just run `PlexRequests.exe`! (Mono compatible `mono PlexRequests.exe`)
# FAQ
Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ)
# Docker
Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile:
# Contributors
We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it!
Please feed free to submit a pull request!
# Donation
If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet)
## A massive thanks to everyone below for all their help!
[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat, [EddiYo](https://github.com/EddiYo), [SaskiFX](https://github.com/SaskiFX), [zenjabba](https://github.com/zenjabba)
## Stats
[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput)
![](http://i.imgur.com/s4nswSA.png?1)
____
[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex)
[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open")
[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net)
[![Stories in Progress](https://badge.waffle.io/tidusjar/PlexRequests.Net.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/PlexRequests.Net)
This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy!
I wanted to write a similar application in .Net!
# Features
* Movie and TV Show searching, can't find something on Plex? Just request it!
* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports!
* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)!
* Secure authentication so you don't have to worry about those script kiddies
* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?!
* We have allowed the ability for a user to add a custom note on a request
* It automatically update the status of requests when they are available on Plex
* Slick, responsive and mobile friendly UI
* Headphones integration!
* Ability to run with a reverse proxy!
# Preview
![Preview](http://i.imgur.com/DgwkIsW.gif)
#Installation
[Windows Guide!](http://www.htpcguides.com/install-plex-requests-net-windows-system-service/)
[Ubuntu Guide!](http://www.htpcguides.com/install-plex-requests-net-ubuntu-14-x/)
# FAQ
Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ)
# Docker
Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile:
# Contributors
We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it!
Please feed free to submit a pull request!
# Donation
If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet)
## A massive thanks to everyone below for all their help!
[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat, [EddiYo](https://github.com/EddiYo), [SaskiFX](https://github.com/SaskiFX), [zenjabba](https://github.com/zenjabba)
## Stats
[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput)

View file

@ -1,26 +1,26 @@
version: 1.8.{build}
configuration: Release
assembly_info:
patch: true
file: '**\AssemblyInfo.*'
assembly_version: '1.8.0'
assembly_file_version: '{version}'
assembly_informational_version: '1.8.0'
before_build:
- cmd: appveyor-retry nuget restore
build:
verbosity: minimal
after_build:
- cmd: >-
7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\
appveyor PushArtifact PlexRequests.zip
deploy:
- provider: GitHub
release: PlexRequests v$(appveyor_build_version)
auth_token:
secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp
draft: true
on:
branch: master
version: 1.8.{build}
configuration: Release
assembly_info:
patch: true
file: '**\AssemblyInfo.*'
assembly_version: '1.8.3'
assembly_file_version: '{version}'
assembly_informational_version: '1.8.3'
before_build:
- cmd: appveyor-retry nuget restore
build:
verbosity: minimal
after_build:
- cmd: >-
7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\
appveyor PushArtifact PlexRequests.zip
deploy:
- provider: GitHub
release: PlexRequests v$(appveyor_build_version)
auth_token:
secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp
draft: true
on:
branch: master