Merge branch 'dev' into patch-1

This commit is contained in:
Marius Schiffer 2017-03-15 12:15:04 +01:00 committed by GitHub
commit 0e736c0233
291 changed files with 11506 additions and 2430 deletions

View file

@ -26,6 +26,8 @@
#endregion
using Nancy.Cryptography;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
namespace Ombi.UI.Authentication
@ -47,7 +49,8 @@ namespace Ombi.UI.Authentication
/// <summary>Gets or sets the username/identifier mapper</summary>
public IUserRepository LocalUserRepository { get; set; }
public IPlexUserRepository PlexUserRepository { get; set; }
public IExternalUserRepository<PlexUsers> PlexUserRepository { get; set; }
public IExternalUserRepository<EmbyUsers> EmbyUserRepository { get; set; }
/// <summary>Gets or sets RequiresSSL property</summary>
/// <value>The flag that indicates whether SSL is required</value>

View file

@ -208,11 +208,17 @@ namespace Ombi.UI.Authentication
var plexUsers = configuration.PlexUserRepository.GetAll();
var plexUser = plexUsers.FirstOrDefault(x => Guid.Parse(x.LoginId) == userGuid);
var embyUsers = configuration.EmbyUserRepository.GetAll();
var embyUser = embyUsers.FirstOrDefault(x => Guid.Parse(x.LoginId) == userGuid);
if (plexUser != null)
{
identity.UserName = plexUser.Username;
}
if (embyUser != null)
{
identity.UserName = embyUser.Username;
}
var localUsers = configuration.LocalUserRepository.GetAll();

View file

@ -42,6 +42,8 @@ using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Store;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Ombi.UI.Authentication;
using Ombi.UI.Helpers;
@ -88,7 +90,8 @@ namespace Ombi.UI
var config = new CustomAuthenticationConfiguration
{
RedirectUrl = redirect,
PlexUserRepository = container.Get<IPlexUserRepository>(),
PlexUserRepository = container.Get<IExternalUserRepository<PlexUsers>>(),
EmbyUserRepository = container.Get<IExternalUserRepository<EmbyUsers>>(),
LocalUserRepository = container.Get<IUserRepository>()
};

View file

@ -12,7 +12,10 @@
<strong>Email Address: </strong><span ng-bind="selectedUser.emailAddress"></span>
</div>
<div>
<strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span>
<strong>User Type: </strong>
<span ng-if="selectedUser.type === 1">Local User</span>
<span ng-if="selectedUser.type === 2">Emby User</span>
<span ng-if="selectedUser.type === 3">Plex User</span>
</div>
<br />
<br />
@ -49,7 +52,7 @@
<button ng-click="updateUser()" class="btn btn-primary-outline">Update</button>
<button ng-click="deleteUser()" class="btn btn-danger-outline">Delete</button>
<button ng-show="selectedUser.type == 1" ng-click="deleteUser()" class="btn btn-danger-outline">Delete</button>
<button ng-click="closeSidebarClick()" style="float: right; margin-right: 10px;" class="btn btn-danger-outline">Close</button>
</div>
</div>

View file

@ -75,7 +75,9 @@
{{u.permissionsFormattedString}}
</td>
<td ng-hide="hideColumns">
{{u.type === 1 ? 'Local User' : 'Plex User'}}
<span ng-if="u.type === 1">Local User</span>
<span ng-if="u.type === 3">Plex User</span>
<span ng-if="u.type === 2">Emby User</span>
</td>
<td ng-hide="hideColumns" ng-bind="u.lastLoggedIn === minDate ? 'Never' : formatDate(u.lastLoggedIn)"></td>
<td>

View file

@ -20,7 +20,7 @@
$scope.searchTerm = "";
$scope.hideColumns = false;
var ReadOnlyPermission = "Read Only User";
var open = false;

View file

@ -517,3 +517,40 @@ label {
background-color: #3e3e3e;
border: 1px solid transparent; }
.wizard-heading {
text-align: center; }
.wizard-img {
width: 300px;
display: block !important;
margin: 0 auto !important; }
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; }
.pace-inactive {
display: none; }
.pace .pace-progress {
background: #df691a;
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 5px; }
.navbar-brand {
float: left;
padding: 4.5px 15px;
font-size: 19px;
line-height: 21px;
height: 40px; }
.gravatar {
border-radius: 1em; }

File diff suppressed because one or more lines are too long

View file

@ -641,4 +641,48 @@ $border-radius: 10px;
margin-bottom: -1px;
background-color: #3e3e3e;
border: 1px solid transparent;
}
}
.wizard-heading{
text-align: center;
}
.wizard-img{
width: 300px;
display: block $i;
margin: 0 auto $i;
}
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.pace-inactive {
display: none;
}
.pace .pace-progress {
background: $primary-colour;
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 5px;
}
.navbar-brand {
float: left;
padding: 4.5px 15px;
font-size: 19px;
line-height: 21px;
height: 40px;
}
.gravatar{
border-radius:1em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -1,13 +1,13 @@
/*!
* Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.5.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
@ -118,31 +118,31 @@
}
}
.fa-rotate-90 {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.fa-rotate-180 {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.fa-rotate-270 {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.fa-flip-horizontal {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.fa-flip-vertical {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
@ -1383,7 +1383,7 @@
.fa-digg:before {
content: "\f1a6";
}
.fa-pied-piper:before {
.fa-pied-piper-pp:before {
content: "\f1a7";
}
.fa-pied-piper-alt:before {
@ -1509,6 +1509,7 @@
content: "\f1ce";
}
.fa-ra:before,
.fa-resistance:before,
.fa-rebel:before {
content: "\f1d0";
}
@ -1831,6 +1832,7 @@
content: "\f23e";
}
.fa-battery-4:before,
.fa-battery:before,
.fa-battery-full:before {
content: "\f240";
}
@ -2084,3 +2086,252 @@
.fa-percent:before {
content: "\f295";
}
.fa-gitlab:before {
content: "\f296";
}
.fa-wpbeginner:before {
content: "\f297";
}
.fa-wpforms:before {
content: "\f298";
}
.fa-envira:before {
content: "\f299";
}
.fa-universal-access:before {
content: "\f29a";
}
.fa-wheelchair-alt:before {
content: "\f29b";
}
.fa-question-circle-o:before {
content: "\f29c";
}
.fa-blind:before {
content: "\f29d";
}
.fa-audio-description:before {
content: "\f29e";
}
.fa-volume-control-phone:before {
content: "\f2a0";
}
.fa-braille:before {
content: "\f2a1";
}
.fa-assistive-listening-systems:before {
content: "\f2a2";
}
.fa-asl-interpreting:before,
.fa-american-sign-language-interpreting:before {
content: "\f2a3";
}
.fa-deafness:before,
.fa-hard-of-hearing:before,
.fa-deaf:before {
content: "\f2a4";
}
.fa-glide:before {
content: "\f2a5";
}
.fa-glide-g:before {
content: "\f2a6";
}
.fa-signing:before,
.fa-sign-language:before {
content: "\f2a7";
}
.fa-low-vision:before {
content: "\f2a8";
}
.fa-viadeo:before {
content: "\f2a9";
}
.fa-viadeo-square:before {
content: "\f2aa";
}
.fa-snapchat:before {
content: "\f2ab";
}
.fa-snapchat-ghost:before {
content: "\f2ac";
}
.fa-snapchat-square:before {
content: "\f2ad";
}
.fa-pied-piper:before {
content: "\f2ae";
}
.fa-first-order:before {
content: "\f2b0";
}
.fa-yoast:before {
content: "\f2b1";
}
.fa-themeisle:before {
content: "\f2b2";
}
.fa-google-plus-circle:before,
.fa-google-plus-official:before {
content: "\f2b3";
}
.fa-fa:before,
.fa-font-awesome:before {
content: "\f2b4";
}
.fa-handshake-o:before {
content: "\f2b5";
}
.fa-envelope-open:before {
content: "\f2b6";
}
.fa-envelope-open-o:before {
content: "\f2b7";
}
.fa-linode:before {
content: "\f2b8";
}
.fa-address-book:before {
content: "\f2b9";
}
.fa-address-book-o:before {
content: "\f2ba";
}
.fa-vcard:before,
.fa-address-card:before {
content: "\f2bb";
}
.fa-vcard-o:before,
.fa-address-card-o:before {
content: "\f2bc";
}
.fa-user-circle:before {
content: "\f2bd";
}
.fa-user-circle-o:before {
content: "\f2be";
}
.fa-user-o:before {
content: "\f2c0";
}
.fa-id-badge:before {
content: "\f2c1";
}
.fa-drivers-license:before,
.fa-id-card:before {
content: "\f2c2";
}
.fa-drivers-license-o:before,
.fa-id-card-o:before {
content: "\f2c3";
}
.fa-quora:before {
content: "\f2c4";
}
.fa-free-code-camp:before {
content: "\f2c5";
}
.fa-telegram:before {
content: "\f2c6";
}
.fa-thermometer-4:before,
.fa-thermometer:before,
.fa-thermometer-full:before {
content: "\f2c7";
}
.fa-thermometer-3:before,
.fa-thermometer-three-quarters:before {
content: "\f2c8";
}
.fa-thermometer-2:before,
.fa-thermometer-half:before {
content: "\f2c9";
}
.fa-thermometer-1:before,
.fa-thermometer-quarter:before {
content: "\f2ca";
}
.fa-thermometer-0:before,
.fa-thermometer-empty:before {
content: "\f2cb";
}
.fa-shower:before {
content: "\f2cc";
}
.fa-bathtub:before,
.fa-s15:before,
.fa-bath:before {
content: "\f2cd";
}
.fa-podcast:before {
content: "\f2ce";
}
.fa-window-maximize:before {
content: "\f2d0";
}
.fa-window-minimize:before {
content: "\f2d1";
}
.fa-window-restore:before {
content: "\f2d2";
}
.fa-times-rectangle:before,
.fa-window-close:before {
content: "\f2d3";
}
.fa-times-rectangle-o:before,
.fa-window-close-o:before {
content: "\f2d4";
}
.fa-bandcamp:before {
content: "\f2d5";
}
.fa-grav:before {
content: "\f2d6";
}
.fa-etsy:before {
content: "\f2d7";
}
.fa-imdb:before {
content: "\f2d8";
}
.fa-ravelry:before {
content: "\f2d9";
}
.fa-eercast:before {
content: "\f2da";
}
.fa-microchip:before {
content: "\f2db";
}
.fa-snowflake-o:before {
content: "\f2dc";
}
.fa-superpowers:before {
content: "\f2dd";
}
.fa-wpexplorer:before {
content: "\f2de";
}
.fa-meetup:before {
content: "\f2e0";
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.sr-only-focusable:active,
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -95,7 +95,10 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
//if ($tvl.mixItUp('isLoaded')) $tvl.mixItUp('destroy');
//$tvl.mixItUp(mixItUpConfig(activeState)); // init or reinit
}
if (target === "#MoviesTab") {
if (target === "#MoviesTab" || target === "#ActorsTab") {
if (target === "#ActorsTab") {
actorLoad();
}
$('#approveMovies,#deleteMovies').show();
if ($tvl.mixItUp('isLoaded')) {
activeState = $tvl.mixItUp('getState');
@ -564,16 +567,21 @@ $(document).on("click", ".change-root-folder", function (e) {
e.preventDefault();
var $this = $(this);
var $button = $this.parents('.btn-split').children('.change').first();
var rootFolderId = e.target.id
var rootFolderId = e.target.id;
var $form = $this.parents('form').first();
var requestId = $button.attr('id');
if ($button.text() === " Loading...") {
return;
}
loadingButton($button.attr('id'), "success");
loadingButton(requestId, "success");
changeRootFolder($form, rootFolderId, function () {
if ($('#' + requestId + "rootPathMain").length) {
$('#' + requestId + "currentRootPath").text($this.text);
}
});
});
@ -733,6 +741,37 @@ function initLoad() {
}
function actorLoad() {
var $ml = $('#actorMovieList');
if ($ml.mixItUp('isLoaded')) {
activeState = $ml.mixItUp('getState');
$ml.mixItUp('destroy');
}
$ml.html("");
var $newOnly = $('#searchNewOnly').val();
var url = createBaseUrl(base, '/requests/actor' + (!!$newOnly ? '/new' : ''));
$.ajax(url).success(function (results) {
if (results.length > 0) {
results.forEach(function (result) {
var context = buildRequestContext(result, "movie");
var html = searchTemplate(context);
$ml.append(html);
});
$('.customTooltip').tooltipster({
contentCloning: true
});
}
else {
$ml.html(noResultsHtml.format("movie"));
}
$ml.mixItUp(mixItUpConfig());
});
};
function movieLoad() {
var $ml = $('#movieList');
if ($ml.mixItUp('isLoaded')) {

View file

@ -63,6 +63,26 @@ $(function () {
});
// Type in actor search
$("#actorSearchContent").on("input", function () {
triggerActorSearch();
});
// if they toggle the checkbox, we want to refresh our search
$("#actorsSearchNew").click(function () {
triggerActorSearch();
});
function triggerActorSearch()
{
if (searchTimer) {
clearTimeout(searchTimer);
}
searchTimer = setTimeout(function () {
moviesFromActor();
}.bind(this), 800);
}
$('#moviesComingSoon').on('click', function (e) {
e.preventDefault();
moviesComingSoon();
@ -300,7 +320,7 @@ $(function () {
function movieSearch() {
var query = $("#movieSearchContent").val();
var url = createBaseUrl(base, '/search/movie/');
query ? getMovies(url + query) : resetMovies();
query ? getMovies(url + query) : resetMovies("#movieList");
}
function moviesComingSoon() {
@ -313,6 +333,13 @@ $(function () {
getMovies(url);
}
function moviesFromActor() {
var query = $("#actorSearchContent").val();
var $newOnly = $('#actorsSearchNew')[0].checked;
var url = createBaseUrl(base, '/search/actor/' + (!!$newOnly ? 'new/' : ''));
query ? getMovies(url + query, "#actorMovieList", "#actorSearchButton") : resetMovies("#actorMovieList");
}
function popularShows() {
var url = createBaseUrl(base, '/search/tv/popular');
getTvShows(url, true);
@ -330,30 +357,31 @@ $(function () {
getTvShows(url, true);
}
function getMovies(url) {
resetMovies();
$('#movieSearchButton').attr("class", "fa fa-spinner fa-spin");
function getMovies(url, target, button) {
target = target || "#movieList";
button = button || "#movieSearchButton";
resetMovies(target);
$(button).attr("class", "fa fa-spinner fa-spin");
$.ajax(url).success(function (results) {
if (results.length > 0) {
results.forEach(function (result) {
var context = buildMovieContext(result);
var html = searchTemplate(context);
$("#movieList").append(html);
$(target).append(html);
checkNetflix(context.title, context.id);
});
}
else {
$("#movieList").html(noResultsHtml);
$(target).html(noResultsHtml);
}
$('#movieSearchButton').attr("class", "fa fa-search");
$(button).attr("class", "fa fa-search");
});
};
function resetMovies() {
$("#movieList").html("");
function resetMovies(target) {
$(target).html("");
}
function tvSearch() {
@ -394,7 +422,7 @@ $(function () {
if (results.result) {
// It's on Netflix
$('#' + id + 'netflixTab')
.html("<a href='https://www.netflix.com/watch/"+results.netflixId+"' target='_blank'><span class='label label-success'>Avaialble on Netflix</span></a>");
.html("<a href='https://www.netflix.com/watch/"+results.netflixId+"' target='_blank'><span class='label label-success'>Available on Netflix</span></a>");
}
});

View file

@ -3,9 +3,48 @@
// Step 1
$('#firstNext')
.click(function () {
loadArea("plexAuthArea");
loadArea("mediaApplicationChoice");
});
// Plex click
$('#contentBody')
.on("click", "#plexImg", function(e) {
e.preventDefault();
return loadArea("plexAuthArea");
});
$('#contentBody')
.on("click", "#embyImg", function (e) {
e.preventDefault();
return loadArea("embyApiKey");
});
$('#contentBody').on('click', '#embyApiKeySave', function (e) {
e.preventDefault();
var port = $('#portNumber').val();
if (!port) {
generateNotify("Please provide a port number", "warning");
}
$('#spinner').attr("class", "fa fa-spinner fa-spin");
var $form = $("#embyAuthForm");
$.post($form.prop("action"), $form.serialize(), function (response) {
if (response.result === true) {
loadArea("authArea");
} else {
$('#spinner').attr("class", "fa fa-times");
generateNotify(response.message, "warning");
}
});
});
// Step 2 - Get the auth token
$('#contentBody').on('click', '#requestToken', function (e) {
e.preventDefault();

View file

@ -283,11 +283,30 @@ namespace Ombi.UI.Helpers
var assetLocation = GetBaseUrl();
var content = GetContentUrl(assetLocation);
var sb = new StringBuilder();
var asset = $"<link rel=\"SHORTCUT ICON\" href=\"{content}/Content/favicon.ico\" />";
asset += $"<link rel=\"icon\" href=\"{content}/Content/favicon.ico\" type=\"image/ico\" />";
sb.Append($"<link rel=\"SHORTCUT ICON\" href=\"{content}/Content/favicon/favicon.ico\" />");
sb.Append($"<link rel=\"icon\" href=\"{content}/Content/favicon/favicon.ico?v2\" type=\"image/ico\" />");
return helper.Raw(asset);
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"{content}/Content/favicon/apple-icon-57x57.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"{content}/Content/favicon/apple-icon-60x60.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"{content}/Content/favicon/apple-icon-72x72.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"{content}/Content/favicon/apple-icon-76x76.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"{content}/Content/favicon/apple-icon-114x114.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"{content}/Content/favicon/apple-icon-120x120.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"{content}/Content/favicon/apple-icon-144x144.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"{content}/Content/favicon/apple-icon-152x152.png?v2\">");
sb.Append($"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"{content}/Content/favicon/apple-icon-180x180.png?v2\">");
sb.Append($"<link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"{content}/Content/favicon/android-icon-192x192.png?v2\">");
sb.Append($"<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"{content}/Content/favicon/favicon-32x32.png?v2\">");
sb.Append($"<link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"{content}/Content/favicon/favicon-96x96.png?v2\">");
sb.Append($"<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"{content}/Content/favicon/favicon-16x16.png?v2\">");
sb.Append($"<link rel=\"manifest\" href=\"{content}/Content/favicon/manifest.json?v2\">");
sb.Append($"<meta name=\"msapplication-TileColor\" content=\"#ffffff\">");
sb.Append($"<meta name=\"msapplication-TileImage\" content=\"{content}/Content/favicon/ms-icon-144x144.png?v2\">");
sb.Append($"<meta name=\"theme-color\" content=\"#ffffff\">");
return helper.Raw(sb.ToString());
}
public static IHtmlString GetSidebarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string icon = null)
@ -314,6 +333,7 @@ namespace Ombi.UI.Helpers
{
url = $"/{content}{url}";
}
var returnString = context.Request.Path == url ?
$"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>"
: $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>";
@ -328,7 +348,14 @@ namespace Ombi.UI.Helpers
{
url = $"/{content}{url}";
}
if (url.Contains("issues"))
{
var custom = GetCustomizationSettings();
if (!custom.EnableIssues)
{
return helper.Raw(string.Empty);
}
}
var returnString = context.Request.Path == url
? $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>"
: $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>";
@ -336,6 +363,14 @@ namespace Ombi.UI.Helpers
return helper.Raw(returnString);
}
public static IHtmlString ToolTip(this HtmlHelpers helper, string tooltipText)
{
//< span class="customTooltip" title="It also requires users to have the Newsletter feature"><i class="fa fa-info-circle"></i></span>
return
helper.Raw(
$"<span class=\"customTooltip\" title=\"{tooltipText}\"><i class=\"fa fa-info-circle\"></i></span>");
}
public static IHtmlString GetBaseUrl(this HtmlHelpers helper)
{
return helper.Raw(GetBaseUrl());
@ -346,6 +381,12 @@ namespace Ombi.UI.Helpers
return helper.Raw(GetCustomizationSettings().ApplicationName);
}
public static IHtmlString GetMediaServerName(this HtmlHelpers helper)
{
var s = GetEmbySettings();
return helper.Raw(s.Enable ? "Emby" : "Plex");
}
private static string GetBaseUrl()
{
return GetSettings().BaseUrl;
@ -363,7 +404,7 @@ namespace Ombi.UI.Helpers
private static CustomizationSettings GetCustomizationSettings()
{
var returnValue = Cache.GetOrSet(CacheKeys.GetPlexRequestSettings, () =>
var returnValue = Cache.GetOrSet(CacheKeys.GetCustomizationSettings, () =>
{
var settings = Locator.Resolve<ISettingsService<CustomizationSettings>>().GetSettings();
return settings;
@ -371,6 +412,16 @@ namespace Ombi.UI.Helpers
return returnValue;
}
private static EmbySettings GetEmbySettings()
{
var returnValue = Cache.GetOrSet(CacheKeys.GetEmbySettings, () =>
{
var settings = Locator.Resolve<ISettingsService<EmbySettings>>().GetSettings();
return settings;
});
return returnValue;
}
private static string GetLinkUrl(string assetLocation)
{
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}";

View file

@ -41,13 +41,14 @@ namespace Ombi.UI.Helpers
return helper.Raw(htmlString);
}
public static IHtmlString Checkbox(this HtmlHelpers helper, bool check, string name, string display)
public static IHtmlString Checkbox(this HtmlHelpers helper, bool check, string name, string display, string tooltipText = null)
{
var sb = new StringBuilder();
sb.AppendLine("<div class=\"form-group\">");
sb.AppendLine("<div class=\"checkbox\">");
sb.AppendFormat("<input type=\"checkbox\" id=\"{0}\" name=\"{0}\" {2}><label for=\"{0}\">{1}</label>", name, display, check ? "checked=\"checked\"" : string.Empty);
sb.AppendFormat("<input type=\"checkbox\" id=\"{0}\" name=\"{0}\" {2}><label for=\"{0}\">{1} {3}</label>", name, display, check ? "checked=\"checked\"" : string.Empty,
string.IsNullOrEmpty(tooltipText) ? string.Empty : helper.ToolTip(tooltipText).ToHtmlString());
sb.AppendLine("</div>");
sb.AppendLine("</div>");
return helper.Raw(sb.ToString());

View file

@ -72,9 +72,9 @@ namespace Ombi.UI.Helpers
return Security.IsLoggedIn(context);
}
public static bool IsPlexUser(this HtmlHelpers helper)
public static bool IsExternalUser(this HtmlHelpers helper)
{
return Security.IsPlexUser(helper.CurrentUser);
return Security.IsExternalUser(helper.CurrentUser);
}
public static bool IsNormalUser(this HtmlHelpers helper)
{

View file

@ -35,6 +35,7 @@ using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.Services.Jobs.RecentlyAddedNewsletter;
using Ombi.UI.Helpers;
using Quartz;
using Quartz.Impl;
@ -55,9 +56,6 @@ namespace Ombi.UI.Jobs
private IEnumerable<IJobDetail> CreateJobs()
{
var settingsService = Service.Resolve<ISettingsService<ScheduledJobsSettings>>();
var s = settingsService.GetSettings();
var jobs = new List<IJobDetail>();
var jobList = new List<IJobDetail>
@ -73,9 +71,15 @@ namespace Ombi.UI.Jobs
JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build(),
JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build(),
JobBuilder.Create<UserRequestLimitResetter>().WithIdentity("UserRequestLimiter", "Request").Build(),
JobBuilder.Create<RecentlyAdded>().WithIdentity("RecentlyAddedModel", "Email").Build(),
JobBuilder.Create<RecentlyAddedNewsletter>().WithIdentity("RecentlyAddedModel", "Email").Build(),
JobBuilder.Create<FaultQueueHandler>().WithIdentity("FaultQueueHandler", "Fault").Build(),
JobBuilder.Create<RadarrCacher>().WithIdentity("RadarrCacher", "Cache").Build(),
JobBuilder.Create<EmbyEpisodeCacher>().WithIdentity("EmbyEpisodeCacher", "Emby").Build(),
JobBuilder.Create<EmbyAvailabilityChecker>().WithIdentity("EmbyAvailabilityChecker", "Emby").Build(),
JobBuilder.Create<EmbyContentCacher>().WithIdentity("EmbyContentCacher", "Emby").Build(),
JobBuilder.Create<EmbyUserChecker>().WithIdentity("EmbyUserChecker", "Emby").Build(),
};
jobs.AddRange(jobList);
@ -175,6 +179,22 @@ namespace Ombi.UI.Jobs
{
s.RadarrCacher = 60;
}
if (s.EmbyContentCacher == 0)
{
s.EmbyContentCacher = 60;
}
if (s.EmbyAvailabilityChecker == 0)
{
s.EmbyAvailabilityChecker = 60;
}
if (s.EmbyEpisodeCacher == 0)
{
s.EmbyEpisodeCacher = 11;
}
if (s.EmbyUserChecker == 0)
{
s.EmbyUserChecker = 24;
}
var triggers = new List<ITrigger>();
@ -280,6 +300,38 @@ namespace Ombi.UI.Jobs
.WithSimpleSchedule(x => x.WithIntervalInHours(s.FaultQueueHandler).RepeatForever())
.Build();
//Emby
var embyEpisode =
TriggerBuilder.Create()
.WithIdentity("EmbyEpisodeCacher", "Emby")
//.StartNow()
.StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute))
.WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyEpisodeCacher).RepeatForever())
.Build();
var embyContentCacher =
TriggerBuilder.Create()
.WithIdentity("EmbyContentCacher", "Emby")
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyContentCacher).RepeatForever())
.Build();
var embyAvailabilityChecker =
TriggerBuilder.Create()
.WithIdentity("EmbyAvailabilityChecker", "Emby")
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute))
.WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyAvailabilityChecker).RepeatForever())
.Build();
var embyUserChecker =
TriggerBuilder.Create()
.WithIdentity("EmbyUserChecker", "Emby")
//.StartNow()
.StartAt(DateBuilder.FutureDate(1, IntervalUnit.Minute))
.WithSimpleSchedule(x => x.WithIntervalInHours(s.EmbyUserChecker).RepeatForever())
.Build();
triggers.Add(rencentlyAdded);
triggers.Add(plexAvailabilityChecker);
triggers.Add(srCacher);
@ -294,6 +346,10 @@ namespace Ombi.UI.Jobs
triggers.Add(plexCacher);
triggers.Add(plexUserChecker);
triggers.Add(radarrCacher);
triggers.Add(embyEpisode);
triggers.Add(embyAvailabilityChecker);
triggers.Add(embyContentCacher);
triggers.Add(embyUserChecker);
return triggers;
}

View file

@ -33,6 +33,8 @@ namespace Ombi.UI.Models
{
public class ScheduledJobsViewModel : ScheduledJobsSettings
{
public bool Emby { get; set; }
public bool Plex { get; set; }
public Dictionary<string,DateTime> JobRecorder { get; set; }
}
}

View file

@ -32,6 +32,8 @@ namespace Ombi.UI.Models
public class SearchLoadViewModel
{
public PlexRequestSettings Settings { get; set; }
public bool Plex { get; set; }
public bool Emby { get; set; }
public CustomizationSettings CustomizationSettings { get; set; }
}
}

View file

@ -112,7 +112,7 @@ namespace Ombi.UI.Modules.Admin
vm.DbLocation = SqlConfig.CurrentPath;
vm.ApplicationVersion = AssemblyHelper.GetFileVersion();
vm.Branch = EnumHelper<Branches>.GetDisplayValue(systemSettings.Branch);
vm.Branch = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(systemSettings.Branch).DisplayName;
vm.LogLevel = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database"))?.Levels?.FirstOrDefault()?.Name ?? "Unknown";
return vm;

View file

@ -42,6 +42,7 @@ using Nancy.Validation;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Movie;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
@ -92,11 +93,14 @@ namespace Ombi.UI.Modules.Admin
private IJobRecord JobRecorder { get; }
private IAnalytics Analytics { get; }
private IRecentlyAdded RecentlyAdded { get; }
private IMassEmail MassEmail { get; }
private ISettingsService<NotificationSettingsV2> NotifySettings { get; }
private ISettingsService<DiscordNotificationSettings> DiscordSettings { get; }
private IDiscordApi DiscordApi { get; }
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private IRadarrApi RadarrApi { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IEmbyApi EmbyApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public AdminModule(ISettingsService<PlexRequestSettings> prService,
@ -121,10 +125,11 @@ namespace Ombi.UI.Modules.Admin
ICacheProvider cache, ISettingsService<SlackNotificationSettings> slackSettings,
ISlackApi slackApi, ISettingsService<LandingPageSettings> lp,
ISettingsService<ScheduledJobsSettings> scheduler, IJobRecord rec, IAnalytics analytics,
ISettingsService<NotificationSettingsV2> notifyService, IRecentlyAdded recentlyAdded,
ISettingsService<NotificationSettingsV2> notifyService, IRecentlyAdded recentlyAdded, IMassEmail massEmail,
ISettingsService<WatcherSettings> watcherSettings ,
ISettingsService<DiscordNotificationSettings> discord,
IDiscordApi discordapi, ISettingsService<RadarrSettings> settings, IRadarrApi radarrApi
IDiscordApi discordapi, ISettingsService<RadarrSettings> settings, IRadarrApi radarrApi,
ISettingsService<EmbySettings> embySettings, IEmbyApi emby
, ISecurityExtensions security) : base("admin", prService, security)
{
PrService = prService;
@ -155,12 +160,15 @@ namespace Ombi.UI.Modules.Admin
Analytics = analytics;
NotifySettings = notifyService;
RecentlyAdded = recentlyAdded;
MassEmail = massEmail;
WatcherSettings = watcherSettings;
DiscordSettings = discord;
DiscordApi = discordapi;
RadarrSettings = settings;
RadarrApi = radarrApi;
EmbyApi = emby;
EmbySettings = embySettings;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Get["/"] = _ => Admin();
@ -170,7 +178,7 @@ namespace Ombi.UI.Modules.Admin
Post["/", true] = async (x, ct) => await SaveAdmin();
Post["/requestauth"] = _ => RequestAuthToken();
Post["/requestauth", true] = async (x, ct) => await RequestAuthToken();
Get["/getusers"] = _ => GetUsers();
@ -180,6 +188,10 @@ namespace Ombi.UI.Modules.Admin
Get["/plex"] = _ => Plex();
Post["/plex", true] = async (x, ct) => await SavePlex();
Get["/emby", true] = async (x, ct) => await Emby();
Post["/emby", true] = async (x, ct) => await SaveEmby();
Get["/sonarr"] = _ => Sonarr();
Post["/sonarr"] = _ => SaveSonarr();
Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles();
@ -213,6 +225,11 @@ namespace Ombi.UI.Modules.Admin
Get["/newsletter", true] = async (x, ct) => await Newsletter();
Post["/newsletter", true] = async (x, ct) => await SaveNewsletter();
Post["/testnewsletteradminemail"] = x => TestNewsletterAdminEmail();
Get["/massemail"] = _ => MassEmailView();
Post["/testmassadminemail"] = x => TestMassAdminEmail();
Post["/sendmassemail"] = x => SendMassEmail();
Post["/createapikey"] = x => CreateApiKey();
@ -237,7 +254,6 @@ namespace Ombi.UI.Modules.Admin
Get["/notificationsettings", true] = async (x, ct) => await NotificationSettings();
Post["/notificationsettings"] = x => SaveNotificationSettings();
Post["/recentlyAddedTest"] = x => RecentlyAddedTest();
}
private async Task<Negotiator> Authentication()
@ -303,7 +319,7 @@ namespace Ombi.UI.Modules.Admin
: new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" });
}
private Response RequestAuthToken()
private async Task<Response> RequestAuthToken()
{
var user = this.Bind<PlexAuth>();
@ -319,11 +335,11 @@ namespace Ombi.UI.Modules.Admin
return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" });
}
var oldSettings = PlexService.GetSettings();
var oldSettings = await PlexService.GetSettingsAsync();
if (oldSettings != null)
{
oldSettings.PlexAuthToken = model.user.authentication_token;
PlexService.SaveSettings(oldSettings);
await PlexService.SaveSettingsAsync(oldSettings);
}
else
{
@ -331,10 +347,14 @@ namespace Ombi.UI.Modules.Admin
{
PlexAuthToken = model.user.authentication_token
};
PlexService.SaveSettings(newModel);
await PlexService.SaveSettingsAsync(newModel);
}
return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token });
var server = PlexApi.GetServer(model.user.authentication_token);
var machine =
server.Server.FirstOrDefault(x => x.AccessToken == model.user.authentication_token)?.MachineIdentifier;
return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token, Identifier = machine });
}
@ -432,13 +452,32 @@ namespace Ombi.UI.Modules.Admin
private async Task<Response> SavePlex()
{
var plexSettings = this.Bind<PlexSettings>();
var valid = this.Validate(plexSettings);
if (!valid.IsValid)
if (plexSettings.Enable)
{
var valid = this.Validate(plexSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
}
if (plexSettings.Enable)
{
return Response.AsJson(valid.SendJsonError());
var embySettings = await EmbySettings.GetSettingsAsync();
if (embySettings.Enable)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Emby is enabled, we cannot enable Plex and Emby"
});
}
}
if (string.IsNullOrEmpty(plexSettings.MachineIdentifier))
if (string.IsNullOrEmpty(plexSettings.MachineIdentifier) && plexSettings.Enable)
{
//Lookup identifier
var server = PlexApi.GetServer(plexSettings.PlexAuthToken);
@ -453,6 +492,49 @@ namespace Ombi.UI.Modules.Admin
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private async Task<Negotiator> Emby()
{
var settings = await EmbySettings.GetSettingsAsync();
return View["Emby", settings];
}
private async Task<Response> SaveEmby()
{
var emby = this.Bind<EmbySettings>();
var valid = this.Validate(emby);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
if (emby.Enable)
{
var plexSettings = await PlexService.GetSettingsAsync();
if (plexSettings.Enable)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Plex is enabled, we cannot enable Plex and Emby"
});
}
}
// Get the users
var users = EmbyApi.GetUsers(emby.FullUri, emby.ApiKey);
// Find admin
var admin = users.FirstOrDefault(x => x.Policy.IsAdministrator);
emby.AdministratorId = admin?.Id;
var result = await EmbySettings.SaveSettingsAsync(emby);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Emby!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Negotiator Sonarr()
{
var settings = SonarrService.GetSettings();
@ -748,6 +830,10 @@ namespace Ombi.UI.Modules.Admin
{
return Response.AsJson(valid.SendJsonError());
}
if (!settings.Enabled)
{
return Response.AsJson(new CouchPotatoProfiles{list = new List<ProfileList>()});
}
var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey);
// set the cache
@ -850,6 +936,10 @@ namespace Ombi.UI.Modules.Admin
var settings = await NewsLetterService.GetSettingsAsync();
return View["NewsletterSettings", settings];
}
private Negotiator MassEmailView()
{
return View["MassEmail"];
}
private async Task<Response> SaveNewsletter()
{
@ -1042,9 +1132,10 @@ namespace Ombi.UI.Modules.Admin
Analytics.TrackEventAsync(Category.Admin, Action.Update, "Update Landing Page", Username, CookieHelper.GetAnalyticClientId(Cookies));
var plexSettings = await PlexService.GetSettingsAsync();
if (string.IsNullOrEmpty(plexSettings.Ip))
var embySettings = await EmbySettings.GetSettingsAsync();
if (string.IsNullOrEmpty(plexSettings.Ip) && string.IsNullOrEmpty(embySettings.Ip))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "We cannot enable the landing page if Plex is not setup!" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = "We cannot enable the landing page if Plex/Emby is not setup!" });
}
if (settings.Enabled && settings.EnabledNoticeTime && string.IsNullOrEmpty(settings.NoticeMessage))
@ -1063,6 +1154,10 @@ namespace Ombi.UI.Modules.Admin
{
var s = await ScheduledJobSettings.GetSettingsAsync();
var allJobs = await JobRecorder.GetJobsAsync();
var emby = await EmbySettings.GetSettingsAsync();
var plex = await PlexService.GetSettingsAsync();
var dict = new Dictionary<string, DateTime>();
@ -1076,13 +1171,32 @@ namespace Ombi.UI.Modules.Admin
}
else
{
dict.Add(j.Name,j.LastRun);
if (j.Name.Contains("Plex"))
{
if (plex.Enable)
{
dict.Add(j.Name, j.LastRun);
}
}
else if (j.Name.Contains("Emby"))
{
if (emby.Enable)
{
dict.Add(j.Name, j.LastRun);
}
}
else
{
dict.Add(j.Name, j.LastRun);
}
}
}
var model = new ScheduledJobsViewModel
{
Emby = emby.Enable,
Plex = plex.Enable,
CouchPotatoCacher = s.CouchPotatoCacher,
PlexAvailabilityChecker = s.PlexAvailabilityChecker,
SickRageCacher = s.SickRageCacher,
@ -1095,7 +1209,13 @@ namespace Ombi.UI.Modules.Admin
FaultQueueHandler = s.FaultQueueHandler,
PlexEpisodeCacher = s.PlexEpisodeCacher,
PlexUserChecker = s.PlexUserChecker,
UserRequestLimitResetter = s.UserRequestLimitResetter
UserRequestLimitResetter = s.UserRequestLimitResetter,
EmbyAvailabilityChecker = s.EmbyAvailabilityChecker,
EmbyContentCacher = s.EmbyContentCacher,
EmbyEpisodeCacher = s.EmbyEpisodeCacher,
EmbyUserChecker = s.EmbyUserChecker,
RadarrCacher = s.RadarrCacher,
WatcherCacher = s.WatcherCacher
};
return View["SchedulerSettings", model];
}
@ -1158,13 +1278,13 @@ namespace Ombi.UI.Modules.Admin
var model = this.Bind<NotificationSettingsV2>();
return View["NotificationSettings", model];
}
private Response RecentlyAddedTest()
private Response TestNewsletterAdminEmail()
{
try
{
Log.Debug("Clicked TEST");
RecentlyAdded.Test();
Log.Debug("Clicked Admin Newsletter Email Test");
RecentlyAdded.RecentlyAddedAdminTest();
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Sent email to administrator" });
}
catch (Exception e)
@ -1173,5 +1293,50 @@ namespace Ombi.UI.Modules.Admin
return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
}
}
private Response TestMassAdminEmail()
{
try
{
var settings = this.Bind<MassEmailSettings>();
Log.Debug("Clicked Admin Mass Email Test");
if (settings.Subject == null) {
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please Set a Subject" });
}
if (settings.Body == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please Set a Body" });
}
MassEmail.MassEmailAdminTest(settings.Body.Replace("\n", "<br/>"), settings.Subject);
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Sent email to administrator" });
}
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
}
}
private Response SendMassEmail()
{
try
{
var settings = this.Bind<MassEmailSettings>();
Log.Debug("Clicked Admin Mass Email Test");
if (settings.Subject == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please Set a Subject" });
}
if (settings.Body == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please Set a Body" });
}
MassEmail.SendMassEmail(settings.Body.Replace("\n", "<br/>"), settings.Subject);
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Sent email to All users" });
}
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
}
}
}
}

View file

@ -25,7 +25,10 @@
// ************************************************************************/
#endregion
using System;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using Ombi.Core;
using Ombi.Core.SettingModels;
@ -48,6 +51,7 @@ namespace Ombi.UI.Modules.Admin
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Get["Index", "/faultqueue"] = x => Index();
Get["DeleteFault", "/deleteFault", true] = async (x,ct) => await DeleteFault(Convert.ToInt32(Request.Form.id));
}
private IRepository<RequestQueue> RequestQueue { get; }
@ -69,5 +73,35 @@ namespace Ombi.UI.Modules.Admin
return View["RequestFaultQueue", model];
}
public async Task<Response> DeleteFault(int faultId)
{
if (faultId == 0)
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Fault does not exist"
});
}
var fault = await RequestQueue.GetAsync(faultId);
if (fault == null)
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Fault does not exist"
});
}
await RequestQueue.DeleteAsync(fault);
return Response.AsJson(new JsonResponseModel
{
Result = true
});
}
}
}

View file

@ -69,6 +69,7 @@ namespace Ombi.UI.Modules.Admin
Post["/sonarrrootfolders"] = _ => GetSonarrRootFolders();
Post["/radarrrootfolders"] = _ => GetSonarrRootFolders();
Get["/watcher", true] = async (x, ct) => await Watcher();
Post["/watcher", true] = async (x, ct) => await SaveWatcher();
@ -156,7 +157,7 @@ namespace Ombi.UI.Modules.Admin
var cp = await CpSettings.GetSettingsAsync();
if (cp.Enabled)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is enabled, we cannot enable Watcher and CouchPotato" });
return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is enabled, we cannot enable Radarr and CouchPotato" });
}
var valid = this.Validate(radarrSettings);
@ -191,7 +192,22 @@ namespace Ombi.UI.Modules.Admin
{
var settings = this.Bind<SonarrSettings>();
var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
// set the cache
if (rootFolders != null)
{
Cache.Set(CacheKeys.SonarrRootFolders, rootFolders);
}
return Response.AsJson(rootFolders);
}
private Response GetRadarrRootFolders()
{
var settings = this.Bind<RadarrSettings>();
var rootFolders = RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
// set the cache
if (rootFolders != null)

View file

@ -33,6 +33,7 @@ using Ombi.Core.SettingModels;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.Services.Jobs.Interfaces;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
@ -44,7 +45,8 @@ namespace Ombi.UI.Modules.Admin
ISecurityExtensions security, IPlexContentCacher contentCacher, ISonarrCacher sonarrCacher, IWatcherCacher watcherCacher,
IRadarrCacher radarrCacher, ICouchPotatoCacher cpCacher, IStoreBackup store, ISickRageCacher srCacher, IAvailabilityChecker plexChceker,
IStoreCleanup cleanup, IUserRequestLimitResetter requestLimit, IPlexEpisodeCacher episodeCacher, IRecentlyAdded recentlyAdded,
IFaultQueueHandler faultQueueHandler, IPlexUserChecker plexUserChecker) : base("admin", settingsService, security)
IFaultQueueHandler faultQueueHandler, IPlexUserChecker plexUserChecker, IEmbyAvailabilityChecker embyAvailabilityChecker, IEmbyEpisodeCacher embyEpisode,
IEmbyContentCacher embyContentCacher, IEmbyUserChecker embyUser) : base("admin", settingsService, security)
{
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
@ -62,6 +64,10 @@ namespace Ombi.UI.Modules.Admin
RecentlyAdded = recentlyAdded;
FaultQueueHandler = faultQueueHandler;
PlexUserChecker = plexUserChecker;
EmbyAvailabilityChecker = embyAvailabilityChecker;
EmbyContentCacher = embyContentCacher;
EmbyEpisodeCacher = embyEpisode;
EmbyUserChecker = embyUser;
Post["/schedulerun", true] = async (x, ct) => await ScheduleRun((string)Request.Form.key);
}
@ -80,10 +86,16 @@ namespace Ombi.UI.Modules.Admin
private IRecentlyAdded RecentlyAdded { get; }
private IFaultQueueHandler FaultQueueHandler { get; }
private IPlexUserChecker PlexUserChecker { get; }
private IEmbyAvailabilityChecker EmbyAvailabilityChecker { get; }
private IEmbyContentCacher EmbyContentCacher { get; }
private IEmbyEpisodeCacher EmbyEpisodeCacher { get; }
private IEmbyUserChecker EmbyUserChecker { get; }
private async Task<Response> ScheduleRun(string key)
{
await Task.Yield();
if (key.Equals(JobNames.PlexCacher, StringComparison.CurrentCultureIgnoreCase))
{
PlexContentCacher.CacheContent();
@ -132,7 +144,7 @@ namespace Ombi.UI.Modules.Admin
}
if (key.Equals(JobNames.RecentlyAddedEmail, StringComparison.CurrentCultureIgnoreCase))
{
RecentlyAdded.Start();
RecentlyAdded.StartNewsLetter();
}
if (key.Equals(JobNames.FaultQueueHandler, StringComparison.CurrentCultureIgnoreCase))
{
@ -142,6 +154,22 @@ namespace Ombi.UI.Modules.Admin
{
RequestLimit.Start();
}
if (key.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase))
{
EmbyEpisodeCacher.Start();
}
if (key.Equals(JobNames.EmbyCacher, StringComparison.CurrentCultureIgnoreCase))
{
EmbyContentCacher.CacheContent();
}
if (key.Equals(JobNames.EmbyChecker, StringComparison.CurrentCultureIgnoreCase))
{
EmbyAvailabilityChecker.CheckAndUpdateAll();
}
if (key.Equals(JobNames.EmbyUserChecker, StringComparison.CurrentCultureIgnoreCase))
{
EmbyUserChecker.Start();
}
return Response.AsJson(new JsonResponseModel { Result = true });

View file

@ -35,7 +35,10 @@ using MarkdownSharp;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Ombi.Api.Interfaces;
using Ombi.Common.Processes;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
using Ombi.Helpers;
@ -49,11 +52,13 @@ namespace Ombi.UI.Modules.Admin
{
public class SystemStatusModule : BaseModule
{
public SystemStatusModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> ss, ISecurityExtensions security, IAnalytics a) : base("admin", settingsService, security)
public SystemStatusModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> ss,
ISecurityExtensions security, IAnalytics a, IAppveyorApi appveyor) : base("admin", settingsService, security)
{
Cache = cache;
SystemSettings = ss;
Analytics = a;
AppveyorApi = appveyor;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
@ -61,11 +66,13 @@ namespace Ombi.UI.Modules.Admin
Post["/save", true] = async (x, ct) => await Save();
Post["/autoupdate"] = x => AutoUpdate();
Get["/changes", true] = async (x, ct) => await GetLatestChanges();
}
private ICacheProvider Cache { get; }
private ISettingsService<SystemSettings> SystemSettings { get; }
private IAnalytics Analytics { get; }
private IAppveyorApi AppveyorApi { get; }
private async Task<Negotiator> Status()
{
@ -81,19 +88,19 @@ namespace Ombi.UI.Modules.Admin
{
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.Stable),
Name =EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.Stable).DisplayName,
Value = Branches.Stable,
Selected = settings.Branch == Branches.Stable
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.EarlyAccessPreview),
Name = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.EarlyAccessPreview).DisplayName,
Value = Branches.EarlyAccessPreview,
Selected = settings.Branch == Branches.EarlyAccessPreview
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.Dev),
Name = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.Dev).DisplayName,
Value = Branches.Dev,
Selected = settings.Branch == Branches.Dev
},
@ -102,12 +109,40 @@ namespace Ombi.UI.Modules.Admin
return View["Status", settings];
}
public async Task<Response> GetLatestChanges()
{
var settings = await SystemSettings.GetSettingsAsync();
var branchName = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(settings.Branch).BranchName;
var changes = AppveyorApi.GetProjectHistory(branchName);
var currentVersion = AssemblyHelper.GetProductVersion();
var model = new List<RecentUpdatesModel>();
foreach (var build in changes.builds)
{
model.Add(new RecentUpdatesModel
{
Date = build.finished,
Message = BuildAppveyorMessage(build.message, build.messageExtended),
Version = build.version,
Installed = currentVersion.Equals(build.version, StringComparison.CurrentCultureIgnoreCase) ,
Branch = branchName
});
}
return Response.AsJson(model);
}
private string BuildAppveyorMessage(string message, string extended)
{
return extended == null ? message : $"{message} {extended}";
}
private async Task<Response> Save()
{
var settings = this.Bind<SystemSettings>();
Analytics.TrackEventAsync(Category.Admin, Action.Update, $"Updated Branch {EnumHelper<Branches>.GetDisplayValue(settings.Branch)}", Username, CookieHelper.GetAnalyticClientId(Cookies));
Analytics.TrackEventAsync(Category.Admin, Action.Update, $"Updated Branch {EnumHelper<Branches>.GetBranchValue<BranchAttribute>(settings.Branch).DisplayName}", Username, CookieHelper.GetAnalyticClientId(Cookies));
await SystemSettings.SaveSettingsAsync(settings);
// Clear the cache
@ -123,7 +158,7 @@ namespace Ombi.UI.Modules.Admin
var url = Request.Form["url"];
var args = (string)Request.Form["args"].ToString();
var lowered = args.ToLower();
var appPath = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(SystemStatusModule)).Location ?? string.Empty) ?? string.Empty, "Ombi.Updater.exe");
var appPath = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(SystemStatusModule)).Location ?? string.Empty) ?? string.Empty, Path.Combine("UpdateService", "Ombi.Updater.exe"));
if (!string.IsNullOrEmpty(lowered))
{
@ -133,7 +168,7 @@ namespace Ombi.UI.Modules.Admin
}
}
var startArgs = string.IsNullOrEmpty(lowered) ? appPath : $"{lowered} Ombi.Updater.exe";
var startArgs = string.IsNullOrEmpty(lowered) || lowered == "Nancy.DynamicDictionaryValue".ToLower() ? appPath : $"{lowered} Ombi.Updater.exe";
var startInfo = Type.GetType("Mono.Runtime") != null
? new ProcessStartInfo(startArgs) { Arguments = $"{url} {lowered}", }
@ -141,7 +176,7 @@ namespace Ombi.UI.Modules.Admin
Process.Start(startInfo);
Environment.Exit(0);
//Environment.Exit(0);
return Nancy.Response.NoBody;
}

View file

@ -46,7 +46,7 @@ namespace Ombi.UI.Modules
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,
ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security,
IWatcherApi watcherApi, IRadarrApi radarrApi) : base("test", pr, security)
IWatcherApi watcherApi, IRadarrApi radarrApi, IEmbyApi emby) : base("test", pr, security)
{
this.RequiresAuthentication();
@ -57,6 +57,7 @@ namespace Ombi.UI.Modules
HeadphonesApi = hpApi;
WatcherApi = watcherApi;
RadarrApi = radarrApi;
Emby = emby;
Post["/cp"] = _ => CouchPotatoTest();
Post["/sonarr"] = _ => SonarrTest();
@ -66,6 +67,7 @@ namespace Ombi.UI.Modules
Post["/headphones"] = _ => HeadphonesTest();
Post["/plexdb"] = _ => TestPlexDb();
Post["/watcher"] = _ => WatcherTest();
Post["/emby"] = _ => EmbyTest();
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
@ -76,6 +78,7 @@ namespace Ombi.UI.Modules
private IHeadphonesApi HeadphonesApi { get; }
private IWatcherApi WatcherApi { get; }
private IRadarrApi RadarrApi { get; }
private IEmbyApi Emby { get; set; }
private Response CouchPotatoTest()
{
@ -213,7 +216,7 @@ namespace Ombi.UI.Modules
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Plex, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Plex's status: ");
Log.Warn(e);
@ -225,6 +228,35 @@ namespace Ombi.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response EmbyTest()
{
var emby = this.Bind<EmbySettings>();
var valid = this.Validate(emby);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = Emby.GetUsers(emby?.FullUri, emby?.ApiKey);
return status != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Emby successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Emby, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Emby's users: ");
Log.Warn(e);
var message = $"Could not connect to Emby, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Emby, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response SickRageTest()
{

View file

@ -193,7 +193,7 @@ namespace Ombi.UI.Modules
{
// Approve it
request.Approved = true;
Log.Warn("We approved movie: {0} but could not add it to CouchPotato/Watcher because it has not been setup", request.Title);
Log.Warn("We approved movie: {0} but could not add it to CouchPotato/Watcher/Radarr because it has not been setup", request.Title);
// Update the record
var inserted = await Service.UpdateRequestAsync(request);

View file

@ -145,7 +145,7 @@ namespace Ombi.UI.Modules
Deleted = issue.Deleted,
Type = issue.Type,
ProviderId = issue.ProviderId,
PosterUrl = issue.PosterUrl,
PosterUrl = issue.PosterUrl.Contains("https://image.tmdb.org/t/p/w150/") ? issue.PosterUrl : $"https://image.tmdb.org/t/p/w150/{issue.PosterUrl}",
Id = issue.Id
};
return View["Details", m];

View file

@ -40,12 +40,15 @@ namespace Ombi.UI.Modules
public class LandingPageModule : BaseModule
{
public LandingPageModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<LandingPageSettings> landing,
ISettingsService<PlexSettings> ps, IPlexApi pApi, IResourceLinker linker, ISecurityExtensions security) : base("landing", settingsService, security)
ISettingsService<PlexSettings> ps, IPlexApi pApi, IResourceLinker linker, ISecurityExtensions security, ISettingsService<EmbySettings> emby,
IEmbyApi embyApi) : base("landing", settingsService, security)
{
LandingSettings = landing;
PlexSettings = ps;
PlexApi = pApi;
Linker = linker;
EmbySettings = emby;
EmbyApi = embyApi;
Get["LandingPageIndex","/", true] = async (x, ct) =>
{
@ -75,26 +78,49 @@ namespace Ombi.UI.Modules
private ISettingsService<LandingPageSettings> LandingSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexApi PlexApi { get; }
private IEmbyApi EmbyApi { get; }
private IResourceLinker Linker { get; }
private async Task<Response> CheckStatus()
{
var plexSettings = await PlexSettings.GetSettingsAsync();
if (string.IsNullOrEmpty(plexSettings.PlexAuthToken) || string.IsNullOrEmpty(plexSettings.Ip))
if (plexSettings.Enable)
{
return Response.AsJson(false);
}
try
{
var status = PlexApi.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri);
return Response.AsJson(status != null);
}
catch (Exception)
{
return Response.AsJson(false);
if (string.IsNullOrEmpty(plexSettings.PlexAuthToken) || string.IsNullOrEmpty(plexSettings.Ip))
{
return Response.AsJson(false);
}
try
{
var status = PlexApi.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri);
return Response.AsJson(status != null);
}
catch (Exception)
{
return Response.AsJson(false);
}
}
var emby = await EmbySettings.GetSettingsAsync();
if (emby.Enable)
{
if (string.IsNullOrEmpty(emby.AdministratorId) || string.IsNullOrEmpty(emby.Ip))
{
return Response.AsJson(false);
}
try
{
var status = EmbyApi.GetSystemInformation(emby.ApiKey, emby.FullUri);
return Response.AsJson(status?.Version != null);
}
catch (Exception)
{
return Response.AsJson(false);
}
}
return Response.AsJson(false);
}
}
}

View file

@ -29,10 +29,12 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
using Ombi.Core.Users;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
@ -43,14 +45,16 @@ namespace Ombi.UI.Modules
{
public class LayoutModule : BaseAuthModule
{
public LayoutModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISettingsService<SystemSettings> settings, IJobRecord rec, ISecurityExtensions security) : base("layout", pr, security)
public LayoutModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISettingsService<SystemSettings> settings, IJobRecord rec, ISecurityExtensions security, IUserHelper helper) : base("layout", pr, security)
{
Cache = provider;
SystemSettings = settings;
Job = rec;
UserHelper = helper;
Get["/", true] = async (x,ct) => await CheckLatestVersion();
Get["/cacher", true] = async (x,ct) => await CacherRunning();
Get["/gravatar"] = x => GetGravatarImage();
}
private ICacheProvider Cache { get; }
@ -58,6 +62,7 @@ namespace Ombi.UI.Modules
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SystemSettings> SystemSettings { get; }
private IJobRecord Job { get; }
private IUserHelper UserHelper { get; }
private async Task<Response> CheckLatestVersion()
{
@ -116,5 +121,31 @@ namespace Ombi.UI.Modules
return Response.AsJson(new { CurrentlyRunning = false, IsAdmin });
}
}
private Response GetGravatarImage()
{
if (LoggedIn)
{
var user = UserHelper.GetUser(Username);
var hashed = StringHasher.CalcuateMd5Hash(user.EmailAddress);
if (string.IsNullOrEmpty(hashed))
{
return Response.AsJson(new JsonResponseModel
{
Result = false
});
}
return
Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"https://www.gravatar.com/avatar/{hashed}"
});
}
else
{
return Response.AsJson(new JsonResponseModel {Result = false});
}
}
}
}

View file

@ -33,6 +33,7 @@ using Nancy;
using Nancy.Responses.Negotiation;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Sonarr;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
@ -65,9 +66,13 @@ namespace Ombi.UI.Modules
ISickRageApi sickRageApi,
ICacheProvider cache,
IAnalytics an,
INotificationEngine engine,
IPlexNotificationEngine engine,
IEmbyNotificationEngine embyEngine,
ISecurityExtensions security,
ISettingsService<CustomizationSettings> customSettings) : base("requests", prSettings, security)
ISettingsService<CustomizationSettings> customSettings,
ISettingsService<EmbySettings> embyS,
ISettingsService<RadarrSettings> radarr,
IRadarrApi radarrApi) : base("requests", prSettings, security)
{
Service = service;
PrSettings = prSettings;
@ -81,8 +86,12 @@ namespace Ombi.UI.Modules
CpApi = cpApi;
Cache = cache;
Analytics = an;
NotificationEngine = engine;
PlexNotificationEngine = engine;
EmbyNotificationEngine = embyEngine;
CustomizationSettings = customSettings;
EmbySettings = embyS;
Radarr = radarr;
RadarrApi = radarrApi;
Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies();
@ -96,7 +105,8 @@ namespace Ombi.UI.Modules
Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
Post["/changeRootFolder", true] = async (x, ct) => await ChangeRootFolder((int) Request.Form.requestId, (int) Request.Form.rootFolderId);
Post["/changeRootFoldertv", true] = async (x, ct) => await ChangeRootFolder(RequestType.TvShow, (int)Request.Form.requestId, (int)Request.Form.rootFolderId);
Post["/changeRootFoldermovie", true] = async (x, ct) => await ChangeRootFolder(RequestType.Movie, (int)Request.Form.requestId, (int)Request.Form.rootFolderId);
Get["/UpdateFilters", true] = async (x, ct) => await GetFilterAndSortSettings();
}
@ -111,11 +121,15 @@ namespace Ombi.UI.Modules
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private ISettingsService<RadarrSettings> Radarr { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISonarrApi SonarrApi { get; }
private IRadarrApi RadarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; }
private INotificationEngine NotificationEngine { get; }
private INotificationEngine PlexNotificationEngine { get; }
private INotificationEngine EmbyNotificationEngine { get; }
private async Task<Negotiator> LoadRequests()
{
@ -138,32 +152,64 @@ namespace Ombi.UI.Modules
}
List<QualityModel> qualities = new List<QualityModel>();
var rootFolders = new List<RootFolderModel>();
var radarr = await Radarr.GetSettingsAsync();
if (IsAdmin)
{
var cpSettings = CpSettings.GetSettings();
if (cpSettings.Enabled)
try
{
try
var cpSettings = await CpSettings.GetSettingsAsync();
if (cpSettings.Enabled)
{
var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
try
{
return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false);
});
if (result != null)
var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
{
return
await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey))
.ConfigureAwait(false);
});
if (result != null)
{
qualities =
result.list.Select(x => new QualityModel { Id = x._id, Name = x.label }).ToList();
}
}
catch (Exception e)
{
qualities = result.list.Select(x => new QualityModel { Id = x._id, Name = x.label }).ToList();
Log.Info(e);
}
}
catch (Exception e)
if (radarr.Enabled)
{
Log.Info(e);
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.RadarrRootFolders, async () =>
{
return await Task.Run(() => RadarrApi.GetRootFolders(radarr.ApiKey, radarr.FullUri));
});
rootFolders =
rootFoldersResult.Select(
x => new RootFolderModel { Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace })
.ToList();
var result = await Cache.GetOrSetAsync(CacheKeys.RadarrQualityProfiles, async () =>
{
return await Task.Run(() => RadarrApi.GetProfiles(radarr.ApiKey, radarr.FullUri));
});
qualities = result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList();
}
}
catch (Exception e)
{
Log.Error(e);
}
}
var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests);
var allowViewUsers = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ViewUsers);
var viewModel = dbMovies.Select(movie => new RequestViewModel
{
ProviderId = movie.ProviderId,
@ -180,7 +226,7 @@ namespace Ombi.UI.Modules
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = canManageRequest ? movie.AllUsers.ToArray() : new string[] { },
RequestedUsers = canManageRequest || allowViewUsers ? movie.AllUsers.ToArray() : new string[] { },
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = canManageRequest,
@ -188,6 +234,9 @@ namespace Ombi.UI.Modules
Denied = movie.Denied,
DeniedReason = movie.DeniedReason,
Qualities = qualities.ToArray(),
HasRootFolders = rootFolders.Any(),
RootFolders = rootFolders.ToArray(),
CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null
}).ToList();
return Response.AsJson(viewModel);
@ -220,14 +269,14 @@ namespace Ombi.UI.Modules
});
qualities = result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList();
var rootFoldersResult =await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () =>
{
return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
rootFolders = rootFoldersResult.Select(x => new RootFolderModel { Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace}).ToList();
}
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () =>
{
return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
rootFolders = rootFoldersResult.Select(x => new RootFolderModel { Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace }).ToList();
}
else
{
var sickRageSettings = await SickRageSettings.GetSettingsAsync();
@ -247,6 +296,8 @@ namespace Ombi.UI.Modules
var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests);
var allowViewUsers = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ViewUsers);
var viewModel = dbTv.Select(tv => new RequestViewModel
{
ProviderId = tv.ProviderId,
@ -254,7 +305,7 @@ namespace Ombi.UI.Modules
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath.Contains("http:") ? tv.PosterPath.Replace("http:", "https:") : tv.PosterPath, // We make the poster path https on request, but this is just incase
PosterPath = tv.PosterPath?.Contains("http:") ?? false ? tv.PosterPath?.Replace("http:", "https:") : tv.PosterPath ?? string.Empty, // We make the poster path https on request, but this is just incase
ReleaseDate = tv.ReleaseDate,
ReleaseDateTicks = tv.ReleaseDate.Ticks,
RequestedDate = tv.RequestedDate,
@ -263,7 +314,7 @@ namespace Ombi.UI.Modules
Approved = tv.Available || tv.Approved,
Title = tv.Title,
Overview = tv.Overview,
RequestedUsers = canManageRequest ? tv.AllUsers.ToArray() : new string[] { },
RequestedUsers = canManageRequest || allowViewUsers ? tv.AllUsers.ToArray() : new string[] { },
ReleaseYear = tv.ReleaseDate.Year.ToString(),
Available = tv.Available,
Admin = canManageRequest,
@ -273,7 +324,7 @@ namespace Ombi.UI.Modules
TvSeriesRequestType = tv.SeasonsRequested,
Qualities = qualities.ToArray(),
Episodes = tv.Episodes.ToArray(),
RootFolders = rootFolders.ToArray(),
RootFolders = rootFolders.ToArray(),
HasRootFolders = rootFolders.Any(),
CurrentRootPath = sonarrSettings.Enabled ? GetRootPath(tv.RootFolderSelected, sonarrSettings).Result : null
}).ToList();
@ -293,13 +344,48 @@ namespace Ombi.UI.Modules
return r.path;
}
// Return default path
return rootFoldersResult.FirstOrDefault(x => x.id.Equals(int.Parse(sonarrSettings.RootPath)))?.path ?? string.Empty;
int outRoot;
var defaultPath = int.TryParse(sonarrSettings.RootPath, out outRoot);
if (defaultPath)
{
// Return default path
return rootFoldersResult.FirstOrDefault(x => x.id.Equals(outRoot))?.path ?? string.Empty;
}
else
{
return rootFoldersResult.FirstOrDefault()?.path ?? string.Empty;
}
}
private async Task<string> GetRootPath(int pathId, RadarrSettings radarrSettings)
{
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.RadarrRootFolders, async () =>
{
return await Task.Run(() => RadarrApi.GetRootFolders(radarrSettings.ApiKey, radarrSettings.FullUri));
});
foreach (var r in rootFoldersResult.Where(r => r.id == pathId))
{
return r.path;
}
int outRoot;
var defaultPath = int.TryParse(radarrSettings.RootPath, out outRoot);
if (defaultPath)
{
// Return default path
return rootFoldersResult.FirstOrDefault(x => x.id.Equals(outRoot))?.path ?? string.Empty;
}
else
{
return rootFoldersResult.FirstOrDefault()?.path ?? string.Empty;
}
}
private async Task<Response> GetAlbumRequests()
{
var settings = PrSettings.GetSettings();
var dbAlbum = await Service.GetAllAsync();
dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album);
if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) && !IsAdmin)
@ -438,8 +524,21 @@ namespace Ombi.UI.Modules
originalRequest.Available = available;
var result = await Service.UpdateRequestAsync(originalRequest);
var plexService = await PlexSettings.GetSettingsAsync();
await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken, available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined);
var plexSettings = await PlexSettings.GetSettingsAsync();
if (plexSettings.Enable)
{
await
PlexNotificationEngine.NotifyUsers(originalRequest,
available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined);
}
var embySettings = await EmbySettings.GetSettingsAsync();
if (embySettings.Enable)
{
await EmbyNotificationEngine.NotifyUsers(originalRequest,
available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined);
}
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" });
@ -461,11 +560,21 @@ namespace Ombi.UI.Modules
return Response.AsJson(vm);
}
private async Task<Response> ChangeRootFolder(int id, int rootFolderId)
private async Task<Response> ChangeRootFolder(RequestType type, int id, int rootFolderId)
{
// Get all root folders
var settings = await SonarrSettings.GetSettingsAsync();
var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
var rootFolders = new List<SonarrRootFolder>();
if (type == RequestType.TvShow)
{
// Get all root folders
var settings = await SonarrSettings.GetSettingsAsync();
rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
else
{
var settings = await Radarr.GetSettingsAsync();
rootFolders = RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
// Get Request
var allRequests = await Service.GetAllAsync();
@ -473,7 +582,7 @@ namespace Ombi.UI.Modules
if (request == null)
{
return Response.AsJson(new JsonResponseModel {Result = false});
return Response.AsJson(new JsonResponseModel { Result = false });
}
foreach (var folder in rootFolders)
@ -487,7 +596,7 @@ namespace Ombi.UI.Modules
await Service.UpdateRequestAsync(request);
return Response.AsJson(new JsonResponseModel {Result = true});
}
}
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
}

View file

@ -47,6 +47,8 @@ namespace Ombi.UI.Modules
public async Task<Response> Netflix(string title)
{
await Task.Yield();
var result = NetflixApi.CheckNetflix(title);
if (!string.IsNullOrEmpty(result.Message))

View file

@ -50,9 +50,11 @@ using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.Services.Notification;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Ombi.UI.Helpers;
@ -67,8 +69,8 @@ namespace Ombi.UI.Modules
{
public class SearchModule : BaseAuthModule
{
public SearchModule(ICacheProvider cache,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
public SearchModule(ICacheProvider cache,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker plexChecker,
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<SickRageSettings> sickRageService, ISickRageApi srApi,
INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi,
@ -77,7 +79,8 @@ namespace Ombi.UI.Modules
ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth,
IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email,
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue, IRepository<PlexContent> content,
ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi, ISettingsService<CustomizationSettings> cus)
ISecurityExtensions security, IMovieSender movieSender, IRadarrCacher radarrCacher, ITraktApi traktApi, ISettingsService<CustomizationSettings> cus,
IEmbyAvailabilityChecker embyChecker, IRepository<EmbyContent> embyContent, ISettingsService<EmbySettings> embySettings)
: base("search", prSettings, security)
{
Auth = auth;
@ -86,7 +89,7 @@ namespace Ombi.UI.Modules
PrService = prSettings;
MovieApi = new TheMovieDbApi();
Cache = cache;
Checker = checker;
PlexChecker = plexChecker;
CpCacher = cpCacher;
SonarrCacher = sonarrCacher;
SickRageCacher = sickRageCacher;
@ -112,9 +115,14 @@ namespace Ombi.UI.Modules
RadarrCacher = radarrCacher;
TraktApi = traktApi;
CustomizationSettings = cus;
EmbyChecker = embyChecker;
EmbyContentRepository = embyContent;
EmbySettings = embySettings;
Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad();
Get["actor/{searchTerm}", true] = async (x, ct) => await SearchPerson((string)x.searchTerm);
Get["actor/new/{searchTerm}", true] = async (x, ct) => await SearchPerson((string)x.searchTerm, true);
Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm);
Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm);
Get["music/{searchTerm}", true] = async (x, ct) => await SearchAlbum((string)x.searchTerm);
@ -135,7 +143,7 @@ namespace Ombi.UI.Modules
async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons);
Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode");
Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId);
Get["/seasons"] = x => GetSeasons();
Get["/episodes", true] = async (x, ct) => await GetEpisodes();
}
@ -143,6 +151,7 @@ namespace Ombi.UI.Modules
private IWatcherCacher WatcherCacher { get; }
private IMovieSender MovieSender { get; }
private IRepository<PlexContent> PlexContentRepository { get; }
private IRepository<EmbyContent> EmbyContentRepository { get; }
private TvMazeApi TvApi { get; }
private IPlexApi PlexApi { get; }
private TheMovieDbApi MovieApi { get; }
@ -152,13 +161,15 @@ namespace Ombi.UI.Modules
private IRequestService RequestService { get; }
private ICacheProvider Cache { get; }
private ISettingsService<AuthenticationSettings> Auth { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<PlexSettings> PlexService { get; }
private ISettingsService<PlexRequestSettings> PrService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; }
private ISettingsService<SickRageSettings> SickRageService { get; }
private ISettingsService<HeadphonesSettings> HeadphonesService { get; }
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
private IAvailabilityChecker Checker { get; }
private IAvailabilityChecker PlexChecker { get; }
private IEmbyAvailabilityChecker EmbyChecker { get; }
private ICouchPotatoCacher CpCacher { get; }
private ISonarrCacher SonarrCacher { get; }
private ISickRageCacher SickRageCacher { get; }
@ -173,15 +184,27 @@ namespace Ombi.UI.Modules
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private long _plexMovieCacheTime = 0;
private IEnumerable<PlexContent> _plexMovies;
private long _embyMovieCacheTime = 0;
private IEnumerable<EmbyContent> _embyMovies;
private long _dbMovieCacheTime = 0;
private Dictionary<int, RequestedModel> _dbMovies;
private async Task<Negotiator> RequestLoad()
{
var settings = await PrService.GetSettingsAsync();
var custom = await CustomizationSettings.GetSettingsAsync();
var emby = await EmbySettings.GetSettingsAsync();
var plex = await PlexService.GetSettingsAsync();
var searchViewModel = new SearchLoadViewModel
{
Settings = settings,
CustomizationSettings = custom
CustomizationSettings = custom,
Emby = emby.Enable,
Plex = plex.Enable
};
@ -209,6 +232,53 @@ namespace Ombi.UI.Modules
return await ProcessMovies(MovieSearchType.Search, searchTerm);
}
private async Task<Response> SearchPerson(string searchTerm)
{
var movies = TransformMovieListToMovieResultList(await MovieApi.SearchPerson(searchTerm));
return await TransformMovieResultsToResponse(movies);
}
private async Task<Response> SearchPerson(string searchTerm, bool filterExisting)
{
var movies = TransformMovieListToMovieResultList(await MovieApi.SearchPerson(searchTerm, AlreadyAvailable));
return await TransformMovieResultsToResponse(movies);
}
private async Task<bool> AlreadyAvailable(int id, string title, string year)
{
var plexSettings = await PlexService.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
return IsMovieInCache(id, String.Empty) ||
(plexSettings.Enable && PlexChecker.IsMovieAvailable(PlexMovies(), title, year)) ||
(embySettings.Enable && EmbyChecker.IsMovieAvailable(EmbyMovies(), title, year, String.Empty));
}
private IEnumerable<PlexContent> PlexMovies()
{ long now = DateTime.Now.Ticks;
if(_plexMovies == null || (now - _plexMovieCacheTime) > 10000)
{
var content = PlexContentRepository.GetAll();
_plexMovies = PlexChecker.GetPlexMovies(content);
_plexMovieCacheTime = now;
}
return _plexMovies;
}
private IEnumerable<EmbyContent> EmbyMovies()
{
long now = DateTime.Now.Ticks;
if (_embyMovies == null || (now - _embyMovieCacheTime) > 10000)
{
var content = EmbyContentRepository.GetAll();
_embyMovies = EmbyChecker.GetEmbyMovies(content);
_embyMovieCacheTime = now;
}
return _embyMovies;
}
private Response GetTvPoster(int theTvDbId)
{
var result = TvApi.ShowLookupByTheTvDbId(theTvDbId);
@ -220,15 +290,10 @@ namespace Ombi.UI.Modules
}
return banner;
}
private async Task<Response> ProcessMovies(MovieSearchType searchType, string searchTerm)
{
List<MovieResult> apiMovies;
switch (searchType)
{
case MovieSearchType.Search:
var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false);
apiMovies = movies.Select(x =>
private List<MovieResult> TransformSearchMovieListToMovieResultList(List<TMDbLib.Objects.Search.SearchMovie> searchMovies)
{
return searchMovies.Select(x =>
new MovieResult
{
Adult = x.Adult,
@ -247,6 +312,39 @@ namespace Ombi.UI.Modules
VoteCount = x.VoteCount
})
.ToList();
}
private List<MovieResult> TransformMovieListToMovieResultList(List<TMDbLib.Objects.Movies.Movie> movies)
{
return movies.Select(x =>
new MovieResult
{
Adult = x.Adult,
BackdropPath = x.BackdropPath,
GenreIds = x.Genres.Select(y => y.Id).ToList(),
Id = x.Id,
OriginalLanguage = x.OriginalLanguage,
OriginalTitle = x.OriginalTitle,
Overview = x.Overview,
Popularity = x.Popularity,
PosterPath = x.PosterPath,
ReleaseDate = x.ReleaseDate,
Title = x.Title,
Video = x.Video,
VoteAverage = x.VoteAverage,
VoteCount = x.VoteCount
})
.ToList();
}
private async Task<Response> ProcessMovies(MovieSearchType searchType, string searchTerm)
{
List<MovieResult> apiMovies;
switch (searchType)
{
case MovieSearchType.Search:
var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false);
apiMovies = TransformSearchMovieListToMovieResultList(movies);
break;
case MovieSearchType.CurrentlyPlaying:
apiMovies = await MovieApi.GetCurrentPlayingMovies();
@ -259,21 +357,31 @@ namespace Ombi.UI.Modules
break;
}
var allResults = await RequestService.GetAllAsync();
allResults = allResults.Where(x => x.Type == RequestType.Movie);
return await TransformMovieResultsToResponse(apiMovies);
}
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
var dbMovies = distinctResults.ToDictionary(x => x.ProviderId);
private async Task<Dictionary<int, RequestedModel>> RequestedMovies()
{
long now = DateTime.Now.Ticks;
if (_dbMovies == null || (now - _dbMovieCacheTime) > 10000)
{
var allResults = await RequestService.GetAllAsync();
allResults = allResults.Where(x => x.Type == RequestType.Movie);
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
_dbMovies = distinctResults.ToDictionary(x => x.ProviderId);
_dbMovieCacheTime = now;
}
return _dbMovies;
}
var cpCached = CpCacher.QueuedIds();
var watcherCached = WatcherCacher.QueuedIds();
var radarrCached = RadarrCacher.QueuedIds();
var content = PlexContentRepository.GetAll();
var plexMovies = Checker.GetPlexMovies(content);
private async Task<Response> TransformMovieResultsToResponse(List<MovieResult> movies)
{
await Task.Yield();
var viewMovies = new List<SearchMovieViewModel>();
var counter = 0;
foreach (var movie in apiMovies)
Dictionary<int, RequestedModel> dbMovies = await RequestedMovies();
foreach (var movie in movies)
{
var viewMovie = new SearchMovieViewModel
{
@ -293,11 +401,10 @@ namespace Ombi.UI.Modules
VoteCount = movie.VoteCount
};
var imdbId = string.Empty;
if (counter <= 5) // Let's only do it for the first 5 items
{
var movieInfo = MovieApi.GetMovieInformationWithVideos(movie.Id);
// TODO needs to be careful about this, it's adding extra time to search...
// https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2
viewMovie.ImdbId = movieInfo?.imdb_id;
@ -313,16 +420,37 @@ namespace Ombi.UI.Modules
counter++;
}
var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), dbMovies);
var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(),
imdbId);
if (plexMovie != null)
var plexSettings = await PlexService.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
if (plexSettings.Enable)
{
viewMovie.Available = true;
viewMovie.PlexUrl = plexMovie.Url;
var content = PlexContentRepository.GetAll();
var plexMovies = PlexChecker.GetPlexMovies(content);
var plexMovie = PlexChecker.GetMovie(plexMovies.ToArray(), movie.Title,
movie.ReleaseDate?.Year.ToString(),
viewMovie.ImdbId);
if (plexMovie != null)
{
viewMovie.Available = true;
viewMovie.PlexUrl = plexMovie.Url;
}
}
else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db
if (embySettings.Enable)
{
var embyContent = EmbyContentRepository.GetAll();
var embyMovies = EmbyChecker.GetEmbyMovies(embyContent);
var embyMovie = EmbyChecker.GetMovie(embyMovies.ToArray(), movie.Title,
movie.ReleaseDate?.Year.ToString(), viewMovie.ImdbId);
if (embyMovie != null)
{
viewMovie.Available = true;
}
}
if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db
{
var dbm = dbMovies[movie.Id];
@ -330,20 +458,11 @@ namespace Ombi.UI.Modules
viewMovie.Approved = dbm.Approved;
viewMovie.Available = dbm.Available;
}
else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db
else if (canSee)
{
viewMovie.Approved = true;
viewMovie.Requested = true;
}
else if(watcherCached.Contains(imdbId) && canSee) // compare to the watcher db
{
viewMovie.Approved = true;
viewMovie.Requested = true;
}
else if (radarrCached.Contains(movie.Id) && canSee)
{
viewMovie.Approved = true;
viewMovie.Requested = true;
bool exists = IsMovieInCache(movie, viewMovie.ImdbId);
viewMovie.Approved = exists;
viewMovie.Requested = exists;
}
viewMovies.Add(viewMovie);
}
@ -351,6 +470,19 @@ namespace Ombi.UI.Modules
return Response.AsJson(viewMovies);
}
private bool IsMovieInCache(MovieResult movie, string imdbId)
{ int id = movie.Id;
return IsMovieInCache(id, imdbId);
}
private bool IsMovieInCache(int id, string imdbId)
{ var cpCached = CpCacher.QueuedIds();
var watcherCached = WatcherCacher.QueuedIds();
var radarrCached = RadarrCacher.QueuedIds();
return cpCached.Contains(id) || watcherCached.Contains(imdbId) || radarrCached.Contains(id);
}
private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests,
Dictionary<int, RequestedModel> moviesInDb)
{
@ -372,11 +504,11 @@ namespace Ombi.UI.Modules
case ShowSearchType.Popular:
Analytics.TrackEventAsync(Category.Search, Action.TvShow, "Popular", Username, CookieHelper.GetAnalyticClientId(Cookies));
var popularShows = await TraktApi.GetPopularShows();
foreach (var popularShow in popularShows)
{
var theTvDbId = int.Parse(popularShow.Ids.Tvdb.ToString());
var model = new SearchTvShowViewModel
{
FirstAired = popularShow.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"),
@ -405,6 +537,11 @@ namespace Ombi.UI.Modules
{
var show = anticipatedShow.Show;
var theTvDbId = int.Parse(show.Ids.Tvdb.ToString());
var result = TvApi.ShowLookupByTheTvDbId(theTvDbId);
if (result == null)
{
continue;
}
var model = new SearchTvShowViewModel
{
@ -434,6 +571,12 @@ namespace Ombi.UI.Modules
{
var show = watched.Show;
var theTvDbId = int.Parse(show.Ids.Tvdb.ToString());
var result = TvApi.ShowLookupByTheTvDbId(theTvDbId);
if (result == null)
{
continue;
}
var model = new SearchTvShowViewModel
{
FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"),
@ -462,6 +605,12 @@ namespace Ombi.UI.Modules
{
var show = watched.Show;
var theTvDbId = int.Parse(show.Ids.Tvdb.ToString());
var result = TvApi.ShowLookupByTheTvDbId(theTvDbId);
if (result == null)
{
continue;
}
var model = new SearchTvShowViewModel
{
FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"),
@ -493,51 +642,55 @@ namespace Ombi.UI.Modules
private async Task<List<SearchTvShowViewModel>> MapToTvModel(List<SearchTvShowViewModel> shows, PlexRequestSettings prSettings)
{
var plexSettings = await PlexService.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var providerId = string.Empty;
// Get the requests
var allResults = await RequestService.GetAllAsync();
allResults = allResults.Where(x => x.Type == RequestType.TvShow);
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
var dbTv = distinctResults.ToDictionary(x => x.ProviderId);
// Check the external applications
var sonarrCached = SonarrCacher.QueuedIds().ToList();
var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays
var dbTv = distinctResults.ToDictionary(x => x.ImdbId);
var content = PlexContentRepository.GetAll();
var plexTvShows = Checker.GetPlexTvShows(content).ToList();
var plexTvShows = PlexChecker.GetPlexTvShows(content);
var embyContent = EmbyContentRepository.GetAll();
var embyCached = EmbyChecker.GetEmbyTvShows(embyContent).ToList();
foreach (var show in shows)
{
if (plexSettings.AdvancedSearch)
var providerId = show.Id.ToString();
if (embySettings.Enable)
{
providerId = show.Id.ToString();
var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4), providerId);
if (embyShow != null)
{
show.Available = true;
}
}
if (plexSettings.Enable)
{
var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4),
providerId);
if (plexShow != null)
{
show.Available = true;
show.PlexUrl = plexShow.Url;
}
}
var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), show.SeriesName, show.FirstAired?.Substring(0, 4),
providerId);
if (plexShow != null)
if (show.ImdbId != null && !show.Available)
{
show.Available = true;
show.PlexUrl = plexShow.Url;
}
else
{
if (dbTv.ContainsKey(show.Id))
var imdbId = show.ImdbId;
if (dbTv.ContainsKey(imdbId))
{
var dbt = dbTv[show.Id];
var dbt = dbTv[imdbId];
show.Requested = true;
show.Episodes = dbt.Episodes.ToList();
show.Approved = dbt.Approved;
}
if (sonarrCached.Select(x => x.TvdbId).Contains(show.Id) || sickRageCache.Contains(show.Id))
// compare to the sonarr/sickrage db
{
show.Requested = true;
}
}
}
return shows;
@ -549,6 +702,7 @@ namespace Ombi.UI.Modules
Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username,
CookieHelper.GetAnalyticClientId(Cookies));
var plexSettings = await PlexService.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var prSettings = await PrService.GetSettingsAsync();
var providerId = string.Empty;
@ -571,7 +725,9 @@ namespace Ombi.UI.Modules
var sonarrCached = SonarrCacher.QueuedIds();
var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays
var content = PlexContentRepository.GetAll();
var plexTvShows = Checker.GetPlexTvShows(content);
var plexTvShows = PlexChecker.GetPlexTvShows(content);
var embyContent = EmbyContentRepository.GetAll();
var embyCached = EmbyChecker.GetEmbyTvShows(embyContent);
var viewTv = new List<SearchTvShowViewModel>();
foreach (var t in apiTv)
@ -605,20 +761,28 @@ namespace Ombi.UI.Modules
EnableTvRequestsForOnlySeries = (prSettings.DisableTvRequestsByEpisode && prSettings.DisableTvRequestsBySeason)
};
providerId = viewT.Id.ToString();
if (plexSettings.AdvancedSearch)
if (embySettings.Enable)
{
providerId = viewT.Id.ToString();
var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId);
if (embyShow != null)
{
viewT.Available = true;
}
}
var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4),
if (plexSettings.Enable)
{
var plexShow = PlexChecker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4),
providerId);
if (plexShow != null)
{
viewT.Available = true;
viewT.PlexUrl = plexShow.Url;
if (plexShow != null)
{
viewT.Available = true;
viewT.PlexUrl = plexShow.Url;
}
}
else if (t.show?.externals?.thetvdb != null)
if (t.show?.externals?.thetvdb != null && !viewT.Available)
{
var tvdbid = (int)t.show.externals.thetvdb;
if (dbTv.ContainsKey(tvdbid))
@ -658,7 +822,7 @@ namespace Ombi.UI.Modules
var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId);
var content = PlexContentRepository.GetAll();
var plexAlbums = Checker.GetPlexAlbums(content);
var plexAlbums = PlexChecker.GetPlexAlbums(content);
var viewAlbum = new List<SearchMusicViewModel>();
foreach (var a in apiAlbums)
@ -678,7 +842,7 @@ namespace Ombi.UI.Modules
DateTime release;
DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release);
var artist = a.ArtistCredit?.FirstOrDefault()?.artist;
var plexAlbum = Checker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name);
var plexAlbum = PlexChecker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name);
if (plexAlbum != null)
{
viewA.Available = true;
@ -719,7 +883,7 @@ namespace Ombi.UI.Modules
Message = "You have reached your weekly request limit for Movies! Please contact your admin."
});
}
var embySettings = await EmbySettings.GetSettingsAsync();
Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username,
CookieHelper.GetAnalyticClientId(Cookies));
var movieInfo = await MovieApi.GetMovieInformation(movieId);
@ -760,8 +924,8 @@ namespace Ombi.UI.Modules
{
var content = PlexContentRepository.GetAll();
var movies = Checker.GetPlexMovies(content);
if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
var movies = PlexChecker.GetPlexMovies(content);
if (PlexChecker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
{
return
Response.AsJson(new JsonResponseModel
@ -778,7 +942,7 @@ namespace Ombi.UI.Modules
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName)
Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName,GetMediaServerName())
});
}
//#endif
@ -817,13 +981,13 @@ namespace Ombi.UI.Modules
return
Response.AsJson(new JsonResponseModel
{
Message = "Could not add movie, please contract your administrator",
Message = "Could not add movie, please contact your administrator",
Result = false
});
}
if (!result.MovieSendingEnabled)
{
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
@ -918,7 +1082,7 @@ namespace Ombi.UI.Modules
});
}
}
var embySettings = await EmbySettings.GetSettingsAsync();
var showInfo = TvApi.ShowLookupByTheTvDbId(showId);
DateTime firstAir;
DateTime.TryParse(showInfo.premiered, out firstAir);
@ -1025,7 +1189,7 @@ namespace Ombi.UI.Modules
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}"
Message = $"{fullShowName} {string.Format(Resources.UI.Search_AlreadyInPlex,embySettings.Enable ? "Emby" : "Plex")}"
});
}
}
@ -1043,66 +1207,134 @@ namespace Ombi.UI.Modules
try
{
var content = PlexContentRepository.GetAll();
var shows = Checker.GetPlexTvShows(content);
var providerId = string.Empty;
var plexSettings = await PlexService.GetSettingsAsync();
if (plexSettings.AdvancedSearch)
if (plexSettings.Enable)
{
providerId = showId.ToString();
}
if (episodeRequest)
{
var cachedEpisodesTask = await Checker.GetEpisodes();
var cachedEpisodes = cachedEpisodesTask.ToList();
foreach (var d in difference) // difference is from an existing request
{
if (
cachedEpisodes.Any(
x =>
x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber &&
x.ProviderId == providerId))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message =
$"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}"
});
}
}
var content = PlexContentRepository.GetAll();
var shows = PlexChecker.GetPlexTvShows(content);
var diff = await GetEpisodeRequestDifference(showId, model);
model.Episodes = diff.ToList();
}
else
{
if (plexSettings.EnableTvEpisodeSearching)
var providerId = string.Empty;
if (plexSettings.AdvancedSearch)
{
foreach (var s in showInfo.Season)
providerId = showId.ToString();
}
if (episodeRequest)
{
var cachedEpisodesTask = await PlexChecker.GetEpisodes();
var cachedEpisodes = cachedEpisodesTask.ToList();
foreach (var d in difference) // difference is from an existing request
{
var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber);
if (result)
if (
cachedEpisodes.Any(
x =>
x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber &&
x.ProviderId == providerId))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}"
Message =
$"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}"
});
}
}
var diff = await GetEpisodeRequestDifference(showId, model);
model.Episodes = diff.ToList();
}
else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4),
providerId, model.SeasonList))
else
{
return
Response.AsJson(new JsonResponseModel
if (plexSettings.EnableTvEpisodeSearching)
{
foreach (var s in showInfo.Season)
{
Result = false,
Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}"
});
var result = PlexChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber,
s.EpisodeNumber);
if (result)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{fullShowName} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}"
});
}
}
}
else if (PlexChecker.IsTvShowAvailable(shows.ToArray(), showInfo.name,
showInfo.premiered?.Substring(0, 4),
providerId, model.SeasonList))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{fullShowName} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}"
});
}
}
}
if (embySettings.Enable)
{
var embyContent = EmbyContentRepository.GetAll();
var embyMovies = EmbyChecker.GetEmbyTvShows(embyContent);
var providerId = showId.ToString();
if (episodeRequest)
{
var cachedEpisodesTask = await EmbyChecker.GetEpisodes();
var cachedEpisodes = cachedEpisodesTask.ToList();
foreach (var d in difference) // difference is from an existing request
{
if (
cachedEpisodes.Any(
x =>
x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber &&
x.ProviderId == providerId))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message =
$"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {string.Format(Resources.UI.Search_AlreadyInPlex,GetMediaServerName())}"
});
}
}
var diff = await GetEpisodeRequestDifference(showId, model);
model.Episodes = diff.ToList();
}
else
{
if (embySettings.EnableEpisodeSearching)
{
foreach (var s in showInfo.Season)
{
var result = EmbyChecker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber,
s.EpisodeNumber);
if (result)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{fullShowName} is already in Emby!"
});
}
}
}
else if (EmbyChecker.IsTvShowAvailable(embyMovies.ToArray(), showInfo.name,
showInfo.premiered?.Substring(0, 4),
providerId, model.SeasonList))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"{fullShowName} is already in Emby!"
});
}
}
}
}
@ -1112,7 +1344,7 @@ namespace Ombi.UI.Modules
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName)
Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName,GetMediaServerName())
});
}
@ -1187,9 +1419,7 @@ namespace Ombi.UI.Modules
private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings)
{
var sendNotification = ShouldAutoApprove(type)
? !prSettings.IgnoreNotifyForAutoApprovedRequests
: true;
var sendNotification = !ShouldAutoApprove(type) || !prSettings.IgnoreNotifyForAutoApprovedRequests;
if (IsAdmin)
{
@ -1261,8 +1491,8 @@ namespace Ombi.UI.Modules
var content = PlexContentRepository.GetAll();
var albums = Checker.GetPlexAlbums(content);
var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"),
var albums = PlexChecker.GetPlexAlbums(content);
var alreadyInPlex = PlexChecker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"),
artist.name);
if (alreadyInPlex)
@ -1280,6 +1510,7 @@ namespace Ombi.UI.Modules
{
Title = albumInfo.title,
MusicBrainzId = albumInfo.id,
ReleaseId = releaseId,
Overview = albumInfo.disambiguation,
PosterPath = img,
Type = RequestType.Album,
@ -1348,7 +1579,7 @@ namespace Ombi.UI.Modules
return img;
}
private Response GetSeasons()
{
var seriesId = (int)Request.Query.tvId;
@ -1390,7 +1621,8 @@ namespace Ombi.UI.Modules
var existingRequest = requests.FirstOrDefault(x => x.Type == RequestType.TvShow && x.TvDbId == providerId.ToString());
var show = await Task.Run(() => TvApi.ShowLookupByTheTvDbId(providerId));
var tvMaxeEpisodes = await Task.Run(() => TvApi.EpisodeLookup(show.id));
var tvMazeEpisodesTask = await Task.Run(() => TvApi.EpisodeLookup(show.id));
var tvMazeEpisodes = tvMazeEpisodesTask.ToList();
var sonarrEpisodes = new List<SonarrEpisodes>();
if (sonarrEnabled)
@ -1400,26 +1632,59 @@ namespace Ombi.UI.Modules
sonarrEpisodes = sonarrEp?.ToList() ?? new List<SonarrEpisodes>();
}
var plexCacheTask = await Checker.GetEpisodes(providerId);
var plexCache = plexCacheTask.ToList();
foreach (var ep in tvMaxeEpisodes)
var plexSettings = await PlexService.GetSettingsAsync();
if (plexSettings.Enable)
{
var requested = existingRequest?.Episodes
.Any(episodesModel =>
ep.number == episodesModel.EpisodeNumber && ep.season == episodesModel.SeasonNumber) ?? false;
var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season);
var inSonarr = sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile);
model.Add(new EpisodeListViewModel
var plexCacheTask = await PlexChecker.GetEpisodes(providerId);
var plexCache = plexCacheTask.ToList();
foreach (var ep in tvMazeEpisodes)
{
Id = show.id,
SeasonNumber = ep.season,
EpisodeNumber = ep.number,
Requested = requested || alreadyInPlex || inSonarr,
Name = ep.name,
EpisodeId = ep.id
});
var requested = existingRequest?.Episodes
.Any(episodesModel =>
ep.number == episodesModel.EpisodeNumber &&
ep.season == episodesModel.SeasonNumber) ?? false;
var alreadyInPlex = plexCache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season);
var inSonarr =
sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile);
model.Add(new EpisodeListViewModel
{
Id = show.id,
SeasonNumber = ep.season,
EpisodeNumber = ep.number,
Requested = requested || alreadyInPlex || inSonarr,
Name = ep.name,
EpisodeId = ep.id
});
}
}
var embySettings = await EmbySettings.GetSettingsAsync();
if (embySettings.Enable)
{
var embyCacheTask = await EmbyChecker.GetEpisodes(providerId);
var cache = embyCacheTask.ToList();
foreach (var ep in tvMazeEpisodes)
{
var requested = existingRequest?.Episodes
.Any(episodesModel =>
ep.number == episodesModel.EpisodeNumber &&
ep.season == episodesModel.SeasonNumber) ?? false;
var alreadyInEmby = cache.Any(x => x.EpisodeNumber == ep.number && x.SeasonNumber == ep.season);
var inSonarr =
sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.hasFile);
model.Add(new EpisodeListViewModel
{
Id = show.id,
SeasonNumber = ep.season,
EpisodeNumber = ep.number,
Requested = requested || alreadyInEmby || inSonarr,
Name = ep.name,
EpisodeId = ep.id
});
}
}
return model;
@ -1649,5 +1914,12 @@ namespace Ombi.UI.Modules
return
Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp });
}
private string GetMediaServerName()
{
var e = EmbySettings.GetSettings();
return e.Enable ? "Emby" : "Plex";
}
}
}

View file

@ -34,7 +34,9 @@ using Nancy;
using Nancy.Extensions;
using Nancy.Linker;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
@ -44,6 +46,8 @@ using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Ombi.UI.Authentication;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
@ -54,59 +58,23 @@ namespace Ombi.UI.Modules
public class UserLoginModule : BaseModule
{
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> pr,
ISettingsService<LandingPageSettings> lp, IAnalytics a, IResourceLinker linker, IRepository<UserLogins> userLogins, IPlexUserRepository plexUsers, ICustomUserMapper custom,
ISecurityExtensions security, ISettingsService<UserManagementSettings> userManagementSettings)
ISettingsService<LandingPageSettings> lp, IAnalytics a, IResourceLinker linker, IRepository<UserLogins> userLogins, IExternalUserRepository<PlexUsers> plexUsers, ICustomUserMapper custom,
ISecurityExtensions security, ISettingsService<UserManagementSettings> userManagementSettings, IEmbyApi embyApi, ISettingsService<EmbySettings> emby, IExternalUserRepository<EmbyUsers> embyU)
: base("userlogin", pr, security)
{
AuthService = auth;
LandingPageSettings = lp;
Analytics = a;
Api = api;
PlexApi = api;
PlexSettings = plexSettings;
Linker = linker;
UserLogins = userLogins;
PlexUserRepository = plexUsers;
CustomUserMapper = custom;
UserManagementSettings = userManagementSettings;
//Get["UserLoginIndex", "/", true] = async (x, ct) =>
//{
// if (Request.Query["landing"] == null)
// {
// var s = await LandingPageSettings.GetSettingsAsync();
// if (s.Enabled)
// {
// if (s.BeforeLogin) // Before login
// {
// if (string.IsNullOrEmpty(Username))
// {
// // They are not logged in
// return
// Context.GetRedirect(Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString());
// }
// return Context.GetRedirect(Linker.BuildRelativeUri(Context, "SearchIndex").ToString());
// }
// // After login
// if (string.IsNullOrEmpty(Username))
// {
// // Not logged in yet
// return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString() + "?landing");
// }
// // Send them to landing
// var landingUrl = Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString();
// return Context.GetRedirect(landingUrl);
// }
// }
// if (!string.IsNullOrEmpty(Username) || IsAdmin)
// {
// var url = Linker.BuildRelativeUri(Context, "SearchIndex").ToString();
// return Response.AsRedirect(url);
// }
// var settings = await AuthService.GetSettingsAsync();
// return View["Index", settings];
//};
EmbySettings = emby;
EmbyApi = embyApi;
EmbyUserRepository = embyU;
Post["/", true] = async (x, ct) => await LoginUser();
Get["/logout"] = x => Logout();
@ -157,11 +125,14 @@ namespace Ombi.UI.Modules
private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<LandingPageSettings> LandingPageSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private IPlexApi Api { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexApi PlexApi { get; }
private IEmbyApi EmbyApi { get; }
private IResourceLinker Linker { get; }
private IAnalytics Analytics { get; }
private IRepository<UserLogins> UserLogins { get; }
private IPlexUserRepository PlexUserRepository { get; }
private IExternalUserRepository<PlexUsers> PlexUserRepository { get; }
private IExternalUserRepository<EmbyUsers> EmbyUserRepository { get; }
private ICustomUserMapper CustomUserMapper { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
@ -180,39 +151,69 @@ namespace Ombi.UI.Modules
}
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var authenticated = false;
var isOwner = false;
var userId = string.Empty;
EmbyUser embyUser = null;
if (settings.UserAuthentication) // Check against the users in Plex
if (plexSettings.Enable)
{
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
if (authenticated)
if (settings.UserAuthentication) // Check against the users in Plex
{
userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken);
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
if (authenticated)
{
userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken);
}
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, username))
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
userId = GetOwnerId(plexSettings.PlexAuthToken, username);
}
Log.Debug("Friends list result = {0}", authenticated);
}
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, username))
else if (!settings.UserAuthentication) // No auth, let them pass!
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
userId = GetOwnerId(plexSettings.PlexAuthToken, username);
}
UsersModel dbUser = await IsDbuser(username);
if (dbUser != null) // in the db?
{
var perms = (Permissions)dbUser.Permissions;
authenticated = true;
isOwner = perms.HasFlag(Permissions.Administrator);
userId = dbUser.UserGuid;
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if (!settings.UserAuthentication) // No auth, let them pass!
if (embySettings.Enable)
{
if (settings.UserAuthentication) // Check against the users in Plex
{
Log.Debug("Need to auth");
authenticated = CheckIfEmbyUser(username, embySettings);
if (authenticated)
{
embyUser = GetEmbyUser(username, embySettings);
userId = embyUser?.Id;
}
if (embyUser?.Policy?.IsAdministrator ?? false)
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if (!settings.UserAuthentication) // No auth, let them pass!
{
authenticated = true;
}
}
UsersModel dbUser = await IsDbuser(username);
if (dbUser != null) // in the db?
{
var perms = (Permissions)dbUser.Permissions;
authenticated = true;
isOwner = perms.HasFlag(Permissions.Administrator);
userId = dbUser.UserGuid;
}
if (settings.UsePassword || isOwner || Security.HasPermissions(username, Permissions.Administrator))
@ -230,7 +231,7 @@ namespace Ombi.UI.Modules
{
return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass });
}
var result = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner);
var result = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner, plexSettings.Enable, embySettings.Enable);
var landingSettings = await LandingPageSettings.GetSettingsAsync();
@ -292,36 +293,68 @@ namespace Ombi.UI.Modules
var userId = string.Empty;
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
if (settings.UserAuthentication) // Authenticate with Plex
// attempt local login first as it has the least amount of overhead
userId = CustomUserMapper.ValidateUser(username, password)?.ToString();
if (userId != null)
{
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication)Api.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
authenticated = true;
}
else if (userId == null && plexSettings.Enable)
{
if (settings.UserAuthentication) // Authenticate with Plex
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, signedIn.user?.username))
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication) PlexApi.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
userId = signedIn.user.uuid;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
userId = signedIn.user.uuid;
}
}
if (string.IsNullOrEmpty(userId))
else if (userId == null && embySettings.Enable)
{
// Local user?
userId = CustomUserMapper.ValidateUser(username, password)?.ToString();
if (userId != null)
if (settings.UserAuthentication) // Authenticate with Emby
{
authenticated = true;
Log.Debug("Need to auth and also provide pass");
EmbyUser signedIn = null;
try
{
signedIn = (EmbyUser)EmbyApi.LogIn(username, password, embySettings.ApiKey, embySettings.FullUri);
}
catch (Exception e)
{
Log.Error(e);
}
if (signedIn != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (signedIn?.Policy?.IsAdministrator ?? false)
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
else
{
authenticated = CheckIfEmbyUser(username, embySettings);
Log.Debug("Friends list result = {0}", authenticated);
}
userId = signedIn?.Id;
}
}
}
@ -330,8 +363,7 @@ namespace Ombi.UI.Modules
return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass });
}
var m = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner);
var m = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner, plexSettings.Enable, embySettings.Enable);
var landingSettings = await LandingPageSettings.GetSettingsAsync();
@ -354,7 +386,6 @@ namespace Ombi.UI.Modules
return CustomModuleExtensions.LoginAndRedirect(this, m.LoginGuid, null, retVal.ToString());
}
return Response.AsJson(new { result = true, url = retVal.ToString() });
}
private async Task<Response> LoginUser()
@ -399,7 +430,7 @@ namespace Ombi.UI.Modules
if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex
{
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication)Api.SignIn(username, password);
var signedIn = (PlexAuthentication)PlexApi.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");
@ -553,59 +584,47 @@ namespace Ombi.UI.Modules
public string UserId { get; set; }
}
private async Task<LoginModel> AuthenticationSetup(string userId, string username, int dateTimeOffset, Guid loginGuid, bool isOwner)
private async Task<LoginModel> AuthenticationSetup(string userId, string username, int dateTimeOffset, Guid loginGuid, bool isOwner, bool plex, bool emby)
{
var m = new LoginModel();
var settings = await AuthService.GetSettingsAsync();
var localUsers = await CustomUserMapper.GetUsersAsync();
var plexLocalUsers = await PlexUserRepository.GetAllAsync();
var embyLocalUsers = await EmbyUserRepository.GetAllAsync();
var localUser = false;
UserLogins.Insert(new UserLogins { UserId = userId, Type = UserType.PlexUser, LastLoggedIn = DateTime.UtcNow });
Log.Debug("We are authenticated! Setting session.");
// Add to the session (Used in the BaseModules)
Session[SessionKeys.UsernameKey] = username;
Session[SessionKeys.ClientDateTimeOffsetKey] = dateTimeOffset;
var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username);
if (plexLocal != null)
if (plex)
{
loginGuid = Guid.Parse(plexLocal.LoginId);
var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username);
if (plexLocal != null)
{
loginGuid = Guid.Parse(plexLocal.LoginId);
}
}
if (emby)
{
var embyLocal = embyLocalUsers.FirstOrDefault(x => x.Username == username);
if (embyLocal != null)
{
loginGuid = Guid.Parse(embyLocal.LoginId);
}
}
var dbUser = localUsers.FirstOrDefault(x => x.UserName == username);
if (dbUser != null)
{
loginGuid = Guid.Parse(dbUser.UserGuid);
localUser = true;
}
//if (loginGuid != Guid.Empty)
//{
// if (!settings.UserAuthentication)// Do not need to auth make admin use login screen for now TODO remove this
// {
// if (dbUser != null)
// {
// var perms = (Permissions)dbUser.Permissions;
// if (perms.HasFlag(Permissions.Administrator))
// {
// var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
// Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword;
// //return Response.AsRedirect(uri.ToString());
// }
// }
// if (plexLocal != null)
// {
// var perms = (Permissions)plexLocal.Permissions;
// if (perms.HasFlag(Permissions.Administrator))
// {
// var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
// Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword;
// //return Response.AsRedirect(uri.ToString());
// }
// }
// }
//}
if (loginGuid == Guid.Empty && settings.UserAuthentication)
{
var defaultSettings = UserManagementSettings.GetSettings();
@ -620,21 +639,52 @@ namespace Ombi.UI.Modules
defaultPermissions += (int)Permissions.Administrator;
}
}
// Looks like we still don't have an entry, so this user does not exist
await PlexUserRepository.InsertAsync(new PlexUsers
if (plex)
{
PlexUserId = userId,
UserAlias = string.Empty,
Permissions = (int)defaultPermissions,
Features = UserManagementHelper.GetPermissions(defaultSettings),
Username = username,
EmailAddress = string.Empty, // We don't have it, we will get it on the next scheduled job run (in 30 mins)
LoginId = loginGuid.ToString()
});
// Looks like we still don't have an entry, so this user does not exist
await PlexUserRepository.InsertAsync(new PlexUsers
{
PlexUserId = userId,
UserAlias = string.Empty,
Permissions = (int) defaultPermissions,
Features = UserManagementHelper.GetPermissions(defaultSettings),
Username = username,
EmailAddress = string.Empty,
// We don't have it, we will get it on the next scheduled job run (in 30 mins)
LoginId = loginGuid.ToString()
});
}
if (emby)
{
await EmbyUserRepository.InsertAsync(new EmbyUsers
{
EmbyUserId = userId,
UserAlias = string.Empty,
Permissions = (int)defaultPermissions,
Features = UserManagementHelper.GetPermissions(defaultSettings),
Username = username,
EmailAddress = string.Empty,
LoginId = loginGuid.ToString()
});
}
}
m.LoginGuid = loginGuid;
m.UserId = userId;
var type = UserType.LocalUser;
if (localUser)
{
type = UserType.LocalUser;
}
else if (plex)
{
type = UserType.PlexUser;
}
else if (emby)
{
type = UserType.EmbyUser;;
}
UserLogins.Insert(new UserLogins { UserId = userId, Type = type, LastLoggedIn = DateTime.UtcNow });
return m;
}
@ -651,7 +701,7 @@ namespace Ombi.UI.Modules
private bool CheckIfUserIsOwner(string authToken, string userName)
{
var userAccount = Api.GetAccount(authToken);
var userAccount = PlexApi.GetAccount(authToken);
if (userAccount == null)
{
return false;
@ -661,7 +711,7 @@ namespace Ombi.UI.Modules
private string GetOwnerId(string authToken, string userName)
{
var userAccount = Api.GetAccount(authToken);
var userAccount = PlexApi.GetAccount(authToken);
if (userAccount == null)
{
return string.Empty;
@ -671,15 +721,45 @@ namespace Ombi.UI.Modules
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{
var users = Api.GetUsers(authToken);
var users = PlexApi.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 CheckIfEmbyUser(string username, EmbySettings s)
{
try
{
var users = EmbyApi.GetUsers(s.FullUri, s.ApiKey);
var allUsers = users?.Where(x => !string.IsNullOrEmpty(x.Name));
return allUsers != null && allUsers.Any(x => x.Name.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
catch (Exception e)
{
Log.Error(e);
return false;
}
}
private EmbyUser GetEmbyUser(string username, EmbySettings s)
{
try
{
var users = EmbyApi.GetUsers(s.FullUri, s.ApiKey);
var allUsers = users?.Where(x => !string.IsNullOrEmpty(x.Name));
return allUsers?.FirstOrDefault(x => x.Name.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
catch (Exception e)
{
Log.Error(e);
return null;
}
}
private string GetUserIdIsInPlexFriends(string username, string authToken)
{
var users = Api.GetUsers(authToken);
var users = PlexApi.GetUsers(authToken);
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title));
return allUsers?.Where(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)).Select(x => x.Id).FirstOrDefault();
}

View file

@ -7,6 +7,7 @@ using Nancy.Extensions;
using Nancy.Responses.Negotiation;
using Newtonsoft.Json;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.Models;
@ -16,6 +17,8 @@ using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Ombi.UI.Models;
using Ombi.UI.Models.UserManagement;
@ -26,8 +29,8 @@ namespace Ombi.UI.Modules
{
public class UserManagementModule : BaseModule
{
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins, IPlexUserRepository plexRepo
, ISecurityExtensions security, IRequestService req, IAnalytics ana) : base("usermanagement", pr, security)
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins, IExternalUserRepository<PlexUsers> plexRepo
, ISecurityExtensions security, IRequestService req, IAnalytics ana, ISettingsService<EmbySettings> embyService, IEmbyApi embyApi, IExternalUserRepository<EmbyUsers> embyRepo) : base("usermanagement", pr, security)
{
#if !DEBUG
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
@ -40,6 +43,9 @@ namespace Ombi.UI.Modules
PlexRequestSettings = pr;
RequestService = req;
Analytics = ana;
EmbySettings = embyService;
EmbyApi = embyApi;
EmbyRepository = embyRepo;
Get["/"] = x => Load();
@ -57,10 +63,13 @@ namespace Ombi.UI.Modules
private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private IRepository<UserLogins> UserLoginsRepo { get; }
private IPlexUserRepository PlexUsersRepository { get; }
private IExternalUserRepository<PlexUsers> PlexUsersRepository { get; }
private IExternalUserRepository<EmbyUsers> EmbyRepository { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IRequestService RequestService { get; }
private IAnalytics Analytics { get; }
private IEmbyApi EmbyApi { get; }
private Negotiator Load()
{
@ -69,47 +78,20 @@ namespace Ombi.UI.Modules
private async Task<Response> LoadUsers()
{
var localUsers = await UserMapper.GetUsersAsync();
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>();
var userLogins = UserLoginsRepo.GetAll().ToList();
foreach (var user in localUsers)
{
var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid);
model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
var plexSettings = await PlexSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken))
var embySettings = await EmbySettings.GetSettingsAsync();
if (plexSettings.Enable)
{
//Get Plex Users
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
if (plexUsers != null && plexUsers.User != null) {
foreach (var u in plexUsers.User) {
var dbUser = plexDbUsers.FirstOrDefault (x => x.PlexUserId == u.Id);
var userDb = userLogins.FirstOrDefault (x => x.UserId == u.Id);
// We don't have the user in the database yet
if (dbUser == null) {
model.Add (MapPlexUser (u, null, userDb?.LastLoggedIn ?? DateTime.MinValue));
} else {
// The Plex User is in the database
model.Add (MapPlexUser (u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
}
// Also get the server admin
var account = PlexApi.GetAccount(plexSettings.PlexAuthToken);
if (account != null)
{
var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == account.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == account.Id);
model.Add(MapPlexAdmin(account, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
model.AddRange(await LoadPlexUsers());
}
if (embySettings.Enable)
{
model.AddRange(await LoadEmbyUsers());
}
model.AddRange(await LoadLocalUsers());
return Response.AsJson(model);
}
@ -217,64 +199,95 @@ namespace Ombi.UI.Modules
}
var plexSettings = await PlexSettings.GetSettingsAsync();
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id);
var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id);
var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id);
if (plexDbUser != null && plexUser != null)
if (plexSettings.Enable)
{
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias);
plexDbUser.UserAlias = model.Alias;
plexDbUser.EmailAddress = model.EmailAddress;
await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
// So it could actually be the admin
var account = PlexApi.GetAccount(plexSettings.PlexAuthToken);
if (plexDbUser != null && account != null)
{
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias);
plexDbUser.UserAlias = model.Alias;
await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexAdmin(account, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
// We have a Plex Account but he's not in the DB
if (plexUser != null)
{
var user = new PlexUsers
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id);
var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id);
var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id);
if (plexDbUser != null && plexUser != null)
{
Permissions = permissionsValue,
Features = featuresValue,
UserAlias = model.Alias,
PlexUserId = plexUser.Id,
EmailAddress = plexUser.Email,
Username = plexUser.Title,
LoginId = Guid.NewGuid().ToString()
};
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
await PlexUsersRepository.InsertAsync(user);
await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias);
plexDbUser.UserAlias = model.Alias;
plexDbUser.EmailAddress = model.EmailAddress;
await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
// So it could actually be the admin
var account = PlexApi.GetAccount(plexSettings.PlexAuthToken);
if (plexDbUser != null && account != null)
{
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias);
plexDbUser.UserAlias = model.Alias;
await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexAdmin(account, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
// We have a Plex Account but he's not in the DB
if (plexUser != null)
{
var user = new PlexUsers
{
Permissions = permissionsValue,
Features = featuresValue,
UserAlias = model.Alias,
PlexUserId = plexUser.Id,
EmailAddress = plexUser.Email,
Username = plexUser.Title,
LoginId = Guid.NewGuid().ToString()
};
await PlexUsersRepository.InsertAsync(user);
var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
}
var embySettings = await EmbySettings.GetSettingsAsync();
if (embySettings.Enable)
{
var embyDbUsers = await EmbyRepository.GetAllAsync();
var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey);
var selectedDbUser = embyDbUsers.FirstOrDefault(x => x.EmbyUserId == model.Id);
var embyUser = embyUsers.FirstOrDefault(x => x.Id == model.Id);
var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id);
if (selectedDbUser != null && embyUser != null)
{
// We have a user in the DB for this Plex Account
selectedDbUser.Permissions = permissionsValue;
selectedDbUser.Features = featuresValue;
await UpdateRequests(selectedDbUser.Username, selectedDbUser.UserAlias, model.Alias);
selectedDbUser.UserAlias = model.Alias;
selectedDbUser.EmailAddress = model.EmailAddress;
await EmbyRepository.UpdateAsync(selectedDbUser);
var retUser = MapEmbyUser(embyUser, selectedDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
return null; // We should never end up here.
}
@ -416,7 +429,7 @@ namespace Ombi.UI.Modules
var m = new UserManagementUsersViewModel
{
Id = plexInfo.Id,
PermissionsFormattedString = newUser ? "Processing..." :( permissions == 0 ? "None" : permissions.ToString()),
PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()),
FeaturesFormattedString = newUser ? "Processing..." : features.ToString(),
Username = plexInfo.Title,
Type = UserType.PlexUser,
@ -436,6 +449,36 @@ namespace Ombi.UI.Modules
return m;
}
private UserManagementUsersViewModel MapEmbyUser(EmbyUser embyInfo, EmbyUsers dbUser, DateTime lastLoggedIn)
{
var newUser = false;
if (dbUser == null)
{
newUser = true;
dbUser = new EmbyUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = embyInfo.Id,
PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()),
FeaturesFormattedString = newUser ? "Processing..." : features.ToString(),
Username = embyInfo.Name,
Type = UserType.EmbyUser,
EmailAddress =dbUser.EmailAddress,
Alias = dbUser?.UserAlias ?? string.Empty,
LastLoggedIn = lastLoggedIn,
ManagedUser = false
};
m.Permissions.AddRange(GetPermissions(permissions));
m.Features.AddRange(GetFeatures(features));
return m;
}
private UserManagementUsersViewModel MapPlexAdmin(PlexAccount plexInfo, PlexUsers dbUser, DateTime lastLoggedIn)
{
var newUser = false;
@ -505,6 +548,93 @@ namespace Ombi.UI.Modules
}
return retVal;
}
private async Task<IEnumerable<UserManagementUsersViewModel>> LoadLocalUsers()
{
var localUsers = await UserMapper.GetUsersAsync(); var userLogins = UserLoginsRepo.GetAll().ToList();
var model = new List<UserManagementUsersViewModel>();
foreach (var user in localUsers)
{
var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid);
model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
return model;
}
private async Task<IEnumerable<UserManagementUsersViewModel>> LoadPlexUsers()
{
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>();
var userLogins = UserLoginsRepo.GetAll().ToList();
var plexSettings = await PlexSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken))
{
//Get Plex Users
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
if (plexUsers?.User != null)
{
foreach (var u in plexUsers.User)
{
var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == u.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id);
// We don't have the user in the database yet
if (dbUser == null)
{
model.Add(MapPlexUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
else
{
// The Plex User is in the database
model.Add(MapPlexUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
}
// Also get the server admin
var account = PlexApi.GetAccount(plexSettings.PlexAuthToken);
if (account != null)
{
var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == account.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == account.Id);
model.Add(MapPlexAdmin(account, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
return model;
}
private async Task<IEnumerable<UserManagementUsersViewModel>> LoadEmbyUsers()
{
var embyDbUsers = await EmbyRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>();
var userLogins = UserLoginsRepo.GetAll().ToList();
var embySettings = await EmbySettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(embySettings.ApiKey))
{
//Get Plex Users
var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey);
if (embyUsers != null)
{
foreach (var u in embyUsers)
{
var dbUser = embyDbUsers.FirstOrDefault(x => x.EmbyUserId == u.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id);
// We don't have the user in the database yet
model.Add(dbUser == null
? MapEmbyUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue)
: MapEmbyUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
}
return model;
}
}
}

View file

@ -1,4 +1,5 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserWizardModule.cs
@ -23,6 +24,7 @@
// 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;
@ -50,8 +52,11 @@ namespace Ombi.UI.Modules
{
public class UserWizardModule : BaseModule
{
public UserWizardModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<PlexSettings> plex, IPlexApi plexApi,
ISettingsService<AuthenticationSettings> auth, ICustomUserMapper m, IAnalytics a, ISecurityExtensions security) : base("wizard", pr, security)
public UserWizardModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<PlexSettings> plex,
IPlexApi plexApi,
ISettingsService<AuthenticationSettings> auth, ICustomUserMapper m, IAnalytics a,
ISecurityExtensions security, IEmbyApi embyApi,
ISettingsService<EmbySettings> embySettings) : base("wizard", pr, security)
{
PlexSettings = plex;
PlexApi = plexApi;
@ -59,10 +64,13 @@ namespace Ombi.UI.Modules
Auth = auth;
Mapper = m;
Analytics = a;
EmbySettings = embySettings;
EmbyApi = embyApi;
Get["/", true] = async (x, ct) =>
{
a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username, CookieHelper.GetAnalyticClientId(Cookies));
a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username,
CookieHelper.GetAnalyticClientId(Cookies));
var settings = await PlexRequestSettings.GetSettingsAsync();
@ -76,7 +84,10 @@ namespace Ombi.UI.Modules
Post["/plex", true] = async (x, ct) => await Plex();
Post["/plexrequest", true] = async (x, ct) => await PlexRequest();
Post["/auth", true] = async (x, ct) => await Authentication();
Post["/createuser",true] = async (x,ct) => await CreateUser();
Post["/createuser", true] = async (x, ct) => await CreateUser();
Post["/embyauth", true] = async (x, ct) => await EmbyAuth();
}
private ISettingsService<PlexSettings> PlexSettings { get; }
@ -85,6 +96,8 @@ namespace Ombi.UI.Modules
private ISettingsService<AuthenticationSettings> Auth { get; }
private ICustomUserMapper Mapper { get; }
private IAnalytics Analytics { get; }
private IEmbyApi EmbyApi { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
@ -95,23 +108,31 @@ namespace Ombi.UI.Modules
if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Please provide a valid username and password" });
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Please provide a valid username and password"
});
}
var model = PlexApi.SignIn(user.username, user.password);
if (model?.user == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect username or password!" });
return
Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect username or password!" });
}
// Set the auth token in the session so we can use it in the next form
Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token;
var servers = PlexApi.GetServer(model.user.authentication_token);
var firstServer = servers.Server.FirstOrDefault();
return Response.AsJson(new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme });
return
Response.AsJson(
new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme });
}
private async Task<Response> Plex()
@ -122,7 +143,8 @@ namespace Ombi.UI.Modules
{
return Response.AsJson(valid.SendJsonError());
}
form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); // Set the auth token from the previous form
form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString();
// Set the auth token from the previous form
// Get the machine ID from the settings (This could have changed)
try
@ -131,6 +153,7 @@ namespace Ombi.UI.Modules
var firstServer = servers.Server.FirstOrDefault(x => x.AccessToken == form.PlexAuthToken);
Session[SessionKeys.UserWizardMachineId] = firstServer?.MachineIdentifier;
form.MachineIdentifier = firstServer?.MachineIdentifier;
}
catch (Exception e)
{
@ -143,7 +166,12 @@ namespace Ombi.UI.Modules
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." });
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not save the settings to the database, please try again."
});
}
private async Task<Response> PlexRequest()
@ -158,14 +186,19 @@ namespace Ombi.UI.Modules
currentSettings.SearchForMovies = form.SearchForMovies;
currentSettings.SearchForTvShows = form.SearchForTvShows;
currentSettings.SearchForMusic = form.SearchForMusic;
var result = await PlexRequestSettings.SaveSettingsAsync(currentSettings);
if (result)
{
return Response.AsJson(new { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." });
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not save the settings to the database, please try again."
});
}
private async Task<Response> Authentication()
@ -177,14 +210,21 @@ namespace Ombi.UI.Modules
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save the settings to the database, please try again." });
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not save the settings to the database, please try again."
});
}
private async Task<Response> CreateUser()
{
var username = (string)Request.Form.Username;
var userId = Mapper.CreateUser(username, Request.Form.Password, EnumHelper<Permissions>.All() - (int)Permissions.ReadOnlyUser, 0);
Analytics.TrackEventAsync(Category.Wizard, Action.Finish, "Finished the wizard", username, CookieHelper.GetAnalyticClientId(Cookies));
var userId = Mapper.CreateUser(username, Request.Form.Password,
EnumHelper<Permissions>.All() - (int)Permissions.ReadOnlyUser, 0);
Analytics.TrackEventAsync(Category.Wizard, Action.Finish, "Finished the wizard", username,
CookieHelper.GetAnalyticClientId(Cookies));
Session[SessionKeys.UsernameKey] = username;
// Destroy the Plex Auth Token
@ -197,7 +237,55 @@ namespace Ombi.UI.Modules
var baseUrl = string.IsNullOrEmpty(settings.BaseUrl) ? string.Empty : $"/{settings.BaseUrl}";
return CustomModuleExtensions.LoginAndRedirect(this,(Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search");
return CustomModuleExtensions.LoginAndRedirect(this, (Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search");
}
private async Task<Response> EmbyAuth()
{
var ip = (string)Request.Form.Ip;
var port = (int)Request.Form.Port;
var apiKey = (string)Request.Form.ApiKey;
var ssl = (bool)Request.Form.Ssl;
var settings = new EmbySettings
{
ApiKey = apiKey,
Enable = true,
Ip = ip,
Port = port,
Ssl = ssl,
};
try
{
// Test that we can connect
var result = EmbyApi.GetUsers(settings.FullUri, apiKey);
if (result != null && result.Any())
{
settings.AdministratorId = result.FirstOrDefault(x => x.Policy.IsAdministrator)?.Id ?? string.Empty;
await EmbySettings.SaveSettingsAsync(settings);
return Response.AsJson(new JsonResponseModel
{
Result = true
});
}
}
catch (Exception e)
{
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"Could not connect to Emby, please check your settings. Error: {e.Message}"
});
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not connect to Emby, please check your settings."
});
}
}
}

View file

@ -50,6 +50,8 @@ namespace Ombi.UI.NinjectModules
Bind<IDiscordApi>().To<DiscordApi>();
Bind<IRadarrApi>().To<RadarrApi>();
Bind<ITraktApi>().To<TraktApi>();
Bind<IEmbyApi>().To<EmbyApi>();
Bind<IAppveyorApi>().To<AppveyorApi>();
}
}
}

View file

@ -56,7 +56,8 @@ namespace Ombi.UI.NinjectModules
Bind<ICustomUserMapper>().To<UserMapper>();
Bind<INotificationService>().To<NotificationService>().InSingletonScope();
Bind<INotificationEngine>().To<NotificationEngine>();
Bind<IPlexNotificationEngine>().To<PlexNotificationEngine>();
Bind<IEmbyNotificationEngine>().To<EmbyNotificationEngine>();
Bind<IStatusChecker>().To<StatusChecker>();

View file

@ -40,6 +40,7 @@ namespace Ombi.UI.NinjectModules
{
Bind<IRepository<UsersModel>>().To<UserRepository<UsersModel>>();
Bind(typeof(IRepository<>)).To(typeof(GenericRepository<>));
Bind(typeof(IExternalUserRepository<>)).To(typeof(BaseExternalUserRepository<>));
Bind<IRequestService>().To<JsonRequestModelRequestService>();
Bind<IRequestRepository>().To<RequestJsonRepository>();
@ -48,7 +49,6 @@ namespace Ombi.UI.NinjectModules
Bind<IJobRecord>().To<JobRecord>();
Bind<IUserRepository>().To<UserRepository>();
Bind<IPlexUserRepository>().To<PlexUserRepository>();
}
}

View file

@ -31,6 +31,8 @@ using Ombi.Core.Queue;
using Ombi.Helpers.Analytics;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.Services.Jobs.Interfaces;
using Ombi.Services.Jobs.RecentlyAddedNewsletter;
using Ombi.UI.Jobs;
using Quartz;
using Quartz.Impl;
@ -47,7 +49,8 @@ namespace Ombi.UI.NinjectModules
Bind<IWatcherCacher>().To<WatcherCacher>();
Bind<ISonarrCacher>().To<SonarrCacher>();
Bind<ISickRageCacher>().To<SickRageCacher>();
Bind<IRecentlyAdded>().To<RecentlyAdded>();
Bind<IRecentlyAdded>().To<RecentlyAddedNewsletter>();
Bind<IMassEmail>().To<RecentlyAddedNewsletter>();
Bind<IRadarrCacher>().To<RadarrCacher>();
Bind<IPlexContentCacher>().To<PlexContentCacher>();
Bind<IJobFactory>().To<CustomJobFactory>();
@ -58,6 +61,14 @@ namespace Ombi.UI.NinjectModules
Bind<IPlexEpisodeCacher>().To<PlexEpisodeCacher>();
Bind<IFaultQueueHandler>().To<FaultQueueHandler>();
Bind<IPlexUserChecker>().To<PlexUserChecker>();
Bind<IEmbyAvailabilityChecker>().To<EmbyAvailabilityChecker>();
Bind<IEmbyContentCacher>().To<EmbyContentCacher>();
Bind<IEmbyEpisodeCacher>().To<EmbyEpisodeCacher>();
Bind<IEmbyUserChecker>().To<EmbyUserChecker>();
Bind<IEmbyAddedNewsletter>().To<EmbyAddedNewsletter>();
Bind<IPlexNewsletter>().To<PlexRecentlyAddedNewsletter>();
Bind<IAnalytics>().To<Analytics>();
Bind<ISchedulerFactory>().To<StdSchedulerFactory>();

View file

@ -266,13 +266,13 @@
<Compile Include="Modules\Admin\UserManagementSettingsModule.cs" />
<Compile Include="Modules\Admin\FaultQueueModule.cs" />
<Compile Include="Modules\Admin\SystemStatusModule.cs" />
<Compile Include="Modules\ApiDocsModule.cs" />
<Compile Include="Modules\ApiSettingsMetadataModule.cs" />
<Compile Include="Modules\ApiUserMetadataModule.cs" />
<Compile Include="Modules\ApiRequestMetadataModule.cs" />
<Compile Include="Modules\ApiSettingsModule.cs" />
<Compile Include="Modules\ApiUserModule.cs" />
<Compile Include="Modules\BaseApiModule.cs" />
<Compile Include="Modules\Api\ApiDocsModule.cs" />
<Compile Include="Modules\Api\ApiSettingsMetadataModule.cs" />
<Compile Include="Modules\Api\ApiUserMetadataModule.cs" />
<Compile Include="Modules\Api\ApiRequestMetadataModule.cs" />
<Compile Include="Modules\Api\ApiSettingsModule.cs" />
<Compile Include="Modules\Api\ApiUserModule.cs" />
<Compile Include="Modules\Api\BaseApiModule.cs" />
<Compile Include="Modules\BaseModule.cs" />
<Compile Include="Modules\BetaModule.cs" />
<Compile Include="Modules\CultureModule.cs" />
@ -295,6 +295,7 @@
</Compile>
<Compile Include="Start\StartupOptions.cs" />
<Compile Include="Start\UpdateValue.cs" />
<Compile Include="Validators\EmbyValidator.cs" />
<Compile Include="Validators\RadarrValidator.cs" />
<Compile Include="Validators\WatcherValidator.cs" />
<Compile Include="Validators\SlackSettingsValidator.cs" />
@ -389,9 +390,105 @@
<DependentUpon>datepicker.css</DependentUpon>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\android-icon-144x144.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\android-icon-192x192.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\android-icon-36x36.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\android-icon-48x48.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\android-icon-72x72.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\android-icon-96x96.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-114x114.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-120x120.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-144x144.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-152x152.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-180x180.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-57x57.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-60x60.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-72x72.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-76x76.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon-precomposed.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\apple-icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\browserconfig.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\favicon-16x16.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\favicon-32x32.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\favicon-96x96.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\favicon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\ms-icon-144x144.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\ms-icon-150x150.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\ms-icon-310x310.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\favicon\ms-icon-70x70.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\helpers\bootbox.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\emby-logo-dark.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\emby-logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\logo original.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\plex-logo-reversed.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\plex-logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\issue-details.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -483,9 +580,6 @@
<Content Include="Content\bootstrap-datetimepicker.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\tooltip\plugins\tooltipster\sideTip\themes\tooltipster-sideTip-borderless.min.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@ -547,7 +641,7 @@
<Content Include="Views\UserManagement\Index.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Compile Include="Modules\ApiRequestModule.cs" />
<Compile Include="Modules\Api\ApiRequestModule.cs" />
<Compile Include="Models\ApiModel.cs" />
<Compile Include="Models\UserManagement\UserManagementUsersViewModel.cs" />
</ItemGroup>
@ -623,6 +717,9 @@
</None>
<None Include="Content\base.scss" />
<None Include="Content\bootstrap-datetimepicker-build.less" />
<None Include="Content\favicon\manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Content\Themes\original.scss" />
<None Include="Content\Themes\plex.scss" />
<Content Include="Content\pace.min.js">
@ -779,6 +876,9 @@
<Content Include="Views\Integration\Watcher.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Views\Admin\MassEmail.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Views\Admin\NewsletterSettings.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -803,6 +903,9 @@
<Content Include="Views\Integration\Radarr.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Admin\Emby.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Web.Debug.config">
<DependentUpon>web.config</DependentUpon>
</None>
@ -838,6 +941,10 @@
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
<Name>Ombi.Api</Name>
</ProjectReference>
<ProjectReference Include="..\Ombi.Common\Ombi.Common.csproj">
<Project>{BFD45569-90CF-47CA-B575-C7B0FF97F67B}</Project>
<Name>Ombi.Common</Name>
</ProjectReference>
<ProjectReference Include="..\Ombi.Core.Migration\Ombi.Core.Migration.csproj">
<Project>{8406EE57-D533-47C0-9302-C6B5F8C31E55}</Project>
<Name>Ombi.Core.Migration</Name>

View file

@ -29,6 +29,7 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using CommandLine;
using Microsoft.Owin.Hosting;
@ -41,6 +42,7 @@ using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Store;
using Ombi.Store.Repository;
using Ombi.UI.Modules.Admin;
using Ombi.UI.Start;
namespace Ombi.UI
@ -50,7 +52,6 @@ namespace Ombi.UI
private static Logger Log = LogManager.GetCurrentClassLogger();
static void Main(string[] args)
{
var result = Parser.Default.ParseArguments<StartupOptions>(args);
var baseUrl = result.MapResult(
o => o.BaseUrl,
@ -75,7 +76,7 @@ namespace Ombi.UI
var s = new Setup();
var cn = s.SetupDb(baseUrl);
s.CacheQualityProfiles();
ConfigureTargets(cn);
ConfigureTargets(cn);
SetupLogging();
if (port == -1 || port == 3579)

View file

@ -121,13 +121,13 @@
<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>
<value>Ønsker du at se en film eller tv-show, men det er i øjeblikket ikke på {0}? Log nedenfor med dit 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>
<value>Brugernavn</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Brugernavn</value>
@ -211,7 +211,7 @@
<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>
<value>Ønsker at se noget, der ikke i øjeblikket på {0}?! Intet problem! Bare søge efter det nedenfor og anmode den !</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Søg</value>
@ -409,7 +409,7 @@
<value>allerede er blevet anmodet !!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>Vi kunne ikke kontrollere, om {0} er i Plex, er du sikker på det er korrekt setup ?!</value>
<value>Vi kunne ikke kontrollere, om {0} er i {1}, er du sikker på det er korrekt setup ?!</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Noget gik galt tilføjer filmen til CouchPotato! Tjek venligst din opsætning.!</value>
@ -418,7 +418,7 @@
<value>Du har nået din ugentlige anmodning grænse for film! Kontakt din administrator.!</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>er allerede i Plex !!</value>
<value>er allerede i {0}!!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Noget gik galt tilføjer filmen til SickRage! Tjek venligst din opsætning.!</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Du har nået din ugentlige anmodning grænse for tv-shows! Kontakt din administrator.!</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Beklager, men denne funktionalitet er i øjeblikket kun for brugere med Plex konti!</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Beklager, men din administrator har endnu ikke gjort det muligt denne funktionalitet.!</value>
</data>

View file

@ -121,13 +121,13 @@
<value>Anmelden</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Möchtest Du einen Film oder eine Serie schauen, die momentan noch nicht auf Plex ist? Dann logge dich unten ein und fordere es an!</value>
<value>Möchten Sie einen Film oder eine Serie schauen, die momentan noch nicht auf {0}ist? Dann loggen Sie sich unten ein und fordern Sie das Material an!</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Deine Login-Daten werden nur zur Authorisierung deines Plex-Konto verwendet.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Benutzername</value>
<value>Benutzername</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Benutzername</value>
@ -211,7 +211,7 @@
<value>Alben</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Möchtest Du etwas schauen, das derzeit nicht auf Plex ist?! Kein Problem! Suche einfach unten danach und frage es an!</value>
<value>Möchtest Du etwas schauen, das derzeit nicht auf {0} ist?! Kein Problem! Suche einfach unten danach und frage es an!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Suche</value>
@ -409,7 +409,7 @@
<value>wurde schon angefragt</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>Wir konnten nicht prüfen ob {0} bereits auf Plex ist. Bist du sicher dass alles richtig installiert ist?</value>
<value>Wir konnten nicht prüfen ob {0} bereits auf {1}ist. Bist du sicher dass alles richtig installiert ist?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Etwas ging etwas schief beim Hinzufügen des Filmes zu CouchPotato! Bitte überprüfe deine Einstellungen.</value>
@ -418,7 +418,7 @@
<value>Du hast dein wöchentliches Anfragekontingent für neue Filme erreicht. Bitte kontaktiere den Administrator.</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>ist bereits auf Plex!</value>
<value>ist bereits auf {0}!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Etwas ging etwas schief beim Hinzufügen des Filmes zu SickRage! Bitte überprüfe deine Einstellungen.</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Du hast dein wöchentliches Anfragekontingent für neue Serien erreicht. Bitte kontaktiere den Administrator.</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Entschuldige, aber diese Funktion ist momentan nur für Benutzer mit Plex-Accounts freigeschaltet.</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Entschuldige, aber dein Administrator hat diese Funktion noch nicht freigeschaltet.</value>
</data>

View file

@ -121,13 +121,13 @@
<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>
<value>¿Quieres ver una película o programa de televisión, pero no es actualmente en {0}? Ingresa abajo con su nombre de usuario y contraseña !</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>
<value>nombre de usuario</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Username</value>
@ -211,7 +211,7 @@
<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>
<value>¿Quieres ver algo que no se encuentra actualmente en {0}?! ¡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>
@ -409,7 +409,7 @@
<value>ya ha sido solicitada !!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>No hemos podido comprobar si {0} está en Plex, ¿estás seguro de que es correcta la configuración ?!</value>
<value>No hemos podido comprobar si {0} está en {1}, ¿estás seguro de que es correcta la configuración ?!</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Algo salió mal la adición de la película para CouchPotato! Por favor verifica la configuracion.!</value>
@ -418,7 +418,7 @@
<value>Ha llegado a su límite de solicitudes semanales de películas! Por favor, póngase en contacto con su administrador.!</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>ya está en Plex !!</value>
<value>ya está en {0}!!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Algo salió mal la adición de la película para SickRage! Por favor verifica la configuracion.!</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Ha llegado a su límite de solicitudes semanales de programas de televisión! Por favor, póngase en contacto con su administrador.!</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Lo sentimos, pero esta funcionalidad es actualmente sólo para los usuarios con cuentas Plex!</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Lo sentimos, pero el administrador aún no ha habilitado esta funcionalidad.!</value>
</data>

View file

@ -121,13 +121,13 @@
<value>Connexion</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans Plex ? Demandez-le ici !</value>
<value>Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans {0}? Demandez-le ici !</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>Nom d'utilisateur Plex.tv</value>
<value>Nom d'utilisateur</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nom dutilisateur</value>
@ -211,7 +211,7 @@
<value>Albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans Plex ?! Aucun problème ! Il suffit d'effectuer une recherche ci-dessous et d'en faire la demande!</value>
<value>Vous souhaitez avoir accès à un contenu qui n'est pas encore disponible dans {0} ?! Aucun problème ! Il suffit d'effectuer une recherche ci-dessous et d'en faire la demande!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Rechercher</value>
@ -409,7 +409,7 @@
<value>a déjà été demandé!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>Nous ne pouvons pas vérifier que {0} est présent dans Plex, êtes-vous sûr que la configuration est correcte?</value>
<value>Nous ne pouvons pas vérifier que {0} est présent dans {1}, êtes-vous sûr que la configuration est correcte?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Une erreur s'est produite lors de l'ajout du film dans CouchPotato! Merci de bien vouloir vérifier vos paramètres.</value>
@ -418,7 +418,7 @@
<value>Vous avez atteint votre quota hebdomadaire de demandes pour les films! Merci de bien vouloir contacter l'administrateur.</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>est déjà présent dans Plex!</value>
<value>est déjà présent dans {0}!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Une erreur s'est produite lors de l'ajout de la série TV dans SickRage! Merci de bien vouloir vérifier vos paramètres.</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Vous avez atteint votre quota hebdomadaire de demandes pour les séries TV! Merci de bien vouloir contacter l'administrateur.</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Désolé mais cette fonctionnalité est réservée aux utilisateurs possédant un compte Plex.</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Désolé mais l'administrateur n'a pas encore activé cette fonctionnalité.</value>
</data>

View file

@ -121,13 +121,13 @@
<value>Accesso</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Vuoi guardare un film o una serie tv ma non è attualmente in Plex? Effettua il login con il tuo username e la password Plex.tv !</value>
<value>Vuoi guardare un film o una serie tv ma non è attualmente in {0}? Effettua il login con il tuo username e la password !</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>I dati di accesso vengono utilizzati solo per autenticare l'account Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Nome utente</value>
<value>Nome utente</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nome utente</value>
@ -214,7 +214,7 @@
<value>Msuica</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Vuoi guardare qualcosa che non è attualmente in Plex?! Non c'è problema! Basta cercarla qui sotto e richiederla!</value>
<value>Vuoi guardare qualcosa che non è attualmente in {0}?! Non c'è problema! Basta cercarla qui sotto e richiederla!</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggerimenti</value>
@ -409,7 +409,7 @@
<value>è già stato richiesto!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>Non siamo riusciti a controllare se {0} è in Plex, sei sicuro che sia configurato correttamente?</value>
<value>Non siamo riusciti a controllare se {0} è in {1}, sei sicuro che sia configurato correttamente?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Qualcosa è andato storto aggiungendo il film a CouchPotato! Controlla le impostazioni</value>
@ -418,7 +418,7 @@
<value>Hai raggiunto il numero massimo di richieste settimanali per i Film! Contatta l'amministratore</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>è già disponibile in Plex!</value>
<value>è già disponibile in {0}!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Qualcosa è andato storto aggiungendo il film a SickRage! Controlla le impostazioni</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Hai raggiunto il numero massimo di richieste settimanali per le Serie TV! Contatta l'amministratore</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Spiacente, ma questa funzione è disponibile solo per gli utenti con un account Plex.</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Spiacente, ma l'amministratore non ha ancora abilitato questa funzionalità.</value>
</data>

View file

@ -121,13 +121,13 @@
<value>Inloggen</value>
</data>
<data name="UserLogin_Paragraph" xml:space="preserve">
<value>Wilt u een film of een tv serie kijken, maar staat deze niet op Plex? Log hieronder in met uw gebruikersnaam en wachtwoord van Plex.tv</value>
<value>Wilt u een film of een tv serie kijken, maar staat deze niet op {0}? Log hieronder in met uw gebruikersnaam en wachtwoord van</value>
</data>
<data name="UserLogin_Paragraph_SpanHover" xml:space="preserve">
<value>Uw login gegevens worden alleen gebruikt om uw account te verifiëren bij Plex.</value>
</data>
<data name="UserLogin_Username" xml:space="preserve">
<value>Plex.tv Gebruikersnaam</value>
<value> Gebruikersnaam</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Gebruikersnaam</value>
@ -217,7 +217,7 @@
<value>Albums</value>
</data>
<data name="Search_Paragraph" xml:space="preserve">
<value>Wilt u kijken naar iets dat dat momenteel niet op Plex is?! Geen probleem! zoek hieronder en vraag het aan!</value>
<value>Wilt u kijken naar iets dat dat momenteel niet op {0} is?! Geen probleem! zoek hieronder en vraag het aan!</value>
</data>
<data name="Search_Suggestions" xml:space="preserve">
<value>Suggesties</value>
@ -409,10 +409,7 @@
<value>Is al aangevraagd!</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>Staat al op Plex!</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Sorry, deze functie is momenteel alleen voor gebruikers met een Plex account.</value>
<value>Staat al op {0}!</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Sorry, uw administrator heeft deze functie nog niet geactiveerd.</value>
@ -424,7 +421,7 @@
<value>Kon niet opslaan, probeer het later nog eens.</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>We konden niet controleren of {0} al in plex bestaat, weet je zeker dat het correct is ingesteld?</value>
<value>We konden niet controleren of {0} al in {1} bestaat, weet je zeker dat het correct is ingesteld?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Er is iets foutgegaan tijdens het toevoegen van de film aan CouchPotato! Controleer je instellingen</value>

View file

@ -121,13 +121,13 @@
<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>
<value>Quer assistir a um filme ou programa de TV, mas não está atualmente em {0}? Entre abaixo com seu nome de usuário e senha !</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>
<value>usuário</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Nome de usuário</value>
@ -211,7 +211,7 @@
<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>
<value>Quer assistir algo que não está atualmente em {0}?! Sem problemas! Basta procurá-lo abaixo e solicitá-lo !!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Buscar</value>
@ -409,7 +409,7 @@
<value>já foi solicitado !!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>Nós não poderia verificar se {0} está em Plex, você tem certeza que é configurada corretamente ?!</value>
<value>Nós não poderia verificar se {0} está em {1}, você tem certeza que é configurada corretamente ?!</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Algo deu errado adicionando o filme para CouchPotato! Verifique as suas opções.!</value>
@ -418,7 +418,7 @@
<value>Atingiu seu limite semanal de solicitação para filmes! Entre em contato com seu administrador.</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>Já está no Plex!</value>
<value>Já está no {0}!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Algo deu errado adicionar o filme para SickRage! Por favor, verifique suas configurações.</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Atingiu seu limite semanal de solicitação para programas de TV! Entre em contato com seu administrador.</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Desculpe, mas essa funcionalidade é atualmente somente para os usuários com contas de Plex</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Desculpe, mas o administrador não permitiu ainda esta funcionalidade.</value>
</data>

View file

@ -1,76 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
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.
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.
-->
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" msdata:Ordinal="1" />
<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">
@ -89,26 +109,26 @@
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<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=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<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>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>
<value>Want to watch a movie or tv show but it's not currently on {0}?
Login below with your 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>
<value>Username </value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Username</value>
@ -191,8 +211,11 @@
<data name="Search_Albums" xml:space="preserve">
<value>Albums</value>
</data>
<data name="Search_NewOnly" xml:space="preserve">
<value>Don't include titles that are already requested/available</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>
<value>Want to watch something that is not currently on {0}?! No problem! Just search for it below and request it!</value>
</data>
<data name="Search_Title" xml:space="preserve">
<value>Search</value>
@ -390,7 +413,7 @@
<value>has already been requested!</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>We could not check if {0} is in Plex, are you sure it's correctly setup?</value>
<value>We could not check if {0} is in {1}, are you sure it's correctly setup?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Something went wrong adding the movie to CouchPotato! Please check your settings.</value>
@ -399,7 +422,7 @@
<value>You have reached your weekly request limit for Movies! Please contact your admin.</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>is already in Plex!</value>
<value>is already in {0}!</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Something went wrong adding the movie to SickRage! Please check your settings.</value>
@ -416,9 +439,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>You have reached your weekly request limit for TV Shows! Please contact your admin.</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Sorry, but this functionality is currently only for users with Plex accounts</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Sorry, but your administrator has not yet enabled this functionality.</value>
</data>
@ -468,7 +488,7 @@
<value>TV show status</value>
</data>
<data name="Layout_CacherRunning" xml:space="preserve">
<value>Currently we are indexing all of the available tv shows and movies on the Plex server, so there might be some unexpected behavior. This shouldn't take too long.</value>
<value>Currently we are indexing all of the available tv shows and movies on the media server, so there might be some unexpected behavior. This shouldn't take too long.</value>
</data>
<data name="Layout_Usermanagement" xml:space="preserve">
<value>User Management</value>
@ -476,4 +496,7 @@
<data name="UserLogin_AdminUsePassword" xml:space="preserve">
<value>If you are an administrator, please use the other login page</value>
</data>
<data name="Search_Actors" xml:space="preserve">
<value>Actors</value>
</data>
</root>

View file

@ -121,13 +121,13 @@
<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>
<value>Vill du titta på en film eller TV-show, men det är inte närvarande på {0}? Logga in nedan med 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>
<value>Användarnamn</value>
</data>
<data name="UserLogin_Username_Placeholder" xml:space="preserve">
<value>Användarnamn</value>
@ -214,7 +214,7 @@
<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>
<value>Vill titta på något som inte är närvarande på {0}?! 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>
@ -409,7 +409,7 @@
<value>har redan begärts</value>
</data>
<data name="Search_CouldNotCheckPlex" xml:space="preserve">
<value>Vi kunde inte kontrollera om {0} är i Plex, är du säker det är korrekt installation?</value>
<value>Vi kunde inte kontrollera om {0} är i {1}, är du säker det är korrekt installation?</value>
</data>
<data name="Search_CouchPotatoError" xml:space="preserve">
<value>Något gick fel att lägga till filmen i CouchPotato! Kontrollera inställningarna.</value>
@ -418,7 +418,7 @@
<value>Du har nått din weekly begäran gräns för filmer! Kontakta din admin.</value>
</data>
<data name="Search_AlreadyInPlex" xml:space="preserve">
<value>är redan i Plex</value>
<value>är redan i {0}</value>
</data>
<data name="Search_SickrageError" xml:space="preserve">
<value>Något gick fel att lägga till filmen i SickRage! Kontrollera inställningarna.</value>
@ -435,9 +435,6 @@
<data name="Search_WeeklyRequestLimitTVShow" xml:space="preserve">
<value>Du har nått din weekly begäran gräns för TV-program! Kontakta din admin.</value>
</data>
<data name="Search_ErrorPlexAccountOnly" xml:space="preserve">
<value>Ledsen, men denna funktion är för närvarande endast för användare med Plex konton</value>
</data>
<data name="Search_ErrorNotEnabled" xml:space="preserve">
<value>Ledsen, men administratören har ännu inte aktiverat denna funktion.</value>
</data>

View file

@ -223,7 +223,7 @@ namespace Ombi.UI.Resources {
}
/// <summary>
/// Looks up a localized string similar to Currently we are indexing all of the available tv shows and movies on the Plex server, so there might be some unexpected behavior. This shouldn&apos;t take too long..
/// Looks up a localized string similar to Currently we are indexing all of the available tv shows and movies on the media server, so there might be some unexpected behavior. This shouldn&apos;t take too long..
/// </summary>
public static string Layout_CacherRunning {
get {
@ -717,6 +717,15 @@ namespace Ombi.UI.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Actors.
/// </summary>
public static string Search_Actors {
get {
return ResourceManager.GetString("Search_Actors", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Albums.
/// </summary>
@ -736,7 +745,7 @@ namespace Ombi.UI.Resources {
}
/// <summary>
/// Looks up a localized string similar to is already in Plex!.
/// Looks up a localized string similar to is already in {0}!.
/// </summary>
public static string Search_AlreadyInPlex {
get {
@ -790,7 +799,7 @@ namespace Ombi.UI.Resources {
}
/// <summary>
/// Looks up a localized string similar to We could not check if {0} is in Plex, are you sure it&apos;s correctly setup?.
/// Looks up a localized string similar to We could not check if {0} is in {1}, are you sure it&apos;s correctly setup?.
/// </summary>
public static string Search_CouldNotCheckPlex {
get {
@ -816,15 +825,6 @@ namespace Ombi.UI.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Sorry, but this functionality is currently only for users with Plex accounts.
/// </summary>
public static string Search_ErrorPlexAccountOnly {
get {
return ResourceManager.GetString("Search_ErrorPlexAccountOnly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to First Season.
/// </summary>
@ -888,6 +888,15 @@ namespace Ombi.UI.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Don&apos;t include titles that are already requested/available.
/// </summary>
public static string Search_NewOnly {
get {
return ResourceManager.GetString("Search_NewOnly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Requested yet.
/// </summary>
@ -907,7 +916,7 @@ namespace Ombi.UI.Resources {
}
/// <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!.
/// Looks up a localized string similar to Want to watch something that is not currently on {0}?! No problem! Just search for it below and request it!.
/// </summary>
public static string Search_Paragraph {
get {
@ -1132,8 +1141,8 @@ namespace Ombi.UI.Resources {
}
/// <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!.
/// Looks up a localized string similar to Want to watch a movie or tv show but it&apos;s not currently on {0}?
/// Login below with your username and password!.
/// </summary>
public static string UserLogin_Paragraph {
get {
@ -1178,7 +1187,7 @@ namespace Ombi.UI.Resources {
}
/// <summary>
/// Looks up a localized string similar to Plex.tv Username .
/// Looks up a localized string similar to Username .
/// </summary>
public static string UserLogin_Username {
get {

View file

@ -31,6 +31,7 @@ using Ninject;
using Ninject.Planning.Bindings.Resolvers;
using Ninject.Syntax;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.Migration;
@ -83,6 +84,7 @@ namespace Ombi.UI
var scheduler = new Scheduler();
// Reset any jobs running
var jobSettings = kernel.Get<IRepository<ScheduledJobs>>();
var all = jobSettings.GetAll();

View file

@ -0,0 +1,42 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SonarrValidator.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 FluentValidation;
using Ombi.Core.SettingModels;
namespace Ombi.UI.Validators
{
public class EmbyValidator : AbstractValidator<EmbySettings>
{
public EmbyValidator()
{
RuleFor(request => request.Ip).NotNull().WithMessage("You must specify a IP/Host name.");
RuleFor(request => request.Port).NotNull().WithMessage("You must specify a Port.");
RuleFor(request => request.ApiKey).NotNull().WithMessage("You must specify a Api Key.");
}
}
}

View file

@ -37,7 +37,7 @@ namespace Ombi.UI.Validators
RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key.");
RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name.");
RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port.");
RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile.");
RuleFor(request => request.QualityProfile).NotEmpty().NotNull().WithMessage("You must specify a Quality Profile.");
}
}
}

View file

@ -1,4 +1,5 @@
@using Ombi.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<Ombi.Core.SettingModels.AuthenticationSettings>
@Html.Partial("Shared/Partial/_Sidebar")
@{
@ -17,40 +18,11 @@
<form class="form-horizontal" method="POST" action="@formAction" id="mainForm">
<fieldset>
<legend>Authentication Settings</legend>
<div class="form-group">
<div class="checkbox">
@if (Model.UserAuthentication)
{
<input type="checkbox" id="userAuth" name="UserAuthentication" checked="checked">
<label for="userAuth">Enable User Authentication</label>
}
else
{
<input type="checkbox" id="userAuth" name="UserAuthentication">
<label for="userAuth">Enable User Authentication</label>
}
</div>
</div>
<div class="form-group">
<div class="checkbox">
@if (Model.UsePassword)
{
<input type="checkbox" id="UsePassword" name="UsePassword" checked="checked">
<label id="passLabel" for="UsePassword">Require users to login with their passwords</label>
}
else
{
<input type="checkbox" id="UsePassword" name="UsePassword">
<label id="passLabel" for="UsePassword">Require users to login with their passwords</label>
}
</div>
</div>
@Html.Checkbox(Model.UserAuthentication, "UserAuthentication", "Enable User Authentication", "If enabled we will check the login name against a user in your local users list or Plex/Emby users.")
@Html.Checkbox(Model.UsePassword, "UsePassword", "Require users to login with their passwords", "If enabled, users must provide a valid password to log into Ombi")
<br />
@ -59,7 +31,9 @@
<br />
<p class="form-group">A comma separated list of users that you do not want to login.</p>
<p class="form-group">A comma separated list of users that you do not want to login.
@Html.ToolTip("This is a 'blacklist', if you have users that you do not want to log in, enter them here!")</p>
<div class="form-group">
<label for="DeniedUsers" class="control-label">Denied Users</label>
<div >
@ -83,20 +57,13 @@
$(function () {
var base = '@Html.GetBaseUrl()';
$('.customTooltip').tooltipster({
contentCloning: true
});
changeDisabledStatus($('#UsePassword'), @Model.UserAuthentication.ToString().ToLower(), $('#passLabel'));
if ($('#PlexAuthToken')) {
loadUserList();
}
$('#refreshUsers').click(function (e) {
e.preventDefault();
loadUserList();
});
$('#mainForm').on('click', '#userAuth', function () {
var checked = this.checked;
changeDisabledStatus($('#UsePassword'), checked, $('#passLabel'));
@ -112,41 +79,5 @@
$label.css("color", "grey");
}
}
function loadUserList() {
$('#users').html("");
var url = "getusers";
$.ajax({
type: "Get",
url: url,
dataType: "json",
success: function (response) {
$('#users').html("");
if(!response.result){
generateNotify(response.message,"danger");
$('#users').append("<option>Error!</option>");
return;
}
if (response.users.length > 0) {
$(response.users).each(function () {
$('#users').append("<option>" + this + "</option>");
});
} else {
$('#users').append("<option>No Users, Please refresh!</option>");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
$('#users').html("");
$('#users').append("<option>Error!</option>");
}
});
}
});
</script>

View file

@ -0,0 +1,150 @@
@using Ombi.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<Ombi.Core.SettingModels.EmbySettings>
@Html.Partial("Shared/Partial/_Sidebar")
@{
int port;
if (Model.Port == 0)
{
port = 8096;
}
else
{
port = Model.Port;
}
}
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Emby Settings</legend>
@Html.Checkbox(Model.Enable, "Enable", "Enabled")
<div class="form-group">
<label for="Ip" class="control-label">Emby Hostname or IP</label>
<div>
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" value="@Model.Ip">
</div>
</div>
<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>
<div class="form-group">
<div class="checkbox">
@if (Model.Ssl)
{
<input type="checkbox" id="Ssl" name="Ssl" checked="checked"><label for="Ssl">SSL</label>
}
else
{
<input type="checkbox" id="Ssl" name="Ssl"><label for="Ssl">SSL</label>
}
</div>
</div>
@Html.Checkbox(Model.EnableEpisodeSearching, "EnableEpisodeSearching", "Enable Episode Searching")
@Html.ToolTip("This will allow Ombi to search through all of the episodes stored on Emby")
<div class="form-group">
<label for="SubDir" class="control-label">Emby Base Url</label>
<div>
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
</div>
</div>
<div class="form-group">
<label for="ApiKey" class="control-label">Emby Api Key</label>
<div class="">
<input type="text" class="form-control-custom form-control" id="ApiKey" name="ApiKey" placeholder="Api Key" value="@Model.ApiKey">
</div>
</div>
<div class="form-group">
<div>
<button id="testEmby" type="submit" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function () {
var base = '@Html.GetBaseUrl()';
$('#testEmby').click(function (e) {
e.preventDefault();
var url = createBaseUrl(base, '/test/emby');
var $form = $("#mainForm");
$('#spinner').attr("class", "fa fa-spinner fa-spin");
$.ajax({
type: $form.prop("method"),
url: url,
data: $form.serialize(),
dataType: "json",
success: function (response) {
$('#spinner').attr("class", "");
console.log(response);
if (response.result === true) {
generateNotify(response.message, "success");
$('#spinner').attr("class", "fa fa-check");
} else {
generateNotify(response.message, "warning");
$('#spinner').attr("class", "fa fa-times");
}
},
error: function (e) {
$('#spinner').attr("class", "fa fa-times");
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$('#save').click(function (e) {
e.preventDefault();
var port = $('#portNumber').val();
if (isNaN(port)) {
generateNotify("You must specify a Port.", "warning");
return;
}
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
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

@ -54,7 +54,7 @@
<p class="form-group">Notice Message</p>
<div class="form-group">
<div>
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. Plex will be down for maintaince (HTML is allowed)" value="@Model.NoticeMessage">@Model.NoticeMessage</textarea>
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. The server will be down for maintaince (HTML is allowed)" value="@Model.NoticeMessage">@Model.NoticeMessage</textarea>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show more