Merge pull request #13 from tidusjar/develop

Bringing branch up to date
pull/2225/head
Anojh Thayaparan 7 years ago committed by GitHub
commit 2cf0612b43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,9 +1,20 @@
# Changelog
## (unreleased)
## v3.0.3268 (2018-04-28)
### **Fixes**
- Potential fix for #2119. [Jamie Rees]
- Use the Application URL if we have it to fix #2201. [Jamie]
## v3.0.3239 (2018-04-26)
### **New Features**
- Update appveyor.yml. [Jamie]
- Added paging to the TV Requests page. [Jamie Rees]
- Added Paging to the Movie Requests Page. [Jamie Rees]
@ -18,6 +29,8 @@
### **Fixes**
- Clean up the error code when the OAuth user is not authorized. [Jamie]
- More improvements to the Plex OAuth, Added the ability to turn it off if needed. [Jamie]
- Fixed bug #2188 #2134. [Jamie]

@ -11,15 +11,15 @@ namespace Ombi.Api.Plex.Models
public string summary { get; set; }
public int index { get; set; }
public float rating { get; set; }
public int viewCount { get; set; }
public int lastViewedAt { get; set; }
//public int viewCount { get; set; }
//public int lastViewedAt { get; set; }
public int year { get; set; }
public string thumb { get; set; }
public string art { get; set; }
public string banner { get; set; }
public string theme { get; set; }
public string duration { get; set; }
public string originallyAvailableAt { get; set; }
//public string duration { get; set; }
//public string originallyAvailableAt { get; set; }
public int leafCount { get; set; }
public int viewedLeafCount { get; set; }
public int childCount { get; set; }

@ -57,21 +57,8 @@ namespace Ombi.Core.Authentication
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
{
Uri url;
if (websiteAddress.IsNullOrEmpty())
{
var settings = await _customizationSettingsService.GetSettingsAsync();
if (settings.ApplicationUrl.IsNullOrEmpty())
{
return null;
}
url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl, false);
}
else
{
url = _api.GetOAuthUrl(pinId, code, websiteAddress, false);
}
var settings = await _customizationSettingsService.GetSettingsAsync();
var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false);
return url;
}

@ -17,7 +17,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId);
Task<IEnumerable<MovieRequests>> Filter(FilterViewModel vm);
Task<FilterResult<MovieRequests>> Filter(FilterViewModel vm);
}
}

@ -131,11 +131,11 @@ namespace Ombi.Core.Engine
List<MovieRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).Skip(position).Take(count).ToListAsync();
allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
else
{
allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).ToListAsync();
allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
allRequests.ForEach(x =>
{
@ -380,10 +380,13 @@ namespace Ombi.Core.Engine
return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!" };
}
public async Task<IEnumerable<MovieRequests>> Filter(FilterViewModel vm)
public async Task<FilterResult<MovieRequests>> Filter(FilterViewModel vm)
{
var shouldHide = await HideFromOtherUsers();
var requests = shouldHide.Hide ? MovieRepository.GetWithUser(shouldHide.UserId) : MovieRepository.GetWithUser();
var requests = shouldHide.Hide
? MovieRepository.GetWithUser(shouldHide.UserId)
: MovieRepository.GetWithUser();
switch (vm.AvailabilityFilter)
{
case FilterType.None:
@ -415,7 +418,14 @@ namespace Ombi.Core.Engine
throw new ArgumentOutOfRangeException();
}
return requests;
var count = await requests.CountAsync();
requests = requests.Skip(vm.Position).Take(vm.Count);
var retVal = new FilterResult<MovieRequests>
{
Total = count,
Collection = requests
};
return retVal;
}
}
}

@ -141,7 +141,7 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Skip(position).Take(count).ToListAsync();
.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
// Filter out children
@ -153,7 +153,7 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Skip(position).Take(count).ToListAsync();
.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
return allRequests;

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests
{
public class FilterResult<T>
{
public int Total { get; set; }
public IEnumerable<T> Collection { get; set; }
}
}

@ -4,6 +4,8 @@
{
public FilterType AvailabilityFilter { get; set; }
public FilterType StatusFilter { get; set; }
public int Position { get; set; }
public int Count { get; set; }
}
public enum FilterType

@ -177,6 +177,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISickRageSync, SickRageSync>();
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
services.AddTransient<INewsletterJob, NewsletterJob>();
services.AddTransient<IPlexRecentlyAddedSync, PlexRecentlyAddedSync>();
}
}
}

@ -39,7 +39,7 @@ namespace Ombi.Notifications.Agents
protected override bool ValidateConfiguration(MobileNotificationSettings settings)
{
return false;
return true;
}
protected override async Task NewRequest(NotificationOptions model, MobileNotificationSettings settings)
@ -211,7 +211,7 @@ namespace Ombi.Notifications.Agents
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings)
{
if (!playerIds.Any())
if (playerIds == null || !playerIds.Any())
{
return;
}

@ -0,0 +1,43 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Ombi.Core.Settings;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Schedule.Tests
{
[TestFixture]
public class NewsletterTests
{
[TestCaseSource(nameof(EpisodeListData))]
public string BuildEpisodeListTest(List<int> episodes)
{
var emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
var customziation = new Mock<ISettingsService<CustomizationSettings>>();
var newsletterSettings = new Mock<ISettingsService<NewsletterSettings>>();
var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null);
var ep = new List<int>();
foreach (var i in episodes)
{
ep.Add(i);
}
var result = newsletter.BuildEpisodeList(ep);
return result;
}
public static IEnumerable<TestCaseData> EpisodeListData
{
get
{
yield return new TestCaseData(new List<int>{1,2,3,4,5,6}).Returns("1-6").SetName("Simple 1-6");
yield return new TestCaseData(new List<int>{1,2,3,4,5,6,8,9}).Returns("1-6, 8-9").SetName("Simple 1-6, 8-9");
yield return new TestCaseData(new List<int>{1,99,101,555,468,469}).Returns("1, 99, 101, 555, 468-469").SetName("More Complex");
yield return new TestCaseData(new List<int>{1}).Returns("1").SetName("Single Episode");
}
}
}
}

@ -8,10 +8,10 @@
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -19,7 +19,7 @@ namespace Ombi.Schedule
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
INewsletterJob newsletter)
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex)
{
_plexContentSync = plexContentSync;
_radarrSync = radarrSync;
@ -33,9 +33,11 @@ namespace Ombi.Schedule
_srSync = srSync;
_refreshMetadata = refresh;
_newsletter = newsletter;
_plexRecentlyAddedSync = recentlyAddedPlex;
}
private readonly IPlexContentSync _plexContentSync;
private readonly IPlexRecentlyAddedSync _plexRecentlyAddedSync;
private readonly IRadarrSync _radarrSync;
private readonly IOmbiAutomaticUpdater _updater;
private readonly IPlexUserImporter _plexUserImporter;
@ -56,7 +58,7 @@ namespace Ombi.Schedule
RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s));
RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(false), JobSettingsHelper.PlexContent(s));
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(true), JobSettingsHelper.PlexRecentlyAdded(s));
RecurringJob.AddOrUpdate(() => _plexRecentlyAddedSync.Start(), JobSettingsHelper.PlexRecentlyAdded(s));
RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s));
RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s));
RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s));

@ -9,6 +9,7 @@ using Ombi.Api.Emby.Models.Movie;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Serilog;
@ -19,13 +20,14 @@ namespace Ombi.Schedule.Jobs.Emby
public class EmbyContentSync : IEmbyContentSync
{
public EmbyContentSync(ISettingsService<EmbySettings> settings, IEmbyApi api, ILogger<EmbyContentSync> logger,
IEmbyContentRepository repo, IEmbyEpisodeSync epSync)
IEmbyContentRepository repo, IEmbyEpisodeSync epSync, IRefreshMetadata metadata)
{
_logger = logger;
_settings = settings;
_api = api;
_repo = repo;
_episodeSync = epSync;
_metadata = metadata;
_settings.ClearCache();
}
@ -34,6 +36,7 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly IEmbyApi _api;
private readonly IEmbyContentRepository _repo;
private readonly IEmbyEpisodeSync _episodeSync;
private readonly IRefreshMetadata _metadata;
public async Task Start()
@ -56,6 +59,7 @@ namespace Ombi.Schedule.Jobs.Emby
// Episodes
BackgroundJob.Enqueue(() => _episodeSync.Start());
BackgroundJob.Enqueue(() => _metadata.Start());
}

@ -98,14 +98,14 @@ namespace Ombi.Schedule.Jobs.Ombi
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
// Filter out the ones that we haven't sent yet
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
var plexEpisodesToSend =
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds);
var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(),
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds);
var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(),
addedEmbyEpisodesLogIds);
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
@ -114,7 +114,7 @@ namespace Ombi.Schedule.Jobs.Ombi
if (test)
{
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10);
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10);
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10);
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
body = await BuildHtml(plexm, embym, plext, embyt, settings);
@ -564,21 +564,8 @@ namespace Ombi.Schedule.Jobs.Ombi
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
{
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
var epSb = new StringBuilder();
for (var i = 0; i < orderedEpisodes.Count; i++)
{
var ep = orderedEpisodes[i];
if (i < orderedEpisodes.Count - 1)
{
epSb.Append($"{ep.EpisodeNumber},");
}
else
{
epSb.Append($"{ep.EpisodeNumber}");
}
}
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {epSb}");
var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
finalsb.Append("<br />");
}
@ -615,6 +602,49 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
public string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
{
var epSb = new StringBuilder();
var previousEpisodes = new List<int>();
var previousEpisode = -1;
foreach (var ep in orderedEpisodes)
{
if (ep - 1 == previousEpisode)
{
// This is the next one
previousEpisodes.Add(ep);
}
else
{
if (previousEpisodes.Count > 1)
{
// End it
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, ");
}
else if (previousEpisodes.Count == 1)
{
epSb.Append($"{previousEpisodes.FirstOrDefault()}, ");
}
// New one
previousEpisodes.Clear();
previousEpisodes.Add(ep);
}
previousEpisode = ep;
}
if (previousEpisodes.Count > 1)
{
// Got some left over
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}");
}
else if(previousEpisodes.Count == 1)
{
epSb.Append(previousEpisodes.FirstOrDefault());
}
return epSb.ToString();
}
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
{
var series = new List<EmbyContent>();
@ -688,21 +718,8 @@ namespace Ombi.Schedule.Jobs.Ombi
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
{
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
var epSb = new StringBuilder();
for (var i = 0; i < orderedEpisodes.Count; i++)
{
var ep = orderedEpisodes[i];
if (i < orderedEpisodes.Count - 1)
{
epSb.Append($"{ep.EpisodeNumber},");
}
else
{
epSb.Append($"{ep.EpisodeNumber}");
}
}
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {epSb}");
var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
finalsb.Append("<br />");
}

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Schedule.Jobs.Plex
{
public interface IPlexRecentlyAddedSync : IBaseJob
{
void Start();
}
}

@ -37,6 +37,7 @@ using Ombi.Api.Plex.Models;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -46,13 +47,14 @@ namespace Ombi.Schedule.Jobs.Plex
public class PlexContentSync : IPlexContentSync
{
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
IPlexEpisodeSync epsiodeSync)
IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh)
{
Plex = plex;
PlexApi = plexApi;
Logger = logger;
Repo = repo;
EpisodeSync = epsiodeSync;
Metadata = metadataRefresh;
plex.ClearCache();
}
@ -61,6 +63,7 @@ namespace Ombi.Schedule.Jobs.Plex
private ILogger<PlexContentSync> Logger { get; }
private IPlexContentRepository Repo { get; }
private IPlexEpisodeSync EpisodeSync { get; }
private IRefreshMetadata Metadata { get; }
public async Task CacheContent(bool recentlyAddedSearch = false)
{
@ -87,6 +90,7 @@ namespace Ombi.Schedule.Jobs.Plex
Logger.LogInformation("Starting EP Cacher");
BackgroundJob.Enqueue(() => EpisodeSync.Start());
BackgroundJob.Enqueue(() => Metadata.Start());
}
private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
@ -115,187 +119,30 @@ namespace Ombi.Schedule.Jobs.Plex
var contentToAdd = new HashSet<PlexServerContent>();
foreach (var content in allContent)
{
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
Logger.LogInformation("Found some episodes, this must be a recently added sync");
foreach (var epInfo in content.Metadata)
{
var grandParentKey = epInfo.grandparentRatingKey;
// Lookup the rating key
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, grandParentKey);
var show = showMetadata.MediaContainer.Metadata.FirstOrDefault();
if(show == null)
{
continue;
}
await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch);
}
}
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
// Process Shows
Logger.LogInformation("Processing TV Shows");
foreach (var show in content.Metadata ?? new Metadata[] { })
{
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
show.ratingKey);
var seasonsContent = new List<PlexSeasonsContent>();
foreach (var season in seasonList.MediaContainer.Metadata)
{
seasonsContent.Add(new PlexSeasonsContent
{
ParentKey = season.parentRatingKey,
SeasonKey = season.ratingKey,
SeasonNumber = season.index,
PlexContentId = show.ratingKey
});
}
// Do we already have this item?
// Let's try and match
var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title
&& x.ReleaseYear == show.year.ToString()
&& x.Type == PlexMediaTypeEntity.Show);
// Just double check the rating key, since this is our unique constraint
var existingKey = await Repo.GetByKey(show.ratingKey);
if (existingKey != null)
{
// Damn son.
// Let's check if they match up
var doesMatch = show.title.Equals(existingKey.Title,
StringComparison.CurrentCulture);
if (!doesMatch)
{
// Something fucked up on Plex at somepoint... Damn, rebuild of lib maybe?
// Lets delete the matching key
await Repo.Delete(existingKey);
existingKey = null;
}
}
if (existingContent != null)
{
// Just check the key
if (existingKey != null)
{
// The rating key is all good!
}
else
{
// This means the rating key has changed somehow.
// Should probably delete this and get the new one
var oldKey = existingContent.Key;
Repo.DeleteWithoutSave(existingContent);
// Because we have changed the rating key, we need to change all children too
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
if (episodeToChange.Any())
{
foreach (var e in episodeToChange)
{
Repo.DeleteWithoutSave(e);
}
}
await Repo.SaveChangesAsync();
existingContent = null;
}
}
// The ratingKey keeps changing...
//var existingContent = await Repo.GetByKey(show.ratingKey);
if (existingContent != null)
{
try
{
Logger.LogInformation("We already have show {0} checking for new seasons",
existingContent.Title);
// Ok so we have it, let's check if there are any new seasons
var itemAdded = false;
foreach (var season in seasonsContent)
{
var seasonExists =
existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey);
if (seasonExists != null)
{
// We already have this season
continue;
}
existingContent.Seasons.Add(season);
itemAdded = true;
}
if (itemAdded) await Repo.Update(existingContent);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e,
"Exception when adding new seasons to title {0}", existingContent.Title);
}
}
else
{
try
{
Logger.LogInformation("New show {0}, so add it", show.title);
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
// But it does not contain the `guid` property that we need to pull out thetvdb id...
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
show.ratingKey);
var providerIds =
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
var item = new PlexServerContent
{
AddedAt = DateTime.Now,
Key = show.ratingKey,
ReleaseYear = show.year.ToString(),
Type = PlexMediaTypeEntity.Show,
Title = show.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>()
};
if (providerIds.Type == ProviderType.ImdbId)
{
item.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
item.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
item.TvDbId = providerIds.TheTvDb;
}
// Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false;
var existingMovieDbId = false;
var existingTvDbId = false;
if (item.ImdbId.HasValue())
{
existingImdb = await Repo.GetAll().AnyAsync(x =>
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TheMovieDbId.HasValue())
{
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TvDbId.HasValue())
{
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (existingImdb || existingTvDbId || existingMovieDbId)
{
// We already have it!
continue;
}
item.Seasons.ToList().AddRange(seasonsContent);
contentToAdd.Add(item);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}",
show.title);
}
}
if (contentToAdd.Count > 500)
{
await Repo.AddRange(contentToAdd);
contentToAdd.Clear();
}
await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch);
}
}
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
@ -382,6 +229,193 @@ namespace Ombi.Schedule.Jobs.Plex
}
}
private async Task ProcessTvShow(PlexServers servers, Metadata show, HashSet<PlexServerContent> contentToAdd, bool recentlyAdded)
{
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
show.ratingKey);
var seasonsContent = new List<PlexSeasonsContent>();
foreach (var season in seasonList.MediaContainer.Metadata)
{
seasonsContent.Add(new PlexSeasonsContent
{
ParentKey = season.parentRatingKey,
SeasonKey = season.ratingKey,
SeasonNumber = season.index,
PlexContentId = show.ratingKey
});
}
// Do we already have this item?
// Let's try and match
var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title
&& x.ReleaseYear == show.year.ToString()
&& x.Type == PlexMediaTypeEntity.Show);
// Just double check the rating key, since this is our unique constraint
var existingKey = await Repo.GetByKey(show.ratingKey);
if (existingKey != null)
{
// Damn son.
// Let's check if they match up
var doesMatch = show.title.Equals(existingKey.Title,
StringComparison.CurrentCulture);
if (!doesMatch)
{
// Something fucked up on Plex at somepoint... Damn, rebuild of lib maybe?
// Lets delete the matching key
await Repo.Delete(existingKey);
existingKey = null;
}
}
if (existingContent != null)
{
// Just check the key
if (existingKey != null)
{
// The rating key is all good!
}
else
{
// This means the rating key has changed somehow.
// Should probably delete this and get the new one
var oldKey = existingContent.Key;
Repo.DeleteWithoutSave(existingContent);
// Because we have changed the rating key, we need to change all children too
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
if (episodeToChange.Any())
{
foreach (var e in episodeToChange)
{
Repo.DeleteWithoutSave(e);
}
}
await Repo.SaveChangesAsync();
existingContent = null;
}
}
// The ratingKey keeps changing...
//var existingContent = await Repo.GetByKey(show.ratingKey);
if (existingContent != null)
{
try
{
Logger.LogInformation("We already have show {0} checking for new seasons",
existingContent.Title);
// Ok so we have it, let's check if there are any new seasons
var itemAdded = false;
foreach (var season in seasonsContent)
{
var seasonExists =
existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey);
if (seasonExists != null)
{
// We already have this season
continue;
}
existingContent.Seasons.Add(season);
itemAdded = true;
}
if (itemAdded) await Repo.Update(existingContent);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e,
"Exception when adding new seasons to title {0}", existingContent.Title);
}
}
else
{
try
{
Logger.LogInformation("New show {0}, so add it", show.title);
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
// But it does not contain the `guid` property that we need to pull out thetvdb id...
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
show.ratingKey);
var providerIds =
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
var item = new PlexServerContent
{
AddedAt = DateTime.Now,
Key = show.ratingKey,
ReleaseYear = show.year.ToString(),
Type = PlexMediaTypeEntity.Show,
Title = show.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>()
};
if (providerIds.Type == ProviderType.ImdbId)
{
item.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
item.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
item.TvDbId = providerIds.TheTvDb;
}
// Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false;
var existingMovieDbId = false;
var existingTvDbId = false;
if (item.ImdbId.HasValue())
{
existingImdb = await Repo.GetAll().AnyAsync(x =>
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TheMovieDbId.HasValue())
{
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TvDbId.HasValue())
{
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (existingImdb || existingTvDbId || existingMovieDbId)
{
// We already have it!
return;
}
item.Seasons.ToList().AddRange(seasonsContent);
contentToAdd.Add(item);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}",
show.title);
}
}
if (contentToAdd.Count > 500 || recentlyAdded)
{
await Repo.AddRange(contentToAdd);
contentToAdd.Clear();
}
}
/// <summary>
/// Gets all the library sections.
/// If the user has specified only certain libraries then we will only look for those

@ -50,12 +50,13 @@ namespace Ombi.Schedule.Jobs.Plex
await Cache(server);
}
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
}
catch (Exception e)
{
_log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed");
}
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
}
private async Task Cache(PlexServers settings)

@ -31,6 +31,7 @@ namespace Ombi.Schedule.Jobs
public enum PlexMediaType
{
Movie = 0,
Show = 1
Show = 1,
Episode = 2,
}
}

@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
using Hangfire;
namespace Ombi.Schedule.Jobs.Plex
{
public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync
{
public PlexRecentlyAddedSync(IPlexContentSync sync)
{
_sync = sync;
}
private readonly IPlexContentSync _sync;
public void Start()
{
BackgroundJob.Enqueue(() => _sync.CacheContent(true));
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_sync?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

@ -21,7 +21,7 @@ namespace Ombi.Settings.Settings.Models
}
public static string PlexContent(JobSettings s)
{
return Get(s.PlexContentSync, Cron.HourInterval(6));
return Get(s.PlexContentSync, Cron.Daily(2));
}
public static string PlexRecentlyAdded(JobSettings s)
{
@ -50,7 +50,7 @@ namespace Ombi.Settings.Settings.Models
}
public static string RefreshMetadata(JobSettings s)
{
return Get(s.RefreshMetadata, Cron.Daily(3));
return Get(s.RefreshMetadata, Cron.DayInterval(2));
}
private static string Get(string settings, string defaultCron)

@ -51,6 +51,15 @@ namespace Ombi.Store.Entities
public string Url { get; set; }
public ICollection<EmbyEpisode> Episodes { get; set; }
[NotMapped]
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
[NotMapped]
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
[NotMapped]
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
}
public enum EmbyMediaType

@ -1,132 +1,134 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Ombi.Api.Emby;
using Ombi.Api.Plex;
using Ombi.Config;
using Ombi.Controllers;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Models;
using Ombi.Notifications;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Context;
using Ombi.Store.Entities;
//using System.Collections.Generic;
//using System.Linq;
//using System.Threading;
//using System.Threading.Tasks;
//using AutoMapper;
//using Microsoft.AspNetCore.Http;
//using Microsoft.AspNetCore.Http.Features.Authentication;
//using Microsoft.AspNetCore.Identity;
//using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
//using Microsoft.EntityFrameworkCore;
//using Microsoft.EntityFrameworkCore.Infrastructure;
//using Microsoft.Extensions.DependencyInjection;
//using Microsoft.Extensions.Options;
//using Moq;
//using NUnit.Framework;
//using Ombi.Api.Emby;
//using Ombi.Api.Plex;
//using Ombi.Config;
//using Ombi.Controllers;
//using Ombi.Core.Authentication;
//using Ombi.Core.Settings;
//using Ombi.Core.Settings.Models.External;
//using Ombi.Models;
//using Ombi.Notifications;
//using Ombi.Schedule.Jobs.Ombi;
//using Ombi.Settings.Settings.Models;
//using Ombi.Settings.Settings.Models.Notifications;
//using Ombi.Store.Context;
//using Ombi.Store.Entities;
namespace Ombi.Tests
{
[TestFixture]
[Ignore("Need to sort out the DB, looks like it's using the real one...")]
public class IdentityControllerTests
{
[SetUp]
public void Setup()
{
_plexApi = new Mock<IPlexApi>();
_embyApi = new Mock<IEmbyApi>();
_mapper = new Mock<IMapper>();
_emailProvider = new Mock<IEmailProvider>();
_emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
_customizationSettings = new Mock<ISettingsService<CustomizationSettings>>();
_welcomeEmail = new Mock<IWelcomeEmail>();
_embySettings = new Mock<ISettingsService<EmbySettings>>();
_plexSettings = new Mock<ISettingsService<PlexSettings>>();
//namespace Ombi.Tests
//{
// [TestFixture]
// [Ignore("Need to sort out the DB, looks like it's using the real one...")]
// public class IdentityControllerTests
// {
// [SetUp]
// public void Setup()
// {
// _plexApi = new Mock<IPlexApi>();
// _embyApi = new Mock<IEmbyApi>();
// _mapper = new Mock<IMapper>();
// _emailProvider = new Mock<IEmailProvider>();
// _emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
// _customizationSettings = new Mock<ISettingsService<CustomizationSettings>>();
// _welcomeEmail = new Mock<IWelcomeEmail>();
// _embySettings = new Mock<ISettingsService<EmbySettings>>();
// _plexSettings = new Mock<ISettingsService<PlexSettings>>();
var services = new ServiceCollection();
services.AddEntityFrameworkInMemoryDatabase()
.AddDbContext<OmbiContext>();
services.AddIdentity<OmbiUser, IdentityRole>()
.AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
// var services = new ServiceCollection();
// services.AddEntityFrameworkInMemoryDatabase()
// .AddDbContext<OmbiContext>();
// services.AddIdentity<OmbiUser, IdentityRole>()
// .AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
services.AddTransient(x => _plexApi.Object);
services.AddTransient(x => _embyApi.Object);
services.AddTransient(x => _customizationSettings.Object);
services.AddTransient(x => _welcomeEmail.Object);
services.AddTransient(x => _emailSettings.Object);
services.AddTransient(x => _emailProvider.Object);
services.AddTransient(x => _mapper.Object);
services.AddTransient(x => _embySettings.Object);
services.AddTransient(x => _plexSettings.Object);
// services.AddTransient(x => _plexApi.Object);
// services.AddTransient(x => _embyApi.Object);
// services.AddTransient(x => _customizationSettings.Object);
// services.AddTransient(x => _welcomeEmail.Object);
// services.AddTransient(x => _emailSettings.Object);
// services.AddTransient(x => _emailProvider.Object);
// services.AddTransient(x => _mapper.Object);
// services.AddTransient(x => _embySettings.Object);
// services.AddTransient(x => _plexSettings.Object);
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 1;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.User.AllowedUserNameCharacters = string.Empty;
});
// services.Configure<IdentityOptions>(options =>
// {
// options.Password.RequireDigit = false;
// options.Password.RequiredLength = 1;
// options.Password.RequireLowercase = false;
// options.Password.RequireNonAlphanumeric = false;
// options.Password.RequireUppercase = false;
// options.User.AllowedUserNameCharacters = string.Empty;
// });
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
_serviceProvider = services.BuildServiceProvider();
_userManager = _serviceProvider.GetRequiredService<OmbiUserManager>();
Controller = new IdentityController(_userManager, _mapper.Object, _serviceProvider.GetService<RoleManager<IdentityRole>>(), _emailProvider.Object,
_emailSettings.Object, _customizationSettings.Object,_welcomeEmail.Object, null, null, null, null, null, null, null, null);
}
// // Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
// var context = new DefaultHttpContext();
// context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
// services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
// _serviceProvider = services.BuildServiceProvider();
// _userManager = _serviceProvider.GetRequiredService<OmbiUserManager>();
private OmbiUserManager _userManager;
private Mock<IEmailProvider> _emailProvider;
private Mock<ISettingsService<EmailNotificationSettings>> _emailSettings;
private Mock<ISettingsService<CustomizationSettings>> _customizationSettings;
private Mock<ISettingsService<EmbySettings>> _embySettings;
private Mock<ISettingsService<PlexSettings>> _plexSettings;
private Mock<IWelcomeEmail> _welcomeEmail;
private Mock<IMapper> _mapper;
private Mock<IPlexApi> _plexApi;
private Mock<IEmbyApi> _embyApi;
private ServiceProvider _serviceProvider;
// Controller = new IdentityController(_userManager, _mapper.Object,
// _serviceProvider.GetService<RoleManager<IdentityRole>>(), _emailProvider.Object,
// _emailSettings.Object, _customizationSettings.Object, _welcomeEmail.Object, null, null, null, null,
// null, null, null, null, null);
// }
private IdentityController Controller { get; set; }
// private OmbiUserManager _userManager;
// private Mock<IEmailProvider> _emailProvider;
// private Mock<ISettingsService<EmailNotificationSettings>> _emailSettings;
// private Mock<ISettingsService<CustomizationSettings>> _customizationSettings;
// private Mock<ISettingsService<EmbySettings>> _embySettings;
// private Mock<ISettingsService<PlexSettings>> _plexSettings;
// private Mock<IWelcomeEmail> _welcomeEmail;
// private Mock<IMapper> _mapper;
// private Mock<IPlexApi> _plexApi;
// private Mock<IEmbyApi> _embyApi;
// private ServiceProvider _serviceProvider;
[Test]
public async Task CreateWizardUser_Should_CreateUser_WhenThereAreNoOtherUsers()
{
var model = new CreateUserWizardModel()
{
Password = "a",
Username = "b"
};
// private IdentityController Controller { get; set; }
var result = await Controller.CreateWizardUser(model);
// [Test]
// public async Task CreateWizardUser_Should_CreateUser_WhenThereAreNoOtherUsers()
// {
// var model = new CreateUserWizardModel()
// {
// Password = "a",
// Username = "b"
// };
Assert.That(result, Is.True);
}
// var result = await Controller.CreateWizardUser(model);
[Test]
public async Task CreateWizardUser_ShouldNot_CreateUser_WhenThereAreOtherUsers()
{
var um = _serviceProvider.GetService<OmbiUserManager>();
var r = await um.CreateAsync(new OmbiUser { UserName = "aaaa",UserType = UserType.LocalUser}, "bbb");
var model = new CreateUserWizardModel
{
Password = "a",
Username = "b"
};
// Assert.That(result, Is.True);
// }
var result = await Controller.CreateWizardUser(model);
// [Test]
// public async Task CreateWizardUser_ShouldNot_CreateUser_WhenThereAreOtherUsers()
// {
// var um = _serviceProvider.GetService<OmbiUserManager>();
// var r = await um.CreateAsync(new OmbiUser { UserName = "aaaa",UserType = UserType.LocalUser}, "bbb");
// var model = new CreateUserWizardModel
// {
// Password = "a",
// Username = "b"
// };
Assert.That(result, Is.False);
}
}
}
// var result = await Controller.CreateWizardUser(model);
// Assert.That(result, Is.False);
// }
// }
//}

@ -1,4 +1,4 @@
<div class="{{ user.name && (hasRole('Admin') || hasRole('PowerUser')) ? 'adminUser' : 'user'}}">
<div [ngClass]="user.name && roleClass()">
<p-growl [value]="notificationService.messages" [life]="3000"></p-growl>
<nav *ngIf="showNav" class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
@ -72,7 +72,7 @@
<ul class="nav navbar-nav navbar-right">
<li *ngIf="hasRole('Admin') " [routerLinkActive]="['active']">
<li *ngIf="hasRole('Admin') " id="settings" [routerLinkActive]="['active']">
<a [routerLink]="['/Settings/About']">
<i *ngIf="!updateAvailable" class="fa fa-cog"></i>

@ -73,6 +73,15 @@ export class AppComponent implements OnInit {
});
}
public roleClass() {
if (this.user.roles.some(r => r === "Admin")) {
return "adminUser";
} else if (this.user.roles.some(r => r === "PowerUser")) {
return "powerUser";
}
return "user";
}
public hasRole(role: string): boolean {
return this.user.roles.some(r => r === role);
}

@ -18,6 +18,11 @@ export interface IMovieRequests extends IFullBaseRequest {
qualityOverrideTitle: string;
}
export interface IFilterResult<T> {
total: number;
collection: T[];
}
export interface IMovieUpdateModel {
id: number;
}
@ -103,6 +108,8 @@ export interface IMovieRequestModel {
export interface IFilter {
availabilityFilter: FilterType;
statusFilter: FilterType;
position: number;
count: number;
}
export enum FilterType {

@ -5,12 +5,25 @@
<h1>{{issue.title}} </h1>
<div class="col-md-6">
<img class="img-responsive poster" src="{{posterPath}}" alt="poster">
<span class="label label-info">{{IssueStatus[issue.status]}}</span>
<span class="label label-success">{{issue.issueCategory.value}}</span>
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}</h3>
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3>
<h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3>
<div class="issue-status">
<span *ngIf="issue.status === IssueStatus.Pending" id="pendingLabel" class="label label-warning">{{IssueStatus[issue.status]}}</span>
<span *ngIf="issue.status === IssueStatus.InProgress" id="inprogressLabel" class="label label-info">{{IssueStatus[issue.status]}}</span>
<span *ngIf="issue.status === IssueStatus.Resolved" id="resolvedLabel" class="label label-success">{{IssueStatus[issue.status]}}</span>
</div>
<span class="label label-success">{{issue.issueCategory.value}}</span>
<br>
<span class="reported-by">
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}:</h3>
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}:</h3>
</span>
<span class="reported-user">
<h3 *ngIf="issue.userReported?.alias">{{issue.userReported.alias}}</h3>
<h3 *ngIf="!issue.userReported?.alias">{{issue.userReported.userName}}</h3>
</span>
<br>
<span class="subject-category"><h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}:</h3></span>
<span class="subject"><h3 *ngIf="issue.subject">{{issue.subject}}</h3></span>
<br>
<div class="form-group">
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>

@ -37,7 +37,7 @@ export class MovieRequestsComponent implements OnInit {
public filterType = FilterType;
public order: string = "requestedDate";
public reverse = false;
public reverse = true;
public totalMovies: number = 100;
private currentlyLoaded: number;
@ -72,7 +72,10 @@ export class MovieRequestsComponent implements OnInit {
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
this.filter = {
availabilityFilter: FilterType.None,
statusFilter: FilterType.None};
statusFilter: FilterType.None,
count: this.amountToLoad,
position: 0,
};
}
public paginate(event: IPagenator) {
@ -174,8 +177,9 @@ export class MovieRequestsComponent implements OnInit {
this.filter.availabilityFilter = filter;
this.requestService.filterMovies(this.filter)
.subscribe(x => {
this.setOverrides(x);
this.movieRequests = x;
this.totalMovies = x.total;
this.setOverrides(x.collection);
this.movieRequests = x.collection;
});
}
@ -184,8 +188,9 @@ export class MovieRequestsComponent implements OnInit {
this.filter.statusFilter = filter;
this.requestService.filterMovies(this.filter)
.subscribe(x => {
this.setOverrides(x);
this.movieRequests = x;
this.totalMovies = x.total;
this.setOverrides(x.collection);
this.movieRequests = x.collection;
});
}
@ -223,7 +228,8 @@ export class MovieRequestsComponent implements OnInit {
}
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
if(this.filter.availabilityFilter === FilterType.None && this.filter.statusFilter === FilterType.None) {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
.subscribe(x => {
this.setOverrides(x);
if(!this.movieRequests) {
@ -232,6 +238,16 @@ export class MovieRequestsComponent implements OnInit {
this.movieRequests = x;
this.currentlyLoaded = currentlyLoaded + amountToLoad;
});
} else {
this.filter.position = currentlyLoaded;
this.requestService.filterMovies(this.filter)
.subscribe(x => {
this.setOverrides(x.collection);
this.totalMovies = x.total;
this.movieRequests = x.collection;
this.currentlyLoaded = currentlyLoaded + amountToLoad;
});
}
}
private updateRequest(request: IMovieRequests) {

@ -85,7 +85,7 @@
<br/>
<div *ngIf="result.available">
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
<a *ngIf="result.embyUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
</div>
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">

@ -129,7 +129,7 @@
</a>
</div>
<div *ngIf="node.data.embyUrl && node.data.available">
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.embyUrl}}"
<a style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{node.data.embyUrl}}"
target="_blank">
<i class="fa fa-eye"></i> {{ 'Search.ViewOnEmby' | translate }}
</a>

@ -6,7 +6,7 @@ import { Observable } from "rxjs/Rx";
import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces";
import { IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests, ITvUpdateModel } from "../interfaces";
import { IChildRequests, IFilter, IFilterResult, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests,ITvUpdateModel } from "../interfaces";
import { ITvRequestViewModel } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
@ -114,7 +114,7 @@ export class RequestService extends ServiceHelpers {
public deleteChild(child: IChildRequests): Observable<boolean> {
return this.http.delete<boolean>(`${this.url}tv/child/${child.id}`, {headers: this.headers});
}
public filterMovies(filter: IFilter): Observable<IMovieRequests[]> {
return this.http.post<IMovieRequests[]>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers});
public filterMovies(filter: IFilter): Observable<IFilterResult<IMovieRequests>> {
return this.http.post<IFilterResult<IMovieRequests>>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers});
}
}

@ -41,14 +41,6 @@
<small *ngIf="form.get('automaticUpdater').hasError('required')" class="error-text">The Automatic Update is required</small>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('automaticUpdater')?.value)">Test</button>
</div>
<div class="form-group">
<div>
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
@ -92,8 +84,12 @@
<small *ngIf="form.get('newsletter').hasError('required')" class="error-text">The Newsletter is required</small>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('newsletter')?.value)">Test</button>
</div>
</div>
<div class="form-group">
<div>
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</form>
</fieldset>
</div>
@ -102,4 +98,4 @@
<ul *ngIf="testModel">
<li *ngFor="let item of testModel.schedule">{{item | date:'short'}}</li>
</ul>
</p-dialog>
</p-dialog>

@ -30,7 +30,14 @@ export class MobileComponent implements OnInit {
});
});
this.mobileService.getUserDeviceList().subscribe(x => this.userList = x);
this.mobileService.getUserDeviceList().subscribe(x => {
if(x.length <= 0) {
this.userList = [];
this.userList.push({username:"None",devices:0});
} else {
this.userList = x;
}
});
}
public onSubmit(form: FormGroup) {

@ -54,6 +54,7 @@
<i class="fa fa-bell-o" aria-hidden="true"></i> Notifications <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Mobile']">Mobile</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/MassEmail']">Mass Email</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>

@ -7,4 +7,19 @@ export class SettingsMenuComponent {
public ignore(event: any): void {
event.preventDefault();
}
public ngOnInit() {
const element = document.getElementById("settings");
if (element != null) {
element.classList.add("active");
}
}
public ngOnDestroy() {
const element = document.getElementById("settings");
if (element != null) {
element.classList.remove("active");
}
}
}

@ -1,10 +1,8 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from "../../auth/auth.service";
import { IdentityService } from "../../services";
import { NotificationService } from "../../services";
import { SettingsService } from "../../services";
@Component({
templateUrl: "./createadmin.component.html",
@ -15,28 +13,12 @@ export class CreateAdminComponent {
public password: string;
constructor(private identityService: IdentityService, private notificationService: NotificationService,
private router: Router, private auth: AuthService, private settings: SettingsService) { }
private router: Router) { }
public createUser() {
this.identityService.createWizardUser({username: this.username, password: this.password, usePlexAdminAccount: false}).subscribe(x => {
if (x) {
// Log me in.
this.auth.login({ username: this.username, password: this.password, rememberMe: false, usePlexOAuth:false }).subscribe(c => {
localStorage.setItem("id_token", c.access_token);
// Mark that we have done the settings now
this.settings.getOmbi().subscribe(ombi => {
ombi.wizard = true;
this.settings.saveOmbi(ombi).subscribe(x => {
this.router.navigate(["search"]);
});
});
});
this.router.navigate(["login"]);
} else {
this.notificationService.error("There was an error... You might want to put this on Github...");
}

@ -2,8 +2,7 @@
import { Router } from "@angular/router";
import { PlexService } from "../../services";
import { IdentityService, NotificationService, SettingsService } from "../../services";
import { AuthService } from "./../../auth/auth.service";
import { IdentityService, NotificationService } from "../../services";
@Component({
templateUrl: "./plex.component.html",
@ -15,9 +14,7 @@ export class PlexComponent {
constructor(private plexService: PlexService, private router: Router,
private notificationService: NotificationService,
private identityService: IdentityService,
private settings: SettingsService,
private auth: AuthService) { }
private identityService: IdentityService) { }
public requestAuthToken() {
this.plexService.logIn(this.login, this.password).subscribe(x => {
@ -32,25 +29,7 @@ export class PlexComponent {
usePlexAdminAccount: true,
}).subscribe(y => {
if (y) {
this.auth.login({ username: this.login, password: this.password, rememberMe: false, usePlexOAuth: false }).subscribe(c => {
localStorage.setItem("id_token", c.access_token);
// Mark that we have done the settings now
this.settings.getOmbi().subscribe(ombi => {
ombi.wizard = true;
this.settings.saveOmbi(ombi).subscribe(s => {
this.settings.getUserManagementSettings().subscribe(usr => {
usr.importPlexAdmin = true;
this.settings.saveUserManagementSettings(usr).subscribe(saved => {
this.router.navigate(["login"]);
});
});
});
});
});
this.router.navigate(["login"]);
} else {
this.notificationService.error("Could not get the Plex Admin Information");
return;

@ -962,4 +962,15 @@ a > h4:hover {
.ui-state-active {
background-color: $primary-colour-outline $i;
color: black $i;
}
#themeContent {
font-family: monospace;
}
.reported-by,
.reported-user,
.subject-category,
.subject {
display: inline-block;
}

@ -48,6 +48,7 @@ namespace Ombi.Controllers
public IdentityController(OmbiUserManager user, IMapper mapper, RoleManager<IdentityRole> rm, IEmailProvider prov,
ISettingsService<EmailNotificationSettings> s,
ISettingsService<CustomizationSettings> c,
ISettingsService<OmbiSettings> ombiSettings,
IWelcomeEmail welcome,
IMovieRequestRepository m,
ITvRequestRepository t,
@ -73,6 +74,7 @@ namespace Ombi.Controllers
_issuesRepository = issues;
_requestLogRepository = requestLog;
_issueCommentsRepository = issueComments;
OmbiSettings = ombiSettings;
}
private OmbiUserManager UserManager { get; }
@ -81,6 +83,7 @@ namespace Ombi.Controllers
private IEmailProvider EmailProvider { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private ISettingsService<OmbiSettings> OmbiSettings { get; }
private IWelcomeEmail WelcomeEmail { get; }
private IMovieRequestRepository MovieRepo { get; }
private ITvRequestRepository TvRepo { get; }
@ -107,7 +110,7 @@ namespace Ombi.Controllers
public async Task<bool> CreateWizardUser([FromBody] CreateUserWizardModel user)
{
var users = UserManager.Users;
if (users.Any())
if (users.Any(x => !x.UserName.Equals("api", StringComparison.CurrentCultureIgnoreCase)))
{
// No one should be calling this. Only the wizard
return false;
@ -174,6 +177,12 @@ namespace Ombi.Controllers
{
LogErrors(result);
}
// Update the wizard flag
var settings = await OmbiSettings.GetSettingsAsync();
settings.Wizard = true;
await OmbiSettings.SaveSettingsAsync(settings);
return result.Succeeded;
}

@ -352,7 +352,7 @@ namespace Ombi.Controllers
/// <param name="vm"></param>
/// <returns></returns>
[HttpPost("movie/filter")]
public async Task<IEnumerable<MovieRequests>> Filter([FromBody] FilterViewModel vm)
public async Task<FilterResult<MovieRequests>> Filter([FromBody] FilterViewModel vm)
{
return await MovieRequestEngine.Filter(vm);
}

Loading…
Cancel
Save