mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-21 13:53:19 -07:00
All Sln changes
This commit is contained in:
parent
b5855f2644
commit
796f0fc188
615 changed files with 68 additions and 747 deletions
113
Ombi.Services/Jobs/CouchPotatoCacher.cs
Normal file
113
Ombi.Services/Jobs/CouchPotatoCacher.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
325
Ombi.Services/Jobs/FaultQueueHandler.cs
Normal file
325
Ombi.Services/Jobs/FaultQueueHandler.cs
Normal file
|
@ -0,0 +1,325 @@
|
|||
#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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
private IRepository<RequestQueue> Repo { get; }
|
||||
private IJobRecord Record { get; }
|
||||
private ISonarrApi SonarrApi { get; }
|
||||
private ISickRageApi SrApi { 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 Execute(IJobExecutionContext context)
|
||||
{
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
// 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);
|
||||
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, CouchPotatoSettings cp)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cp.Enabled)
|
||||
{
|
||||
var result = CpApi.AddMovie(model.ImdbId, cp.ApiKey, model.Title,
|
||||
cp.FullUri, cp.ProfileId);
|
||||
|
||||
if (result)
|
||||
{
|
||||
// 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 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 cpSettings = CpSettings.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, cpSettings);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
69
Ombi.Services/Jobs/HtmlTemplateGenerator.cs
Normal file
69
Ombi.Services/Jobs/HtmlTemplateGenerator.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
10
Ombi.Services/Jobs/IPlexContentCacher.cs
Normal file
10
Ombi.Services/Jobs/IPlexContentCacher.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using Quartz;
|
||||
|
||||
namespace Ombi.Services.Jobs
|
||||
{
|
||||
public interface IPlexContentCacher
|
||||
{
|
||||
void CacheContent();
|
||||
void Execute(IJobExecutionContext context);
|
||||
}
|
||||
}
|
10
Ombi.Services/Jobs/IRecentlyAdded.cs
Normal file
10
Ombi.Services/Jobs/IRecentlyAdded.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using Quartz;
|
||||
|
||||
namespace Ombi.Services.Jobs
|
||||
{
|
||||
public interface IRecentlyAdded
|
||||
{
|
||||
void Execute(IJobExecutionContext context);
|
||||
void Test();
|
||||
}
|
||||
}
|
45
Ombi.Services/Jobs/JobNames.cs
Normal file
45
Ombi.Services/Jobs/JobNames.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
#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 SonarrCacher = "Sonarr Cacher";
|
||||
public const string SrCacher = "SickRage Cacher";
|
||||
public const string PlexChecker = "Plex Availability Cacher";
|
||||
public const string PlexCacher = "Plex Cacher";
|
||||
public const string StoreCleanup = "Database Cleanup";
|
||||
public const string RequestLimitReset = "Request Limit Reset";
|
||||
public const string EpisodeCacher = "Plex 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";
|
||||
|
||||
}
|
||||
}
|
90
Ombi.Services/Jobs/JobRecord.cs
Normal file
90
Ombi.Services/Jobs/JobRecord.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
476
Ombi.Services/Jobs/PlexAvailabilityChecker.cs
Normal file
476
Ombi.Services/Jobs/PlexAvailabilityChecker.cs
Normal file
|
@ -0,0 +1,476 @@
|
|||
#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, INotificationEngine 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 (!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, plexSettings.PlexAuthToken, 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(PlexContent[] plexMovies, string title, string year, string providerId = null)
|
||||
{
|
||||
var movie = GetMovie(plexMovies, title, year, providerId);
|
||||
return movie != null;
|
||||
}
|
||||
|
||||
public PlexContent GetMovie(PlexContent[] plexMovies, string title, string year, string providerId = null)
|
||||
{
|
||||
if (plexMovies.Length == 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(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(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(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(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?.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 true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
355
Ombi.Services/Jobs/PlexContentCacher.cs
Normal file
355
Ombi.Services/Jobs/PlexContentCacher.cs
Normal file
|
@ -0,0 +1,355 @@
|
|||
#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, INotificationEngine 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 (!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)
|
||||
}));
|
||||
}
|
||||
}
|
||||
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),
|
||||
|
||||
}));
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
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)
|
||||
{
|
||||
PlexContent.Insert(new PlexContent
|
||||
{
|
||||
ProviderId = m.ProviderId,
|
||||
ReleaseYear = m.ReleaseYear ?? string.Empty,
|
||||
Title = m.Title,
|
||||
Type = Store.Models.Plex.PlexMediaType.Movie,
|
||||
Url = m.Url
|
||||
});
|
||||
}
|
||||
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)
|
||||
{
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
PlexContent.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 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?.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 true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
177
Ombi.Services/Jobs/PlexEpisodeCacher.cs
Normal file
177
Ombi.Services/Jobs/PlexEpisodeCacher.cs
Normal file
|
@ -0,0 +1,177 @@
|
|||
#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.Repository;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Services.Jobs
|
||||
{
|
||||
public class PlexEpisodeCacher : IJob
|
||||
{
|
||||
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)
|
||||
{
|
||||
if(string.IsNullOrEmpty(metadataVideo.GrandparentTitle))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var epInfo = PlexHelper.GetSeasonsAndEpisodesFromPlexGuid(metadataVideo.Guid);
|
||||
entities.TryAdd(
|
||||
new PlexEpisodes
|
||||
{
|
||||
EpisodeNumber = epInfo.EpisodeNumber,
|
||||
EpisodeTitle = metadataVideo.Title,
|
||||
ProviderId = epInfo.ProviderId,
|
||||
RatingKey = metadataVideo.RatingKey,
|
||||
SeasonNumber = epInfo.SeasonNumber,
|
||||
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, typeof(PlexEpisodes).GetPropertyNames());
|
||||
|
||||
if (!result)
|
||||
{
|
||||
Log.Error("Saving the Plex episodes to the DB Failed");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
175
Ombi.Services/Jobs/PlexUserChecker.cs
Normal file
175
Ombi.Services/Jobs/PlexUserChecker.cs
Normal file
|
@ -0,0 +1,175 @@
|
|||
#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.Services.Interfaces;
|
||||
using Ombi.Store.Models;
|
||||
using Ombi.Store.Repository;
|
||||
using Quartz;
|
||||
|
||||
namespace Ombi.Services.Jobs
|
||||
{
|
||||
public class PlexUserChecker : IJob
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public PlexUserChecker(IPlexUserRepository 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 IPlexUserRepository 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 Execute(IJobExecutionContext context)
|
||||
{
|
||||
JobRecord.SetRunning(true, JobNames.PlexUserChecker);
|
||||
|
||||
try
|
||||
{
|
||||
var settings = PlexSettings.GetSettings();
|
||||
if (string.IsNullOrEmpty(settings.PlexAuthToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var plexUsers = PlexApi.GetUsers(settings.PlexAuthToken);
|
||||
var userManagementSettings = UserManagementSettings.GetSettings();
|
||||
var requests = RequestService.GetAll().ToList();
|
||||
|
||||
var dbUsers = Repo.GetAll().ToList();
|
||||
var localUsers = LocalUserRepository.GetAll().ToList();
|
||||
foreach (var user in plexUsers.User)
|
||||
{
|
||||
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;
|
||||
|
||||
// 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.Username, 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.Username, StringComparison.CurrentCultureIgnoreCase));
|
||||
dbUser.Username = user.Username;
|
||||
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.Username); // 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.Username,
|
||||
LoginId = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
Repo.Insert(m);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
JobRecord.SetRunning(false, JobNames.PlexUserChecker);
|
||||
JobRecord.Record(JobNames.PlexUserChecker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
520
Ombi.Services/Jobs/RecentlyAdded.cs
Normal file
520
Ombi.Services/Jobs/RecentlyAdded.cs
Normal file
|
@ -0,0 +1,520 @@
|
|||
#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
|
||||
{
|
||||
public class RecentlyAdded : HtmlTemplateGenerator, IJob, IRecentlyAdded
|
||||
{
|
||||
public RecentlyAdded(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
|
||||
ISettingsService<EmailNotificationSettings> email, IJobRecord rec,
|
||||
ISettingsService<NewletterSettings> newsletter,
|
||||
IPlexReadOnlyDatabase db, IUserHelper userHelper)
|
||||
{
|
||||
JobRecord = rec;
|
||||
Api = api;
|
||||
PlexSettings = plexSettings;
|
||||
EmailSettings = email;
|
||||
NewsletterSettings = newsletter;
|
||||
PlexDb = db;
|
||||
UserHelper = userHelper;
|
||||
}
|
||||
|
||||
private IPlexApi Api { get; }
|
||||
private TvMazeApi TvApi = new TvMazeApi();
|
||||
private readonly TheMovieDbApi _movieApi = new TheMovieDbApi();
|
||||
private const int MetadataTypeTv = 4;
|
||||
private const int MetadataTypeMovie = 1;
|
||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
|
||||
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
|
||||
private IJobRecord JobRecord { get; }
|
||||
private IPlexReadOnlyDatabase PlexDb { get; }
|
||||
private IUserHelper UserHelper { get; }
|
||||
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = NewsletterSettings.GetSettings();
|
||||
if (!settings.SendRecentlyAddedEmail)
|
||||
{
|
||||
return;
|
||||
}
|
||||
JobRecord.SetRunning(true, JobNames.RecentlyAddedEmail);
|
||||
Start(settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
JobRecord.Record(JobNames.RecentlyAddedEmail);
|
||||
JobRecord.SetRunning(false, JobNames.RecentlyAddedEmail);
|
||||
}
|
||||
}
|
||||
|
||||
public void Test()
|
||||
{
|
||||
Log.Debug("Starting Test Newsletter");
|
||||
var settings = NewsletterSettings.GetSettings();
|
||||
Start(settings, true);
|
||||
}
|
||||
|
||||
private void Start(NewletterSettings newletterSettings, bool testEmail = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var plexSettings = PlexSettings.GetSettings();
|
||||
Log.Debug("Got Plex Settings");
|
||||
|
||||
var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri);
|
||||
Log.Debug("Getting Plex Library Sections");
|
||||
|
||||
var tvSections = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib
|
||||
Log.Debug("Filtered sections for TV");
|
||||
var movieSection = libs.Directories.Where(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)); // We could have more than 1 lib
|
||||
Log.Debug("Filtered sections for Movies");
|
||||
|
||||
var plexVersion = Api.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri).Version;
|
||||
|
||||
var html = string.Empty;
|
||||
if (plexVersion.StartsWith("1.3"))
|
||||
{
|
||||
var tvMetadata = new List<Metadata>();
|
||||
var movieMetadata = new List<Metadata>();
|
||||
foreach (var tvSection in tvSections)
|
||||
{
|
||||
var item = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||
tvSection?.Key);
|
||||
if (item?.MediaContainer?.Metadata != null)
|
||||
{
|
||||
tvMetadata.AddRange(item?.MediaContainer?.Metadata);
|
||||
}
|
||||
}
|
||||
Log.Debug("Got RecentlyAdded TV Shows");
|
||||
foreach (var movie in movieSection)
|
||||
{
|
||||
var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
|
||||
if (recentlyAddedMovies?.MediaContainer?.Metadata != null)
|
||||
{
|
||||
movieMetadata.AddRange(recentlyAddedMovies?.MediaContainer?.Metadata);
|
||||
}
|
||||
}
|
||||
Log.Debug("Got RecentlyAdded Movies");
|
||||
|
||||
Log.Debug("Started Generating Movie HTML");
|
||||
GenerateMovieHtml(movieMetadata, plexSettings, sb);
|
||||
Log.Debug("Finished Generating Movie HTML");
|
||||
Log.Debug("Started Generating TV HTML");
|
||||
GenerateTvHtml(tvMetadata, plexSettings, sb);
|
||||
Log.Debug("Finished Generating TV HTML");
|
||||
|
||||
var template = new RecentlyAddedTemplate();
|
||||
html = template.LoadTemplate(sb.ToString());
|
||||
Log.Debug("Loaded the template");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old API
|
||||
var tvChild = new List<RecentlyAddedChild>();
|
||||
var movieChild = new List<RecentlyAddedChild>();
|
||||
foreach (var tvSection in tvSections)
|
||||
{
|
||||
var recentlyAddedTv = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection?.Key);
|
||||
if (recentlyAddedTv?._children != null)
|
||||
{
|
||||
tvChild.AddRange(recentlyAddedTv?._children);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Debug("Got RecentlyAdded TV Shows");
|
||||
foreach (var movie in movieSection)
|
||||
{
|
||||
var recentlyAddedMovies = Api.RecentlyAddedOld(plexSettings.PlexAuthToken, plexSettings.FullUri, movie?.Key);
|
||||
if (recentlyAddedMovies?._children != null)
|
||||
{
|
||||
tvChild.AddRange(recentlyAddedMovies?._children);
|
||||
}
|
||||
}
|
||||
Log.Debug("Got RecentlyAdded Movies");
|
||||
|
||||
Log.Debug("Started Generating Movie HTML");
|
||||
GenerateMovieHtml(movieChild, plexSettings, sb);
|
||||
Log.Debug("Finished Generating Movie HTML");
|
||||
Log.Debug("Started Generating TV HTML");
|
||||
GenerateTvHtml(tvChild, plexSettings, sb);
|
||||
Log.Debug("Finished Generating TV HTML");
|
||||
|
||||
var template = new RecentlyAddedTemplate();
|
||||
html = template.LoadTemplate(sb.ToString());
|
||||
Log.Debug("Loaded the template");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Send(newletterSettings, html, plexSettings, testEmail);
|
||||
}
|
||||
|
||||
private void GenerateMovieHtml(List<RecentlyAddedChild> movies, PlexSettings plexSettings, StringBuilder sb)
|
||||
{
|
||||
var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<RecentlyAddedChild>();
|
||||
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)
|
||||
{
|
||||
var plexGUID = string.Empty;
|
||||
try
|
||||
{
|
||||
var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||
movie.ratingKey.ToString());
|
||||
|
||||
plexGUID = metaData.Video.Guid;
|
||||
|
||||
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(plexGUID);
|
||||
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 e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Log.Error(
|
||||
"Exception when trying to process a Movie, either in getting the metadata from Plex OR getting the information from TheMovieDB, Plex GUID = {0}",
|
||||
plexGUID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
|
||||
}
|
||||
sb.Append("</table><br/><br/>");
|
||||
}
|
||||
|
||||
private void GenerateMovieHtml(List<Metadata> movies, PlexSettings plexSettings, StringBuilder sb)
|
||||
{
|
||||
var orderedMovies = movies.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).ToList() ?? new List<Metadata>();
|
||||
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)
|
||||
{
|
||||
var plexGUID = string.Empty;
|
||||
try
|
||||
{
|
||||
var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||
movie.ratingKey.ToString());
|
||||
|
||||
plexGUID = metaData.Video.Guid;
|
||||
|
||||
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(plexGUID);
|
||||
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 e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Log.Error(
|
||||
"Exception when trying to process a Movie, either in getting the metadata from Plex OR getting the information from TheMovieDB, Plex GUID = {0}",
|
||||
plexGUID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
|
||||
}
|
||||
sb.Append("</table><br/><br/>");
|
||||
}
|
||||
|
||||
private void GenerateTvHtml(List<RecentlyAddedChild> tv, PlexSettings plexSettings, StringBuilder sb)
|
||||
{
|
||||
var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).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 plexGUID = string.Empty;
|
||||
try
|
||||
{
|
||||
|
||||
var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||
t.parentRatingKey.ToString());
|
||||
|
||||
plexGUID = parentMetaData.Directory.Guid;
|
||||
|
||||
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(plexGUID)));
|
||||
|
||||
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.grandparentTitle} - {t.title} {t.originallyAvailableAt?.Substring(0, 4)}";
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
||||
Header(sb, 3, title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
AddParagraph(sb, $"Season: {t.parentIndex}, Episode: {t.index}");
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, string.IsNullOrEmpty(t.summary) ? info.summary : t.summary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Log.Error(
|
||||
"Exception when trying to process a TV Show, either in getting the metadata from Plex OR getting the information from TVMaze, Plex GUID = {0}",
|
||||
plexGUID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
sb.Append("</table><br/><br/>");
|
||||
}
|
||||
|
||||
private void GenerateTvHtml(List<Metadata> tv, PlexSettings plexSettings, StringBuilder sb)
|
||||
{
|
||||
var orderedTv = tv.OrderByDescending(x => x?.addedAt.UnixTimeStampToDateTime()).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 plexGUID = string.Empty;
|
||||
try
|
||||
{
|
||||
|
||||
var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||
t.parentRatingKey.ToString());
|
||||
|
||||
plexGUID = parentMetaData.Directory.Guid;
|
||||
|
||||
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(plexGUID)));
|
||||
|
||||
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.grandparentTitle} - {t.title} {t.originallyAvailableAt?.Substring(0, 4)}";
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
||||
Header(sb, 3, title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
AddParagraph(sb, $"Season: {t.parentIndex}, Episode: {t.index}");
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, string.IsNullOrEmpty(t.summary) ? info.summary : t.summary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Log.Error(
|
||||
"Exception when trying to process a TV Show, either in getting the metadata from Plex OR getting the information from TVMaze, Plex GUID = {0}",
|
||||
plexGUID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
sb.Append("</table><br/><br/>");
|
||||
}
|
||||
|
||||
private void Send(NewletterSettings newletterSettings, string html, PlexSettings plexSettings, bool testEmail = false)
|
||||
{
|
||||
Log.Debug("Entering Send");
|
||||
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 = "New Content on Plex!",
|
||||
};
|
||||
Log.Debug("Created Plain/HTML MIME body");
|
||||
|
||||
if (!testEmail)
|
||||
{
|
||||
var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification);
|
||||
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)
|
||||
{
|
||||
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));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void EndLoopHtml(StringBuilder sb)
|
||||
{
|
||||
sb.Append("</td>");
|
||||
sb.Append("<hr>");
|
||||
sb.Append("<br>");
|
||||
sb.Append("<br>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
100
Ombi.Services/Jobs/SickRageCacher.cs
Normal file
100
Ombi.Services/Jobs/SickRageCacher.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
115
Ombi.Services/Jobs/SonarrCacher.cs
Normal file
115
Ombi.Services/Jobs/SonarrCacher.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
161
Ombi.Services/Jobs/StoreBackup.cs
Normal file
161
Ombi.Services/Jobs/StoreBackup.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
#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
|
||||
{
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
90
Ombi.Services/Jobs/StoreCleanup.cs
Normal file
90
Ombi.Services/Jobs/StoreCleanup.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
#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
|
||||
{
|
||||
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 Execute(IJobExecutionContext context)
|
||||
{
|
||||
JobRecord.SetRunning(true, JobNames.CpCacher);
|
||||
Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
58
Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.cs
Normal file
58
Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
187
Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html
Normal file
187
Ombi.Services/Jobs/Templates/RecentlyAddedTemplate.html
Normal 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>Plex Requests .Net</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"> </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;">Plex Requests 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/ROTp8mn.png" 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 to Plex!</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"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
121
Ombi.Services/Jobs/UserRequestLimitResetter.cs
Normal file
121
Ombi.Services/Jobs/UserRequestLimitResetter.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: UserRequestLimitResetter.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using 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
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public UserRequestLimitResetter(IJobRecord record, IRepository<RequestLimit> repo, ISettingsService<PlexRequestSettings> settings)
|
||||
{
|
||||
Record = record;
|
||||
Repo = repo;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
private IJobRecord Record { get; }
|
||||
private IRepository<RequestLimit> Repo { get; }
|
||||
private ISettingsService<PlexRequestSettings> Settings { get; }
|
||||
|
||||
public void AlbumLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
|
||||
{
|
||||
if (s.AlbumWeeklyRequestLimit == 0)
|
||||
{
|
||||
return; // The limit has not been set
|
||||
}
|
||||
CheckAndDelete(allUsers, RequestType.Album);
|
||||
}
|
||||
|
||||
public void MovieLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
|
||||
{
|
||||
if (s.MovieWeeklyRequestLimit == 0)
|
||||
{
|
||||
return; // The limit has not been set
|
||||
}
|
||||
CheckAndDelete(allUsers, RequestType.Movie);
|
||||
}
|
||||
|
||||
public void TvLimit(PlexRequestSettings s, IEnumerable<RequestLimit> allUsers)
|
||||
{
|
||||
if (s.TvWeeklyRequestLimit == 0)
|
||||
{
|
||||
return; // The limit has not been set
|
||||
}
|
||||
CheckAndDelete(allUsers, RequestType.TvShow);
|
||||
}
|
||||
|
||||
private void CheckAndDelete(IEnumerable<RequestLimit> allUsers, RequestType type)
|
||||
{
|
||||
var users = allUsers.Where(x => x.RequestType == type);
|
||||
foreach (var u in users)
|
||||
{
|
||||
var daysDiff = (u.FirstRequestDate - DateTime.UtcNow.AddDays(-7)).TotalDays;
|
||||
if (daysDiff <= 0)
|
||||
{
|
||||
Repo.Delete(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue