The move!

This commit is contained in:
Jamie.Rees 2017-05-16 08:31:44 +01:00
commit 25526cc4d9
1147 changed files with 85 additions and 8524 deletions

View file

@ -0,0 +1,113 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Movie;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Quartz;
namespace Ombi.Services.Jobs
{
public class CouchPotatoCacher : IJob, ICouchPotatoCacher
{
public CouchPotatoCacher(ISettingsService<CouchPotatoSettings> cpSettings, ICouchPotatoApi cpApi, ICacheProvider cache, IJobRecord rec)
{
CpSettings = cpSettings;
CpApi = cpApi;
Cache = cache;
Job = rec;
}
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ICacheProvider Cache { get; }
private ICouchPotatoApi CpApi { get; }
private IJobRecord Job { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Queued()
{
Log.Trace("Getting the settings");
var settings = CpSettings.GetSettings();
if (settings.Enabled)
{
Job.SetRunning(true, JobNames.CpCacher);
Log.Trace("Getting all movies from CouchPotato");
try
{
var movies = CpApi.GetMovies(settings.FullUri, settings.ApiKey, new[] { "active" });
if (movies != null)
{
Cache.Set(CacheKeys.CouchPotatoQueued, movies, CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
catch (System.Exception ex)
{
Log.Error(ex, "Failed caching queued items from CouchPotato");
}
finally
{
Job.Record(JobNames.CpCacher);
Job.SetRunning(false, JobNames.CpCacher);
}
}
}
// we do not want to set here...
public int[] QueuedIds()
{
try
{
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
var items = movies?.movies?.Select(x => x.info?.tmdb_id);
if(items != null)
{
return items.Cast<int>().ToArray();
}
return new int[] { };
}
catch (Exception e)
{
Log.Error(e);
return new int[] {};
}
}
public void Execute(IJobExecutionContext context)
{
Queued();
}
}
}

View file

@ -0,0 +1,362 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Models;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz;
using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs
{
public class EmbyAvailabilityChecker : IJob, IEmbyAvailabilityChecker
{
public EmbyAvailabilityChecker(ISettingsService<EmbySettings> embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<EmbyEpisodes> repo, IEmbyNotificationEngine e, IRepository<EmbyContent> content)
{
Emby = embySettings;
RequestService = request;
EmbyApi = emby;
Cache = cache;
Notification = notify;
Job = rec;
UserNotifyRepo = users;
EpisodeRepo = repo;
NotificationEngine = e;
EmbyContent = content;
}
private ISettingsService<EmbySettings> Emby { get; }
private IRepository<EmbyEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IEmbyApi EmbyApi { get; }
private ICacheProvider Cache { get; }
private INotificationService Notification { get; }
private IJobRecord Job { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; }
private INotificationEngine NotificationEngine { get; }
private IRepository<EmbyContent> EmbyContent { get; }
public void CheckAndUpdateAll()
{
var embySettings = Emby.GetSettings();
if (!embySettings.Enable)
{
return;
}
if (!ValidateSettings(embySettings))
{
Log.Debug("Validation of the Emby settings failed.");
return;
}
var content = EmbyContent.GetAll().ToList();
var movies = GetEmbyMovies(content).ToArray();
var shows = GetEmbyTvShows(content).ToArray();
var albums = GetEmbyMusic(content).ToArray();
var requests = RequestService.GetAll();
var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray();
if (!requestedModels.Any())
{
Log.Debug("There are no requests to check.");
return;
}
var modifiedModel = new List<RequestedModel>();
foreach (var r in requestedModels)
{
var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy");
bool matchResult;
switch (r.Type)
{
case RequestType.Movie:
matchResult = IsMovieAvailable(movies, r.Title, releaseDate, r.ImdbId);
break;
case RequestType.TvShow:
if (!embySettings.EnableEpisodeSearching)
{
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
}
else
{
matchResult = r.Episodes.Any() ?
r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber)) :
IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
}
break;
case RequestType.Album:
//matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName); // TODO Emby
matchResult = false;
break;
default:
throw new ArgumentOutOfRangeException();
}
if (matchResult)
{
r.Available = true;
modifiedModel.Add(r);
continue;
}
}
Log.Debug("Requests that will be updated count {0}", modifiedModel.Count);
if (modifiedModel.Any())
{
NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable);
RequestService.BatchUpdate(modifiedModel);
}
}
public IEnumerable<EmbyContent> GetEmbyMovies(IEnumerable<EmbyContent> content)
{
return content.Where(x => x.Type == EmbyMediaType.Movie);
}
public bool IsMovieAvailable(IEnumerable<EmbyContent> embyMovies, string title, string year, string providerId)
{
var movie = GetMovie(embyMovies, title, year, providerId);
return movie != null;
}
public EmbyContent GetMovie(IEnumerable<EmbyContent> embyMovies, string title, string year, string providerId)
{
if (embyMovies.Count() == 0)
{
return null;
}
foreach (var movie in embyMovies)
{
if (string.IsNullOrEmpty(movie.Title) || movie.PremierDate == DateTime.MinValue)
{
continue;
}
if (!string.IsNullOrEmpty(movie.ProviderId) &&
movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return movie;
}
if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
movie.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
return movie;
}
}
return null;
}
public IEnumerable<EmbyContent> GetEmbyTvShows(IEnumerable<EmbyContent> content)
{
return content.Where(x => x.Type == EmbyMediaType.Series);
}
public bool IsTvShowAvailable(IEnumerable<EmbyContent> embyShows, string title, string year, string providerId, int[] seasons = null)
{
var show = GetTvShow(embyShows, title, year, providerId, seasons);
return show != null;
}
public EmbyContent GetTvShow(IEnumerable<EmbyContent> embyShows, string title, string year, string providerId,
int[] seasons = null)
{
foreach (var show in embyShows)
{
//if (show.ProviderId == providerId && seasons != null) // TODO Emby
//{
// var showSeasons = ByteConverterHelper.ReturnObject<int[]>(show.Seasons);
// if (seasons.Any(season => showSeasons.Contains(season)))
// {
// return show;
// }
// return null;
//}
if (!string.IsNullOrEmpty(show.ProviderId) &&
show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return show;
}
if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
show.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
return show;
}
}
return null;
}
public bool IsEpisodeAvailable(string theTvDbId, int season, int episode)
{
var ep = EpisodeRepo.Custom(
connection =>
{
connection.Open();
var result = connection.Query<EmbyEpisodes>("select * from EmbyEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
}).ToList();
if (!ep.Any())
{
Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}", theTvDbId, season, episode);
return false;
}
foreach (var result in ep)
{
if (result.ProviderId.Equals(theTvDbId) && result.EpisodeNumber == episode && result.SeasonNumber == season)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets the episode's db in the cache.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<EmbyEpisodes>> GetEpisodes()
{
var episodes = await EpisodeRepo.GetAllAsync();
if (episodes == null)
{
return new HashSet<EmbyEpisodes>();
}
return episodes;
}
/// <summary>
/// Gets the episode's stored in the db and then filters on the TheTvDBId.
/// </summary>
/// <param name="theTvDbId">The tv database identifier.</param>
/// <returns></returns>
public async Task<IEnumerable<EmbyEpisodes>> GetEpisodes(int theTvDbId)
{
var ep = await EpisodeRepo.CustomAsync(async connection =>
{
connection.Open();
var result = await connection.QueryAsync<EmbyEpisodes>(@"select ee.* from EmbyEpisodes ee inner join EmbyContent ec
on ee.ParentId = ec.EmbyId
where ec.ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
});
var embyEpisodes = ep as EmbyEpisodes[] ?? ep.ToArray();
if (!embyEpisodes.Any())
{
Log.Info("Episode db info is not available.");
return new List<EmbyEpisodes>();
}
return embyEpisodes;
}
public IEnumerable<EmbyContent> GetEmbyMusic(IEnumerable<EmbyContent> content)
{
return content.Where(x => x.Type == EmbyMediaType.Music);
}
private bool ValidateSettings(EmbySettings emby)
{
if (emby.Enable)
{
if (string.IsNullOrEmpty(emby?.Ip) || string.IsNullOrEmpty(emby?.ApiKey) || string.IsNullOrEmpty(emby?.AdministratorId))
{
Log.Warn("A setting is null, Ensure Emby is configured correctly");
return false;
}
}
return emby.Enable;
}
public void Execute(IJobExecutionContext context)
{
Job.SetRunning(true, JobNames.EmbyChecker);
try
{
CheckAndUpdateAll();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyChecker);
Job.SetRunning(false, JobNames.EmbyChecker);
}
}
public void Start()
{
Job.SetRunning(true, JobNames.EmbyChecker);
try
{
CheckAndUpdateAll();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyChecker);
Job.SetRunning(false, JobNames.EmbyChecker);
}
}
}
}

View file

@ -0,0 +1,298 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs.Interfaces;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using Quartz;
using EmbyMediaType = Ombi.Api.Models.Emby.EmbyMediaType;
namespace Ombi.Services.Jobs
{
public class EmbyContentCacher : IJob, IEmbyContentCacher
{
public EmbyContentCacher(ISettingsService<EmbySettings> embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache,
IJobRecord rec, IRepository<EmbyEpisodes> repo, IRepository<EmbyContent> content)
{
Emby = embySettings;
RequestService = request;
EmbyApi = emby;
Cache = cache;
Job = rec;
EpisodeRepo = repo;
EmbyContent = content;
}
private ISettingsService<EmbySettings> Emby { get; }
private IRepository<EmbyEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IEmbyApi EmbyApi { get; }
private ICacheProvider Cache { get; }
private IJobRecord Job { get; }
private IRepository<EmbyContent> EmbyContent { get; }
public void CacheContent()
{
var embySettings = Emby.GetSettings();
if (!embySettings.Enable)
{
return;
}
if (!ValidateSettings(embySettings))
{
Log.Debug("Validation of emby settings failed.");
return;
}
CachedLibraries(embySettings);
}
public List<EmbyMovieItem> GetMovies()
{
var settings = Emby.GetSettings();
return EmbyApi.GetAllMovies(settings.ApiKey, settings.AdministratorId, settings.FullUri).Items;
}
public List<EmbySeriesItem> GetTvShows()
{
var settings = Emby.GetSettings();
return EmbyApi.GetAllShows(settings.ApiKey, settings.AdministratorId, settings.FullUri).Items;
}
private void CachedLibraries(EmbySettings embySettings)
{
if (!ValidateSettings(embySettings))
{
Log.Warn("The settings are not configured");
}
try
{
var movies = GetMovies();
// Delete everything
EmbyContent.Custom(connection =>
{
connection.Open();
connection.Query("delete from EmbyContent where type = @type", new { type = 0 });
return new List<EmbyContent>();
});
foreach (var m in movies)
{
if (m.Type.Equals("boxset", StringComparison.CurrentCultureIgnoreCase))
{
var info = EmbyApi.GetCollection(m.Id, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri);
foreach (var item in info.Items)
{
var movieInfo = EmbyApi.GetInformation(item.Id, EmbyMediaType.Movie, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri).MovieInformation;
ProcessMovies(movieInfo);
}
}
else
{
var movieInfo = EmbyApi.GetInformation(m.Id, EmbyMediaType.Movie, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri).MovieInformation;
ProcessMovies(movieInfo);
}
}
var tv = GetTvShows();
// Delete everything
EmbyContent.Custom(connection =>
{
connection.Open();
connection.Query("delete from EmbyContent where type = @type", new { type = 1 });
return new List<EmbyContent>();
});
foreach (var t in tv)
{
var tvInfo = EmbyApi.GetInformation(t.Id, EmbyMediaType.Series, embySettings.ApiKey,
embySettings.AdministratorId, embySettings.FullUri).SeriesInformation;
if (string.IsNullOrEmpty(tvInfo.ProviderIds?.Tvdb))
{
Log.Error("Provider Id on tv {0} is null", t.Name);
continue;
}
// Check if it exists
var item = EmbyContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<EmbyContent>("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = tvInfo.ProviderIds.Tvdb, type = 1 });
connection.Dispose();
return media;
});
if (item != null && item.EmbyId != t.Id)
{
// delete this item since the Id has changed
EmbyContent.Delete(item);
item = null;
}
if (item == null)
{
EmbyContent.Insert(new EmbyContent
{
ProviderId = tvInfo.ProviderIds.Tvdb,
PremierDate = tvInfo.PremiereDate,
Title = tvInfo.Name,
Type = Store.Models.Plex.EmbyMediaType.Series,
EmbyId = t.Id,
AddedAt = DateTime.UtcNow
});
}
}
//TODO Emby
//var albums = GetPlexAlbums(results);
//foreach (var a in albums)
//{
// if (string.IsNullOrEmpty(a.ProviderId))
// {
// Log.Error("Provider Id on album {0} is null", a.Title);
// continue;
// }
// // Check if it exists
// var item = EmbyContent.Custom(connection =>
// {
// connection.Open();
// var media = connection.QueryFirstOrDefault<PlexContent>("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { a.ProviderId, type = 2 });
// connection.Dispose();
// return media;
// });
// if (item == null)
// {
// EmbyContent.Insert(new PlexContent
// {
// ProviderId = a.ProviderId,
// ReleaseYear = a.ReleaseYear ?? string.Empty,
// Title = a.Title,
// Type = Store.Models.Plex.PlexMediaType.Artist,
// Url = a.Url
// });
// }
//}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to obtain Emby libraries");
}
}
private bool ValidateSettings(EmbySettings emby)
{
if (emby.Enable)
{
if (emby?.Ip == null || string.IsNullOrEmpty(emby?.ApiKey))
{
Log.Warn("A setting is null, Ensure Emby is configured correctly, and we have a Emby Auth token.");
return false;
}
}
return emby.Enable;
}
public void Execute(IJobExecutionContext context)
{
Job.SetRunning(true, JobNames.EmbyCacher);
try
{
CacheContent();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyCacher);
Job.SetRunning(false, JobNames.EmbyCacher);
}
}
private void ProcessMovies(EmbyMovieInformation movieInfo)
{
if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb))
{
Log.Error("Provider Id on movie {0} is null", movieInfo.Name);
return;
}
// Check if it exists
var item = EmbyContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<EmbyContent>("select * from EmbyContent where ProviderId = @ProviderId and type = @type", new { ProviderId = movieInfo.ProviderIds.Imdb, type = 0 });
connection.Dispose();
return media;
});
if (item != null && item.EmbyId != movieInfo.Id)
{
// delete this item since the Id has changed
EmbyContent.Delete(item);
item = null;
}
if (item == null)
{
// Doesn't exist, insert it
EmbyContent.Insert(new EmbyContent
{
ProviderId = movieInfo.ProviderIds.Imdb,
PremierDate = movieInfo.PremiereDate,
Title = movieInfo.Name,
Type = Store.Models.Plex.EmbyMediaType.Movie,
EmbyId = movieInfo.Id,
AddedAt = DateTime.UtcNow
});
}
}
}
}

View file

@ -0,0 +1,180 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexEpisodeCacher.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs.Interfaces;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class EmbyEpisodeCacher : IJob, IEmbyEpisodeCacher
{
public EmbyEpisodeCacher(ISettingsService<EmbySettings> embySettings, IEmbyApi emby, ICacheProvider cache,
IJobRecord rec, IRepository<EmbyEpisodes> repo, ISettingsService<ScheduledJobsSettings> jobs)
{
Emby = embySettings;
EmbyApi = emby;
Cache = cache;
Job = rec;
Repo = repo;
Jobs = jobs;
}
private ISettingsService<EmbySettings> Emby { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IEmbyApi EmbyApi { get; }
private ICacheProvider Cache { get; }
private IJobRecord Job { get; }
private IRepository<EmbyEpisodes> Repo { get; }
private ISettingsService<ScheduledJobsSettings> Jobs { get; }
private const string TableName = "EmbyEpisodes";
// Note, once an episode exists, we store it and it always exists.
// We might want to look at checking if something has been removed from the server in the future.
public void CacheEpisodes(EmbySettings settings)
{
var allEpisodes = EmbyApi.GetAllEpisodes(settings.ApiKey, settings.AdministratorId, settings.FullUri);
var model = new List<EmbyEpisodes>();
foreach (var ep in allEpisodes.Items)
{
var epInfo = EmbyApi.GetInformation(ep.Id, EmbyMediaType.Episode, settings.ApiKey,
settings.AdministratorId, settings.FullUri);
if (epInfo.EpisodeInformation?.ProviderIds?.Tvdb == null)
{
continue;
}
// Check it this episode exists
var item = Repo.Custom(connection =>
{
connection.Open();
var media =
connection.QueryFirstOrDefault<EmbyEpisodes>(
"select * from EmbyEpisodes where ProviderId = @ProviderId",
new {ProviderId = epInfo.EpisodeInformation?.ProviderIds?.Tvdb});
connection.Dispose();
return media;
});
if (item != null)
{
if (item.EmbyId != ep.Id) // The id's dont match, delete it
{
Repo.Delete(item);
item = null;
}
}
if (item == null)
{
// add it
model.Add(new EmbyEpisodes
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumber,
SeasonNumber = ep.ParentIndexNumber,
EpisodeTitle = ep.Name,
ParentId = ep.SeriesId,
ShowTitle = ep.SeriesName,
ProviderId = epInfo.EpisodeInformation.ProviderIds.Tvdb,
AddedAt = DateTime.UtcNow
});
}
}
// Insert the new items
var result = Repo.BatchInsert(model, TableName);
if (!result)
{
Log.Error("Saving the emby episodes to the DB Failed");
}
}
public void Start()
{
try
{
var s = Emby.GetSettings();
if (!s.EnableEpisodeSearching)
{
return;
}
Job.SetRunning(true, JobNames.EmbyEpisodeCacher);
CacheEpisodes(s);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyEpisodeCacher);
Job.SetRunning(false, JobNames.EmbyEpisodeCacher);
}
}
public void Execute(IJobExecutionContext context)
{
try
{
var s = Emby.GetSettings();
if (!s.EnableEpisodeSearching)
{
return;
}
Job.SetRunning(true, JobNames.EmbyEpisodeCacher);
CacheEpisodes(s);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EmbyEpisodeCacher);
Job.SetRunning(false, JobNames.EmbyEpisodeCacher);
}
}
}
}

View file

@ -0,0 +1,136 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StoreCleanup.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.Users;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class EmbyUserChecker : IJob, IEmbyUserChecker
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public EmbyUserChecker(IExternalUserRepository<EmbyUsers> plexUsers, IEmbyApi embyApi, IJobRecord rec, ISettingsService<EmbySettings> embyS, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings,
IRequestService requestService, IUserRepository localUser)
{
Repo = plexUsers;
JobRecord = rec;
EmbyApi = embyApi;
EmbySettings = embyS;
PlexRequestSettings = prSettings;
UserManagementSettings = umSettings;
RequestService = requestService;
LocalUserRepository = localUser;
}
private IJobRecord JobRecord { get; }
private IEmbyApi EmbyApi { get; }
private IExternalUserRepository<EmbyUsers> Repo { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
private IRequestService RequestService { get; }
private IUserRepository LocalUserRepository { get; }
public void Start()
{
JobRecord.SetRunning(true, JobNames.EmbyUserChecker);
try
{
var settings = EmbySettings.GetSettings();
if (string.IsNullOrEmpty(settings.ApiKey) || !settings.Enable)
{
return;
}
var embyUsers = EmbyApi.GetUsers(settings.FullUri, settings.ApiKey);
var userManagementSettings = UserManagementSettings.GetSettings();
var dbUsers = Repo.GetAll().ToList();
// Regular users
foreach (var user in embyUsers)
{
var dbUser = dbUsers.FirstOrDefault(x => x.EmbyUserId == user.Id);
if (dbUser != null)
{
// we already have a user
continue;
}
// Looks like it's a new user!
var m = new EmbyUsers
{
EmbyUserId = user.Id,
Permissions = UserManagementHelper.GetPermissions(userManagementSettings),
Features = UserManagementHelper.GetFeatures(userManagementSettings),
UserAlias = string.Empty,
Username = user.Name,
LoginId = Guid.NewGuid().ToString()
};
// If it's the admin, give them the admin permission
if (user.Policy.IsAdministrator)
{
if (!((Permissions) m.Permissions).HasFlag(Permissions.Administrator))
{
m.Permissions += (int)Permissions.Administrator;
}
}
Repo.Insert(m);
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
JobRecord.SetRunning(false, JobNames.EmbyUserChecker);
JobRecord.Record(JobNames.EmbyUserChecker);
}
}
public void Execute(IJobExecutionContext context)
{
Start();
}
}
}

View file

@ -0,0 +1,337 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserRequestLimitResetter.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class FaultQueueHandler : IJob, IFaultQueueHandler
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public FaultQueueHandler(IJobRecord record, IRepository<RequestQueue> repo, ISonarrApi sonarrApi,
ISickRageApi srApi, ISettingsService<SonarrSettings> sonarrSettings, ISettingsService<SickRageSettings> srSettings,
ICouchPotatoApi cpApi, ISettingsService<CouchPotatoSettings> cpsettings, IRequestService requestService,
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi headphonesApi, ISettingsService<PlexRequestSettings> prSettings,
ISecurityExtensions security, IMovieSender movieSender, ICacheProvider cache)
{
Record = record;
Repo = repo;
SonarrApi = sonarrApi;
SrApi = srApi;
CpApi = cpApi;
HpApi = headphonesApi;
RequestService = requestService;
SickrageSettings = srSettings;
SonarrSettings = sonarrSettings;
CpSettings = cpsettings;
HeadphoneSettings = hpSettings;
Security = security;
PrSettings = prSettings.GetSettings();
MovieSender = movieSender;
Cache = cache;
}
private IMovieSender MovieSender { get; }
private IRepository<RequestQueue> Repo { get; }
private IJobRecord Record { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SrApi { get; }
private ICacheProvider Cache { get; }
private ICouchPotatoApi CpApi { get; }
private IHeadphonesApi HpApi { get; }
private IRequestService RequestService { get; }
private PlexRequestSettings PrSettings { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickrageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISettingsService<HeadphonesSettings> HeadphoneSettings { get; }
private ISecurityExtensions Security { get; }
public void Start()
{
Record.SetRunning(true, JobNames.CpCacher);
try
{
var faultedRequests = Repo.GetAll().ToList();
var missingInfo = faultedRequests.Where(x => x.FaultType == FaultType.MissingInformation).ToList();
ProcessMissingInformation(missingInfo);
var transientErrors = faultedRequests.Where(x => x.FaultType == FaultType.RequestFault).ToList();
ProcessTransientErrors(transientErrors);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Record.Record(JobNames.FaultQueueHandler);
Record.SetRunning(false, JobNames.CpCacher);
}
}
public void Execute(IJobExecutionContext context)
{
Start();
}
private void ProcessMissingInformation(List<RequestQueue> requests)
{
if (!requests.Any())
{
return;
}
var sonarrSettings = SonarrSettings.GetSettings();
var sickrageSettings = SickrageSettings.GetSettings();
var tv = requests.Where(x => x.Type == RequestType.TvShow);
// TV
var tvApi = new TvMazeApi();
foreach (var t in tv)
{
var providerId = int.Parse(t.PrimaryIdentifier);
var showInfo = tvApi.ShowLookup(providerId);
if (showInfo.externals?.thetvdb != null)
{
// We now have the info
var tvModel = ByteConverterHelper.ReturnObject<RequestedModel>(t.Content);
tvModel.ProviderId = showInfo.externals.thetvdb.Value;
var result = ProcessTvShow(tvModel, sonarrSettings, sickrageSettings);
if (!result)
{
// we now have the info but couldn't add it, so add it back into the queue but with a different fault
t.Content = ByteConverterHelper.ReturnBytes(tvModel);
t.FaultType = FaultType.RequestFault;
t.LastRetry = DateTime.UtcNow;
Repo.Update(t);
}
else
{
// Make sure it's been requested
var existingRequests = RequestService.GetAll();
var thisItem = existingRequests.Any(x => x.Title.Equals(tvModel.Title));
if (!thisItem)
{
tvModel.Approved = true;
RequestService.AddRequest(tvModel);
}
// Successful, remove from the fault queue
Repo.Delete(t);
}
}
}
}
private bool ProcessTvShow(RequestedModel tvModel, SonarrSettings sonarr, SickRageSettings sickrage)
{
try
{
var sender = new TvSenderOld(SonarrApi, SrApi, Cache);
if (sonarr.Enabled)
{
var task = sender.SendToSonarr(sonarr, tvModel, sonarr.QualityProfile);
var a = task.Result;
if (string.IsNullOrEmpty(a?.title))
{
// Couldn't send it
return false;
}
return true;
}
if (sickrage.Enabled)
{
var result = sender.SendToSickRage(sickrage, tvModel);
if (result?.result != "success")
{
// Couldn't send it
return false;
}
// Approve it
tvModel.Approved = true;
RequestService.UpdateRequest(tvModel);
return true;
}
return false;
}
catch (Exception e)
{
Log.Error(e);
return false; // It fails so it will get added back into the queue
}
}
private bool ProcessMovies(RequestedModel model)
{
try
{
var result = MovieSender.Send(model).Result;
if (result.Result)
{
// Approve it now
model.Approved = true;
RequestService.UpdateRequest(model);
return true;
};
return false;
}
catch (Exception e)
{
Log.Error(e);
return false; // It fails so it will get added back into the queue
}
}
private bool ProcessAlbums(RequestedModel model, HeadphonesSettings hp)
{
try
{
if (hp.Enabled)
{
var sender = new HeadphonesSender(HpApi, hp, RequestService);
var result = sender.AddAlbum(model).Result;
if (result)
{
if (ShouldAutoApprove(model.Type, PrSettings, model.RequestedUsers))
// Approve it now
model.Approved = true;
RequestService.UpdateRequest(model);
};
return result;
}
return false;
}
catch (Exception e)
{
Log.Error(e);
return false; // It fails so it will get added back into the queue
}
}
private void ProcessTransientErrors(List<RequestQueue> requests)
{
var sonarrSettings = SonarrSettings.GetSettings();
var sickrageSettings = SickrageSettings.GetSettings();
var hpSettings = HeadphoneSettings.GetSettings();
if (!requests.Any())
{
return;
}
foreach (var request in requests)
{
var model = ByteConverterHelper.ReturnObject<RequestedModel>(request.Content);
bool result;
switch (request.Type)
{
case RequestType.Movie:
result = ProcessMovies(model);
break;
case RequestType.TvShow:
result = ProcessTvShow(model, sonarrSettings, sickrageSettings);
break;
case RequestType.Album:
result = ProcessAlbums(model, hpSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
if (!result)
{
// we now have the info but couldn't add it, so do nothing now.
request.LastRetry = DateTime.UtcNow;
Repo.Update(request);
}
else
{
// Successful, remove from the fault queue
Repo.Delete(request);
}
}
}
public bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings, List<string> username)
{
foreach (var user in username)
{
var admin = Security.HasPermissions(user, Permissions.Administrator);
// if the user is an admin or they are whitelisted, they go ahead and allow auto-approval
if (admin) return true;
// check by request type if the category requires approval or not
switch (requestType)
{
case RequestType.Movie:
return Security.HasPermissions(user, Permissions.AutoApproveMovie);
case RequestType.TvShow:
return Security.HasPermissions(user, Permissions.AutoApproveTv);
case RequestType.Album:
return Security.HasPermissions(user, Permissions.AutoApproveAlbum);
default:
return false;
}
}
return false;
}
}
}

View file

@ -0,0 +1,69 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: HtmlTemplateGenerator.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Text;
namespace Ombi.Services.Jobs
{
public abstract class HtmlTemplateGenerator
{
protected virtual void AddParagraph(StringBuilder stringBuilder, string text, int fontSize = 14, string fontWeight = "normal")
{
stringBuilder.AppendFormat("<p style=\"font-family: sans-serif; font-size: {1}px; font-weight: {2}; margin: 0; Margin-bottom: 15px;\">{0}</p>", text, fontSize, fontWeight);
}
protected virtual void AddImageInsideTable(StringBuilder sb, string url)
{
sb.Append("<tr>");
sb.Append("<td align=\"center\">");
sb.AppendFormat(
"<img src=\"{0}\" width=\"400px\" text-align=\"center\" />",
url);
sb.Append("</td>");
sb.Append("</tr>");
}
protected virtual void Href(StringBuilder sb, string url)
{
sb.AppendFormat("<a href=\"{0}\">", url);
}
protected virtual void EndTag(StringBuilder sb, string tag)
{
sb.AppendFormat("</{0}>", tag);
}
protected virtual void Header(StringBuilder sb, int size, string text, string fontWeight = "normal")
{
sb.AppendFormat(
"<h{0} style=\"font-family: sans-serif; font-weight: {2}; margin: 0; Margin-bottom: 15px;\">{1}</h{0}>",
size, text, fontWeight);
}
}
}

View file

@ -0,0 +1,10 @@
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IEmbyUserChecker
{
void Execute(IJobExecutionContext context);
void Start();
}
}

View file

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Models.Emby;
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IEmbyAvailabilityChecker
{
void CheckAndUpdateAll();
void Execute(IJobExecutionContext context);
IEnumerable<EmbyContent> GetEmbyMovies(IEnumerable<EmbyContent> content);
IEnumerable<EmbyContent> GetEmbyMusic(IEnumerable<EmbyContent> content);
IEnumerable<EmbyContent> GetEmbyTvShows(IEnumerable<EmbyContent> content);
Task<IEnumerable<EmbyEpisodes>> GetEpisodes();
Task<IEnumerable<EmbyEpisodes>> GetEpisodes(int theTvDbId);
EmbyContent GetMovie(IEnumerable<EmbyContent> embyMovies, string title, string year, string providerId);
EmbyContent GetTvShow(IEnumerable<EmbyContent> embyShows, string title, string year, string providerId, int[] seasons = null);
bool IsEpisodeAvailable(string theTvDbId, int season, int episode);
bool IsMovieAvailable(IEnumerable<EmbyContent> embyMovies, string title, string year, string providerId);
bool IsTvShowAvailable(IEnumerable<EmbyContent> embyShows, string title, string year, string providerId, int[] seasons = null);
void Start();
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Ombi.Api.Models.Emby;
using Quartz;
namespace Ombi.Services.Jobs.Interfaces
{
public interface IEmbyContentCacher
{
void CacheContent();
void Execute(IJobExecutionContext context);
List<EmbyMovieItem> GetMovies();
List<EmbySeriesItem> GetTvShows();
}
}

View file

@ -0,0 +1,12 @@
using Ombi.Core.SettingModels;
using Quartz;
namespace Ombi.Services.Jobs.Interfaces
{
public interface IEmbyEpisodeCacher
{
void CacheEpisodes(EmbySettings settings);
void Execute(IJobExecutionContext context);
void Start();
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Ombi.Core.SettingModels;
using Ombi.Store;
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IFaultQueueHandler
{
void Execute(IJobExecutionContext context);
bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings, List<string> username);
void Start();
}
}

View file

@ -0,0 +1,12 @@
using Ombi.Core.SettingModels;
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IPlexEpisodeCacher
{
void CacheEpisodes(PlexSettings settings);
void Execute(IJobExecutionContext context);
void Start();
}
}

View file

@ -0,0 +1,10 @@
using Quartz;
namespace Ombi.Services.Jobs
{
public interface IPlexUserChecker
{
void Execute(IJobExecutionContext context);
void Start();
}
}

View file

@ -0,0 +1,51 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: JobNames.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace Ombi.Services.Jobs
{
public static class JobNames
{
public const string StoreBackup = "Database Backup";
public const string CpCacher = "CouchPotato Cacher";
public const string WatcherCacher = "Watcher Cacher";
public const string SonarrCacher = "Sonarr Cacher";
public const string RadarrCacher = "Radarr Cacher";
public const string SrCacher = "SickRage Cacher";
public const string PlexChecker = "Plex Availability Cacher";
public const string EmbyChecker = "Emby Availability Cacher";
public const string PlexCacher = "Plex Cacher";
public const string EmbyCacher = "Emby Cacher";
public const string StoreCleanup = "Database Cleanup";
public const string RequestLimitReset = "Request Limit Reset";
public const string EpisodeCacher = "Plex Episode Cacher";
public const string EmbyEpisodeCacher = "Emby Episode Cacher";
public const string RecentlyAddedEmail = "Recently Added Email Notification";
public const string FaultQueueHandler = "Request Fault Queue Handler";
public const string PlexUserChecker = "Plex User Checker";
public const string EmbyUserChecker = "Emby User Checker";
}
}

View file

@ -0,0 +1,90 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: JobRecord.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Services.Interfaces;
using Ombi.Store.Models;
using Ombi.Store.Repository;
namespace Ombi.Services.Jobs
{
public class JobRecord : IJobRecord
{
public JobRecord(IRepository<ScheduledJobs> repo)
{
Repo = repo;
}
private IRepository<ScheduledJobs> Repo { get; }
public void Record(string jobName)
{
var allJobs = Repo.GetAll();
var storeJob = allJobs.FirstOrDefault(x => x.Name == jobName);
if (storeJob != null)
{
storeJob.LastRun = DateTime.UtcNow;
Repo.Update(storeJob);
}
else
{
var job = new ScheduledJobs { LastRun = DateTime.UtcNow, Name = jobName };
Repo.Insert(job);
}
}
public void SetRunning(bool running, string jobName)
{
var allJobs = Repo.GetAll();
var storeJob = allJobs.FirstOrDefault(x => x.Name == jobName);
if (storeJob != null)
{
storeJob.Running = running;
Repo.Update(storeJob);
}
else
{
var job = new ScheduledJobs { Running = running, Name = jobName };
Repo.Insert(job);
}
}
public async Task<IEnumerable<ScheduledJobs>> GetJobsAsync()
{
return await Repo.GetAllAsync();
}
public IEnumerable<ScheduledJobs> GetJobs()
{
return Repo.GetAll();
}
}
}

View file

@ -0,0 +1,503 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Models;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz;
using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs
{
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
{
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, IPlexNotificationEngine e, IRepository<PlexContent> content)
{
Plex = plexSettings;
RequestService = request;
PlexApi = plex;
Cache = cache;
Notification = notify;
Job = rec;
UserNotifyRepo = users;
EpisodeRepo = repo;
NotificationEngine = e;
PlexContent = content;
}
private ISettingsService<PlexSettings> Plex { get; }
private IRepository<PlexEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IPlexApi PlexApi { get; }
private ICacheProvider Cache { get; }
private INotificationService Notification { get; }
private IJobRecord Job { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; }
private INotificationEngine NotificationEngine { get; }
private IRepository<PlexContent> PlexContent { get; }
public void CheckAndUpdateAll()
{
var plexSettings = Plex.GetSettings();
if (!plexSettings.Enable)
{
return;
}
if (!ValidateSettings(plexSettings))
{
Log.Debug("Validation of the plex settings failed.");
return;
}
//var libraries = CachedLibraries(plexSettings, true); //force setting the cache (10 min intervals via scheduler)
//if (libraries == null || !libraries.Any())
//{
// Log.Debug("Did not find any libraries in Plex.");
// return;
//}
var content = PlexContent.GetAll().ToList();
var movies = GetPlexMovies(content).ToArray();
var shows = GetPlexTvShows(content).ToArray();
var albums = GetPlexAlbums(content).ToArray();
var requests = RequestService.GetAll();
var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray();
if (!requestedModels.Any())
{
Log.Debug("There are no requests to check.");
return;
}
var modifiedModel = new List<RequestedModel>();
foreach (var r in requestedModels)
{
var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy");
bool matchResult;
switch (r.Type)
{
case RequestType.Movie:
matchResult = IsMovieAvailable(movies, r.Title, releaseDate, r.ImdbId);
break;
case RequestType.TvShow:
if (!plexSettings.EnableTvEpisodeSearching)
{
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
}
else
{
matchResult = r.Episodes.Any() ?
r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber)) :
IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
}
break;
case RequestType.Album:
matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName);
break;
default:
throw new ArgumentOutOfRangeException();
}
if (matchResult)
{
r.Available = true;
modifiedModel.Add(r);
continue;
}
}
Log.Debug("Requests that will be updated count {0}", modifiedModel.Count);
if (modifiedModel.Any())
{
NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable);
RequestService.BatchUpdate(modifiedModel);
}
}
public List<PlexMovie> GetPlexMoviesOld()
{
var settings = Plex.GetSettings();
var movies = new List<PlexMovie>();
var libs = Cache.Get<List<PlexSearch>>(CacheKeys.PlexLibaries);
if (libs != null)
{
var movieLibs = libs.Where(x =>
x.Video.Any(y =>
y.Type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)
)
).ToArray();
foreach (var lib in movieLibs)
{
movies.AddRange(lib.Video.Select(video => new PlexMovie
{
ReleaseYear = video.Year,
Title = video.Title,
ProviderId = video.ProviderId,
Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey)
}));
}
}
return movies;
}
public IEnumerable<PlexContent> GetPlexMovies(IEnumerable<PlexContent> content)
{
return content.Where(x => x.Type == Store.Models.Plex.PlexMediaType.Movie);
}
public bool IsMovieAvailable(IEnumerable<PlexContent> plexMovies, string title, string year, string providerId = null)
{
var movie = GetMovie(plexMovies, title, year, providerId);
return movie != null;
}
public PlexContent GetMovie(IEnumerable<PlexContent> plexMovies, string title, string year, string providerId = null)
{
if (plexMovies.Count() == 0)
{
return null;
}
var advanced = !string.IsNullOrEmpty(providerId);
foreach (var movie in plexMovies)
{
if (string.IsNullOrEmpty(movie.Title) || string.IsNullOrEmpty(movie.ReleaseYear))
{
continue;
}
if (advanced)
{
if (!string.IsNullOrEmpty(movie.ProviderId) &&
movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return movie;
}
}
if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
movie.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
return movie;
}
}
return null;
}
public IEnumerable<PlexContent> GetPlexTvShows(IEnumerable<PlexContent> content)
{
return content.Where(x => x.Type == Store.Models.Plex.PlexMediaType.Show);
}
public bool IsTvShowAvailable(IEnumerable<PlexContent> plexShows, string title, string year, string providerId = null, int[] seasons = null)
{
var show = GetTvShow(plexShows, title, year, providerId, seasons);
return show != null;
}
public PlexContent GetTvShow(IEnumerable<PlexContent> plexShows, string title, string year, string providerId = null,
int[] seasons = null)
{
var advanced = !string.IsNullOrEmpty(providerId);
foreach (var show in plexShows)
{
if (advanced)
{
if (show.ProviderId == providerId && seasons != null)
{
var showSeasons = ByteConverterHelper.ReturnObject<int[]>(show.Seasons);
if (seasons.Any(season => showSeasons.Contains(season)))
{
return show;
}
return null;
}
if (!string.IsNullOrEmpty(show.ProviderId) &&
show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
return show;
}
}
if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
show.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
return show;
}
}
return null;
}
public bool IsEpisodeAvailable(string theTvDbId, int season, int episode)
{
var ep = EpisodeRepo.Custom(
connection =>
{
connection.Open();
var result = connection.Query<PlexEpisodes>("select * from PlexEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
}).ToList();
if (!ep.Any())
{
Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}", theTvDbId, season, episode);
return false;
}
foreach (var result in ep)
{
if (result.ProviderId.Equals(theTvDbId) && result.EpisodeNumber == episode && result.SeasonNumber == season)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets the episode's db in the cache.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<PlexEpisodes>> GetEpisodes()
{
var episodes = await EpisodeRepo.GetAllAsync();
if (episodes == null)
{
return new HashSet<PlexEpisodes>();
}
return episodes;
}
/// <summary>
/// Gets the episode's stored in the db and then filters on the TheTvDBId.
/// </summary>
/// <param name="theTvDbId">The tv database identifier.</param>
/// <returns></returns>
public async Task<IEnumerable<PlexEpisodes>> GetEpisodes(int theTvDbId)
{
var ep = await EpisodeRepo.CustomAsync(async connection =>
{
connection.Open();
var result = await connection.QueryAsync<PlexEpisodes>("select * from PlexEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId });
return result;
});
var plexEpisodeses = ep as PlexEpisodes[] ?? ep.ToArray();
if (!plexEpisodeses.Any())
{
Log.Info("Episode db info is not available.");
return new List<PlexEpisodes>();
}
return plexEpisodeses;
}
public IEnumerable<PlexContent> GetPlexAlbums(IEnumerable<PlexContent> content)
{
return content.Where(x => x.Type == Store.Models.Plex.PlexMediaType.Artist);
}
public bool IsAlbumAvailable(IEnumerable<PlexContent> plexAlbums, string title, string year, string artist)
{
return plexAlbums.Any(x =>
x.Title.Contains(title) &&
x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase));
}
public PlexContent GetAlbum(IEnumerable<PlexContent> plexAlbums, string title, string year, string artist)
{
return plexAlbums.FirstOrDefault(x =>
x.Title.Contains(title) &&
x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase));
}
private List<PlexSearch> CachedLibraries(PlexSettings plexSettings, bool setCache)
{
var results = new List<PlexSearch>();
if (!ValidateSettings(plexSettings))
{
Log.Warn("The settings are not configured");
return results; // don't error out here, just let it go! let it goo!!!
}
try
{
// TODO what the fuck was I thinking
if (setCache)
{
results = GetLibraries(plexSettings);
if (plexSettings.AdvancedSearch)
{
foreach (PlexSearch t in results)
{
foreach (Directory1 t1 in t.Directory)
{
var currentItem = t1;
var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
currentItem.RatingKey);
// Get the seasons for each show
if (currentItem.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
var seasons = PlexApi.GetSeasons(plexSettings.PlexAuthToken, plexSettings.FullUri,
currentItem.RatingKey);
// We do not want "all episodes" this as a season
var filtered = seasons.Directory.Where(x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase));
t1.Seasons.AddRange(filtered);
}
var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Directory.Guid);
t1.ProviderId = providerId;
}
foreach (Video t1 in t.Video)
{
var currentItem = t1;
var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
currentItem.RatingKey);
var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid);
t1.ProviderId = providerId;
}
}
}
if (results != null)
{
Cache.Set(CacheKeys.PlexLibaries, results, CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
else
{
results = Cache.GetOrSet(CacheKeys.PlexLibaries, () =>
GetLibraries(plexSettings), CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to obtain Plex libraries");
}
return results;
}
private List<PlexSearch> GetLibraries(PlexSettings plexSettings)
{
var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri);
var libs = new List<PlexSearch>();
if (sections != null)
{
foreach (var dir in sections.Directories ?? new List<Directory>())
{
var lib = PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.Key);
if (lib != null)
{
libs.Add(lib);
}
}
}
return libs;
}
private bool ValidateSettings(PlexSettings plex)
{
if (plex.Enable)
{
if (plex?.Ip == null || plex?.PlexAuthToken == null)
{
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return false;
}
}
return plex.Enable;
}
public void Execute(IJobExecutionContext context)
{
Job.SetRunning(true, JobNames.PlexChecker);
try
{
CheckAndUpdateAll();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.PlexChecker);
Job.SetRunning(false, JobNames.PlexChecker);
}
}
public void Start()
{
Job.SetRunning(true, JobNames.PlexChecker);
try
{
CheckAndUpdateAll();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.PlexChecker);
Job.SetRunning(false, JobNames.PlexChecker);
}
}
}
}

View file

@ -0,0 +1,438 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Models;
using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz;
using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs
{
public class PlexContentCacher : IJob, IPlexContentCacher
{
public PlexContentCacher(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, IPlexNotificationEngine e, IRepository<PlexContent> content)
{
Plex = plexSettings;
RequestService = request;
PlexApi = plex;
Cache = cache;
Notification = notify;
Job = rec;
UserNotifyRepo = users;
EpisodeRepo = repo;
NotificationEngine = e;
PlexContent = content;
}
private ISettingsService<PlexSettings> Plex { get; }
private IRepository<PlexEpisodes> EpisodeRepo { get; }
private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IPlexApi PlexApi { get; }
private ICacheProvider Cache { get; }
private INotificationService Notification { get; }
private IJobRecord Job { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; }
private INotificationEngine NotificationEngine { get; }
private IRepository<PlexContent> PlexContent { get; }
public void CacheContent()
{
var plexSettings = Plex.GetSettings();
if (!plexSettings.Enable)
{
return;
}
if (!ValidateSettings(plexSettings))
{
Log.Debug("Validation of the plex settings failed.");
return;
}
var libraries = CachedLibraries(plexSettings);
if (libraries == null || !libraries.Any())
{
Log.Debug("Did not find any libraries in Plex.");
return;
}
}
public List<PlexMovie> GetPlexMovies(List<PlexSearch> libs)
{
var settings = Plex.GetSettings();
var movies = new List<PlexMovie>();
if (libs != null)
{
var movieLibs = libs.Where(x =>
x.Video.Any(y =>
y.Type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)
)
).ToArray();
foreach (var lib in movieLibs)
{
movies.AddRange(lib.Video.Select(video => new PlexMovie
{
ReleaseYear = video.Year,
Title = video.Title,
ProviderId = video.ProviderId,
Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey),
ItemId = video.RatingKey
}));
}
}
return movies;
}
public List<PlexTvShow> GetPlexTvShows(List<PlexSearch> libs)
{
var settings = Plex.GetSettings();
var shows = new List<PlexTvShow>();
if (libs != null)
{
var withDir = libs.Where(x => x.Directory != null);
var tvLibs = withDir.Where(x =>
x.Directory.Any(y =>
y.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)
)
).ToArray();
foreach (var lib in tvLibs)
{
shows.AddRange(lib.Directory.Select(x => new PlexTvShow // shows are in the directory list
{
Title = x.Title,
ReleaseYear = x.Year,
ProviderId = x.ProviderId,
Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(),
Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey),
ItemId= x.RatingKey
}));
}
}
return shows;
}
public List<PlexAlbum> GetPlexAlbums(List<PlexSearch> libs)
{
var settings = Plex.GetSettings();
var albums = new List<PlexAlbum>();
if (libs != null)
{
var albumLibs = libs.Where(x =>
x.Directory.Any(y =>
y.Type.Equals(PlexMediaType.Artist.ToString(), StringComparison.CurrentCultureIgnoreCase)
)
).ToArray();
foreach (var lib in albumLibs)
{
albums.AddRange(lib.Directory.Select(x => new PlexAlbum()
{
Title = x.Title,
ProviderId = x.ProviderId,
ReleaseYear = x.Year,
Artist = x.ParentTitle,
Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey)
}));
}
}
return albums;
}
private List<PlexSearch> CachedLibraries(PlexSettings plexSettings)
{
var results = new List<PlexSearch>();
if (!ValidateSettings(plexSettings))
{
Log.Warn("The settings are not configured");
return results; // don't error out here, just let it go! let it goo!!!
}
try
{
results = GetLibraries(plexSettings);
if (plexSettings.AdvancedSearch)
{
Log.Debug("Going through all the items now");
Log.Debug($"Item count {results.Count}");
foreach (PlexSearch t in results)
{
foreach (Directory1 t1 in t.Directory)
{
var currentItem = t1;
var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
currentItem.RatingKey);
// Get the seasons for each show
if (currentItem.Type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
var seasons = PlexApi.GetSeasons(plexSettings.PlexAuthToken, plexSettings.FullUri,
currentItem.RatingKey);
// We do not want "all episodes" this as a season
var filtered = seasons.Directory.Where(x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase));
t1.Seasons.AddRange(filtered);
}
var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Directory.Guid);
t1.ProviderId = providerId;
}
foreach (Video t1 in t.Video)
{
var currentItem = t1;
var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
currentItem.RatingKey);
var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid);
t1.ProviderId = providerId;
}
}
}
if (results != null)
{
Log.Debug("done all that, moving onto the DB now");
var movies = GetPlexMovies(results);
// Time to destroy the plex movies from the DB
PlexContent.Custom(connection =>
{
connection.Open();
connection.Query("delete from PlexContent where type = @type", new { type = 0 });
return new List<PlexContent>();
});
foreach (var m in movies)
{
if (string.IsNullOrEmpty(m.ProviderId))
{
Log.Error("Provider Id on movie {0} is null",m.Title);
continue;
}
// Check if it exists
var item = PlexContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<PlexContent>("select * from PlexContent where ProviderId = @ProviderId and type = @type", new { m.ProviderId, type = 0 });
connection.Dispose();
return media;
});
if (item == null && !string.IsNullOrEmpty(m.ItemId))
{
// Doesn't exist, insert it
PlexContent.Insert(new PlexContent
{
ProviderId = m.ProviderId,
ReleaseYear = m.ReleaseYear ?? string.Empty,
Title = m.Title,
Type = Store.Models.Plex.PlexMediaType.Movie,
Url = m.Url,
ItemId = m.ItemId,
AddedAt = DateTime.UtcNow,
});
}
}
Log.Debug("Done movies");
var tv = GetPlexTvShows(results);
// Time to destroy the plex tv from the DB
PlexContent.Custom(connection =>
{
connection.Open();
connection.Query("delete from PlexContent where type = @type", new { type = 1 });
return new List<PlexContent>();
});
foreach (var t in tv)
{
if (string.IsNullOrEmpty(t.ProviderId))
{
Log.Error("Provider Id on tv {0} is null", t.Title);
continue;
}
// Check if it exists
var item = PlexContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<PlexContent>("select * from PlexContent where ProviderId = @ProviderId and type = @type", new { t.ProviderId, type = 1 });
connection.Dispose();
return media;
});
if (item == null && !string.IsNullOrEmpty(t.ItemId))
{
PlexContent.Insert(new PlexContent
{
ProviderId = t.ProviderId,
ReleaseYear = t.ReleaseYear ?? string.Empty,
Title = t.Title,
Type = Store.Models.Plex.PlexMediaType.Show,
Url = t.Url,
Seasons = ByteConverterHelper.ReturnBytes(t.Seasons),
ItemId = t.ItemId,
AddedAt = DateTime.UtcNow,
});
}
}
Log.Debug("Done TV");
var albums = GetPlexAlbums(results);
// Time to destroy the plex movies from the DB
PlexContent.Custom(connection =>
{
connection.Open();
connection.Query("delete from PlexContent where type = @type", new { type = 2 });
return new List<PlexContent>();
});
foreach (var a in albums)
{
if (string.IsNullOrEmpty(a.ProviderId))
{
Log.Error("Provider Id on album {0} is null", a.Title);
continue;
}
// Check if it exists
var item = PlexContent.Custom(connection =>
{
connection.Open();
var media = connection.QueryFirstOrDefault<PlexContent>("select * from PlexContent where ProviderId = @ProviderId and type = @type", new { a.ProviderId, type = 2 });
connection.Dispose();
return media;
});
if (item == null)
{
PlexContent.Insert(new PlexContent
{
ProviderId = a.ProviderId,
ReleaseYear = a.ReleaseYear ?? string.Empty,
Title = a.Title,
Type = Store.Models.Plex.PlexMediaType.Artist,
Url = a.Url,
ItemId = "album",
AddedAt = DateTime.UtcNow,
});
}
}
Log.Debug("Done albums");
}
}
catch (Exception ex)
{
Log.Debug("Exception:");
Log.Debug(ex);
Log.Error(ex, "Failed to obtain Plex libraries");
}
return results;
}
private List<PlexSearch> GetLibraries(PlexSettings plexSettings)
{
Log.Debug("Getting Lib sections");
var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri);
Log.Debug("Going through sections now");
var libs = new List<PlexSearch>();
if (sections != null)
{
foreach (var dir in sections.Directories ?? new List<Directory>())
{
var lib = PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.Key);
if (lib != null)
{
Log.Debug("adding lib");
libs.Add(lib);
}
}
}
return libs;
}
private bool ValidateSettings(PlexSettings plex)
{
if (plex.Enable)
{
if (plex?.Ip == null || plex?.PlexAuthToken == null)
{
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return false;
}
}
return plex.Enable;
}
public void Execute(IJobExecutionContext context)
{
Job.SetRunning(true, JobNames.PlexCacher);
try
{
CacheContent();
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.PlexCacher);
Job.SetRunning(false, JobNames.PlexCacher);
}
}
}
}

View file

@ -0,0 +1,203 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexEpisodeCacher.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz;
using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs
{
public class PlexEpisodeCacher : IJob, IPlexEpisodeCacher
{
public PlexEpisodeCacher(ISettingsService<PlexSettings> plexSettings, IPlexApi plex, ICacheProvider cache,
IJobRecord rec, IRepository<PlexEpisodes> repo, ISettingsService<ScheduledJobsSettings> jobs)
{
Plex = plexSettings;
PlexApi = plex;
Cache = cache;
Job = rec;
Repo = repo;
Jobs = jobs;
}
private ISettingsService<PlexSettings> Plex { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IPlexApi PlexApi { get; }
private ICacheProvider Cache { get; }
private IJobRecord Job { get; }
private IRepository<PlexEpisodes> Repo { get; }
private ISettingsService<ScheduledJobsSettings> Jobs { get; }
private const int ResultCount = 25;
private const string PlexType = "episode";
private const string TableName = "PlexEpisodes";
public void CacheEpisodes(PlexSettings settings)
{
var videoHashset = new HashSet<Video>();
// Ensure Plex is setup correctly
if (string.IsNullOrEmpty(settings.PlexAuthToken))
{
return;
}
// Get the librarys and then get the tv section
var sections = PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri);
var tvSection = sections.Directories.FirstOrDefault(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase));
var tvSectionId = tvSection?.Key;
var currentPosition = 0;
int totalSize;
// Get the first 25 episodes (Paged)
var episodes = PlexApi.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, tvSectionId, currentPosition, ResultCount);
// Parse the total amount of episodes
int.TryParse(episodes.TotalSize, out totalSize);
// Get all of the episodes in batches until we them all (Got'a catch 'em all!)
while (currentPosition < totalSize)
{
videoHashset.UnionWith(PlexApi.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, tvSectionId, currentPosition, ResultCount).Video
.Where(x => x.Type.Equals(PlexType, StringComparison.InvariantCultureIgnoreCase)));
currentPosition += ResultCount;
}
var entities = new ConcurrentDictionary<PlexEpisodes, byte>();
Parallel.ForEach(videoHashset, video =>
{
// Get the individual episode Metadata (This is for us to get the TheTVDBId which also includes the episode number and season number)
var metadata = PlexApi.GetEpisodeMetaData(settings.PlexAuthToken, settings.FullUri, video.RatingKey);
// Loop through the metadata and create the model to insert into the DB
foreach (var metadataVideo in metadata?.Video ?? new List<Video>())
{
if(string.IsNullOrEmpty(metadataVideo.GrandparentTitle))
{
continue;
}
var epInfo = PlexHelper.GetSeasonsAndEpisodesFromPlexGuid(metadataVideo.Guid);
entities.TryAdd(
new PlexEpisodes
{
EpisodeNumber = epInfo?.EpisodeNumber ?? 0,
EpisodeTitle = metadataVideo.Title,
ProviderId = epInfo?.ProviderId ?? "",
RatingKey = metadataVideo.RatingKey,
SeasonNumber = epInfo?.SeasonNumber ?? 0,
ShowTitle = metadataVideo.GrandparentTitle
},
1);
}
});
// Delete all of the current items
Repo.DeleteAll(TableName);
// Insert the new items
var result = Repo.BatchInsert(entities.Select(x => x.Key).ToList(), TableName);
if (!result)
{
Log.Error("Saving the Plex episodes to the DB Failed");
}
}
public void Start()
{
try
{
var s = Plex.GetSettings();
if (!s.EnableTvEpisodeSearching)
{
return;
}
Job.SetRunning(true, JobNames.EpisodeCacher);
CacheEpisodes(s);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EpisodeCacher);
Job.SetRunning(false, JobNames.EpisodeCacher);
}
}
public void Execute(IJobExecutionContext context)
{
try
{
var s = Plex.GetSettings();
if (!s.EnableTvEpisodeSearching)
{
return;
}
var jobs = Job.GetJobs();
var job = jobs.FirstOrDefault(x => x.Name.Equals(JobNames.EpisodeCacher, StringComparison.CurrentCultureIgnoreCase));
if (job != null)
{
if (job.LastRun > DateTime.Now.AddHours(-11)) // If it's been run in the last 11 hours
{
return;
}
}
Job.SetRunning(true, JobNames.EpisodeCacher);
CacheEpisodes(s);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.EpisodeCacher);
Job.SetRunning(false, JobNames.EpisodeCacher);
}
}
}
}

View file

@ -0,0 +1,215 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StoreCleanup.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.Users;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class PlexUserChecker : IJob, IPlexUserChecker
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public PlexUserChecker(IExternalUserRepository<PlexUsers> plexUsers, IPlexApi plexAPi, IJobRecord rec, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings,
IRequestService requestService, IUserRepository localUser)
{
Repo = plexUsers;
JobRecord = rec;
PlexApi = plexAPi;
PlexSettings = plexSettings;
PlexRequestSettings = prSettings;
UserManagementSettings = umSettings;
RequestService = requestService;
LocalUserRepository = localUser;
}
private IJobRecord JobRecord { get; }
private IPlexApi PlexApi { get; }
private IExternalUserRepository<PlexUsers> Repo { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
private IRequestService RequestService { get; }
private IUserRepository LocalUserRepository { get; }
public void Start()
{
JobRecord.SetRunning(true, JobNames.PlexUserChecker);
try
{
var settings = PlexSettings.GetSettings();
if (string.IsNullOrEmpty(settings.PlexAuthToken) || !settings.Enable)
{
return;
}
var plexUsers = PlexApi.GetUsers(settings.PlexAuthToken);
var userManagementSettings = UserManagementSettings.GetSettings();
var mainPlexAccount = PlexApi.GetAccount(settings.PlexAuthToken);
var requests = RequestService.GetAll().ToList();
var dbUsers = Repo.GetAll().ToList();
var localUsers = LocalUserRepository.GetAll().ToList();
// Regular users
foreach (var user in plexUsers?.User ?? new UserFriends[]{})
{
var dbUser = dbUsers.FirstOrDefault(x => x.PlexUserId == user.Id);
if (dbUser != null)
{
// We already have the user, let's check if they have updated any of their info.
var needToUpdate = false;
var usernameChanged = false;
if (!string.IsNullOrEmpty(user.Username)) // If true then this is a managed user, we do not want to update the email since Managed Users do not have email addresses
{
// Do we need up update any info?
if (!dbUser.EmailAddress.Equals(user.Email, StringComparison.CurrentCultureIgnoreCase))
{
dbUser.EmailAddress = user.Email;
needToUpdate = true;
}
}
if (!dbUser.Username.Equals(user.Title, StringComparison.CurrentCultureIgnoreCase))
{
needToUpdate = true;
usernameChanged = true;
}
if (needToUpdate)
{
if (usernameChanged)
{
// The username has changed, let's check if the username matches any local users
var localUser = localUsers.FirstOrDefault(x => x.UserName.Equals(user.Title, StringComparison.CurrentCultureIgnoreCase));
dbUser.Username = user.Title;
if (localUser != null)
{
// looks like we have a local user with the same name...
// We should delete the local user and the Plex user will become the master,
// I am not going to update the Plex Users permissions as that could end up leading to a security vulnerability
// Where anyone could change their Plex Username to the PR.Net server admins name and get all the admin permissions.
LocalUserRepository.Delete(localUser);
}
// Since the username has changed, we need to update all requests with that username (unless we are using the alias! Since the alias won't change)
if (string.IsNullOrEmpty(dbUser.UserAlias))
{
// Update all requests
var requestsWithThisUser = requests.Where(x => x.RequestedUsers.Contains(user.Username)).ToList();
foreach (var r in requestsWithThisUser)
{
r.RequestedUsers.Remove(user.Title); // Remove old
r.RequestedUsers.Add(dbUser.Username); // Add new
}
if (requestsWithThisUser.Any())
{
RequestService.BatchUpdate(requestsWithThisUser);
}
}
}
Repo.Update(dbUser);
}
continue;
}
// Looks like it's a new user!
var m = new PlexUsers
{
PlexUserId = user.Id,
Permissions = UserManagementHelper.GetPermissions(userManagementSettings),
Features = UserManagementHelper.GetFeatures(userManagementSettings),
UserAlias = string.Empty,
EmailAddress = user.Email,
Username = user.Title,
LoginId = Guid.NewGuid().ToString()
};
Repo.Insert(m);
}
// Main Plex user
var dbMainAcc = dbUsers.FirstOrDefault(x => x.Username.Equals(mainPlexAccount.Username, StringComparison.CurrentCulture));
var localMainAcc = localUsers.FirstOrDefault(x => x.UserName.Equals(mainPlexAccount.Username, StringComparison.CurrentCulture));
// TODO if admin acc does exist, check if we need to update it
// Create the local admin account if it doesn't already exist
if (dbMainAcc == null && localMainAcc == null)
{
var a = new PlexUsers
{
PlexUserId = mainPlexAccount.Id,
Permissions = UserManagementHelper.GetPermissions(userManagementSettings),
Features = UserManagementHelper.GetFeatures(userManagementSettings),
UserAlias = string.Empty,
EmailAddress = mainPlexAccount.Email,
Username = mainPlexAccount.Username,
LoginId = Guid.NewGuid().ToString()
};
a.Permissions += (int)Permissions.Administrator; // Make admin
Repo.Insert(a);
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
JobRecord.SetRunning(false, JobNames.PlexUserChecker);
JobRecord.Record(JobNames.PlexUserChecker);
}
}
public void Execute(IJobExecutionContext context)
{
Start();
}
}
}

View file

@ -0,0 +1,114 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Radarr;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Quartz;
namespace Ombi.Services.Jobs
{
public class RadarrCacher : IJob, IRadarrCacher
{
public RadarrCacher(ISettingsService<RadarrSettings> radarrService, IRadarrApi radarrApi, ICacheProvider cache, IJobRecord rec)
{
RadarrSettings = radarrService;
RadarrApi = radarrApi;
Job = rec;
Cache = cache;
}
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private ICacheProvider Cache { get; }
private IRadarrApi RadarrApi { get; }
private IJobRecord Job { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Queued()
{
var settings = RadarrSettings.GetSettings();
if (settings.Enabled)
{
Job.SetRunning(true, JobNames.RadarrCacher);
try
{
var movies = RadarrApi.GetMovies(settings.ApiKey, settings.FullUri);
if (movies != null)
{
var movieIds = new List<int>();
foreach (var m in movies)
{
if (m.tmdbId > 0)
{
movieIds.Add(m.tmdbId);
}
else
{
Log.Error("TMDBId is not > 0 for movie {0}", m.title);
}
}
//var movieIds = movies.Select(x => x.tmdbId).ToList();
Cache.Set(CacheKeys.RadarrMovies, movieIds, CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
catch (System.Exception ex)
{
Log.Error(ex, "Failed caching queued items from Radarr");
}
finally
{
Job.Record(JobNames.RadarrCacher);
Job.SetRunning(false, JobNames.RadarrCacher);
}
}
}
// we do not want to set here...
public int[] QueuedIds()
{
var retVal = new List<int>();
var movies = Cache.Get<List<int>>(CacheKeys.RadarrMovies);
if (movies != null)
{
retVal.AddRange(movies);
}
return retVal.ToArray();
}
public void Execute(IJobExecutionContext context)
{
Queued();
}
}
}

View file

@ -0,0 +1,454 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Services.Jobs.Templates;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Repository;
using TMDbLib.Objects.Exceptions;
using EmbyMediaType = Ombi.Store.Models.Plex.EmbyMediaType;
using Polly;
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public class EmbyAddedNewsletter : HtmlTemplateGenerator, IEmbyAddedNewsletter
{
public EmbyAddedNewsletter(IEmbyApi api, ISettingsService<EmbySettings> embySettings,
ISettingsService<EmailNotificationSettings> email,
ISettingsService<NewletterSettings> newsletter, IRepository<RecentlyAddedLog> log,
IRepository<EmbyContent> embyContent, IRepository<EmbyEpisodes> episodes)
{
Api = api;
EmbySettings = embySettings;
EmailSettings = email;
NewsletterSettings = newsletter;
Content = embyContent;
MovieApi = new TheMovieDbApi();
TvApi = new TvMazeApi();
Episodes = episodes;
RecentlyAddedLog = log;
}
private IEmbyApi Api { get; }
private TheMovieDbApi MovieApi { get; }
private TvMazeApi TvApi { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IRepository<EmbyContent> Content { get; }
private IRepository<EmbyEpisodes> Episodes { get; }
private IRepository<RecentlyAddedLog> RecentlyAddedLog { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public Newsletter GetNewsletter(bool test)
{
try
{
return GetHtml(test);
}
catch (Exception e)
{
Log.Error(e);
return null;
}
}
private class EmbyRecentlyAddedModel
{
public EmbyInformation EmbyInformation { get; set; }
public EmbyContent EmbyContent { get; set; }
public List<EmbyEpisodeInformation> EpisodeInformation { get; set; }
}
private Newsletter GetHtml(bool test)
{
var sb = new StringBuilder();
var newsletter = new Newsletter();
var embySettings = EmbySettings.GetSettings();
var embyContent = Content.GetAll().ToList();
var series = embyContent.Where(x => x.Type == EmbyMediaType.Series).ToList();
var episodes = Episodes.GetAll().ToList();
var movie = embyContent.Where(x => x.Type == EmbyMediaType.Movie).ToList();
var recentlyAdded = RecentlyAddedLog.GetAll().ToList();
var firstRun = !recentlyAdded.Any();
var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList();
var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList();
var filteredSeries = series.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList();
var info = new List<EmbyRecentlyAddedModel>();
foreach (var m in filteredMovies.OrderByDescending(x => x.AddedAt))
{
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) =>
Log.Error(exception, "Exception thrown when processing an emby movie for the newsletter, Retrying {0}", timespan));
var result = policy.Execute(() =>
{
var i = Api.GetInformation(m.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Movie,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
return new EmbyRecentlyAddedModel
{
EmbyInformation = i,
EmbyContent = m
};
});
info.Add(result);
}
GenerateMovieHtml(info, sb);
newsletter.MovieCount = info.Count;
info.Clear();
// Check if there are any epiosdes, then get the series info.
// Otherwise then just add the series to the newsletter
if (test && !filteredEp.Any() && episodes.Any())
{
// if this is a test make sure we show something
filteredEp = episodes.Take(5).ToList();
}
if (filteredEp.Any())
{
var recentlyAddedModel = new List<EmbyRecentlyAddedModel>();
foreach (var embyEpisodes in filteredEp)
{
try
{
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) =>
Log.Error(exception, "Exception thrown when processing an emby episode for the newsletter, Retrying {0}", timespan));
policy.Execute(() =>
{
// Find related series item
var relatedSeries = series.FirstOrDefault(x => x.EmbyId == embyEpisodes.ParentId);
if (relatedSeries == null)
{
return;
}
// Get series information
var i = Api.GetInformation(relatedSeries.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
Thread.Sleep(200);
var episodeInfo = Api.GetInformation(embyEpisodes.EmbyId,
Ombi.Api.Models.Emby.EmbyMediaType.Episode,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
// Check if we already have this series
var existingSeries = recentlyAddedModel.FirstOrDefault(x =>
x.EmbyInformation.SeriesInformation.Id.Equals(i.SeriesInformation.Id,
StringComparison.CurrentCultureIgnoreCase));
if (existingSeries != null)
{
existingSeries.EpisodeInformation.Add(episodeInfo.EpisodeInformation);
}
else
{
recentlyAddedModel.Add(new EmbyRecentlyAddedModel
{
EmbyInformation = i,
EpisodeInformation = new List<EmbyEpisodeInformation>() { episodeInfo.EpisodeInformation },
EmbyContent = relatedSeries
});
}
});
}
catch (JsonReaderException)
{
Log.Error("Failed getting information from Emby, we may have overloaded Emby's api... Waiting and we will skip this one and go to the next");
Thread.Sleep(1000);
}
}
info.AddRange(recentlyAddedModel);
}
else
{
foreach (var t in filteredSeries.OrderByDescending(x => x.AddedAt))
{
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) =>
Log.Error(exception, "Exception thrown when processing an emby series for the newsletter, Retrying {0}", timespan));
var item = policy.Execute(() =>
{
var i = Api.GetInformation(t.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series,
embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri);
var model = new EmbyRecentlyAddedModel
{
EmbyContent = t,
EmbyInformation = i,
};
return model;
});
info.Add(item);
}
}
GenerateTvHtml(info, sb);
newsletter.TvCount = info.Count;
var template = new RecentlyAddedTemplate();
var html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
if (!test || firstRun)
{
foreach (var a in filteredMovies)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.EmbyId,
AddedAt = DateTime.UtcNow
});
}
foreach (var a in filteredEp)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.EmbyId,
AddedAt = DateTime.UtcNow
});
}
foreach (var s in filteredSeries)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = s.EmbyId,
AddedAt = DateTime.UtcNow
});
}
}
var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug(escapedHtml);
newsletter.Html = escapedHtml;
return newsletter;
}
private void GenerateMovieHtml(IEnumerable<EmbyRecentlyAddedModel> recentlyAddedMovies, StringBuilder sb)
{
var movies = recentlyAddedMovies?.ToList() ?? new List<EmbyRecentlyAddedModel>();
if (!movies.Any())
{
return;
}
var orderedMovies = movies.OrderByDescending(x => x.EmbyContent.AddedAt).Select(x => x.EmbyInformation.MovieInformation).ToList();
sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies)
{
// We have a try within a try so we can catch the rate limit without ending the loop (finally block)
try
{
try
{
var imdbId = movie.ProviderIds.Imdb;
var info = MovieApi.GetMovieInformation(imdbId).Result;
if (info == null)
{
throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi");
}
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
catch (Exception limit)
{
// We have hit a limit, we need to now wait.
Thread.Sleep(TimeSpan.FromSeconds(10));
Log.Info(limit);
}
}
catch (Exception e)
{
Log.Error(e);
Log.Error("Error for movie with IMDB Id = {0}", movie.ProviderIds.Imdb);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private class TvModel
{
public EmbySeriesInformation Series { get; set; }
public List<EmbyEpisodeInformation> Episodes { get; set; }
}
private void GenerateTvHtml(IEnumerable<EmbyRecentlyAddedModel> recenetlyAddedTv, StringBuilder sb)
{
var tv = recenetlyAddedTv?.ToList() ?? new List<EmbyRecentlyAddedModel>();
if (!tv.Any())
{
return;
}
var orderedTv = tv.OrderByDescending(x => x.EmbyContent.AddedAt).ToList();
// TV
sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv)
{
var seriesItem = t.EmbyInformation.SeriesInformation;
var relatedEpisodes = t.EpisodeInformation;
var endLoop = false;
try
{
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(seriesItem.ProviderIds.Tvdb));
if (info == null) continue;
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https"); // Always use the Https banners
}
AddImageInsideTable(sb, banner);
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{seriesItem.Name} {seriesItem.PremiereDate.Year}";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title);
EndTag(sb, "a");
if (relatedEpisodes != null)
{
var results = relatedEpisodes.GroupBy(p => p.ParentIndexNumber,
(key, g) => new
{
ParentIndexNumber = key,
IndexNumber = g.ToList()
}
);
// Group the episodes
foreach (var embyEpisodeInformation in results.OrderBy(x => x.ParentIndexNumber))
{
var epSb = new StringBuilder();
var orderedEpisodes = embyEpisodeInformation.IndexNumber.OrderBy(x => x.IndexNumber).ToList();
for (var i = 0; i < orderedEpisodes.Count; i++)
{
var ep = orderedEpisodes[i];
if (i < embyEpisodeInformation.IndexNumber.Count - 1)
{
epSb.Append($"{ep.IndexNumber},");
}
else
{
epSb.Append(ep.IndexNumber);
}
}
AddParagraph(sb, $"Season: {embyEpisodeInformation.ParentIndexNumber}, Episode: {epSb}");
}
}
if (info.genres.Any())
{
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
AddParagraph(sb, string.IsNullOrEmpty(seriesItem.Overview) ? info.summary : seriesItem.Overview);
endLoop = true;
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
if (endLoop)
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void EndLoopHtml(StringBuilder sb)
{
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
sb.Append("<hr />");
sb.Append("<br />");
sb.Append("<br />");
sb.Append("</td>");
sb.Append("</tr>");
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public interface IEmbyAddedNewsletter
{
Newsletter GetNewsletter(bool test);
}
}

View file

@ -0,0 +1,7 @@
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public interface IPlexNewsletter
{
Newsletter GetNewsletter(bool test);
}
}

View file

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

View file

@ -0,0 +1,425 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
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;
using Ombi.Helpers;
using Ombi.Services.Jobs.Templates;
using Ombi.Store.Models;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using TMDbLib.Objects.Exceptions;
using PlexMediaType = Ombi.Store.Models.Plex.PlexMediaType;
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public class
PlexRecentlyAddedNewsletter : HtmlTemplateGenerator, IPlexNewsletter
{
public PlexRecentlyAddedNewsletter(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmailNotificationSettings> email,
ISettingsService<NewletterSettings> newsletter, IRepository<RecentlyAddedLog> log,
IRepository<PlexContent> embyContent, IRepository<PlexEpisodes> episodes)
{
Api = api;
PlexSettings = plexSettings;
EmailSettings = email;
NewsletterSettings = newsletter;
Content = embyContent;
MovieApi = new TheMovieDbApi();
TvApi = new TvMazeApi();
Episodes = episodes;
RecentlyAddedLog = log;
}
private IPlexApi Api { get; }
private TheMovieDbApi MovieApi { get; }
private TvMazeApi TvApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IRepository<PlexContent> Content { get; }
private IRepository<PlexEpisodes> Episodes { get; }
private IRepository<RecentlyAddedLog> RecentlyAddedLog { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public Newsletter GetNewsletter(bool test)
{
try
{
return GetHtml(test);
}
catch (Exception e)
{
Log.Error(e);
return null;
}
}
private class PlexRecentlyAddedModel
{
public PlexMetadata Metadata { get; set; }
public PlexContent Content { get; set; }
public List<PlexEpisodeMetadata> EpisodeMetadata { get; set; }
}
private Newsletter GetHtml(bool test)
{
var sb = new StringBuilder();
var newsletter = new Newsletter();
var plexSettings = PlexSettings.GetSettings();
var plexContent = Content.GetAll().ToList();
var series = plexContent.Where(x => x.Type == PlexMediaType.Show).ToList();
var episodes = Episodes.GetAll().ToList();
var movie = plexContent.Where(x => x.Type == PlexMediaType.Movie).ToList();
var recentlyAdded = RecentlyAddedLog.GetAll().ToList();
var firstRun = !recentlyAdded.Any();
var filteredMovies = movie.Where(m => recentlyAdded.All(x => x.ProviderId != m.ProviderId)).ToList();
var filteredEp = episodes.Where(m => recentlyAdded.All(x => x.ProviderId != m.RatingKey)).ToList();
var filteredSeries = series.Where(x => recentlyAdded.All(c => c.ProviderId != x.ProviderId)).ToList();
var info = new List<PlexRecentlyAddedModel>();
if (test && !filteredMovies.Any())
{
// if this is a test make sure we show something
filteredMovies = movie.Take(5).ToList();
}
foreach (var m in filteredMovies.OrderByDescending(x => x.AddedAt))
{
var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, m.ItemId);
if (i.Video == null)
{
continue;
}
info.Add(new PlexRecentlyAddedModel
{
Metadata = i,
Content = m
});
}
GenerateMovieHtml(info, sb);
newsletter.MovieCount = info.Count;
info.Clear();
if (test && !filteredEp.Any() && episodes.Any())
{
// if this is a test make sure we show something
filteredEp = episodes.Take(5).ToList();
}
if (filteredEp.Any())
{
var recentlyAddedModel = new List<PlexRecentlyAddedModel>();
foreach (var plexEpisodes in filteredEp)
{
// Find related series item
var relatedSeries = series.FirstOrDefault(x => x.ProviderId == plexEpisodes.ProviderId);
if (relatedSeries == null)
{
continue;
}
// Get series information
var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, relatedSeries.ItemId);
var episodeInfo = Api.GetEpisodeMetaData(plexSettings.PlexAuthToken, plexSettings.FullUri, plexEpisodes.RatingKey);
// Check if we already have this series
var existingSeries = recentlyAddedModel.FirstOrDefault(x =>
x.Metadata.Directory.RatingKey == i.Directory.RatingKey);
if (existingSeries != null)
{
existingSeries.EpisodeMetadata.Add(episodeInfo);
}
else
{
recentlyAddedModel.Add(new PlexRecentlyAddedModel
{
Metadata = i,
EpisodeMetadata = new List<PlexEpisodeMetadata>() { episodeInfo },
Content = relatedSeries
});
}
}
info.AddRange(recentlyAddedModel);
}
else
{
if (test && !filteredSeries.Any())
{
// if this is a test make sure we show something
filteredSeries = series.Take(5).ToList();
}
foreach (var t in filteredSeries.OrderByDescending(x => x.AddedAt))
{
var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, t.ItemId);
if (i.Directory == null)
{
continue;
}
info.Add(new PlexRecentlyAddedModel
{
Metadata = i,
Content = t
});
}
}
GenerateTvHtml(info, sb);
newsletter.TvCount = info.Count;
var template = new RecentlyAddedTemplate();
var html = template.LoadTemplate(sb.ToString());
Log.Debug("Loaded the template");
if (!test || firstRun)
{
foreach (var a in filteredMovies)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.ProviderId,
AddedAt = DateTime.UtcNow
});
}
foreach (var a in filteredEp)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.RatingKey,
AddedAt = DateTime.UtcNow
});
}
foreach (var a in filteredSeries)
{
RecentlyAddedLog.Insert(new RecentlyAddedLog
{
ProviderId = a.ProviderId,
AddedAt = DateTime.UtcNow
});
}
}
var escapedHtml = new string(html.Where(c => !char.IsControl(c)).ToArray());
Log.Debug(escapedHtml);
newsletter.Html = escapedHtml;
return newsletter;
}
private void GenerateMovieHtml(IEnumerable<PlexRecentlyAddedModel> recentlyAddedMovies, StringBuilder sb)
{
var movies = recentlyAddedMovies?.ToList() ?? new List<PlexRecentlyAddedModel>();
if (!movies.Any())
{
return;
}
var orderedMovies = movies.OrderByDescending(x => x.Content.AddedAt).ToList();
sb.Append("<h1>New Movies:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var movie in orderedMovies)
{
// We have a try within a try so we can catch the rate limit without ending the loop (finally block)
try
{
try
{
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(movie.Metadata.Video.Guid);
var info = MovieApi.GetMovieInformation(imdbId).Result;
if (info == null)
{
throw new Exception($"Movie with Imdb id {imdbId} returned null from the MovieApi");
}
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
Header(sb, 3, $"{info.Title} {info.ReleaseDate?.ToString("yyyy") ?? string.Empty}");
EndTag(sb, "a");
if (info.Genres.Any())
{
AddParagraph(sb,
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
}
AddParagraph(sb, info.Overview);
}
catch (RequestLimitExceededException limit)
{
// We have hit a limit, we need to now wait.
Thread.Sleep(TimeSpan.FromSeconds(10));
Log.Info(limit);
}
}
catch (Exception e)
{
Log.Error(e);
Log.Error("Error for movie with IMDB Id = {0}", movie.Metadata.Video.Guid);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private class TvModel
{
public EmbySeriesInformation Series { get; set; }
public List<EmbyEpisodeInformation> Episodes { get; set; }
}
private void GenerateTvHtml(IEnumerable<PlexRecentlyAddedModel> recenetlyAddedTv, StringBuilder sb)
{
var tv = recenetlyAddedTv?.ToList() ?? new List<PlexRecentlyAddedModel>();
if (!tv.Any())
{
return;
}
var orderedTv = tv.OrderByDescending(x => x.Content.AddedAt).ToList();
// TV
sb.Append("<h1>New Episodes:</h1><br /><br />");
sb.Append(
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
foreach (var t in orderedTv)
{
var relatedEpisodes = t.EpisodeMetadata ?? new List<PlexEpisodeMetadata>();
try
{
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(t?.Metadata?.Directory?.Guid ?? string.Empty)));
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https"); // Always use the Https banners
}
AddImageInsideTable(sb, banner);
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{t.Content.Title} {t.Content.ReleaseYear}";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
Header(sb, 3, title);
EndTag(sb, "a");
// Group by the ParentIndex (season number)
var results = relatedEpisodes.GroupBy(p => p.Video.FirstOrDefault()?.ParentIndex,
(key, g) => new
{
ParentIndexNumber = key,
IndexNumber = g.ToList()
}
);
// Group the episodes
foreach (var epInformation in results.OrderBy(x => x.ParentIndexNumber))
{
var orderedEpisodes = epInformation.IndexNumber.OrderBy(x => Convert.ToInt32(x.Video.FirstOrDefault().Index)).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.Video.FirstOrDefault().Index},");
}
else
{
epSb.Append($"{ep.Video.FirstOrDefault().Index}");
}
}
AddParagraph(sb, $"Season: {epInformation.ParentIndexNumber}, Episode: {epSb}");
}
if (info.genres.Any())
{
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
AddParagraph(sb, string.IsNullOrEmpty(t.Metadata.Directory.Summary) ? t.Metadata.Directory.Summary : info.summary);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
EndLoopHtml(sb);
}
}
sb.Append("</table><br /><br />");
}
private void EndLoopHtml(StringBuilder sb)
{
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
sb.Append("<hr />");
sb.Append("<br />");
sb.Append("<br />");
sb.Append("</td>");
sb.Append("</tr>");
}
}
}

View file

@ -0,0 +1,284 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MailKit.Net.Smtp;
using MimeKit;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.Users;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs.Templates;
using Quartz;
namespace Ombi.Services.Jobs.RecentlyAddedNewsletter
{
public class RecentlyAddedNewsletter : HtmlTemplateGenerator, IJob, IRecentlyAdded, IMassEmail
{
public RecentlyAddedNewsletter(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmailNotificationSettings> email, IJobRecord rec,
ISettingsService<NewletterSettings> newsletter,
IUserHelper userHelper, IEmbyAddedNewsletter embyNews,
ISettingsService<EmbySettings> embyS,
IPlexNewsletter plex)
{
JobRecord = rec;
Api = api;
PlexSettings = plexSettings;
EmailSettings = email;
NewsletterSettings = newsletter;
UserHelper = userHelper;
EmbyNewsletter = embyNews;
EmbySettings = embyS;
PlexNewsletter = plex;
}
private IPlexApi Api { get; }
private TvMazeApi TvApi = new TvMazeApi();
private readonly TheMovieDbApi _movieApi = new TheMovieDbApi();
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
private IJobRecord JobRecord { get; }
private IUserHelper UserHelper { get; }
private IEmbyAddedNewsletter EmbyNewsletter { get; }
private IPlexNewsletter PlexNewsletter { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public void StartNewsLetter()
{
try
{
var settings = NewsletterSettings.GetSettings();
if (!settings.SendRecentlyAddedEmail)
{
return;
}
JobRecord.SetRunning(true, JobNames.RecentlyAddedEmail);
StartNewsLetter(settings);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
JobRecord.Record(JobNames.RecentlyAddedEmail);
JobRecord.SetRunning(false, JobNames.RecentlyAddedEmail);
}
}
public void Execute(IJobExecutionContext context)
{
StartNewsLetter();
}
public void RecentlyAddedAdminTest()
{
Log.Debug("Starting Recently Added Newsletter Test");
var settings = NewsletterSettings.GetSettings();
StartNewsLetter(settings, true);
}
public void MassEmailAdminTest(string html, string subject)
{
Log.Debug("Starting Mass Email Test");
var template = new MassEmailTemplate();
var body = template.LoadTemplate(html);
SendMassEmail(body, subject, true);
}
public void SendMassEmail(string html, string subject)
{
Log.Debug("Starting Mass Email Test");
var template = new MassEmailTemplate();
var body = template.LoadTemplate(html);
SendMassEmail(body, subject, false);
}
private void StartNewsLetter(NewletterSettings newletterSettings, bool testEmail = false)
{
var embySettings = EmbySettings.GetSettings();
if (embySettings.Enable)
{
var letter = EmbyNewsletter.GetNewsletter(testEmail) ?? new Newsletter();
if (letter.Send || testEmail)
{
SendNewsletter(newletterSettings, letter.Html, testEmail, "New Content On Emby!");
}
else
{
Log.Warn("There is no new content to send the newsletter");
}
}
else
{
var plexSettings = PlexSettings.GetSettings();
if (plexSettings.Enable)
{
var letter = PlexNewsletter.GetNewsletter(testEmail) ?? new Newsletter();
if (letter.Send || testEmail)
{
SendNewsletter(newletterSettings, letter.Html, testEmail);
}
}
}
}
private void SendMassEmail(string html, string subject, bool testEmail)
{
var settings = EmailSettings.GetSettings();
if (!settings.Enabled || string.IsNullOrEmpty(settings.EmailHost))
{
return;
}
var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." };
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = subject
};
Log.Debug("Created Plain/HTML MIME body");
if (!testEmail)
{
var users = UserHelper.GetUsers(); // Get all users
if (users != null)
{
foreach (var user in users)
{
if (!string.IsNullOrEmpty(user.EmailAddress))
{
message.Bcc.Add(new MailboxAddress(user.Username, user.EmailAddress)); // BCC everyone
}
}
}
}
message.Bcc.Add(new MailboxAddress(settings.EmailUsername, settings.RecipientEmail)); // Include the admin
message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender));
SendMail(settings, message);
}
private void SendNewsletter(NewletterSettings newletterSettings, string html, bool testEmail = false, string subject = "New Content on Plex!")
{
Log.Debug("Entering SendNewsletter");
var settings = EmailSettings.GetSettings();
if (!settings.Enabled || string.IsNullOrEmpty(settings.EmailHost))
{
return;
}
var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." };
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = subject
};
Log.Debug("Created Plain/HTML MIME body");
if (!testEmail)
{
var users = UserHelper.GetUsersWithFeature(Features.Newsletter);
if (users != null)
{
foreach (var user in users)
{
if (!string.IsNullOrEmpty(user.EmailAddress))
{
message.Bcc.Add(new MailboxAddress(user.Username, user.EmailAddress));
}
}
}
if (newletterSettings.CustomUsersEmailAddresses != null
&& newletterSettings.CustomUsersEmailAddresses.Any())
{
foreach (var user in newletterSettings.CustomUsersEmailAddresses)
{
if (!string.IsNullOrEmpty(user))
{
message.Bcc.Add(new MailboxAddress(user, user));
}
}
}
}
message.Bcc.Add(new MailboxAddress(settings.EmailUsername, settings.RecipientEmail)); // Include the admin
message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender));
SendMail(settings, message);
}
private void SendMail(EmailNotificationSettings settings, MimeMessage message)
{
try
{
using (var client = new SmtpClient())
{
client.Connect(settings.EmailHost, settings.EmailPort); // Let MailKit figure out the correct SecureSocketOptions.
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
if (settings.Authentication)
{
client.Authenticate(settings.EmailUsername, settings.EmailPassword);
}
Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated);
Log.Debug("Sending");
client.Send(message);
Log.Debug("Sent");
client.Disconnect(true);
}
}
catch (Exception e)
{
Log.Error(e);
}
}
}
}

View file

@ -0,0 +1,100 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.SickRage;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Quartz;
namespace Ombi.Services.Jobs
{
public class SickRageCacher : IJob, ISickRageCacher
{
public SickRageCacher(ISettingsService<SickRageSettings> srSettings, ISickRageApi srApi, ICacheProvider cache, IJobRecord rec)
{
SrSettings = srSettings;
SrApi = srApi;
Cache = cache;
Job = rec;
}
private ISettingsService<SickRageSettings> SrSettings { get; }
private ICacheProvider Cache { get; }
private ISickRageApi SrApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private IJobRecord Job { get; }
public void Queued()
{
Log.Trace("Getting the settings");
var settings = SrSettings.GetSettings();
if (settings.Enabled)
{
Job.SetRunning(true, JobNames.SrCacher);
Log.Trace("Getting all shows from SickRage");
try
{
var shows = SrApi.GetShows(settings.ApiKey, settings.FullUri);
if (shows != null)
{
Cache.Set(CacheKeys.SickRageQueued, shows.Result, CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
catch (System.Exception ex)
{
Log.Error(ex, "Failed caching queued items from SickRage");
}
finally
{
Job.Record(JobNames.SrCacher);
Job.SetRunning(false, JobNames.SrCacher);
}
}
}
// we do not want to set here...
public int[] QueuedIds()
{
var tv = Cache.Get<SickrageShows>(CacheKeys.SickRageQueued);
return tv?.data?.Values.Select(x => x.tvdbid).ToArray() ?? new int[] { };
}
public void Execute(IJobExecutionContext context)
{
Queued();
}
}
}

View file

@ -0,0 +1,115 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Sonarr;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Models;
using Quartz;
namespace Ombi.Services.Jobs
{
public class SonarrCacher : IJob, ISonarrCacher
{
public SonarrCacher(ISettingsService<SonarrSettings> sonarrSettings, ISonarrApi sonarrApi, ICacheProvider cache, IJobRecord rec)
{
SonarrSettings = sonarrSettings;
SonarrApi = sonarrApi;
Job = rec;
Cache = cache;
}
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ICacheProvider Cache { get; }
private ISonarrApi SonarrApi { get; }
private IJobRecord Job { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Queued()
{
var settings = SonarrSettings.GetSettings();
if (settings.Enabled)
{
Job.SetRunning(true, JobNames.SonarrCacher);
try
{
var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri);
if (series != null)
{
Cache.Set(CacheKeys.SonarrQueued, series, CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
catch (System.Exception ex)
{
Log.Error(ex, "Failed caching queued items from Sonarr");
}
finally
{
Job.Record(JobNames.SonarrCacher);
Job.SetRunning(false, JobNames.SonarrCacher);
}
}
}
// we do not want to set here...
public IEnumerable<SonarrCachedResult> QueuedIds()
{
var result = new List<SonarrCachedResult>();
var series = Cache.Get<List<Series>>(CacheKeys.SonarrQueued);
if (series != null)
{
foreach (var s in series)
{
var cached = new SonarrCachedResult {TvdbId = s.tvdbId};
foreach (var season in s.seasons)
{
cached.Seasons.Add(new SonarrSeasons
{
SeasonNumber = season.seasonNumber,
Monitored = season.monitored
});
}
result.Add(cached);
}
}
return result;
}
public void Execute(IJobExecutionContext context)
{
Queued();
}
}
}

View file

@ -0,0 +1,168 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StoreBackup.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.IO;
using System.Linq;
using NLog;
using Ombi.Services.Interfaces;
using Ombi.Store;
using Quartz;
namespace Ombi.Services.Jobs
{
public class StoreBackup : IJob, IStoreBackup
{
public StoreBackup(ISqliteConfiguration sql, IJobRecord rec)
{
Sql = sql;
JobRecord = rec;
}
private ISqliteConfiguration Sql { get; }
private IJobRecord JobRecord { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Start()
{
JobRecord.SetRunning(true, JobNames.CpCacher);
TakeBackup();
Cleanup();
}
public void Execute(IJobExecutionContext context)
{
JobRecord.SetRunning(true, JobNames.CpCacher);
TakeBackup();
Cleanup();
}
private void TakeBackup()
{
Log.Trace("Starting DB Backup");
var dbPath = Sql.CurrentPath;
var dir = Path.GetDirectoryName(dbPath);
if (dir == null)
{
Log.Warn("We couldn't find the DB path. We cannot backup.");
return;
}
var backupDir = Directory.CreateDirectory(Path.Combine(dir, "Backup"));
if (string.IsNullOrEmpty(dbPath))
{
Log.Warn("Could not find the actual database. We cannot backup.");
return;
}
try
{
if (DoWeNeedToBackup(backupDir.FullName))
{
File.Copy(dbPath, Path.Combine(backupDir.FullName, $"PlexRequests.sqlite_{DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss")}.bak"));
}
}
catch (Exception e)
{
Log.Warn(e);
Log.Warn("Exception when trying to copy the backup.");
}
finally
{
JobRecord.Record(JobNames.StoreBackup);
JobRecord.SetRunning(false, JobNames.CpCacher);
}
}
private void Cleanup()
{
Log.Trace("Starting DB Cleanup");
var dbPath = Sql.CurrentPath;
var dir = Path.GetDirectoryName(dbPath);
if (dir == null)
{
Log.Warn("We couldn't find the DB path. We cannot backup.");
return;
}
var backupDir = Directory.CreateDirectory(Path.Combine(dir, "Backup"));
var files = backupDir.GetFiles();
foreach (var file in files)
{
var dt = ParseName(file.Name);
if (dt < DateTime.Now.AddDays(-7))
{
try
{
File.Delete(file.FullName);
}
catch (Exception ex)
{
Log.Error(ex);
}
}
}
}
private bool DoWeNeedToBackup(string backupPath)
{
var files = Directory.GetFiles(backupPath);
var last = files.LastOrDefault();
if (!string.IsNullOrEmpty(last))
{
var dt = ParseName(Path.GetFileName(last));
if (dt < DateTime.Now.AddHours(-1))
{
return true;
}
return false;
}
// We don't have a backup
return true;
}
private DateTime ParseName(string fileName)
{
var names = fileName.Split(new[] { '_', '.', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (names.Length > 1)
{
DateTime parsed;
DateTime.TryParse(names[2], out parsed);
return parsed;
}
return DateTime.MinValue;
}
}
}

View file

@ -0,0 +1,95 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StoreCleanup.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using Ombi.Services.Interfaces;
using Ombi.Store.Models;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class StoreCleanup : IJob, IStoreCleanup
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public StoreCleanup(IRepository<LogEntity> repo, IJobRecord rec)
{
Repo = repo;
JobRecord = rec;
}
private IJobRecord JobRecord { get; }
private IRepository<LogEntity> Repo { get; }
private const int ItemsToDelete = 1000;
private void Cleanup()
{
try
{
var items = Repo.GetAll();
var ordered = items.OrderByDescending(x => x.Date).ToList();
var itemsToDelete = new List<LogEntity>();
if (ordered.Count > ItemsToDelete)
{
itemsToDelete = ordered.Skip(ItemsToDelete).ToList();
}
foreach (var o in itemsToDelete)
{
Repo.Delete(o);
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
JobRecord.Record(JobNames.StoreCleanup);
JobRecord.SetRunning(false, JobNames.CpCacher);
}
}
public void Start()
{
JobRecord.SetRunning(true, JobNames.CpCacher);
Cleanup();
}
public void Execute(IJobExecutionContext context)
{
JobRecord.SetRunning(true, JobNames.CpCacher);
Cleanup();
}
}
}

View file

@ -0,0 +1,58 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedTemplate.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using NLog;
namespace Ombi.Services.Jobs.Templates
{
public class MassEmailTemplate
{
public string TemplateLocation => Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "Jobs", "Templates", "MassEmailTemplate.html");
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private const string RecentlyAddedKey = "{@MASSEMAIL}";
public string LoadTemplate(string html)
{
try
{
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
sb.Replace(RecentlyAddedKey, html);
return sb.ToString();
}
catch (Exception e)
{
Log.Error(e);
return string.Empty;
}
}
}
}

View file

@ -0,0 +1,181 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Ombi</title>
<style media="all" type="text/css">
@media all {
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
@media all {
.btn-secondary a:hover {
border-color: #34495e !important;
color: #34495e !important;
}
}
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] h2 {
font-size: 22px !important;
margin-bottom: 10px !important;
}
table[class=body] h3 {
font-size: 16px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .header {
margin-bottom: 10px !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
table[class=body] .alert td {
border-radius: 0 !important;
padding: 10px !important;
}
table[class=body] .span-2,
table[class=body] .span-3 {
max-width: none !important;
width: 100% !important;
}
table[class=body] .receipt {
width: 100% !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
}
</style>
</head>
<body class="" style="font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f6f6f6; margin: 0; padding: 0;">
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;" width="100%" bgcolor="#f6f6f6">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto !important; max-width: 580px; padding: 10px; width: 580px;" width="580" valign="top">
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #fff; border-radius: 3px;" width="100%">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td align="center">
<img src="http://i.imgur.com/qQsN78U.png" width="400px" text-align="center" />
</td>
</tr>
<tr>
<td align="left">
{@MASSEMAIL}
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,58 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RecentlyAddedTemplate.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using NLog;
namespace Ombi.Services.Jobs.Templates
{
public class RecentlyAddedTemplate
{
public string TemplateLocation => Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "Jobs", "Templates", "RecentlyAddedTemplate.html");
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private const string RecentlyAddedKey = "{@RECENTLYADDED}";
public string LoadTemplate(string html)
{
try
{
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
sb.Replace(RecentlyAddedKey, html);
return sb.ToString();
}
catch (Exception e)
{
Log.Error(e);
return string.Empty;
}
}
}
}

View file

@ -0,0 +1,187 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Ombi</title>
<style media="all" type="text/css">
@media all {
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
@media all {
.btn-secondary a:hover {
border-color: #34495e !important;
color: #34495e !important;
}
}
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] h2 {
font-size: 22px !important;
margin-bottom: 10px !important;
}
table[class=body] h3 {
font-size: 16px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .header {
margin-bottom: 10px !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
table[class=body] .alert td {
border-radius: 0 !important;
padding: 10px !important;
}
table[class=body] .span-2,
table[class=body] .span-3 {
max-width: none !important;
width: 100% !important;
}
table[class=body] .receipt {
width: 100% !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
}
</style>
</head>
<body class="" style="font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f6f6f6; margin: 0; padding: 0;">
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;" width="100%" bgcolor="#f6f6f6">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto !important; max-width: 580px; padding: 10px; width: 580px;" width="580" valign="top">
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #fff; border-radius: 3px;" width="100%">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td align="center">
<img src="http://i.imgur.com/qQsN78U.png" width="400px" text-align="center" />
</td>
</tr>
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
<br />
<br />
<p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Here is a list of Movies and TV Shows that have recently been added!</p>
</td>
</tr>
</table>
{@RECENTLYADDED}
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,146 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserRequestLimitResetter.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Services.Interfaces;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Services.Jobs
{
public class UserRequestLimitResetter : IJob, IUserRequestLimitResetter
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public UserRequestLimitResetter(IJobRecord record, IRepository<RequestLimit> repo, ISettingsService<PlexRequestSettings> settings)
{
Record = record;
Repo = repo;
Settings = settings;
}
private IJobRecord Record { get; }
private IRepository<RequestLimit> Repo { get; }
private ISettingsService<PlexRequestSettings> Settings { get; }
public void AlbumLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.AlbumWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Album);
}
public void MovieLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.MovieWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.Movie);
}
public void TvLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
{
if (s.TvWeeklyRequestLimit == 0)
{
return; // The limit has not been set
}
CheckAndDelete(allUsers, RequestType.TvShow);
}
private void CheckAndDelete(IEnumerable<RequestLimit> allUsers, RequestType type)
{
var users = allUsers.Where(x => x.RequestType == type);
foreach (var u in users)
{
var daysDiff = (u.FirstRequestDate - DateTime.UtcNow.AddDays(-7)).TotalDays;
if (daysDiff <= 0)
{
Repo.Delete(u);
}
}
}
public void Start()
{
Record.SetRunning(true, JobNames.CpCacher);
try
{
var settings = Settings.GetSettings();
var users = Repo.GetAll();
var requestLimits = users as RequestLimit[] ?? users.ToArray();
MovieLimit(settings, requestLimits);
TvLimit(settings, requestLimits);
AlbumLimit(settings, requestLimits);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Record.Record(JobNames.RequestLimitReset);
Record.SetRunning(false, JobNames.CpCacher);
}
}
public void Execute(IJobExecutionContext context)
{
Record.SetRunning(true, JobNames.CpCacher);
try
{
var settings = Settings.GetSettings();
var users = Repo.GetAll();
var requestLimits = users as RequestLimit[] ?? users.ToArray();
MovieLimit(settings, requestLimits);
TvLimit(settings, requestLimits);
AlbumLimit(settings, requestLimits);
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Record.Record(JobNames.RequestLimitReset);
Record.SetRunning(false, JobNames.CpCacher);
}
}
}
}

View file

@ -0,0 +1,132 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Movie;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Quartz;
namespace Ombi.Services.Jobs
{
public class WatcherCacher : IJob, IWatcherCacher
{
public WatcherCacher(
ISettingsService<WatcherSettings> watcher,
IWatcherApi watcherApi, ICacheProvider cache, IJobRecord rec)
{
WatcherSettings = watcher;
WatcherApi = watcherApi;
Cache = cache;
Job = rec;
}
private ISettingsService<WatcherSettings> WatcherSettings { get; }
private ICacheProvider Cache { get; }
private IWatcherApi WatcherApi { get; }
private IJobRecord Job { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Queued()
{
Log.Trace("Getting the settings");
var watcherSettings = WatcherSettings.GetSettings();
Job.SetRunning(true, JobNames.WatcherCacher);
try
{
if (watcherSettings.Enabled)
{
var movies = WatcherApi.ListMovies(watcherSettings.ApiKey, watcherSettings.FullUri);
if (movies.Error)
{
Log.Error("Error when trying to get Watchers movies");
Log.Error(movies.ErrorMessage);
}
var wantedMovies =
movies?.Movies?.Where(x => x.status.Equals("Wanted", StringComparison.CurrentCultureIgnoreCase));
if (wantedMovies != null && wantedMovies.Any())
{
Cache.Set(CacheKeys.WatcherQueued, movies.Movies.Select(x => x.imdbid).ToArray(), CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.WatcherCacher);
Job.SetRunning(false, JobNames.WatcherCacher);
}
}
// we do not want to set here...
public string[] QueuedIds()
{
try
{
var watcherSettings = WatcherSettings.GetSettings();
if (watcherSettings.Enabled)
{
var movies = Cache.Get<string[]>(CacheKeys.WatcherQueued);
if (movies != null)
{
return movies;
}
return new string[] {};
}
}
catch (Exception e)
{
Log.Error(e);
return new string[] { };
}
return new string[] {};
}
public void Execute(IJobExecutionContext context)
{
Queued();
}
}
}