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
60
Ombi.Services/Interfaces/IAvailabilityChecker.cs
Normal file
60
Ombi.Services/Interfaces/IAvailabilityChecker.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: IAvailabilityChecker.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Store.Models;
|
||||
using Ombi.Store.Models.Plex;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface IAvailabilityChecker
|
||||
{
|
||||
void CheckAndUpdateAll();
|
||||
IEnumerable<PlexContent> GetPlexMovies(IEnumerable<PlexContent> content);
|
||||
bool IsMovieAvailable(PlexContent[] plexMovies, string title, string year, string providerId = null);
|
||||
IEnumerable<PlexContent> GetPlexTvShows(IEnumerable<PlexContent> content);
|
||||
bool IsTvShowAvailable(PlexContent[] plexShows, string title, string year, string providerId = null, int[] seasons = null);
|
||||
IEnumerable<PlexContent> GetPlexAlbums(IEnumerable<PlexContent> content);
|
||||
bool IsAlbumAvailable(PlexContent[] plexAlbums, string title, string year, string artist);
|
||||
bool IsEpisodeAvailable(string theTvDbId, int season, int episode);
|
||||
PlexContent GetAlbum(PlexContent[] plexAlbums, string title, string year, string artist);
|
||||
PlexContent GetMovie(PlexContent[] plexMovies, string title, string year, string providerId = null);
|
||||
PlexContent GetTvShow(PlexContent[] plexShows, string title, string year, string providerId = null, int[] seasons = null);
|
||||
/// <summary>
|
||||
/// Gets the episode's stored in the cache.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<PlexEpisodes>> GetEpisodes();
|
||||
/// <summary>
|
||||
/// Gets the episode's stored in the cache and then filters on the TheTvDBId.
|
||||
/// </summary>
|
||||
/// <param name="theTvDbId">The tv database identifier.</param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<PlexEpisodes>> GetEpisodes(int theTvDbId);
|
||||
}
|
||||
}
|
8
Ombi.Services/Interfaces/ICouchPotatoCacher.cs
Normal file
8
Ombi.Services/Interfaces/ICouchPotatoCacher.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface ICouchPotatoCacher
|
||||
{
|
||||
void Queued();
|
||||
int[] QueuedIds();
|
||||
}
|
||||
}
|
36
Ombi.Services/Interfaces/IIntervals.cs
Normal file
36
Ombi.Services/Interfaces/IIntervals.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: IIntervals.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface IIntervals
|
||||
{
|
||||
TimeSpan Notification { get; } // notification interval for high load
|
||||
}
|
||||
}
|
41
Ombi.Services/Interfaces/IJobRecord.cs
Normal file
41
Ombi.Services/Interfaces/IJobRecord.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: IJobRecord.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Store.Models;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface IJobRecord
|
||||
{
|
||||
void Record(string jobName);
|
||||
Task<IEnumerable<ScheduledJobs>> GetJobsAsync();
|
||||
IEnumerable<ScheduledJobs> GetJobs();
|
||||
void SetRunning(bool running, string jobName);
|
||||
}
|
||||
}
|
47
Ombi.Services/Interfaces/INotification.cs
Normal file
47
Ombi.Services/Interfaces/INotification.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: INotification.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.Threading.Tasks;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Notification;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface INotification
|
||||
{
|
||||
string NotificationName { get; }
|
||||
|
||||
Task NotifyAsync(NotificationModel model);
|
||||
/// <summary>
|
||||
/// Sends a notification to the user, this is usually for testing the settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
Task NotifyAsync(NotificationModel model, Settings settings);
|
||||
}
|
||||
}
|
40
Ombi.Services/Interfaces/INotificationEngine.cs
Normal file
40
Ombi.Services/Interfaces/INotificationEngine.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: INotificationEngine.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Store;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface INotificationEngine
|
||||
{
|
||||
Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, string apiKey, NotificationType type);
|
||||
Task NotifyUsers(RequestedModel modelChanged, string apiKey, NotificationType type);
|
||||
}
|
||||
}
|
53
Ombi.Services/Interfaces/INotificationService.cs
Normal file
53
Ombi.Services/Interfaces/INotificationService.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: INotificationService.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.Threading.Tasks;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Notification;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface INotificationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a notification to the user. This one is used in normal notification scenarios
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
Task Publish(NotificationModel model);
|
||||
/// <summary>
|
||||
/// Sends a notification to the user, this is usually for testing the settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
Task Publish(NotificationModel model, Settings settings);
|
||||
void Subscribe(INotification notification);
|
||||
void UnSubscribe(INotification notification);
|
||||
|
||||
}
|
||||
}
|
8
Ombi.Services/Interfaces/ISickRageCacher.cs
Normal file
8
Ombi.Services/Interfaces/ISickRageCacher.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface ISickRageCacher
|
||||
{
|
||||
void Queued();
|
||||
int[] QueuedIds();
|
||||
}
|
||||
}
|
11
Ombi.Services/Interfaces/ISonarrCacher.cs
Normal file
11
Ombi.Services/Interfaces/ISonarrCacher.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using Ombi.Services.Models;
|
||||
|
||||
namespace Ombi.Services.Interfaces
|
||||
{
|
||||
public interface ISonarrCacher
|
||||
{
|
||||
void Queued();
|
||||
IEnumerable<SonarrCachedResult> QueuedIds();
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Ombi.Services/Models/PlexAlbum.cs
Normal file
12
Ombi.Services/Models/PlexAlbum.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ombi.Services.Models
|
||||
{
|
||||
public class PlexAlbum
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Artist { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
41
Ombi.Services/Models/PlexEpisodeModel.cs
Normal file
41
Ombi.Services/Models/PlexEpisodeModel.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PlexEpisodeModel.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 Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Services.Models
|
||||
{
|
||||
public class PlexEpisodeModel
|
||||
{
|
||||
public EpisodeModelHelper Episodes => PlexHelper.GetSeasonsAndEpisodesFromPlexGuid(Guid);
|
||||
public string Guid { get; set; }
|
||||
public string RatingKey { get; set; }
|
||||
public string EpisodeTitle { get; set; }
|
||||
public string ShowTitle { get; set; }
|
||||
|
||||
}
|
||||
}
|
11
Ombi.Services/Models/PlexMovie.cs
Normal file
11
Ombi.Services/Models/PlexMovie.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ombi.Services.Models
|
||||
{
|
||||
public class PlexMovie
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
12
Ombi.Services/Models/PlexTvShow.cs
Normal file
12
Ombi.Services/Models/PlexTvShow.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ombi.Services.Models
|
||||
{
|
||||
public class PlexTvShow
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public int[] Seasons { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
47
Ombi.Services/Models/SonarrCachedResult.cs
Normal file
47
Ombi.Services/Models/SonarrCachedResult.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: SonarrCachedResult.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;
|
||||
|
||||
namespace Ombi.Services.Models
|
||||
{
|
||||
public class SonarrCachedResult
|
||||
{
|
||||
public SonarrCachedResult()
|
||||
{
|
||||
Seasons = new List<SonarrSeasons>( );
|
||||
}
|
||||
public List<SonarrSeasons> Seasons { get; set; }
|
||||
public int TvdbId { get; set; }
|
||||
}
|
||||
|
||||
public class SonarrSeasons
|
||||
{
|
||||
public int SeasonNumber { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
}
|
||||
}
|
307
Ombi.Services/Notification/EmailMessageNotification.cs
Normal file
307
Ombi.Services/Notification/EmailMessageNotification.cs
Normal file
|
@ -0,0 +1,307 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: EmailMessageNotification.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MimeKit;
|
||||
using NLog;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.Notification.Templates;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Interfaces;
|
||||
using Ombi.Store;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class EmailMessageNotification : INotification
|
||||
{
|
||||
public EmailMessageNotification(ISettingsService<EmailNotificationSettings> settings)
|
||||
{
|
||||
EmailNotificationSettings = settings;
|
||||
}
|
||||
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
|
||||
public string NotificationName => "EmailMessageNotification";
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model)
|
||||
{
|
||||
var configuration = GetConfiguration();
|
||||
await NotifyAsync(model, configuration);
|
||||
}
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||
{
|
||||
if (settings == null) await NotifyAsync(model);
|
||||
|
||||
var emailSettings = (EmailNotificationSettings)settings;
|
||||
|
||||
if (!ValidateConfiguration(emailSettings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (model.NotificationType)
|
||||
{
|
||||
case NotificationType.NewRequest:
|
||||
await EmailNewRequest(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.Issue:
|
||||
await EmailIssue(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.RequestAvailable:
|
||||
await EmailAvailableRequest(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.RequestApproved:
|
||||
await EmailRequestApproved(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.AdminNote:
|
||||
throw new NotImplementedException();
|
||||
|
||||
case NotificationType.Test:
|
||||
await EmailTest(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.RequestDeclined:
|
||||
await EmailRequestDeclined(model, emailSettings);
|
||||
break;
|
||||
case NotificationType.ItemAddedToFaultQueue:
|
||||
await EmailAddedToRequestQueue(model, emailSettings);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private EmailNotificationSettings GetConfiguration()
|
||||
{
|
||||
var settings = EmailNotificationSettings.GetSettings();
|
||||
return settings;
|
||||
}
|
||||
|
||||
private bool ValidateConfiguration(EmailNotificationSettings settings)
|
||||
{
|
||||
if (settings.Authentication)
|
||||
{
|
||||
if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!settings.EnableUserEmailNotifications)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
$"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!",
|
||||
$"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" };
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
$"Plex Requests: New issue for {model.Title}!",
|
||||
$"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!"};
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = $"Plex Requests: New issue for {model.Title}!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailAddedToRequestQueue(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
"Plex Requests: A request could not be added.",
|
||||
$"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying" };
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = $"Plex Requests: A request could not be added"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailRequestDeclined(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
"Plex Requests: Your request has been declined",
|
||||
$"Hello! Your request for {model.Title} has been declined, Sorry!",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! Your request for {model.Title} has been declined, Sorry!", };
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = $"Plex Requests: Your request has been declined"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail));
|
||||
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailRequestApproved(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
"Plex Requests: Your request has been approved!",
|
||||
$"Hello! Your request for {model.Title} has been approved!",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! Your request for {model.Title} has been approved!", };
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = $"Plex Requests: Your request has been approved!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail));
|
||||
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
if (!settings.EnableUserEmailNotifications)
|
||||
{
|
||||
await Task.FromResult(false);
|
||||
}
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
$"Plex Requests: {model.Title} is now available!",
|
||||
$"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" };
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
Subject = $"Plex Requests: {model.Title} is now available!"
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail));
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
private async Task Send(MimeMessage message, EmailNotificationSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
client.Connect(settings.EmailHost, settings.EmailPort); // 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);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings)
|
||||
{
|
||||
var email = new EmailBasicTemplate();
|
||||
var html = email.LoadTemplate(
|
||||
"Test Message",
|
||||
"This is just a test! Success!",
|
||||
model.ImgSrc);
|
||||
var body = new BodyBuilder { HtmlBody = html, };
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody()
|
||||
};
|
||||
message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender));
|
||||
message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail));
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
}
|
||||
}
|
173
Ombi.Services/Notification/NotificationEngine.cs
Normal file
173
Ombi.Services/Notification/NotificationEngine.cs
Normal file
|
@ -0,0 +1,173 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: NotificationEngine.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 NLog;
|
||||
using Ombi.Api.Interfaces;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.Users;
|
||||
using Ombi.Helpers.Permissions;
|
||||
using Ombi.Services.Interfaces;
|
||||
using Ombi.Store;
|
||||
using Ombi.Store.Models;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class NotificationEngine : INotificationEngine
|
||||
{
|
||||
public NotificationEngine(IPlexApi p, IRepository<UsersToNotify> repo, INotificationService service, IUserHelper userHelper)
|
||||
{
|
||||
PlexApi = p;
|
||||
UserNotifyRepo = repo;
|
||||
Notification = service;
|
||||
UserHelper = userHelper;
|
||||
}
|
||||
|
||||
private IPlexApi PlexApi { get; }
|
||||
private IRepository<UsersToNotify> UserNotifyRepo { get; }
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private INotificationService Notification { get; }
|
||||
private IUserHelper UserHelper { get; }
|
||||
|
||||
public async Task NotifyUsers(IEnumerable<RequestedModel> modelChanged, string apiKey, NotificationType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plexUser = PlexApi.GetUsers(apiKey);
|
||||
var userAccount = PlexApi.GetAccount(apiKey);
|
||||
|
||||
var adminUsername = userAccount.Username ?? string.Empty;
|
||||
|
||||
var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification).ToList();
|
||||
Log.Debug("Notifying Users Count {0}", users.Count);
|
||||
foreach (var model in modelChanged)
|
||||
{
|
||||
var selectedUsers = new List<string>();
|
||||
|
||||
foreach (var u in users)
|
||||
{
|
||||
var requestUser = model.RequestedUsers.FirstOrDefault(
|
||||
x => x.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase) || x.Equals(u.UserAlias, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (string.IsNullOrEmpty(requestUser))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
selectedUsers.Add(requestUser);
|
||||
}
|
||||
|
||||
//var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers, StringComparer.CurrentCultureIgnoreCase);
|
||||
foreach (var user in selectedUsers)
|
||||
{
|
||||
Log.Info("Notifying user {0}", user);
|
||||
if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
Log.Info("This user is the Plex server owner");
|
||||
await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title, model.PosterPath, type);
|
||||
return;
|
||||
}
|
||||
|
||||
var email = plexUser.User.FirstOrDefault(x => x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (string.IsNullOrEmpty(email?.Email))
|
||||
{
|
||||
Log.Info("There is no email address for this Plex user, cannot send notification");
|
||||
// We do not have a plex user that requested this!
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);
|
||||
await PublishUserNotification(email.Username, email.Email, model.Title, model.PosterPath, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task NotifyUsers(RequestedModel model, string apiKey, NotificationType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plexUser = PlexApi.GetUsers(apiKey);
|
||||
var userAccount = PlexApi.GetAccount(apiKey);
|
||||
|
||||
var adminUsername = userAccount.Username ?? string.Empty;
|
||||
|
||||
var users = UserHelper.GetUsersWithFeature(Features.RequestAddedNotification).ToList();
|
||||
Log.Debug("Notifying Users Count {0}", users.Count);
|
||||
|
||||
var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers, StringComparer.CurrentCultureIgnoreCase);
|
||||
foreach (var user in selectedUsers)
|
||||
{
|
||||
Log.Info("Notifying user {0}", user);
|
||||
if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
Log.Info("This user is the Plex server owner");
|
||||
await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title, model.PosterPath, type);
|
||||
return;
|
||||
}
|
||||
|
||||
var email = plexUser.User.FirstOrDefault(x => x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (email == null)
|
||||
{
|
||||
Log.Info("There is no email address for this Plex user, cannot send notification");
|
||||
// We do not have a plex user that requested this!
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);
|
||||
await PublishUserNotification(email.Username, email.Email, model.Title, model.PosterPath, type);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishUserNotification(string username, string email, string title, string img, NotificationType type)
|
||||
{
|
||||
var notificationModel = new NotificationModel
|
||||
{
|
||||
User = username,
|
||||
UserEmail = email,
|
||||
NotificationType = type,
|
||||
Title = title,
|
||||
ImgSrc = img
|
||||
};
|
||||
|
||||
// Send the notification to the user.
|
||||
await Notification.Publish(notificationModel);
|
||||
}
|
||||
}
|
||||
}
|
45
Ombi.Services/Notification/NotificationModel.cs
Normal file
45
Ombi.Services/Notification/NotificationModel.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: NotificationModel.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Store;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class NotificationModel
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Body { get; set; }
|
||||
public DateTime DateTime { get; set; }
|
||||
public NotificationType NotificationType { get; set; }
|
||||
public string User { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public RequestType RequestType { get; set; }
|
||||
public string ImgSrc { get; set; }
|
||||
}
|
||||
}
|
103
Ombi.Services/Notification/NotificationService.cs
Normal file
103
Ombi.Services/Notification/NotificationService.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: NotificationService.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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Interfaces;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class NotificationService : INotificationService
|
||||
{
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
public ConcurrentDictionary<string, INotification> Observers { get; } = new ConcurrentDictionary<string, INotification>();
|
||||
|
||||
/// <summary>
|
||||
/// Sends a notification to the user. This one is used in normal notification scenarios
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Publish(NotificationModel model)
|
||||
{
|
||||
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model));
|
||||
|
||||
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a notification to the user, this is usually for testing the settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Publish(NotificationModel model, Settings settings)
|
||||
{
|
||||
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings));
|
||||
|
||||
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Subscribe(INotification notification)
|
||||
{
|
||||
Observers.TryAdd(notification.NotificationName, notification);
|
||||
}
|
||||
|
||||
public void UnSubscribe(INotification notification)
|
||||
{
|
||||
Observers.TryRemove(notification.NotificationName, out notification);
|
||||
}
|
||||
|
||||
private async Task NotifyAsync(INotification notification, NotificationModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
await notification.NotifyAsync(model).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
await notification.NotifyAsync(model, settings).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
Ombi.Services/Notification/PushbulletNotification.cs
Normal file
155
Ombi.Services/Notification/PushbulletNotification.cs
Normal file
|
@ -0,0 +1,155 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PushbulletNotification.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Ombi.Api.Interfaces;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Interfaces;
|
||||
using Ombi.Store;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class PushbulletNotification : INotification
|
||||
{
|
||||
public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService<PushbulletNotificationSettings> settings)
|
||||
{
|
||||
PushbulletApi = pushbulletApi;
|
||||
SettingsService = settings;
|
||||
}
|
||||
private IPushbulletApi PushbulletApi { get; }
|
||||
private ISettingsService<PushbulletNotificationSettings> SettingsService { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
public string NotificationName => "PushbulletNotification";
|
||||
public async Task NotifyAsync(NotificationModel model)
|
||||
{
|
||||
var configuration = GetSettings();
|
||||
await NotifyAsync(model, configuration);
|
||||
}
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||
{
|
||||
if (settings == null) await NotifyAsync(model);
|
||||
|
||||
var pushSettings = (PushbulletNotificationSettings)settings;
|
||||
|
||||
if (!ValidateConfiguration(pushSettings)) return;
|
||||
|
||||
switch (model.NotificationType)
|
||||
{
|
||||
case NotificationType.NewRequest:
|
||||
await PushNewRequestAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.Issue:
|
||||
await PushIssueAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestAvailable:
|
||||
break;
|
||||
case NotificationType.RequestApproved:
|
||||
break;
|
||||
case NotificationType.AdminNote:
|
||||
break;
|
||||
case NotificationType.Test:
|
||||
await PushTestAsync(pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestDeclined:
|
||||
break;
|
||||
case NotificationType.ItemAddedToFaultQueue:
|
||||
await PushFaultQueue(model, pushSettings);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateConfiguration(PushbulletNotificationSettings settings)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.AccessToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private PushbulletNotificationSettings GetSettings()
|
||||
{
|
||||
return SettingsService.GetSettings();
|
||||
}
|
||||
|
||||
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||
{
|
||||
var message = $"The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
|
||||
var pushTitle = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} {model.Title} has been requested!";
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings)
|
||||
{
|
||||
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task PushTestAsync(PushbulletNotificationSettings settings)
|
||||
{
|
||||
var message = "This is just a test! Success!";
|
||||
var pushTitle = "Plex Requests: Test Message!";
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task PushFaultQueue(NotificationModel model, PushbulletNotificationSettings settings)
|
||||
{
|
||||
var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var pushTitle = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} {model.Title} has been requested but could not be added!";
|
||||
await Push(settings, message, pushTitle);
|
||||
}
|
||||
|
||||
private async Task Push(PushbulletNotificationSettings settings, string message, string title)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier);
|
||||
if (result != null)
|
||||
{
|
||||
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
151
Ombi.Services/Notification/PushoverNotification.cs
Normal file
151
Ombi.Services/Notification/PushoverNotification.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: PushbulletNotification.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Ombi.Api.Interfaces;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Interfaces;
|
||||
using Ombi.Store;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class PushoverNotification : INotification
|
||||
{
|
||||
public PushoverNotification(IPushoverApi pushoverApi, ISettingsService<PushoverNotificationSettings> settings)
|
||||
{
|
||||
PushoverApi = pushoverApi;
|
||||
SettingsService = settings;
|
||||
}
|
||||
private IPushoverApi PushoverApi { get; }
|
||||
private ISettingsService<PushoverNotificationSettings> SettingsService { get; }
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
public string NotificationName => "PushoverNotification";
|
||||
public async Task NotifyAsync(NotificationModel model)
|
||||
{
|
||||
var configuration = GetSettings();
|
||||
await NotifyAsync(model, configuration);
|
||||
}
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||
{
|
||||
if (settings == null) await NotifyAsync(model);
|
||||
|
||||
var pushSettings = (PushoverNotificationSettings)settings;
|
||||
|
||||
if (!ValidateConfiguration(pushSettings)) return;
|
||||
|
||||
switch (model.NotificationType)
|
||||
{
|
||||
case NotificationType.NewRequest:
|
||||
await PushNewRequestAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.Issue:
|
||||
await PushIssueAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestAvailable:
|
||||
break;
|
||||
case NotificationType.RequestApproved:
|
||||
break;
|
||||
case NotificationType.AdminNote:
|
||||
break;
|
||||
case NotificationType.Test:
|
||||
await PushTestAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestDeclined:
|
||||
break;
|
||||
case NotificationType.ItemAddedToFaultQueue:
|
||||
await PushFaultQueue(model, pushSettings);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateConfiguration(PushoverNotificationSettings settings)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private PushoverNotificationSettings GetSettings()
|
||||
{
|
||||
return SettingsService.GetSettings();
|
||||
}
|
||||
|
||||
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Plex Requests: Test Message!";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushFaultQueue(NotificationModel model, PushoverNotificationSettings settings)
|
||||
{
|
||||
var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task Push(PushoverNotificationSettings settings, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
|
||||
if (result?.status != 1)
|
||||
{
|
||||
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
166
Ombi.Services/Notification/SlackNotification.cs
Normal file
166
Ombi.Services/Notification/SlackNotification.cs
Normal file
|
@ -0,0 +1,166 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: SlackNotification.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Ombi.Api.Interfaces;
|
||||
using Ombi.Api.Models.Notifications;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.SettingModels;
|
||||
using Ombi.Services.Interfaces;
|
||||
|
||||
namespace Ombi.Services.Notification
|
||||
{
|
||||
public class SlackNotification : INotification
|
||||
{
|
||||
public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn)
|
||||
{
|
||||
Api = api;
|
||||
Settings = sn;
|
||||
}
|
||||
|
||||
public string NotificationName => "SlackNotification";
|
||||
|
||||
private ISlackApi Api { get; }
|
||||
private ISettingsService<SlackNotificationSettings> Settings { get; }
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model)
|
||||
{
|
||||
var settings = Settings.GetSettings();
|
||||
|
||||
await NotifyAsync(model, settings);
|
||||
}
|
||||
|
||||
public async Task NotifyAsync(NotificationModel model, Settings settings)
|
||||
{
|
||||
if (settings == null) await NotifyAsync(model);
|
||||
|
||||
var pushSettings = (SlackNotificationSettings)settings;
|
||||
if (!ValidateConfiguration(pushSettings))
|
||||
{
|
||||
Log.Error("Settings for Slack was not correct, we cannot push a notification");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (model.NotificationType)
|
||||
{
|
||||
case NotificationType.NewRequest:
|
||||
await PushNewRequestAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.Issue:
|
||||
await PushIssueAsync(model, pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestAvailable:
|
||||
break;
|
||||
case NotificationType.RequestApproved:
|
||||
break;
|
||||
case NotificationType.AdminNote:
|
||||
break;
|
||||
case NotificationType.Test:
|
||||
await PushTest(pushSettings);
|
||||
break;
|
||||
case NotificationType.RequestDeclined:
|
||||
break;
|
||||
case NotificationType.ItemAddedToFaultQueue:
|
||||
await PushFaultQueue(model, pushSettings);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PushNewRequestAsync(NotificationModel model, SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"{model.Title} has been requested by user: {model.User}";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushIssueAsync(NotificationModel model, SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushTest(SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"This is a test from Plex Requests, if you can see this then we have successfully pushed a notification!";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task PushFaultQueue(NotificationModel model, SlackNotificationSettings settings)
|
||||
{
|
||||
var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
await Push(settings, message);
|
||||
}
|
||||
|
||||
private async Task Push(SlackNotificationSettings config, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var notification = new SlackNotificationBody { username = config.Username, channel = config.Channel ?? string.Empty, text = message };
|
||||
|
||||
var result = await Api.PushAsync(config.Team, config.Token, config.Service, notification);
|
||||
if (!result.Equals("ok"))
|
||||
{
|
||||
Log.Error("Slack returned a message that was not 'ok', the notification did not get pushed");
|
||||
Log.Error($"Message that slack returned: {result}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateConfiguration(SlackNotificationSettings settings)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.WebhookUrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var a = settings.Team;
|
||||
var b = settings.Service;
|
||||
var c = settings.Token;
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
173
Ombi.Services/Ombi.Services.csproj
Normal file
173
Ombi.Services/Ombi.Services.csproj
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Ombi.Services</RootNamespace>
|
||||
<AssemblyName>Ombi.Services</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dapper, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="BouncyCastle, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
|
||||
<HintPath>..\packages\MimeKit.1.2.22\lib\net45\BouncyCastle.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Common.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e">
|
||||
<HintPath>..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Common.Logging.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e">
|
||||
<HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MailKit, Version=1.2.0.0, Culture=neutral, PublicKeyToken=4e064fe7c44a8f1b">
|
||||
<HintPath>..\packages\MailKit.1.2.21\lib\net451\MailKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MimeKit, Version=1.2.0.0, Culture=neutral, PublicKeyToken=bede1c8a46c66814">
|
||||
<HintPath>..\packages\MimeKit.1.2.22\lib\net45\MimeKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Mono.Data.Sqlite">
|
||||
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Quartz, Version=2.3.3.0, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4">
|
||||
<HintPath>..\packages\Quartz.2.3.3\lib\net40\Quartz.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Interfaces\IJobRecord.cs" />
|
||||
<Compile Include="Interfaces\INotificationEngine.cs" />
|
||||
<Compile Include="Jobs\HtmlTemplateGenerator.cs" />
|
||||
<Compile Include="Jobs\IPlexContentCacher.cs" />
|
||||
<Compile Include="Jobs\IRecentlyAdded.cs" />
|
||||
<Compile Include="Jobs\JobRecord.cs" />
|
||||
<Compile Include="Jobs\JobNames.cs" />
|
||||
<Compile Include="Jobs\PlexContentCacher.cs" />
|
||||
<Compile Include="Jobs\PlexEpisodeCacher.cs" />
|
||||
<Compile Include="Jobs\RecentlyAdded.cs" />
|
||||
<Compile Include="Jobs\StoreBackup.cs" />
|
||||
<Compile Include="Jobs\PlexUserChecker.cs" />
|
||||
<Compile Include="Jobs\StoreCleanup.cs" />
|
||||
<Compile Include="Jobs\CouchPotatoCacher.cs" />
|
||||
<Compile Include="Jobs\PlexAvailabilityChecker.cs" />
|
||||
<Compile Include="Jobs\SickRageCacher.cs" />
|
||||
<Compile Include="Jobs\SonarrCacher.cs" />
|
||||
<Compile Include="Jobs\Templates\RecentlyAddedTemplate.cs" />
|
||||
<Compile Include="Jobs\FaultQueueHandler.cs" />
|
||||
<Compile Include="Jobs\UserRequestLimitResetter.cs" />
|
||||
<Compile Include="Models\PlexAlbum.cs" />
|
||||
<Compile Include="Models\PlexEpisodeModel.cs" />
|
||||
<Compile Include="Models\PlexMovie.cs" />
|
||||
<Compile Include="Models\PlexTvShow.cs" />
|
||||
<Compile Include="Interfaces\ISickRageCacher.cs" />
|
||||
<Compile Include="Interfaces\ISonarrCacher.cs" />
|
||||
<Compile Include="Interfaces\ICouchPotatoCacher.cs" />
|
||||
<Compile Include="Interfaces\IAvailabilityChecker.cs" />
|
||||
<Compile Include="Interfaces\IIntervals.cs" />
|
||||
<Compile Include="Interfaces\INotification.cs" />
|
||||
<Compile Include="Interfaces\INotificationService.cs" />
|
||||
<Compile Include="Models\SonarrCachedResult.cs" />
|
||||
<Compile Include="Notification\EmailMessageNotification.cs" />
|
||||
<Compile Include="Notification\NotificationEngine.cs" />
|
||||
<Compile Include="Notification\NotificationModel.cs" />
|
||||
<Compile Include="Notification\NotificationService.cs" />
|
||||
<Compile Include="Notification\PushoverNotification.cs" />
|
||||
<Compile Include="Notification\PushbulletNotification.cs" />
|
||||
<Compile Include="Notification\SlackNotification.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api.Interfaces\Ombi.Api.Interfaces.csproj">
|
||||
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
|
||||
<Name>Ombi.Api.Interfaces</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Ombi.Api.Models\Ombi.Api.Models.csproj">
|
||||
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
|
||||
<Name>Ombi.Api.Models</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj">
|
||||
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
|
||||
<Name>Ombi.Api</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj">
|
||||
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
|
||||
<Name>Ombi.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj">
|
||||
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
|
||||
<Name>Ombi.Helpers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj">
|
||||
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
|
||||
<Name>Ombi.Store</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Jobs\Templates\RecentlyAddedTemplate.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
37
Ombi.Services/Properties/AssemblyInfo.cs
Normal file
37
Ombi.Services/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Ombi.Services")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Ombi.Services")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("566efa49-68f8-4716-9693-a6b3f2624dea")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]
|
11
Ombi.Services/app.config
Normal file
11
Ombi.Services/app.config
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /></startup></configuration>
|
11
Ombi.Services/packages.config
Normal file
11
Ombi.Services/packages.config
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Common.Logging" version="3.0.0" targetFramework="net45" />
|
||||
<package id="Common.Logging.Core" version="3.0.0" targetFramework="net45" />
|
||||
<package id="Dapper" version="1.50.0-beta8" targetFramework="net45" />
|
||||
<package id="MailKit" version="1.2.21" targetFramework="net45" requireReinstallation="True" />
|
||||
<package id="MimeKit" version="1.2.22" targetFramework="net45" />
|
||||
<package id="Nancy" version="1.4.3" targetFramework="net45" />
|
||||
<package id="NLog" version="4.3.6" targetFramework="net45" />
|
||||
<package id="Quartz" version="2.3.3" targetFramework="net45" />
|
||||
</packages>
|
Loading…
Add table
Add a link
Reference in a new issue