mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-21 05:43:19 -07:00
commit
ea2f4d3e78
108 changed files with 4039 additions and 854 deletions
|
@ -44,6 +44,6 @@ namespace PlexRequests.Api.Interfaces
|
||||||
PlexSearch GetAllEpisodes(string authToken, Uri host, string section, int startPage, int returnCount);
|
PlexSearch GetAllEpisodes(string authToken, Uri host, string section, int startPage, int returnCount);
|
||||||
PlexServer GetServer(string authToken);
|
PlexServer GetServer(string authToken);
|
||||||
PlexSeasonMetadata GetSeasons(string authToken, Uri plexFullHost, string ratingKey);
|
PlexSeasonMetadata GetSeasons(string authToken, Uri plexFullHost, string ratingKey);
|
||||||
RecentlyAdded RecentlyAdded(string authToken, Uri plexFullHost);
|
RecentlyAddedModel RecentlyAdded(string authToken, Uri plexFullHost, string sectionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,6 +39,10 @@ namespace PlexRequests.Api.Interfaces
|
||||||
int seasonCount, int[] seasons, string apiKey, Uri baseUrl, bool monitor = true,
|
int seasonCount, int[] seasons, string apiKey, Uri baseUrl, bool monitor = true,
|
||||||
bool searchForMissingEpisodes = false);
|
bool searchForMissingEpisodes = false);
|
||||||
|
|
||||||
|
SonarrAddSeries AddSeriesNew(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath,
|
||||||
|
int[] seasons, string apiKey, Uri baseUrl, bool monitor = true,
|
||||||
|
bool searchForMissingEpisodes = false);
|
||||||
|
|
||||||
SystemStatus SystemStatus(string apiKey, Uri baseUrl);
|
SystemStatus SystemStatus(string apiKey, Uri baseUrl);
|
||||||
|
|
||||||
List<Series> GetSeries(string apiKey, Uri baseUrl);
|
List<Series> GetSeries(string apiKey, Uri baseUrl);
|
||||||
|
|
|
@ -49,6 +49,7 @@ namespace PlexRequests.Api.Models.Plex
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
public string email { get; set; }
|
public string email { get; set; }
|
||||||
|
public string uuid { get; set; }
|
||||||
public string joined_at { get; set; }
|
public string joined_at { get; set; }
|
||||||
public string username { get; set; }
|
public string username { get; set; }
|
||||||
public string title { get; set; }
|
public string title { get; set; }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#region Copyright
|
#region Copyright
|
||||||
// /************************************************************************
|
// /************************************************************************
|
||||||
// Copyright (c) 2016 Jamie Rees
|
// Copyright (c) 2016 Jamie Rees
|
||||||
// File: RecentlyAdded.cs
|
// File: RecentlyAddedModel.cs
|
||||||
// Created By: Jamie Rees
|
// Created By: Jamie Rees
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
@ -32,53 +32,102 @@ namespace PlexRequests.Api.Models.Plex
|
||||||
public class RecentlyAddedChild
|
public class RecentlyAddedChild
|
||||||
{
|
{
|
||||||
public string _elementType { get; set; }
|
public string _elementType { get; set; }
|
||||||
public string allowSync { get; set; }
|
|
||||||
public string librarySectionID { get; set; }
|
|
||||||
public string librarySectionTitle { get; set; }
|
|
||||||
public string librarySectionUUID { get; set; }
|
|
||||||
public int ratingKey { get; set; }
|
public int ratingKey { get; set; }
|
||||||
public string key { get; set; }
|
public string key { get; set; }
|
||||||
public int parentRatingKey { get; set; }
|
public int parentRatingKey { get; set; }
|
||||||
|
public int grandparentRatingKey { get; set; }
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
public string title { get; set; }
|
public string title { get; set; }
|
||||||
|
public string grandparentKey { get; set; }
|
||||||
public string parentKey { get; set; }
|
public string parentKey { get; set; }
|
||||||
public string parentTitle { get; set; }
|
public string grandparentTitle { get; set; }
|
||||||
public string parentSummary { get; set; }
|
|
||||||
public string summary { get; set; }
|
public string summary { get; set; }
|
||||||
public int index { get; set; }
|
public int index { get; set; }
|
||||||
public int parentIndex { get; set; }
|
public int parentIndex { get; set; }
|
||||||
public string thumb { get; set; }
|
public string thumb { get; set; }
|
||||||
public string art { get; set; }
|
public string art { get; set; }
|
||||||
public string parentThumb { get; set; }
|
public string grandparentThumb { get; set; }
|
||||||
public int leafCount { get; set; }
|
public string grandparentArt { get; set; }
|
||||||
public int viewedLeafCount { get; set; }
|
public int duration { get; set; }
|
||||||
public int addedAt { get; set; }
|
public int addedAt { get; set; }
|
||||||
public int updatedAt { get; set; }
|
public int updatedAt { get; set; }
|
||||||
public List<object> _children { get; set; }
|
public string chapterSource { get; set; }
|
||||||
public string studio { get; set; }
|
public List<Child2> _children { get; set; }
|
||||||
public string contentRating { get; set; }
|
public string contentRating { get; set; }
|
||||||
public string rating { get; set; }
|
public int? year { get; set; }
|
||||||
|
public string parentThumb { get; set; }
|
||||||
|
public string grandparentTheme { get; set; }
|
||||||
|
public string originallyAvailableAt { get; set; }
|
||||||
|
public string titleSort { get; set; }
|
||||||
public int? viewCount { get; set; }
|
public int? viewCount { get; set; }
|
||||||
public int? lastViewedAt { get; set; }
|
public int? lastViewedAt { get; set; }
|
||||||
public int? year { get; set; }
|
|
||||||
public int? duration { get; set; }
|
|
||||||
public string originallyAvailableAt { get; set; }
|
|
||||||
public string chapterSource { get; set; }
|
|
||||||
public string parentTheme { get; set; }
|
|
||||||
public string titleSort { get; set; }
|
|
||||||
public string tagline { get; set; }
|
|
||||||
public int? viewOffset { get; set; }
|
public int? viewOffset { get; set; }
|
||||||
|
public string rating { get; set; }
|
||||||
|
public string studio { get; set; }
|
||||||
|
public string tagline { get; set; }
|
||||||
public string originalTitle { get; set; }
|
public string originalTitle { get; set; }
|
||||||
|
public string audienceRating { get; set; }
|
||||||
|
public string audienceRatingImage { get; set; }
|
||||||
|
public string ratingImage { get; set; }
|
||||||
|
}
|
||||||
|
public class Child3
|
||||||
|
{
|
||||||
|
public string _elementType { get; set; }
|
||||||
|
public string id { get; set; }
|
||||||
|
public string key { get; set; }
|
||||||
|
public double duration { get; set; }
|
||||||
|
public string file { get; set; }
|
||||||
|
public double size { get; set; }
|
||||||
|
public string audioProfile { get; set; }
|
||||||
|
public string container { get; set; }
|
||||||
|
public string videoProfile { get; set; }
|
||||||
|
public string deepAnalysisVersion { get; set; }
|
||||||
|
public string requiredBandwidths { get; set; }
|
||||||
|
public string hasThumbnail { get; set; }
|
||||||
|
public bool? has64bitOffsets { get; set; }
|
||||||
|
public bool? optimizedForStreaming { get; set; }
|
||||||
|
public bool? hasChapterTextStream { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RecentlyAdded
|
public class Child2
|
||||||
|
{
|
||||||
|
public string _elementType { get; set; }
|
||||||
|
public string videoResolution { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public int duration { get; set; }
|
||||||
|
public int bitrate { get; set; }
|
||||||
|
public int width { get; set; }
|
||||||
|
public int height { get; set; }
|
||||||
|
public string aspectRatio { get; set; }
|
||||||
|
public int audioChannels { get; set; }
|
||||||
|
public string audioCodec { get; set; }
|
||||||
|
public string videoCodec { get; set; }
|
||||||
|
public string container { get; set; }
|
||||||
|
public string videoFrameRate { get; set; }
|
||||||
|
public string audioProfile { get; set; }
|
||||||
|
public string videoProfile { get; set; }
|
||||||
|
public List<Child3> _children { get; set; }
|
||||||
|
public string tag { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RecentlyAddedModel
|
||||||
{
|
{
|
||||||
public string _elementType { get; set; }
|
public string _elementType { get; set; }
|
||||||
public string allowSync { get; set; }
|
public string allowSync { get; set; }
|
||||||
|
public string art { get; set; }
|
||||||
public string identifier { get; set; }
|
public string identifier { get; set; }
|
||||||
|
public string librarySectionID { get; set; }
|
||||||
|
public string librarySectionTitle { get; set; }
|
||||||
|
public string librarySectionUUID { get; set; }
|
||||||
public string mediaTagPrefix { get; set; }
|
public string mediaTagPrefix { get; set; }
|
||||||
public string mediaTagVersion { get; set; }
|
public string mediaTagVersion { get; set; }
|
||||||
public string mixedParents { get; set; }
|
public string mixedParents { get; set; }
|
||||||
|
public string nocache { get; set; }
|
||||||
|
public string thumb { get; set; }
|
||||||
|
public string title1 { get; set; }
|
||||||
|
public string title2 { get; set; }
|
||||||
|
public string viewGroup { get; set; }
|
||||||
|
public string viewMode { get; set; }
|
||||||
public List<RecentlyAddedChild> _children { get; set; }
|
public List<RecentlyAddedChild> _children { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -73,7 +73,7 @@
|
||||||
<Compile Include="Plex\PlexStatus.cs" />
|
<Compile Include="Plex\PlexStatus.cs" />
|
||||||
<Compile Include="Plex\PlexMediaType.cs" />
|
<Compile Include="Plex\PlexMediaType.cs" />
|
||||||
<Compile Include="Plex\PlexUserRequest.cs" />
|
<Compile Include="Plex\PlexUserRequest.cs" />
|
||||||
<Compile Include="Plex\RecentlyAdded.cs" />
|
<Compile Include="Plex\RecentlyAddedModel.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SickRage\SickRageBase.cs" />
|
<Compile Include="SickRage\SickRageBase.cs" />
|
||||||
<Compile Include="SickRage\SickrageShows.cs" />
|
<Compile Include="SickRage\SickrageShows.cs" />
|
||||||
|
|
|
@ -4,6 +4,10 @@ namespace PlexRequests.Api.Models.Tv
|
||||||
{
|
{
|
||||||
public class TvMazeShow
|
public class TvMazeShow
|
||||||
{
|
{
|
||||||
|
public TvMazeShow()
|
||||||
|
{
|
||||||
|
Season = new List<TvMazeCustomSeason>();
|
||||||
|
}
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
public string url { get; set; }
|
public string url { get; set; }
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
|
@ -23,10 +27,16 @@ namespace PlexRequests.Api.Models.Tv
|
||||||
public string summary { get; set; }
|
public string summary { get; set; }
|
||||||
public int updated { get; set; }
|
public int updated { get; set; }
|
||||||
public Links _links { get; set; }
|
public Links _links { get; set; }
|
||||||
public int seasonCount { get; set; }
|
public List<TvMazeCustomSeason> Season { get; set; }
|
||||||
public Embedded _embedded { get; set; }
|
public Embedded _embedded { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TvMazeCustomSeason
|
||||||
|
{
|
||||||
|
public int SeasonNumber { get; set; }
|
||||||
|
public int EpisodeNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Season
|
public class Season
|
||||||
{
|
{
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace PlexRequests.Api
|
||||||
Method = Method.POST
|
Method = Method.POST
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHeaders(ref request);
|
AddHeaders(ref request, false);
|
||||||
|
|
||||||
request.AddJsonBody(userModel);
|
request.AddJsonBody(userModel);
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ namespace PlexRequests.Api
|
||||||
Method = Method.GET,
|
Method = Method.GET,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
var users = RetryHandler.Execute(() => Api.ExecuteXml<PlexFriends> (request, new Uri(FriendsUri)),
|
var users = RetryHandler.Execute(() => Api.ExecuteXml<PlexFriends> (request, new Uri(FriendsUri)),
|
||||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan), null);
|
(exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan), null);
|
||||||
|
@ -118,7 +118,7 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddUrlSegment("searchTerm", searchTerm);
|
request.AddUrlSegment("searchTerm", searchTerm);
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
var search = RetryHandler.Execute(() => Api.ExecuteXml<PlexSearch> (request, plexFullHost),
|
var search = RetryHandler.Execute(() => Api.ExecuteXml<PlexSearch> (request, plexFullHost),
|
||||||
(exception, timespan) => Log.Error (exception, "Exception when calling SearchContent for Plex, Retrying {0}", timespan), null);
|
(exception, timespan) => Log.Error (exception, "Exception when calling SearchContent for Plex, Retrying {0}", timespan), null);
|
||||||
|
@ -133,7 +133,7 @@ namespace PlexRequests.Api
|
||||||
Method = Method.GET,
|
Method = Method.GET,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
var users = RetryHandler.Execute(() => Api.ExecuteXml<PlexStatus> (request, uri),
|
var users = RetryHandler.Execute(() => Api.ExecuteXml<PlexStatus> (request, uri),
|
||||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for Plex, Retrying {0}", timespan), null);
|
(exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for Plex, Retrying {0}", timespan), null);
|
||||||
|
@ -148,7 +148,7 @@ namespace PlexRequests.Api
|
||||||
Method = Method.GET,
|
Method = Method.GET,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
var account = RetryHandler.Execute(() => Api.ExecuteXml<PlexAccount> (request, new Uri(GetAccountUri)),
|
var account = RetryHandler.Execute(() => Api.ExecuteXml<PlexAccount> (request, new Uri(GetAccountUri)),
|
||||||
(exception, timespan) => Log.Error (exception, "Exception when calling GetAccount for Plex, Retrying {0}", timespan), null);
|
(exception, timespan) => Log.Error (exception, "Exception when calling GetAccount for Plex, Retrying {0}", timespan), null);
|
||||||
|
@ -164,7 +164,7 @@ namespace PlexRequests.Api
|
||||||
Resource = "library/sections"
|
Resource = "library/sections"
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -193,7 +193,7 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddUrlSegment("libraryId", libraryId);
|
request.AddUrlSegment("libraryId", libraryId);
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -228,7 +228,7 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddUrlSegment("ratingKey", ratingKey);
|
request.AddUrlSegment("ratingKey", ratingKey);
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -253,10 +253,9 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddQueryParameter("type", 4.ToString());
|
request.AddQueryParameter("type", 4.ToString());
|
||||||
request.AddQueryParameter("X-Plex-Container-Start", startPage.ToString());
|
AddLimitHeaders(ref request, startPage, returnCount);
|
||||||
request.AddQueryParameter("X-Plex-Container-Size", returnCount.ToString());
|
|
||||||
request.AddUrlSegment("section", section);
|
request.AddUrlSegment("section", section);
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -281,7 +280,7 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddUrlSegment("itemId", itemId);
|
request.AddUrlSegment("itemId", itemId);
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -311,7 +310,7 @@ namespace PlexRequests.Api
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddUrlSegment("ratingKey", ratingKey);
|
request.AddUrlSegment("ratingKey", ratingKey);
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -338,7 +337,7 @@ namespace PlexRequests.Api
|
||||||
Method = Method.GET,
|
Method = Method.GET,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddHeaders(ref request, authToken);
|
AddHeaders(ref request, authToken, false);
|
||||||
|
|
||||||
var servers = RetryHandler.Execute(() => Api.ExecuteXml<PlexServer>(request, new Uri(ServerUri)),
|
var servers = RetryHandler.Execute(() => Api.ExecuteXml<PlexServer>(request, new Uri(ServerUri)),
|
||||||
(exception, timespan) => Log.Error(exception, "Exception when calling GetServer for Plex, Retrying {0}", timespan));
|
(exception, timespan) => Log.Error(exception, "Exception when calling GetServer for Plex, Retrying {0}", timespan));
|
||||||
|
@ -347,25 +346,22 @@ namespace PlexRequests.Api
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecentlyAdded RecentlyAdded(string authToken, Uri plexFullHost)
|
public RecentlyAddedModel RecentlyAdded(string authToken, Uri plexFullHost, string sectionId)
|
||||||
{
|
{
|
||||||
var request = new RestRequest
|
var request = new RestRequest
|
||||||
{
|
{
|
||||||
Method = Method.GET,
|
Method = Method.GET,
|
||||||
Resource = "library/recentlyAdded"
|
Resource = "library/sections/{sectionId}/recentlyAdded"
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddHeader("X-Plex-Token", authToken);
|
request.AddUrlSegment("sectionId", sectionId);
|
||||||
request.AddHeader("X-Plex-Client-Identifier", $"PlexRequests.Net{Version}");
|
AddHeaders(ref request, authToken, true);
|
||||||
request.AddHeader("X-Plex-Product", "Plex Requests .Net");
|
AddLimitHeaders(ref request, 0, 25);
|
||||||
request.AddHeader("X-Plex-Version", Version);
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lib = RetryHandler.Execute(() => Api.ExecuteJson<RecentlyAdded>(request, plexFullHost),
|
var lib = RetryHandler.Execute(() => Api.ExecuteJson<RecentlyAddedModel>(request, plexFullHost),
|
||||||
(exception, timespan) => Log.Error(exception, "Exception when calling RecentlyAdded for Plex, Retrying {0}", timespan), new[] {
|
(exception, timespan) => Log.Error(exception, "Exception when calling RecentlyAddedModel for Plex, Retrying {0}", timespan), new[] {
|
||||||
TimeSpan.FromSeconds (5),
|
TimeSpan.FromSeconds (5),
|
||||||
TimeSpan.FromSeconds(10),
|
TimeSpan.FromSeconds(10),
|
||||||
TimeSpan.FromSeconds(30)
|
TimeSpan.FromSeconds(30)
|
||||||
|
@ -375,23 +371,30 @@ namespace PlexRequests.Api
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, "There has been a API Exception when attempting to get the Plex RecentlyAdded");
|
Log.Error(e, "There has been a API Exception when attempting to get the Plex RecentlyAddedModel");
|
||||||
return new RecentlyAdded();
|
return new RecentlyAddedModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddHeaders(ref RestRequest request, string authToken)
|
private void AddLimitHeaders(ref RestRequest request, int from, int to)
|
||||||
|
{
|
||||||
|
request.AddHeader("X-Plex-Container-Start", from.ToString());
|
||||||
|
request.AddHeader("X-Plex-Container-Size", to.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddHeaders(ref RestRequest request, string authToken, bool json)
|
||||||
{
|
{
|
||||||
request.AddHeader("X-Plex-Token", authToken);
|
request.AddHeader("X-Plex-Token", authToken);
|
||||||
AddHeaders(ref request);
|
AddHeaders(ref request, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddHeaders(ref RestRequest request)
|
private void AddHeaders(ref RestRequest request, bool json)
|
||||||
{
|
{
|
||||||
request.AddHeader("X-Plex-Client-Identifier", $"PlexRequests.Net{Version}");
|
request.AddHeader("X-Plex-Client-Identifier", $"PlexRequests.Net{Version}");
|
||||||
request.AddHeader("X-Plex-Product", "Plex Requests .Net");
|
request.AddHeader("X-Plex-Product", "Plex Requests .Net");
|
||||||
request.AddHeader("X-Plex-Version", Version);
|
request.AddHeader("X-Plex-Version", Version);
|
||||||
request.AddHeader("Content-Type", "application/xml");
|
request.AddHeader("Content-Type", json ? "application/json" : "application/xml");
|
||||||
|
request.AddHeader("Accept", json ? "application/json" : "application/xml");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,9 @@
|
||||||
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, PublicKeyToken=null">
|
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, PublicKeyToken=null">
|
||||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, PublicKeyToken=null">
|
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -135,6 +135,75 @@ namespace PlexRequests.Api
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SonarrAddSeries AddSeriesNew(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int[] seasons, string apiKey, Uri baseUrl, bool monitor = true, bool searchForMissingEpisodes = false)
|
||||||
|
{
|
||||||
|
var request = new RestRequest
|
||||||
|
{
|
||||||
|
Resource = "/api/Series?",
|
||||||
|
Method = Method.POST
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new SonarrAddSeries
|
||||||
|
{
|
||||||
|
seasonFolder = seasonFolders,
|
||||||
|
title = title,
|
||||||
|
qualityProfileId = qualityId,
|
||||||
|
tvdbId = tvdbId,
|
||||||
|
titleSlug = title,
|
||||||
|
seasons = new List<Season>(),
|
||||||
|
rootFolderPath = rootPath,
|
||||||
|
monitored = monitor
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!searchForMissingEpisodes)
|
||||||
|
{
|
||||||
|
options.addOptions = new AddOptions
|
||||||
|
{
|
||||||
|
searchForMissingEpisodes = false,
|
||||||
|
ignoreEpisodesWithFiles = true,
|
||||||
|
ignoreEpisodesWithoutFiles = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i <= seasons.Length; i++)
|
||||||
|
{
|
||||||
|
var season = new Season
|
||||||
|
{
|
||||||
|
seasonNumber = i,
|
||||||
|
// ReSharper disable once SimplifyConditionalTernaryExpression
|
||||||
|
monitored = true
|
||||||
|
};
|
||||||
|
options.seasons.Add(season);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug("Sonarr API Options:");
|
||||||
|
Log.Debug(options.DumpJson());
|
||||||
|
|
||||||
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
|
request.AddJsonBody(options);
|
||||||
|
|
||||||
|
SonarrAddSeries result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan), new TimeSpan[] {
|
||||||
|
TimeSpan.FromSeconds (1),
|
||||||
|
TimeSpan.FromSeconds(2),
|
||||||
|
});
|
||||||
|
|
||||||
|
result = policy.Execute(() => Api.ExecuteJson<SonarrAddSeries>(request, baseUrl));
|
||||||
|
}
|
||||||
|
catch (JsonSerializationException jse)
|
||||||
|
{
|
||||||
|
Log.Error(jse);
|
||||||
|
var error = Api.ExecuteJson<List<SonarrError>>(request, baseUrl);
|
||||||
|
var messages = error?.Select(x => x.errorMessage).ToList();
|
||||||
|
messages?.ForEach(x => Log.Error(x));
|
||||||
|
result = new SonarrAddSeries { ErrorMessages = messages };
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public SystemStatus SystemStatus(string apiKey, Uri baseUrl)
|
public SystemStatus SystemStatus(string apiKey, Uri baseUrl)
|
||||||
{
|
{
|
||||||
var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET };
|
var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET };
|
||||||
|
|
|
@ -93,7 +93,17 @@ namespace PlexRequests.Api
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
var obj = Api.Execute<TvMazeShow>(request, new Uri(Uri));
|
var obj = Api.Execute<TvMazeShow>(request, new Uri(Uri));
|
||||||
obj.seasonCount = GetSeasonCount(obj.id);
|
|
||||||
|
var episodes = EpisodeLookup(obj.id).ToList();
|
||||||
|
|
||||||
|
foreach (var e in episodes)
|
||||||
|
{
|
||||||
|
obj.Season.Add(new TvMazeCustomSeason
|
||||||
|
{
|
||||||
|
SeasonNumber = e.season,
|
||||||
|
EpisodeNumber = e.number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
@ -110,6 +120,7 @@ namespace PlexRequests.Api
|
||||||
|
|
||||||
return Api.Execute<List<TvMazeSeasons>>(request, new Uri(Uri));
|
return Api.Execute<List<TvMazeSeasons>>(request, new Uri(Uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetSeasonCount(int id)
|
public int GetSeasonCount(int id)
|
||||||
{
|
{
|
||||||
var obj = GetSeasons(id);
|
var obj = GetSeasons(id);
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
<package id="NLog" version="4.3.6" targetFramework="net45" />
|
<package id="NLog" version="4.3.6" targetFramework="net45" />
|
||||||
<package id="Polly-Signed" version="4.3.0" targetFramework="net45" />
|
<package id="Polly-Signed" version="4.3.0" targetFramework="net45" />
|
||||||
<package id="RestSharp" version="105.2.3" targetFramework="net45" />
|
<package id="RestSharp" version="105.2.3" targetFramework="net45" />
|
||||||
|
<package id="System.Net.Http" version="4.0.0" targetFramework="net45" />
|
||||||
<package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" />
|
<package id="TMDbLib" version="0.9.0.0-alpha" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
37
PlexRequests.Core.Migration/IMigration.cs
Normal file
37
PlexRequests.Core.Migration/IMigration.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: IMigration.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.Data;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core.Migration
|
||||||
|
{
|
||||||
|
public interface IMigration
|
||||||
|
{
|
||||||
|
int Version { get; }
|
||||||
|
void Start(IDbConnection con);
|
||||||
|
}
|
||||||
|
}
|
7
PlexRequests.Core.Migration/IMigrationRunner.cs
Normal file
7
PlexRequests.Core.Migration/IMigrationRunner.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PlexRequests.Core.Migration
|
||||||
|
{
|
||||||
|
public interface IMigrationRunner
|
||||||
|
{
|
||||||
|
void MigrateToLatest();
|
||||||
|
}
|
||||||
|
}
|
33
PlexRequests.Core.Migration/Migrate.cs
Normal file
33
PlexRequests.Core.Migration/Migrate.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: Migrate.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 PlexRequests.Core.Migration
|
||||||
|
{
|
||||||
|
public class Migrate
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
43
PlexRequests.Core.Migration/MigrationAttribute.cs
Normal file
43
PlexRequests.Core.Migration/MigrationAttribute.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: Migration.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 PlexRequests.Core.Migration
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class Migration : Attribute
|
||||||
|
{
|
||||||
|
public Migration(int version, string description)
|
||||||
|
{
|
||||||
|
Version = version;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
public int Version { get; private set; }
|
||||||
|
public string Description { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
94
PlexRequests.Core.Migration/MigrationRunner.cs
Normal file
94
PlexRequests.Core.Migration/MigrationRunner.cs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Ninject;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core.Migration
|
||||||
|
{
|
||||||
|
public class MigrationRunner : IMigrationRunner
|
||||||
|
{
|
||||||
|
public MigrationRunner(ISqliteConfiguration db, IKernel kernel)
|
||||||
|
{
|
||||||
|
Db = db;
|
||||||
|
Kernel = kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IKernel Kernel { get; }
|
||||||
|
private ISqliteConfiguration Db { get; }
|
||||||
|
|
||||||
|
public void MigrateToLatest()
|
||||||
|
{
|
||||||
|
var con = Db.DbConnection();
|
||||||
|
var versions = GetMigrations().OrderBy(x => x.Key);
|
||||||
|
|
||||||
|
var dbVersion = con.GetVersionInfo().OrderByDescending(x => x.Version).FirstOrDefault();
|
||||||
|
if (dbVersion == null)
|
||||||
|
{
|
||||||
|
dbVersion = new TableCreation.VersionInfo { Version = 0 };
|
||||||
|
}
|
||||||
|
foreach (var v in versions)
|
||||||
|
{
|
||||||
|
if (v.Value.Version > dbVersion.Version)
|
||||||
|
{
|
||||||
|
// Assuming only one constructor
|
||||||
|
var ctor = v.Key.GetConstructors().FirstOrDefault();
|
||||||
|
var dependencies = new List<object>();
|
||||||
|
|
||||||
|
foreach (var param in ctor.GetParameters())
|
||||||
|
{
|
||||||
|
Console.WriteLine(string.Format(
|
||||||
|
"Param {0} is named {1} and is of type {2}",
|
||||||
|
param.Position, param.Name, param.ParameterType));
|
||||||
|
|
||||||
|
var dep = Kernel.Get(param.ParameterType);
|
||||||
|
dependencies.Add(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = v.Key.GetMethod("Start");
|
||||||
|
if (method != null)
|
||||||
|
{
|
||||||
|
object result = null;
|
||||||
|
var classInstance = Activator.CreateInstance(v.Key, dependencies.Any() ? dependencies.ToArray() : null);
|
||||||
|
|
||||||
|
var parametersArray = new object[] { Db.DbConnection() };
|
||||||
|
|
||||||
|
method.Invoke(classInstance, parametersArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<Type, MigrationModel> GetMigrations()
|
||||||
|
{
|
||||||
|
var migrationTypes = GetTypesWithHelpAttribute(Assembly.GetAssembly(typeof(MigrationRunner)));
|
||||||
|
|
||||||
|
var version = new Dictionary<Type, MigrationModel>();
|
||||||
|
|
||||||
|
foreach (var t in migrationTypes)
|
||||||
|
{
|
||||||
|
var customAttributes = (Migration[])t.GetCustomAttributes(typeof(Migration), true);
|
||||||
|
if (customAttributes.Length > 0)
|
||||||
|
{
|
||||||
|
var attr = customAttributes[0];
|
||||||
|
|
||||||
|
version.Add(t, new MigrationModel { Version = attr.Version, Description = attr.Description });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
|
||||||
|
{
|
||||||
|
return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(Migration), true).Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MigrationModel
|
||||||
|
{
|
||||||
|
public int Version { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
PlexRequests.Core.Migration/Migrations/BaseMigration.cs
Normal file
53
PlexRequests.Core.Migration/Migrations/BaseMigration.cs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: BaseMigration.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.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core.Migration.Migrations
|
||||||
|
{
|
||||||
|
public abstract class BaseMigration
|
||||||
|
{
|
||||||
|
protected void UpdateSchema(IDbConnection con, int version)
|
||||||
|
{
|
||||||
|
var migrations = MigrationRunner.GetMigrations();
|
||||||
|
var model = migrations.Select(x => x.Value).FirstOrDefault(x => x.Version == version);
|
||||||
|
|
||||||
|
if (model != null)
|
||||||
|
{
|
||||||
|
con.AddVersionInfo(new TableCreation.VersionInfo { Version = model.Version, Description = model.Description });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IEnumerable<TableCreation.VersionInfo> GetVersionInfo(IDbConnection con)
|
||||||
|
{
|
||||||
|
return con.GetVersionInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
PlexRequests.Core.Migration/Migrations/Version195.cs
Normal file
99
PlexRequests.Core.Migration/Migrations/Version195.cs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: Version195.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.Data;
|
||||||
|
using PlexRequests.Core.SettingModels;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core.Migration.Migrations
|
||||||
|
{
|
||||||
|
[Migration(1950, "v1.9.5.0")]
|
||||||
|
public class Version195 : BaseMigration, IMigration
|
||||||
|
{
|
||||||
|
public Version195(ISettingsService<PlexRequestSettings> plexRequestSettings, ISettingsService<NewletterSettings> news, ISettingsService<ScheduledJobsSettings> jobs)
|
||||||
|
{
|
||||||
|
PlexRequestSettings = plexRequestSettings;
|
||||||
|
NewsletterSettings = news;
|
||||||
|
Jobs = jobs;
|
||||||
|
}
|
||||||
|
public int Version => 1950;
|
||||||
|
|
||||||
|
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
|
||||||
|
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
|
||||||
|
private ISettingsService<ScheduledJobsSettings> Jobs { get; }
|
||||||
|
|
||||||
|
public void Start(IDbConnection con)
|
||||||
|
{
|
||||||
|
UpdateApplicationSettings();
|
||||||
|
UpdateDb(con);
|
||||||
|
|
||||||
|
UpdateSchema(con, Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDb(IDbConnection con)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateApplicationSettings()
|
||||||
|
{
|
||||||
|
var plex = PlexRequestSettings.GetSettings();
|
||||||
|
var jobSettings = Jobs.GetSettings();
|
||||||
|
var newsLetter = NewsletterSettings.GetSettings();
|
||||||
|
|
||||||
|
newsLetter.SendToPlexUsers = true;
|
||||||
|
UpdateScheduledSettings(jobSettings);
|
||||||
|
|
||||||
|
if (plex.SendRecentlyAddedEmail)
|
||||||
|
{
|
||||||
|
newsLetter.SendRecentlyAddedEmail = plex.SendRecentlyAddedEmail;
|
||||||
|
plex.SendRecentlyAddedEmail = false;
|
||||||
|
PlexRequestSettings.SaveSettings(plex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NewsletterSettings.SaveSettings(newsLetter);
|
||||||
|
Jobs.SaveSettings(jobSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateScheduledSettings(ScheduledJobsSettings settings)
|
||||||
|
{
|
||||||
|
settings.PlexAvailabilityChecker = 60;
|
||||||
|
settings.SickRageCacher = 60;
|
||||||
|
settings.SonarrCacher = 60;
|
||||||
|
settings.CouchPotatoCacher = 60;
|
||||||
|
settings.StoreBackup = 24;
|
||||||
|
settings.StoreCleanup = 24;
|
||||||
|
settings.UserRequestLimitResetter = 12;
|
||||||
|
settings.PlexEpisodeCacher = 12;
|
||||||
|
|
||||||
|
var cron = (Quartz.Impl.Triggers.CronTriggerImpl)CronScheduleBuilder.WeeklyOnDayAndHourAndMinute(DayOfWeek.Friday, 7, 0).Build();
|
||||||
|
settings.RecentlyAddedCron = cron.CronExpressionString; // Weekly CRON at 7 am on Mondays
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.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>{8406EE57-D533-47C0-9302-C6B5F8C31E55}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>PlexRequests.Core.Migration</RootNamespace>
|
||||||
|
<AssemblyName>PlexRequests.Core.Migration</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="Common.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Common.Logging.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Mono.Data.Sqlite">
|
||||||
|
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Ninject">
|
||||||
|
<HintPath>..\packages\Ninject.3.2.0.0\lib\net45-full\Ninject.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Quartz, Version=2.3.3.0, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Quartz.2.3.3\lib\net40\Quartz.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<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" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="IMigration.cs" />
|
||||||
|
<Compile Include="IMigrationRunner.cs" />
|
||||||
|
<Compile Include="Migrate.cs" />
|
||||||
|
<Compile Include="MigrationAttribute.cs" />
|
||||||
|
<Compile Include="MigrationRunner.cs" />
|
||||||
|
<Compile Include="Migrations\BaseMigration.cs" />
|
||||||
|
<Compile Include="Migrations\Version195.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
|
||||||
|
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
|
||||||
|
<Name>PlexRequests.Core</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\PlexRequests.Store\PlexRequests.Store.csproj">
|
||||||
|
<Project>{92433867-2B7B-477B-A566-96C382427525}</Project>
|
||||||
|
<Name>PlexRequests.Store</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="app.config" />
|
||||||
|
<None Include="job_scheduling_data_2_0.xsd">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</None>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</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>
|
36
PlexRequests.Core.Migration/Properties/AssemblyInfo.cs
Normal file
36
PlexRequests.Core.Migration/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
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("PlexRequests.Core.Migration")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("PlexRequests.Core.Migration")]
|
||||||
|
[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("8406ee57-d533-47c0-9302-c6b5f8c31e55")]
|
||||||
|
|
||||||
|
// 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")]
|
11
PlexRequests.Core.Migration/app.config
Normal file
11
PlexRequests.Core.Migration/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>
|
||||||
|
</configuration>
|
361
PlexRequests.Core.Migration/job_scheduling_data_2_0.xsd
Normal file
361
PlexRequests.Core.Migration/job_scheduling_data_2_0.xsd
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns="http://quartznet.sourceforge.net/JobSchedulingData"
|
||||||
|
targetNamespace="http://quartznet.sourceforge.net/JobSchedulingData"
|
||||||
|
elementFormDefault="qualified"
|
||||||
|
version="2.0">
|
||||||
|
|
||||||
|
<xs:element name="job-scheduling-data">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Root level node</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence maxOccurs="unbounded">
|
||||||
|
<xs:element name="pre-processing-commands" type="pre-processing-commandsType" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Commands to be executed before scheduling the jobs and triggers in this file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="processing-directives" type="processing-directivesType" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Directives to be followed while scheduling the jobs and triggers in this file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="schedule" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence maxOccurs="unbounded">
|
||||||
|
<xs:element name="job" type="job-detailType" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
<xs:element name="trigger" type="triggerType" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="version" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Version of the XML Schema instance</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
|
||||||
|
<xs:complexType name="pre-processing-commandsType">
|
||||||
|
<xs:sequence maxOccurs="unbounded">
|
||||||
|
<xs:element name="delete-jobs-in-group" type="xs:string" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Delete all jobs, if any, in the identified group. "*" can be used to identify all groups. Will also result in deleting all triggers related to the jobs.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="delete-triggers-in-group" type="xs:string" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Delete all triggers, if any, in the identified group. "*" can be used to identify all groups. Will also result in deletion of related jobs that are non-durable.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="delete-job" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Delete the identified job if it exists (will also result in deleting all triggers related to it).</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="name" type="xs:string" />
|
||||||
|
<xs:element name="group" type="xs:string" minOccurs="0" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="delete-trigger" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Delete the identified trigger if it exists (will also result in deletion of related jobs that are non-durable).</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="name" type="xs:string" />
|
||||||
|
<xs:element name="group" type="xs:string" minOccurs="0" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="processing-directivesType">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="overwrite-existing-data" type="xs:boolean" minOccurs="0" default="true">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Whether the existing scheduling data (with same identifiers) will be overwritten. If false, and ignore-duplicates is not false, and jobs or triggers with the same names already exist as those in the file, an error will occur.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ignore-duplicates" type="xs:boolean" minOccurs="0" default="false">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>If true (and overwrite-existing-data is false) then any job/triggers encountered in this file that have names that already exist in the scheduler will be ignored, and no error will be produced.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="schedule-trigger-relative-to-replaced-trigger" type="xs:boolean" minOccurs="0" default="false">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>If true trigger's start time is calculated based on earlier run time instead of fixed value. Trigger's start time must be undefined for this to work.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="job-detailType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a JobDetail</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="name" type="xs:string" />
|
||||||
|
<xs:element name="group" type="xs:string" minOccurs="0" />
|
||||||
|
<xs:element name="description" type="xs:string" minOccurs="0" />
|
||||||
|
<xs:element name="job-type" type="xs:string" />
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:element name="durable" type="xs:boolean" />
|
||||||
|
<xs:element name="recover" type="xs:boolean" />
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:element name="job-data-map" type="job-data-mapType" minOccurs="0" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="job-data-mapType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a JobDataMap</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element name="entry" type="entryType" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="entryType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a JobDataMap entry</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="key" type="xs:string" />
|
||||||
|
<xs:element name="value" type="xs:string" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="triggerType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a Trigger</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:choice>
|
||||||
|
<xs:element name="simple" type="simpleTriggerType" />
|
||||||
|
<xs:element name="cron" type="cronTriggerType" />
|
||||||
|
<xs:element name="calendar-interval" type="calendarIntervalTriggerType" />
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="abstractTriggerType" abstract="true">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Common Trigger definitions</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="name" type="xs:string" />
|
||||||
|
<xs:element name="group" type="xs:string" minOccurs="0" />
|
||||||
|
<xs:element name="description" type="xs:string" minOccurs="0" />
|
||||||
|
<xs:element name="job-name" type="xs:string" />
|
||||||
|
<xs:element name="job-group" type="xs:string" minOccurs="0" />
|
||||||
|
<xs:element name="priority" type="xs:nonNegativeInteger" minOccurs="0" />
|
||||||
|
<xs:element name="calendar-name" type="xs:string" minOccurs="0" />
|
||||||
|
<xs:element name="job-data-map" type="job-data-mapType" minOccurs="0" />
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:choice>
|
||||||
|
<xs:element name="start-time" type="xs:dateTime" />
|
||||||
|
<xs:element name="start-time-seconds-in-future" type="xs:nonNegativeInteger" />
|
||||||
|
</xs:choice>
|
||||||
|
<xs:element name="end-time" type="xs:dateTime" minOccurs="0" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="simpleTriggerType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a SimpleTrigger</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexContent>
|
||||||
|
<xs:extension base="abstractTriggerType">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="misfire-instruction" type="simple-trigger-misfire-instructionType" minOccurs="0" />
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:element name="repeat-count" type="repeat-countType" />
|
||||||
|
<xs:element name="repeat-interval" type="xs:nonNegativeInteger" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:complexContent>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="cronTriggerType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a CronTrigger</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexContent>
|
||||||
|
<xs:extension base="abstractTriggerType">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="misfire-instruction" type="cron-trigger-misfire-instructionType" minOccurs="0" />
|
||||||
|
<xs:element name="cron-expression" type="cron-expressionType" />
|
||||||
|
<xs:element name="time-zone" type="xs:string" minOccurs="0" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:complexContent>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="calendarIntervalTriggerType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Define a DateIntervalTrigger</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexContent>
|
||||||
|
<xs:extension base="abstractTriggerType">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="misfire-instruction" type="date-interval-trigger-misfire-instructionType" minOccurs="0" />
|
||||||
|
<xs:element name="repeat-interval" type="xs:nonNegativeInteger" />
|
||||||
|
<xs:element name="repeat-interval-unit" type="interval-unitType" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:complexContent>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:simpleType name="cron-expressionType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Cron expression (see JavaDoc for examples)
|
||||||
|
|
||||||
|
Special thanks to Chris Thatcher (thatcher@butterfly.net) for the regular expression!
|
||||||
|
|
||||||
|
Regular expressions are not my strong point but I believe this is complete,
|
||||||
|
with the caveat that order for expressions like 3-0 is not legal but will pass,
|
||||||
|
and month and day names must be capitalized.
|
||||||
|
If you want to examine the correctness look for the [\s] to denote the
|
||||||
|
seperation of individual regular expressions. This is how I break them up visually
|
||||||
|
to examine them:
|
||||||
|
|
||||||
|
SECONDS:
|
||||||
|
(
|
||||||
|
((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)
|
||||||
|
| (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))
|
||||||
|
| ([\?])
|
||||||
|
| ([\*])
|
||||||
|
) [\s]
|
||||||
|
MINUTES:
|
||||||
|
(
|
||||||
|
((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)
|
||||||
|
| (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))
|
||||||
|
| ([\?])
|
||||||
|
| ([\*])
|
||||||
|
) [\s]
|
||||||
|
HOURS:
|
||||||
|
(
|
||||||
|
((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)
|
||||||
|
| (([\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))
|
||||||
|
| ([\?])
|
||||||
|
| ([\*])
|
||||||
|
) [\s]
|
||||||
|
DAY OF MONTH:
|
||||||
|
(
|
||||||
|
((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)
|
||||||
|
| (([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)
|
||||||
|
| (L(-[0-9])?)
|
||||||
|
| (L(-[1-2][0-9])?)
|
||||||
|
| (L(-[3][0-1])?)
|
||||||
|
| (LW)
|
||||||
|
| ([1-9]W)
|
||||||
|
| ([1-3][0-9]W)
|
||||||
|
| ([\?])
|
||||||
|
| ([\*])
|
||||||
|
)[\s]
|
||||||
|
MONTH:
|
||||||
|
(
|
||||||
|
((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)
|
||||||
|
| (([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))
|
||||||
|
| (((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)
|
||||||
|
| ((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))
|
||||||
|
| ([\?])
|
||||||
|
| ([\*])
|
||||||
|
)[\s]
|
||||||
|
DAY OF WEEK:
|
||||||
|
(
|
||||||
|
(([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)
|
||||||
|
| ([1-7]/([1-7]))
|
||||||
|
| (((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)
|
||||||
|
| ((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)
|
||||||
|
| (([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))(L|LW)?)
|
||||||
|
| (([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)
|
||||||
|
| ([\?])
|
||||||
|
| ([\*])
|
||||||
|
)
|
||||||
|
YEAR (OPTIONAL):
|
||||||
|
(
|
||||||
|
[\s]?
|
||||||
|
([\*])?
|
||||||
|
| ((19[7-9][0-9])|(20[0-9][0-9]))?
|
||||||
|
| (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?
|
||||||
|
| ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?
|
||||||
|
)
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern
|
||||||
|
value="(((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\?])|([\*]))[\s](((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\?])|([\*]))[\s](((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)|(([\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))|([\?])|([\*]))[\s](((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)|(([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)|(L(-[0-9])?)|(L(-[1-2][0-9])?)|(L(-[3][0-1])?)|(LW)|([1-9]W)|([1-3][0-9]W)|([\?])|([\*]))[\s](((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)|(([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))|(((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)|((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))|([\?])|([\*]))[\s]((([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)|([1-7]/([1-7]))|(((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)|((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)|(([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))?(L|LW)?)|(([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)|([\?])|([\*]))([\s]?(([\*])?|(19[7-9][0-9])|(20[0-9][0-9]))?| (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?| ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?)" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="repeat-countType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Number of times to repeat the Trigger (-1 for indefinite)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:restriction base="xs:integer">
|
||||||
|
<xs:minInclusive value="-1" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
|
||||||
|
<xs:simpleType name="simple-trigger-misfire-instructionType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Simple Trigger Misfire Instructions</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="SmartPolicy" />
|
||||||
|
<xs:pattern value="RescheduleNextWithExistingCount" />
|
||||||
|
<xs:pattern value="RescheduleNextWithRemainingCount" />
|
||||||
|
<xs:pattern value="RescheduleNowWithExistingRepeatCount" />
|
||||||
|
<xs:pattern value="RescheduleNowWithRemainingRepeatCount" />
|
||||||
|
<xs:pattern value="FireNow" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="cron-trigger-misfire-instructionType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Cron Trigger Misfire Instructions</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="SmartPolicy" />
|
||||||
|
<xs:pattern value="DoNothing" />
|
||||||
|
<xs:pattern value="FireOnceNow" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="date-interval-trigger-misfire-instructionType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Date Interval Trigger Misfire Instructions</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="SmartPolicy" />
|
||||||
|
<xs:pattern value="DoNothing" />
|
||||||
|
<xs:pattern value="FireOnceNow" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="interval-unitType">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Interval Units</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="Day" />
|
||||||
|
<xs:pattern value="Hour" />
|
||||||
|
<xs:pattern value="Minute" />
|
||||||
|
<xs:pattern value="Month" />
|
||||||
|
<xs:pattern value="Second" />
|
||||||
|
<xs:pattern value="Week" />
|
||||||
|
<xs:pattern value="Year" />
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
|
</xs:schema>
|
6
PlexRequests.Core.Migration/packages.config
Normal file
6
PlexRequests.Core.Migration/packages.config
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?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="Quartz" version="2.3.3" targetFramework="net45" />
|
||||||
|
</packages>
|
11
PlexRequests.Core/IPlexReadOnlyDatabase.cs
Normal file
11
PlexRequests.Core/IPlexReadOnlyDatabase.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using PlexRequests.Store.Models.Plex;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core
|
||||||
|
{
|
||||||
|
public interface IPlexReadOnlyDatabase
|
||||||
|
{
|
||||||
|
IEnumerable<MetadataItems> GetItemsAddedAfterDate(DateTime dateTime);
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,7 +144,7 @@
|
||||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" 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>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<img src="http://i.imgur.com/s4nswSA.png?" width="400px" text-align="center" />
|
<img src="http://i.imgur.com/ROTp8mn.png" text-align="center" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
82
PlexRequests.Core/PlexReadOnlyDatabase.cs
Normal file
82
PlexRequests.Core/PlexReadOnlyDatabase.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: PlexReadOnlyDatabase.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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using PlexRequests.Core.SettingModels;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
using PlexRequests.Store.Models.Plex;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core
|
||||||
|
{
|
||||||
|
public class PlexReadOnlyDatabase : IPlexReadOnlyDatabase
|
||||||
|
{
|
||||||
|
public PlexReadOnlyDatabase(IPlexDatabase plexDatabase, ISettingsService<PlexSettings> plexSettings)
|
||||||
|
{
|
||||||
|
Plex = plexDatabase;
|
||||||
|
|
||||||
|
var settings = plexSettings.GetSettings();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(settings.PlexDatabaseLocationOverride))
|
||||||
|
{
|
||||||
|
//Overriden setting
|
||||||
|
Plex.DbLocation = Path.Combine(settings.PlexDatabaseLocationOverride, "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
|
||||||
|
}
|
||||||
|
else if (Type.GetType("Mono.Runtime") != null)
|
||||||
|
{
|
||||||
|
// Mono
|
||||||
|
Plex.DbLocation = Path.Combine("/var/lib/plexmediaserver/Library/Application Support/", "Plex Media Server", "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default Windows
|
||||||
|
Plex.DbLocation = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"), "Plex Media Server", "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private IPlexDatabase Plex { get; }
|
||||||
|
|
||||||
|
public IEnumerable<MetadataItems> GetItemsAddedAfterDate(DateTime dateTime)
|
||||||
|
{
|
||||||
|
// type 1 = Movie, type 4 = TV Episode
|
||||||
|
var movies = Plex.QueryMetadataItems(@"SELECT * FROM metadata_items
|
||||||
|
WHERE added_at > @AddedAt
|
||||||
|
AND metadata_type = 1
|
||||||
|
AND title <> ''", new { AddedAt = dateTime });
|
||||||
|
|
||||||
|
// Custom query to include the series title
|
||||||
|
var tv = Plex.QueryMetadataItems(@"SELECT series.title AS SeriesTitle, mi.* FROM metadata_items mi
|
||||||
|
INNER JOIN metadata_items season ON mi.parent_id = season.id
|
||||||
|
INNER JOIN metadata_items series ON series.id = season.parent_id
|
||||||
|
WHERE mi.added_at > @AddedAt
|
||||||
|
AND mi.metadata_type = 4", new { AddedAt = dateTime });
|
||||||
|
|
||||||
|
return movies.Union(tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CacheKeys.cs" />
|
<Compile Include="CacheKeys.cs" />
|
||||||
|
<Compile Include="IPlexReadOnlyDatabase.cs" />
|
||||||
<Compile Include="Notification\NotificationMessage.cs" />
|
<Compile Include="Notification\NotificationMessage.cs" />
|
||||||
<Compile Include="Notification\NotificationMessageContent.cs" />
|
<Compile Include="Notification\NotificationMessageContent.cs" />
|
||||||
<Compile Include="Notification\NotificationMessageCurlys.cs" />
|
<Compile Include="Notification\NotificationMessageCurlys.cs" />
|
||||||
|
@ -85,10 +86,12 @@
|
||||||
<Compile Include="Notification\Templates\EmailBasicTemplate.cs" />
|
<Compile Include="Notification\Templates\EmailBasicTemplate.cs" />
|
||||||
<Compile Include="Notification\Templates\IEmailBasicTemplate.cs" />
|
<Compile Include="Notification\Templates\IEmailBasicTemplate.cs" />
|
||||||
<Compile Include="Notification\TransportType.cs" />
|
<Compile Include="Notification\TransportType.cs" />
|
||||||
|
<Compile Include="PlexReadOnlyDatabase.cs" />
|
||||||
<Compile Include="SettingModels\AuthenticationSettings.cs" />
|
<Compile Include="SettingModels\AuthenticationSettings.cs" />
|
||||||
<Compile Include="SettingModels\ExternalSettings.cs" />
|
<Compile Include="SettingModels\ExternalSettings.cs" />
|
||||||
<Compile Include="SettingModels\HeadphonesSettings.cs" />
|
<Compile Include="SettingModels\HeadphonesSettings.cs" />
|
||||||
<Compile Include="SettingModels\LandingPageSettings.cs" />
|
<Compile Include="SettingModels\LandingPageSettings.cs" />
|
||||||
|
<Compile Include="SettingModels\NewsletterSettings.cs" />
|
||||||
<Compile Include="SettingModels\NotificationSettings.cs" />
|
<Compile Include="SettingModels\NotificationSettings.cs" />
|
||||||
<Compile Include="SettingModels\NotificationSettingsV2.cs" />
|
<Compile Include="SettingModels\NotificationSettingsV2.cs" />
|
||||||
<Compile Include="SettingModels\RequestSettings.cs" />
|
<Compile Include="SettingModels\RequestSettings.cs" />
|
||||||
|
@ -141,7 +144,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Notification\Templates\BasicRequestTemplate.html">
|
<Content Include="Notification\Templates\BasicRequestTemplate.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
|
45
PlexRequests.Core/SettingModels/NewsletterSettings.cs
Normal file
45
PlexRequests.Core/SettingModels/NewsletterSettings.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: NewsletterSettings.cs
|
||||||
|
// Created By: Jim MacKenzie
|
||||||
|
//
|
||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using PlexRequests.Helpers;
|
||||||
|
|
||||||
|
namespace PlexRequests.Core.SettingModels
|
||||||
|
{
|
||||||
|
public class NewletterSettings : Settings
|
||||||
|
{
|
||||||
|
public bool SendRecentlyAddedEmail { get; set; }
|
||||||
|
public bool SendToPlexUsers { get; set; }
|
||||||
|
public string CustomUsers { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IEnumerable<string> CustomUsersEmailAddresses => CustomUsers.SplitEmailsByDelimiter(';');
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,8 +58,8 @@ namespace PlexRequests.Core.SettingModels
|
||||||
public bool Wizard { get; set; }
|
public bool Wizard { get; set; }
|
||||||
public bool DisableTvRequestsByEpisode { get; set; }
|
public bool DisableTvRequestsByEpisode { get; set; }
|
||||||
public bool DisableTvRequestsBySeason { get; set; }
|
public bool DisableTvRequestsBySeason { get; set; }
|
||||||
|
[Obsolete("Moved to NewsLetterSettings")]
|
||||||
public bool SendRecentlyAddedEmail { get; set; }
|
public bool SendRecentlyAddedEmail { get; set; }
|
||||||
|
|
||||||
public string CustomDonationUrl { get; set; }
|
public string CustomDonationUrl { get; set; }
|
||||||
public bool EnableCustomDonationUrl { get; set; }
|
public bool EnableCustomDonationUrl { get; set; }
|
||||||
public string CustomDonationMessage { get; set; }
|
public string CustomDonationMessage { get; set; }
|
||||||
|
|
|
@ -40,5 +40,6 @@ namespace PlexRequests.Core.SettingModels
|
||||||
|
|
||||||
public string PlexAuthToken { get; set; }
|
public string PlexAuthToken { get; set; }
|
||||||
public string MachineIdentifier { get; set; }
|
public string MachineIdentifier { get; set; }
|
||||||
|
public string PlexDatabaseLocationOverride { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,23 +24,13 @@
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
// ************************************************************************/
|
// ************************************************************************/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace PlexRequests.Core.SettingModels
|
namespace PlexRequests.Core.SettingModels
|
||||||
{
|
{
|
||||||
public class ScheduledJobsSettings : Settings
|
public class ScheduledJobsSettings : Settings
|
||||||
{
|
{
|
||||||
public ScheduledJobsSettings()
|
|
||||||
{
|
|
||||||
PlexAvailabilityChecker = 60;
|
|
||||||
SickRageCacher = 60;
|
|
||||||
SonarrCacher = 60;
|
|
||||||
CouchPotatoCacher = 60;
|
|
||||||
StoreBackup = 24;
|
|
||||||
StoreCleanup = 24;
|
|
||||||
UserRequestLimitResetter = 12;
|
|
||||||
PlexEpisodeCacher = 12;
|
|
||||||
RecentlyAdded = 168;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int PlexAvailabilityChecker { get; set; }
|
public int PlexAvailabilityChecker { get; set; }
|
||||||
public int SickRageCacher { get; set; }
|
public int SickRageCacher { get; set; }
|
||||||
public int SonarrCacher { get; set; }
|
public int SonarrCacher { get; set; }
|
||||||
|
@ -49,6 +39,8 @@ namespace PlexRequests.Core.SettingModels
|
||||||
public int StoreCleanup { get; set; }
|
public int StoreCleanup { get; set; }
|
||||||
public int UserRequestLimitResetter { get; set; }
|
public int UserRequestLimitResetter { get; set; }
|
||||||
public int PlexEpisodeCacher { get; set; }
|
public int PlexEpisodeCacher { get; set; }
|
||||||
|
[Obsolete("We use the CRON job now")]
|
||||||
public int RecentlyAdded { get; set; }
|
public int RecentlyAdded { get; set; }
|
||||||
|
public string RecentlyAddedCron { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -60,6 +60,8 @@ namespace PlexRequests.Core
|
||||||
TableCreation.Vacuum(Db.DbConnection());
|
TableCreation.Vacuum(Db.DbConnection());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The below code is obsolete, we should use PlexRequests.Core.Migrations.MigrationRunner
|
||||||
var version = CheckSchema();
|
var version = CheckSchema();
|
||||||
if (version > 0)
|
if (version > 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -110,31 +110,35 @@ namespace PlexRequests.Core
|
||||||
Salt = salt,
|
Salt = salt,
|
||||||
Hash = PasswordHasher.ComputeHash(password, salt),
|
Hash = PasswordHasher.ComputeHash(password, salt),
|
||||||
Claims = ByteConverterHelper.ReturnBytes(claims),
|
Claims = ByteConverterHelper.ReturnBytes(claims),
|
||||||
UserProperties = ByteConverterHelper.ReturnBytes(properties ?? new UserProperties())
|
UserProperties = ByteConverterHelper.ReturnBytes(properties ?? new UserProperties()),
|
||||||
};
|
};
|
||||||
Repo.Insert(userModel);
|
Repo.Insert(userModel);
|
||||||
|
|
||||||
var userRecord = Repo.Get(userModel.UserGuid);
|
var userRecord = Repo.Get(userModel.UserGuid);
|
||||||
|
|
||||||
return new Guid(userRecord.UserGuid);
|
return new Guid(userRecord.UserGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeleteUser(string userId)
|
||||||
|
{
|
||||||
|
var user = Repo.Get(userId);
|
||||||
|
Repo.Delete(user);
|
||||||
|
}
|
||||||
|
|
||||||
public Guid? CreateAdmin(string username, string password, UserProperties properties = null)
|
public Guid? CreateAdmin(string username, string password, UserProperties properties = null)
|
||||||
{
|
{
|
||||||
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser, UserClaims.Admin }, properties);
|
return CreateUser(username, password, new[] { UserClaims.RegularUser, UserClaims.PowerUser, UserClaims.Admin }, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid? CreatePowerUser(string username, string password, UserProperties properties = null)
|
public Guid? CreatePowerUser(string username, string password, UserProperties properties = null)
|
||||||
{
|
{
|
||||||
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser }, properties);
|
return CreateUser(username, password, new[] { UserClaims.RegularUser, UserClaims.PowerUser }, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid? CreateRegularUser(string username, string password, UserProperties properties = null)
|
public Guid? CreateRegularUser(string username, string password, UserProperties properties = null)
|
||||||
{
|
{
|
||||||
return CreateUser(username, password, new[] { UserClaims.User }, properties);
|
return CreateUser(username, password, new[] { UserClaims.RegularUser }, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public IEnumerable<string> GetAllClaims()
|
public IEnumerable<string> GetAllClaims()
|
||||||
{
|
{
|
||||||
var properties = typeof(UserClaims).GetConstantsValues<string>();
|
var properties = typeof(UserClaims).GetConstantsValues<string>();
|
||||||
|
@ -194,7 +198,6 @@ namespace PlexRequests.Core
|
||||||
Guid? CreateAdmin(string username, string password, UserProperties properties = null);
|
Guid? CreateAdmin(string username, string password, UserProperties properties = null);
|
||||||
Guid? CreatePowerUser(string username, string password, UserProperties properties = null);
|
Guid? CreatePowerUser(string username, string password, UserProperties properties = null);
|
||||||
Guid? CreateRegularUser(string username, string password, UserProperties properties = null);
|
Guid? CreateRegularUser(string username, string password, UserProperties properties = null);
|
||||||
|
void DeleteUser(string userId);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,5 +38,13 @@ namespace PlexRequests.Helpers
|
||||||
var tzc = TimeZoneInfo.GetSystemTimeZones();
|
var tzc = TimeZoneInfo.GetSystemTimeZones();
|
||||||
return tzc.FirstOrDefault(x => x.BaseUtcOffset.TotalMinutes == -minuteOffset);
|
return tzc.FirstOrDefault(x => x.BaseUtcOffset.TotalMinutes == -minuteOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DateTime UnixTimeStampToDateTime(this int unixTimeStamp)
|
||||||
|
{
|
||||||
|
// Unix timestamp is seconds past epoch
|
||||||
|
System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
|
||||||
|
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||||
|
return dtDateTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,12 @@ namespace PlexRequests.Helpers
|
||||||
$"https://app.plex.tv/web/app#!/server/{machineId}/details/%2Flibrary%2Fmetadata%2F{mediaId}";
|
$"https://app.plex.tv/web/app#!/server/{machineId}/details/%2Flibrary%2Fmetadata%2F{mediaId}";
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string FormatGenres(string tags)
|
||||||
|
{
|
||||||
|
var split = tags.Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
return string.Join(", ", split);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EpisodeModelHelper
|
public class EpisodeModelHelper
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
<Compile Include="TypeHelper.cs" />
|
<Compile Include="TypeHelper.cs" />
|
||||||
<Compile Include="UriHelper.cs" />
|
<Compile Include="UriHelper.cs" />
|
||||||
<Compile Include="UserClaims.cs" />
|
<Compile Include="UserClaims.cs" />
|
||||||
|
<Compile Include="UserType.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
// ************************************************************************/
|
// ************************************************************************/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -64,5 +66,36 @@ namespace PlexRequests.Helpers
|
||||||
}
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> SplitEmailsByDelimiter(this string input, char delimiter)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
yield return string.Empty;
|
||||||
|
}
|
||||||
|
var startIndex = 0;
|
||||||
|
var delimiterIndex = 0;
|
||||||
|
|
||||||
|
while (delimiterIndex >= 0)
|
||||||
|
{
|
||||||
|
delimiterIndex = input.IndexOf(delimiter, startIndex);
|
||||||
|
string substring = input;
|
||||||
|
if (delimiterIndex > 0)
|
||||||
|
{
|
||||||
|
substring = input.Substring(0, delimiterIndex).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!substring.Contains("\"") || substring.IndexOf("\"") != substring.LastIndexOf("\""))
|
||||||
|
{
|
||||||
|
yield return substring;
|
||||||
|
input = input.Substring(delimiterIndex + 1).Trim();
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startIndex = delimiterIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,9 +4,11 @@ namespace PlexRequests.Helpers
|
||||||
{
|
{
|
||||||
public class UserClaims
|
public class UserClaims
|
||||||
{
|
{
|
||||||
public const string Admin = "Admin"; // Can do everything including creating new users and editing settings
|
public const string Admin = nameof(Admin); // Can do everything including creating new users and editing settings
|
||||||
public const string PowerUser = "PowerUser"; // Can only manage the requests, approve etc.
|
public const string PowerUser = nameof(PowerUser); // Can only manage the requests, approve etc.
|
||||||
public const string User = "User"; // Can only request
|
public const string RegularUser = nameof(RegularUser); // Can only request
|
||||||
|
public const string ReadOnlyUser = nameof(ReadOnlyUser); // Can only view stuff
|
||||||
|
public const string Newsletter = nameof(Newsletter); // Has newsletter feature enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
PlexRequests.Helpers/UserType.cs
Normal file
35
PlexRequests.Helpers/UserType.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: UserType.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 PlexRequests.Helpers
|
||||||
|
{
|
||||||
|
|
||||||
|
public enum UserType
|
||||||
|
{
|
||||||
|
PlexUser,
|
||||||
|
LocalUser
|
||||||
|
}
|
||||||
|
}
|
71
PlexRequests.Services/Jobs/HtmlTemplateGenerator.cs
Normal file
71
PlexRequests.Services/Jobs/HtmlTemplateGenerator.cs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Web.UI;
|
||||||
|
|
||||||
|
namespace PlexRequests.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,12 +123,13 @@ namespace PlexRequests.Services.Jobs
|
||||||
case RequestType.TvShow:
|
case RequestType.TvShow:
|
||||||
if (!plexSettings.EnableTvEpisodeSearching)
|
if (!plexSettings.EnableTvEpisodeSearching)
|
||||||
{
|
{
|
||||||
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId);
|
matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
matchResult =
|
matchResult = r.Episodes.Any() ?
|
||||||
r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber));
|
r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber)) :
|
||||||
|
IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RequestType.Album:
|
case RequestType.Album:
|
||||||
|
@ -270,7 +271,7 @@ namespace PlexRequests.Services.Jobs
|
||||||
{
|
{
|
||||||
if (advanced)
|
if (advanced)
|
||||||
{
|
{
|
||||||
if (seasons != null && show.ProviderId == providerId)
|
if (show.ProviderId == providerId && seasons != null)
|
||||||
{
|
{
|
||||||
if (seasons.Any(season => show.Seasons.Contains(season)))
|
if (seasons.Any(season => show.Seasons.Contains(season)))
|
||||||
{
|
{
|
||||||
|
@ -411,6 +412,8 @@ namespace PlexRequests.Services.Jobs
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// TODO what the fuck was I thinking
|
||||||
if (setCache)
|
if (setCache)
|
||||||
{
|
{
|
||||||
results = GetLibraries(plexSettings);
|
results = GetLibraries(plexSettings);
|
||||||
|
|
|
@ -112,6 +112,10 @@ namespace PlexRequests.Services.Jobs
|
||||||
// Loop through the metadata and create the model to insert into the DB
|
// Loop through the metadata and create the model to insert into the DB
|
||||||
foreach (var metadataVideo in metadata.Video)
|
foreach (var metadataVideo in metadata.Video)
|
||||||
{
|
{
|
||||||
|
if(string.IsNullOrEmpty(metadataVideo.GrandparentTitle))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var epInfo = PlexHelper.GetSeasonsAndEpisodesFromPlexGuid(metadataVideo.Guid);
|
var epInfo = PlexHelper.GetSeasonsAndEpisodesFromPlexGuid(metadataVideo.Guid);
|
||||||
entities.TryAdd(
|
entities.TryAdd(
|
||||||
new PlexEpisodes
|
new PlexEpisodes
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#region Copyright
|
#region Copyright
|
||||||
|
|
||||||
// /************************************************************************
|
// /************************************************************************
|
||||||
// Copyright (c) 2016 Jamie Rees
|
// Copyright (c) 2016 Jamie Rees
|
||||||
// File: RecentlyAdded.cs
|
// File: RecentlyAddedModel.cs
|
||||||
// Created By: Jamie Rees
|
// Created By: Jamie Rees
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
// ************************************************************************/
|
// ************************************************************************/
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -40,32 +42,37 @@ using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
using PlexRequests.Services.Interfaces;
|
using PlexRequests.Services.Interfaces;
|
||||||
using PlexRequests.Services.Jobs.Templates;
|
using PlexRequests.Services.Jobs.Templates;
|
||||||
|
using PlexRequests.Store.Models.Plex;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
|
|
||||||
namespace PlexRequests.Services.Jobs
|
namespace PlexRequests.Services.Jobs
|
||||||
{
|
{
|
||||||
public class RecentlyAdded : IJob, IRecentlyAdded
|
public class RecentlyAdded : HtmlTemplateGenerator, IJob, IRecentlyAdded
|
||||||
{
|
{
|
||||||
public RecentlyAdded(IPlexApi api, ISettingsService<PlexSettings> plexSettings, ISettingsService<EmailNotificationSettings> email,
|
public RecentlyAdded(IPlexApi api, ISettingsService<PlexSettings> plexSettings,
|
||||||
ISettingsService<ScheduledJobsSettings> scheduledService, IJobRecord rec, ISettingsService<PlexRequestSettings> plexRequest)
|
ISettingsService<EmailNotificationSettings> email, IJobRecord rec,
|
||||||
|
ISettingsService<NewletterSettings> newsletter,
|
||||||
|
IPlexReadOnlyDatabase db)
|
||||||
{
|
{
|
||||||
JobRecord = rec;
|
JobRecord = rec;
|
||||||
Api = api;
|
Api = api;
|
||||||
PlexSettings = plexSettings;
|
PlexSettings = plexSettings;
|
||||||
EmailSettings = email;
|
EmailSettings = email;
|
||||||
ScheduledJobsSettings = scheduledService;
|
NewsletterSettings = newsletter;
|
||||||
PlexRequestSettings = plexRequest;
|
PlexDb = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IPlexApi Api { get; }
|
private IPlexApi Api { get; }
|
||||||
private TvMazeApi TvApi = new TvMazeApi();
|
private TvMazeApi TvApi = new TvMazeApi();
|
||||||
private readonly TheMovieDbApi _movieApi = new TheMovieDbApi();
|
private readonly TheMovieDbApi _movieApi = new TheMovieDbApi();
|
||||||
|
private const int MetadataTypeTv = 4;
|
||||||
|
private const int MetadataTypeMovie = 1;
|
||||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||||
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
|
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
|
||||||
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
|
private ISettingsService<NewletterSettings> NewsletterSettings { get; }
|
||||||
private ISettingsService<ScheduledJobsSettings> ScheduledJobsSettings { get; }
|
|
||||||
private IJobRecord JobRecord { get; }
|
private IJobRecord JobRecord { get; }
|
||||||
|
private IPlexReadOnlyDatabase PlexDb { get; }
|
||||||
|
|
||||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
@ -73,24 +80,13 @@ namespace PlexRequests.Services.Jobs
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = PlexRequestSettings.GetSettings();
|
var settings = NewsletterSettings.GetSettings();
|
||||||
if (!settings.SendRecentlyAddedEmail)
|
if (!settings.SendRecentlyAddedEmail)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var jobs = JobRecord.GetJobs();
|
|
||||||
var thisJob =
|
|
||||||
jobs.FirstOrDefault(
|
|
||||||
x => x.Name.Equals(JobNames.RecentlyAddedEmail, StringComparison.CurrentCultureIgnoreCase));
|
|
||||||
|
|
||||||
var jobSettings = ScheduledJobsSettings.GetSettings();
|
Start(settings);
|
||||||
|
|
||||||
if (thisJob?.LastRun > DateTime.Now.AddHours(-jobSettings.RecentlyAdded))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Start();
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -104,115 +100,143 @@ namespace PlexRequests.Services.Jobs
|
||||||
|
|
||||||
public void Test()
|
public void Test()
|
||||||
{
|
{
|
||||||
Start(true);
|
var settings = NewsletterSettings.GetSettings();
|
||||||
|
Start(settings, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start(bool testEmail = false)
|
private void Start(NewletterSettings newletterSettings, bool testEmail = false)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var plexSettings = PlexSettings.GetSettings();
|
var plexSettings = PlexSettings.GetSettings();
|
||||||
|
|
||||||
var recentlyAdded = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri);
|
var libs = Api.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri);
|
||||||
|
var tvSection = libs.Directories.FirstOrDefault(x => x.type.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
var movieSection = libs.Directories.FirstOrDefault(x => x.type.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
||||||
var movies =
|
var recentlyAddedTv = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, tvSection.Key);
|
||||||
recentlyAdded._children.Where(x => x.type.Equals("Movie", StringComparison.CurrentCultureIgnoreCase));
|
var recentlyAddedMovies = Api.RecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, movieSection.Key);
|
||||||
var tv =
|
|
||||||
recentlyAdded._children.Where(
|
|
||||||
x => x.type.Equals("season", StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
.GroupBy(x => x.parentTitle)
|
|
||||||
.Select(x => x.FirstOrDefault());
|
|
||||||
|
|
||||||
GenerateMovieHtml(movies, plexSettings, ref sb);
|
GenerateMovieHtml(recentlyAddedMovies, plexSettings, sb);
|
||||||
GenerateTvHtml(tv, plexSettings, ref sb);
|
GenerateTvHtml(recentlyAddedTv, plexSettings, sb);
|
||||||
|
|
||||||
var template = new RecentlyAddedTemplate();
|
var template = new RecentlyAddedTemplate();
|
||||||
var html = template.LoadTemplate(sb.ToString());
|
var html = template.LoadTemplate(sb.ToString());
|
||||||
|
|
||||||
Send(html, plexSettings, testEmail);
|
Send(newletterSettings, html, plexSettings, testEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateMovieHtml(IEnumerable<RecentlyAddedChild> movies, PlexSettings plexSettings, ref StringBuilder sb)
|
private void GenerateMovieHtml(RecentlyAddedModel movies, PlexSettings plexSettings, StringBuilder sb)
|
||||||
{
|
{
|
||||||
sb.Append("<h1>New Movies:</h1><br/><br/>");
|
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%\">");
|
sb.Append(
|
||||||
foreach (var movie in movies)
|
"<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 movies._children.OrderByDescending(x => x.addedAt.UnixTimeStampToDateTime()))
|
||||||
|
{
|
||||||
|
var plexGUID = string.Empty;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
var metaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||||
movie.ratingKey.ToString());
|
movie.ratingKey.ToString());
|
||||||
|
|
||||||
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid);
|
plexGUID = metaData.Video.Guid;
|
||||||
|
|
||||||
|
var imdbId = PlexHelper.GetProviderIdFromPlexGuid(plexGUID);
|
||||||
var info = _movieApi.GetMovieInformation(imdbId).Result;
|
var info = _movieApi.GetMovieInformation(imdbId).Result;
|
||||||
|
|
||||||
sb.Append("<tr>");
|
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/w500{info.BackdropPath}");
|
||||||
sb.Append("<td align=\"center\">");
|
|
||||||
sb.AppendFormat("<img src=\"https://image.tmdb.org/t/p/w500{0}\" width=\"400px\" text-align=\"center\" />", info.BackdropPath);
|
|
||||||
sb.Append("</td>");
|
|
||||||
sb.Append("</tr>");
|
|
||||||
sb.Append("<tr>");
|
|
||||||
sb.Append("<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
|
||||||
|
|
||||||
sb.AppendFormat("<a href=\"https://www.imdb.com/title/{0}/\"><h3 style=\"font-family: sans-serif; font-weight: normal; margin: 0; Margin-bottom: 15px;\">{1} {2}</p></a>",
|
sb.Append("<tr>");
|
||||||
info.ImdbId, info.Title, info.ReleaseDate?.ToString("yyyy") ?? string.Empty);
|
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())
|
if (info.Genres.Any())
|
||||||
{
|
{
|
||||||
sb.AppendFormat(
|
AddParagraph(sb,
|
||||||
"<p style=\"font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;\">Genre: {0}</p>",
|
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
|
||||||
string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray()));
|
|
||||||
}
|
}
|
||||||
sb.AppendFormat("<p style=\"font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;\">{0}</p>", info.Overview);
|
|
||||||
|
|
||||||
sb.Append("<td");
|
AddParagraph(sb, info.Overview);
|
||||||
sb.Append("<hr>");
|
}
|
||||||
sb.Append("<br>");
|
catch (Exception e)
|
||||||
sb.Append("<br>");
|
{
|
||||||
sb.Append("</tr>");
|
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/>");
|
sb.Append("</table><br/><br/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateTvHtml(IEnumerable<RecentlyAddedChild> tv, PlexSettings plexSettings, ref StringBuilder sb)
|
private void GenerateTvHtml(RecentlyAddedModel tv, PlexSettings plexSettings, StringBuilder sb)
|
||||||
{
|
{
|
||||||
// TV
|
// TV
|
||||||
sb.Append("<h1>New Episodes:</h1><br/><br/>");
|
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%\">");
|
sb.Append(
|
||||||
foreach (var t in tv)
|
"<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 tv._children.OrderByDescending(x => x.addedAt.UnixTimeStampToDateTime()))
|
||||||
{
|
{
|
||||||
|
var plexGUID = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
var parentMetaData = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
|
||||||
t.parentRatingKey.ToString());
|
t.parentRatingKey.ToString());
|
||||||
|
|
||||||
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(parentMetaData.Directory.Guid)));
|
plexGUID = parentMetaData.Directory.Guid;
|
||||||
|
|
||||||
|
var info = TvApi.ShowLookupByTheTvDbId(int.Parse(PlexHelper.GetProviderIdFromPlexGuid(plexGUID)));
|
||||||
|
|
||||||
var banner = info.image?.original;
|
var banner = info.image?.original;
|
||||||
if (!string.IsNullOrEmpty(banner))
|
if (!string.IsNullOrEmpty(banner))
|
||||||
{
|
{
|
||||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||||
}
|
}
|
||||||
|
AddImageInsideTable(sb, banner);
|
||||||
|
|
||||||
sb.Append("<tr>");
|
sb.Append("<tr>");
|
||||||
sb.Append("<td align=\"center\">");
|
sb.Append(
|
||||||
sb.AppendFormat("<img src=\"{0}\" width=\"400px\" text-align=\"center\" />", banner);
|
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
||||||
sb.Append("</td>");
|
|
||||||
sb.Append("</tr>");
|
|
||||||
sb.Append("<tr>");
|
|
||||||
sb.Append("<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
|
||||||
|
|
||||||
sb.AppendFormat("<a href=\"https://www.imdb.com/title/{0}/\"><h3 style=\"font-family: sans-serif; font-weight: normal; margin: 0; Margin-bottom: 15px;\">{1} {2}</p></a>",
|
var title = $"{t.grandparentTitle} - {t.title} {t.originallyAvailableAt?.Substring(0, 4)}";
|
||||||
info.externals.imdb, info.name, info.premiered.Substring(0, 4)); // Only the year
|
|
||||||
|
|
||||||
sb.AppendFormat("<p style=\"font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;\">Genre: {0}</p>", string.Join(", ", info.genres.Select(x => x.ToString()).ToArray()));
|
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
||||||
sb.AppendFormat("<p style=\"font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;\">{0}</p>",
|
Header(sb, 3, title);
|
||||||
string.IsNullOrEmpty(parentMetaData.Directory.Summary) ? info.summary : parentMetaData.Directory.Summary); // Episode Summary
|
EndTag(sb, "a");
|
||||||
|
|
||||||
sb.Append("<td");
|
AddParagraph(sb, $"Season: {t.parentIndex}, Episode: {t.index}");
|
||||||
sb.Append("<hr>");
|
if (info.genres.Any())
|
||||||
sb.Append("<br>");
|
{
|
||||||
sb.Append("<br>");
|
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||||
sb.Append("</tr>");
|
}
|
||||||
|
|
||||||
|
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/>");
|
sb.Append("</table><br/><br/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Send(string html, PlexSettings plexSettings, bool testEmail = false)
|
private void Send(NewletterSettings newletterSettings, string html, PlexSettings plexSettings, bool testEmail = false)
|
||||||
{
|
{
|
||||||
var settings = EmailSettings.GetSettings();
|
var settings = EmailSettings.GetSettings();
|
||||||
|
|
||||||
|
@ -229,6 +253,8 @@ namespace PlexRequests.Services.Jobs
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!testEmail)
|
if (!testEmail)
|
||||||
|
{
|
||||||
|
if (newletterSettings.SendToPlexUsers)
|
||||||
{
|
{
|
||||||
var users = Api.GetUsers(plexSettings.PlexAuthToken);
|
var users = Api.GetUsers(plexSettings.PlexAuthToken);
|
||||||
foreach (var user in users.User)
|
foreach (var user in users.User)
|
||||||
|
@ -236,7 +262,16 @@ namespace PlexRequests.Services.Jobs
|
||||||
message.Bcc.Add(new MailboxAddress(user.Username, user.Email));
|
message.Bcc.Add(new MailboxAddress(user.Username, user.Email));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message.Bcc.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender)); // Include the admin
|
|
||||||
|
if (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));
|
message.From.Add(new MailboxAddress(settings.EmailUsername, settings.EmailSender));
|
||||||
try
|
try
|
||||||
|
@ -263,5 +298,15 @@ namespace PlexRequests.Services.Jobs
|
||||||
Log.Error(e);
|
Log.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EndLoopHtml(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<td");
|
||||||
|
sb.Append("<hr>");
|
||||||
|
sb.Append("<br>");
|
||||||
|
sb.Append("<br>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -144,7 +144,7 @@
|
||||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" 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>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<img src="http://i.imgur.com/s4nswSA.png?" width="400px" text-align="center" />
|
<img src="http://i.imgur.com/ROTp8mn.png" text-align="center" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -80,6 +80,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Interfaces\IJobRecord.cs" />
|
<Compile Include="Interfaces\IJobRecord.cs" />
|
||||||
<Compile Include="Interfaces\INotificationEngine.cs" />
|
<Compile Include="Interfaces\INotificationEngine.cs" />
|
||||||
|
<Compile Include="Jobs\HtmlTemplateGenerator.cs" />
|
||||||
<Compile Include="Jobs\IRecentlyAdded.cs" />
|
<Compile Include="Jobs\IRecentlyAdded.cs" />
|
||||||
<Compile Include="Jobs\JobRecord.cs" />
|
<Compile Include="Jobs\JobRecord.cs" />
|
||||||
<Compile Include="Jobs\JobNames.cs" />
|
<Compile Include="Jobs\JobNames.cs" />
|
||||||
|
|
15
PlexRequests.Store/IPlexDatabase.cs
Normal file
15
PlexRequests.Store/IPlexDatabase.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using PlexRequests.Store.Models.Plex;
|
||||||
|
|
||||||
|
namespace PlexRequests.Store
|
||||||
|
{
|
||||||
|
public interface IPlexDatabase
|
||||||
|
{
|
||||||
|
IEnumerable<MetadataItems> GetMetadata();
|
||||||
|
string DbLocation { get; set; }
|
||||||
|
Task<IEnumerable<MetadataItems>> GetMetadataAsync();
|
||||||
|
IEnumerable<MetadataItems> QueryMetadataItems(string query, object param);
|
||||||
|
}
|
||||||
|
}
|
68
PlexRequests.Store/Models/Plex/MetadataItems.cs
Normal file
68
PlexRequests.Store/Models/Plex/MetadataItems.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: MetadataItems.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 Dapper;
|
||||||
|
using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
|
namespace PlexRequests.Store.Models.Plex
|
||||||
|
{
|
||||||
|
[Table("metadata_items")]
|
||||||
|
public class MetadataItems
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int id { get; set; }
|
||||||
|
|
||||||
|
public int library_section_id { get; set; }
|
||||||
|
public int parent_id { get; set; }
|
||||||
|
public int metadata_type { get; set; }
|
||||||
|
public string guid { get; set; }
|
||||||
|
public int media_item_count { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string title_sort { get; set; }
|
||||||
|
public string original_title { get; set; }
|
||||||
|
public string studio { get; set; }
|
||||||
|
public float rating { get; set; }
|
||||||
|
public int rating_count { get; set; }
|
||||||
|
public string tagline { get; set; }
|
||||||
|
public string summary { get; set; }
|
||||||
|
public string trivia { get; set; }
|
||||||
|
public string quotes { get; set; }
|
||||||
|
public string content_rating { get; set; }
|
||||||
|
public int content_rating_age { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
public string tags_genre { get; set; }
|
||||||
|
// SKIP Until Date Times
|
||||||
|
|
||||||
|
public DateTime originally_available_at { get; set; }
|
||||||
|
public DateTime available_at { get; set; }
|
||||||
|
public DateTime expires_at { get; set; }
|
||||||
|
// Skip RefreshedAt and Year
|
||||||
|
public DateTime added_at { get; set; }
|
||||||
|
public string SeriesTitle { get; set; } // Only used in a custom query for the TV Shows
|
||||||
|
}
|
||||||
|
}
|
94
PlexRequests.Store/PlexDatabase.cs
Normal file
94
PlexRequests.Store/PlexDatabase.cs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: PlexDatabase.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.Data;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dapper;
|
||||||
|
using Dapper.Contrib.Extensions;
|
||||||
|
using Mono.Data.Sqlite;
|
||||||
|
using PlexRequests.Store.Models.Plex;
|
||||||
|
|
||||||
|
namespace PlexRequests.Store
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// We should only ever READ, NEVER WRITE!
|
||||||
|
/// </summary>
|
||||||
|
public class PlexDatabase : IPlexDatabase
|
||||||
|
{
|
||||||
|
public PlexDatabase(SqliteFactory provider)
|
||||||
|
{
|
||||||
|
Factory = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqliteFactory Factory { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// https://support.plex.tv/hc/en-us/articles/202915258-Where-is-the-Plex-Media-Server-data-directory-located-
|
||||||
|
/// </summary>
|
||||||
|
public string DbLocation { get; set; }
|
||||||
|
|
||||||
|
private IDbConnection DbConnection()
|
||||||
|
{
|
||||||
|
var fact = Factory.CreateConnection();
|
||||||
|
if (fact == null)
|
||||||
|
{
|
||||||
|
throw new SqliteException("Factory returned null");
|
||||||
|
}
|
||||||
|
fact.ConnectionString = "Data Source=" + DbLocation;
|
||||||
|
return fact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MetadataItems> GetMetadata()
|
||||||
|
{
|
||||||
|
using (var con = DbConnection())
|
||||||
|
{
|
||||||
|
return con.GetAll<MetadataItems>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<MetadataItems>> GetMetadataAsync()
|
||||||
|
{
|
||||||
|
using (var con = DbConnection())
|
||||||
|
{
|
||||||
|
return await con.GetAllAsync<MetadataItems>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MetadataItems> QueryMetadataItems(string query, object param)
|
||||||
|
{
|
||||||
|
using (var con = DbConnection())
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
var data = con.Query<MetadataItems>(query, param);
|
||||||
|
con.Close();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,12 +64,15 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="DbConfiguration.cs" />
|
<Compile Include="DbConfiguration.cs" />
|
||||||
<Compile Include="Entity.cs" />
|
<Compile Include="Entity.cs" />
|
||||||
|
<Compile Include="IPlexDatabase.cs" />
|
||||||
<Compile Include="Models\IssueBlobs.cs" />
|
<Compile Include="Models\IssueBlobs.cs" />
|
||||||
<Compile Include="Models\PlexEpisodes.cs" />
|
<Compile Include="Models\PlexEpisodes.cs" />
|
||||||
<Compile Include="Models\PlexUsers.cs" />
|
<Compile Include="Models\PlexUsers.cs" />
|
||||||
|
<Compile Include="Models\Plex\MetadataItems.cs" />
|
||||||
<Compile Include="Models\ScheduledJobs.cs" />
|
<Compile Include="Models\ScheduledJobs.cs" />
|
||||||
<Compile Include="Models\RequestLimit.cs" />
|
<Compile Include="Models\RequestLimit.cs" />
|
||||||
<Compile Include="Models\UsersToNotify.cs" />
|
<Compile Include="Models\UsersToNotify.cs" />
|
||||||
|
<Compile Include="PlexDatabase.cs" />
|
||||||
<Compile Include="Repository\BaseGenericRepository.cs" />
|
<Compile Include="Repository\BaseGenericRepository.cs" />
|
||||||
<Compile Include="Repository\IRequestRepository.cs" />
|
<Compile Include="Repository\IRequestRepository.cs" />
|
||||||
<Compile Include="Repository\ISettingsRepository.cs" />
|
<Compile Include="Repository\ISettingsRepository.cs" />
|
||||||
|
@ -84,6 +87,7 @@
|
||||||
<Compile Include="Repository\GenericRepository.cs" />
|
<Compile Include="Repository\GenericRepository.cs" />
|
||||||
<Compile Include="RequestedModel.cs" />
|
<Compile Include="RequestedModel.cs" />
|
||||||
<Compile Include="UserEntity.cs" />
|
<Compile Include="UserEntity.cs" />
|
||||||
|
<Compile Include="UserLogins.cs" />
|
||||||
<Compile Include="UsersModel.cs" />
|
<Compile Include="UsersModel.cs" />
|
||||||
<Compile Include="UserRepository.cs" />
|
<Compile Include="UserRepository.cs" />
|
||||||
<Compile Include="Sql.Designer.cs">
|
<Compile Include="Sql.Designer.cs">
|
||||||
|
@ -120,6 +124,7 @@
|
||||||
<Name>PlexRequests.Helpers</Name>
|
<Name>PlexRequests.Helpers</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- 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.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
|
|
@ -27,13 +27,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.SqlClient;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dapper;
|
|
||||||
using Dapper.Contrib.Extensions;
|
using Dapper.Contrib.Extensions;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
using Mono.Data.Sqlite;
|
using Mono.Data.Sqlite;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,15 @@ CREATE TABLE IF NOT EXISTS Users
|
||||||
UserProperties BLOB
|
UserProperties BLOB
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS UserLogins
|
||||||
|
(
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
UserId varchar(50) NOT NULL ,
|
||||||
|
Type INTEGER NOT NULL,
|
||||||
|
LastLoggedIn varchar(100) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS UserLogins_UserId ON UserLogins (UserId);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS GlobalSettings
|
CREATE TABLE IF NOT EXISTS GlobalSettings
|
||||||
(
|
(
|
||||||
|
@ -59,6 +68,12 @@ CREATE TABLE IF NOT EXISTS DBInfo
|
||||||
SchemaVersion INTEGER
|
SchemaVersion INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS VersionInfo
|
||||||
|
(
|
||||||
|
Version INTEGER NOT NULL,
|
||||||
|
Description VARCHAR(100) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS ScheduledJobs
|
CREATE TABLE IF NOT EXISTS ScheduledJobs
|
||||||
(
|
(
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
// ***********************************************************************
|
// ***********************************************************************
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
@ -37,14 +39,14 @@ namespace PlexRequests.Store
|
||||||
/// Creates the tables located in the SqlTables.sql file.
|
/// Creates the tables located in the SqlTables.sql file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="connection">The connection.</param>
|
/// <param name="connection">The connection.</param>
|
||||||
public static void CreateTables(IDbConnection connection)
|
public static void CreateTables(this IDbConnection connection)
|
||||||
{
|
{
|
||||||
connection.Open();
|
connection.Open();
|
||||||
connection.Execute(Sql.SqlTables);
|
connection.Execute(Sql.SqlTables);
|
||||||
connection.Close();
|
connection.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DropTable(IDbConnection con, string tableName)
|
public static void DropTable(this IDbConnection con, string tableName)
|
||||||
{
|
{
|
||||||
using (con)
|
using (con)
|
||||||
{
|
{
|
||||||
|
@ -55,7 +57,7 @@ namespace PlexRequests.Store
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddColumn(IDbConnection connection, string tableName, string alterType, string newColumn, bool isNullable, string dataType)
|
public static void AddColumn(this IDbConnection connection, string tableName, string alterType, string newColumn, bool allowNulls, string dataType)
|
||||||
{
|
{
|
||||||
connection.Open();
|
connection.Open();
|
||||||
var result = connection.Query<TableInfo>($"PRAGMA table_info({tableName});");
|
var result = connection.Query<TableInfo>($"PRAGMA table_info({tableName});");
|
||||||
|
@ -65,7 +67,7 @@ namespace PlexRequests.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = $"ALTER TABLE {tableName} {alterType} {newColumn} {dataType}";
|
var query = $"ALTER TABLE {tableName} {alterType} {newColumn} {dataType}";
|
||||||
if (isNullable)
|
if (!allowNulls)
|
||||||
{
|
{
|
||||||
query = query + " NOT NULL";
|
query = query + " NOT NULL";
|
||||||
}
|
}
|
||||||
|
@ -75,7 +77,7 @@ namespace PlexRequests.Store
|
||||||
connection.Close();
|
connection.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Vacuum(IDbConnection con)
|
public static void Vacuum(this IDbConnection con)
|
||||||
{
|
{
|
||||||
using (con)
|
using (con)
|
||||||
{
|
{
|
||||||
|
@ -109,7 +111,29 @@ namespace PlexRequests.Store
|
||||||
con.Close();
|
con.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<VersionInfo> GetVersionInfo(this IDbConnection con)
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
var result = con.Query<VersionInfo>("SELECT * FROM VersionInfo");
|
||||||
|
con.Close();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddVersionInfo(this IDbConnection con, VersionInfo ver)
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
con.Insert(ver);
|
||||||
|
con.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Table("VersionInfo")]
|
||||||
|
public class VersionInfo
|
||||||
|
{
|
||||||
|
public int Version { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Table("DBInfo")]
|
[Table("DBInfo")]
|
||||||
public class DbInfo
|
public class DbInfo
|
||||||
|
|
41
PlexRequests.Store/UserLogins.cs
Normal file
41
PlexRequests.Store/UserLogins.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: UserLogins.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 Dapper.Contrib.Extensions;
|
||||||
|
using PlexRequests.Helpers;
|
||||||
|
|
||||||
|
namespace PlexRequests.Store
|
||||||
|
{
|
||||||
|
[Table("UserLogins")]
|
||||||
|
public class UserLogins : Entity
|
||||||
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public UserType Type { get; set; }
|
||||||
|
public DateTime LastLoggedIn { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
// ************************************************************************/
|
// ************************************************************************/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using Dapper.Contrib.Extensions;
|
using Dapper.Contrib.Extensions;
|
||||||
|
|
||||||
namespace PlexRequests.Store
|
namespace PlexRequests.Store
|
||||||
|
|
|
@ -63,6 +63,7 @@ namespace PlexRequests.UI.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[Ignore("Needs work")]
|
||||||
public async Task HappyPathSendSeriesToSonarrAllSeason()
|
public async Task HappyPathSendSeriesToSonarrAllSeason()
|
||||||
{
|
{
|
||||||
var seriesResult = new SonarrAddSeries() { title = "ABC"};
|
var seriesResult = new SonarrAddSeries() { title = "ABC"};
|
||||||
|
|
|
@ -179,3 +179,6 @@ button.list-group-item:focus {
|
||||||
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
|
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
|
||||||
border-bottom: 6px solid #333333 !important; }
|
border-bottom: 6px solid #333333 !important; }
|
||||||
|
|
||||||
|
#sidebar-wrapper {
|
||||||
|
background: #252424; }
|
||||||
|
|
||||||
|
|
2
PlexRequests.UI/Content/Themes/plex.min.css
vendored
2
PlexRequests.UI/Content/Themes/plex.min.css
vendored
|
@ -1 +1 @@
|
||||||
.form-control-custom{background-color:#333 !important;}.form-control-custom-disabled{background-color:#252424 !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#df691a;}.scroll-top-wrapper{background-color:#333;}.scroll-top-wrapper:hover{background-color:#df691a;}body{font-family:Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;color:#eee;background-color:#1f1f1f;}.table-striped>tbody>tr:nth-of-type(odd){background-color:#333;}.table-hover>tbody>tr:hover{background-color:#282828;}fieldset{padding:15px;}legend{border-bottom:1px solid #333;}.form-control{color:#fefefe;background-color:#333;}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{margin-left:-0;}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:-15px;}.dropdown-menu{background-color:#282828;}.dropdown-menu .divider{background-color:#333;}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#333;}.input-group-addon{background-color:#333;}.nav>li>a:hover,.nav>li>a:focus{background-color:#df691a;}.nav-tabs>li>a:hover{border-color:#df691a #df691a transparent;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background-color:#df691a;border:1px solid #df691a;}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #df691a;}.navbar-default{background-color:#0a0a0a;}.navbar-default .navbar-brand{color:#df691a;}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#f0ad4e;background-color:#282828;}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background-color:#282828;}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#df691a;color:#fff;}.pagination>li>a,.pagination>li>span{background-color:#282828;}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#333;}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#fefefe;background-color:#333;}.list-group-item{background-color:#282828;}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{background-color:#333;}.input-addon,.input-group-addon{color:#df691a;}.modal-header,.modal-footer{background-color:#282828;}.modal-content{position:relative;background-color:#282828;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:0;}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:300;color:#ebebeb;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#333;border-radius:10px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#333;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #333 !important;}
|
.form-control-custom{background-color:#333 !important;}.form-control-custom-disabled{background-color:#252424 !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#df691a;}.scroll-top-wrapper{background-color:#333;}.scroll-top-wrapper:hover{background-color:#df691a;}body{font-family:Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;color:#eee;background-color:#1f1f1f;}.table-striped>tbody>tr:nth-of-type(odd){background-color:#333;}.table-hover>tbody>tr:hover{background-color:#282828;}fieldset{padding:15px;}legend{border-bottom:1px solid #333;}.form-control{color:#fefefe;background-color:#333;}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{margin-left:-0;}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:-15px;}.dropdown-menu{background-color:#282828;}.dropdown-menu .divider{background-color:#333;}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#333;}.input-group-addon{background-color:#333;}.nav>li>a:hover,.nav>li>a:focus{background-color:#df691a;}.nav-tabs>li>a:hover{border-color:#df691a #df691a transparent;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background-color:#df691a;border:1px solid #df691a;}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #df691a;}.navbar-default{background-color:#0a0a0a;}.navbar-default .navbar-brand{color:#df691a;}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#f0ad4e;background-color:#282828;}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background-color:#282828;}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#df691a;color:#fff;}.pagination>li>a,.pagination>li>span{background-color:#282828;}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#333;}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#fefefe;background-color:#333;}.list-group-item{background-color:#282828;}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{background-color:#333;}.input-addon,.input-group-addon{color:#df691a;}.modal-header,.modal-footer{background-color:#282828;}.modal-content{position:relative;background-color:#282828;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:0;}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:300;color:#ebebeb;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#333;border-radius:10px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#333;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #333 !important;}#sidebar-wrapper{background:#252424;}
|
|
@ -2,7 +2,9 @@
|
||||||
$primary-colour-outline: #ff761b;
|
$primary-colour-outline: #ff761b;
|
||||||
$bg-colour: #333333;
|
$bg-colour: #333333;
|
||||||
$bg-colour-disabled: #252424;
|
$bg-colour-disabled: #252424;
|
||||||
$i: !important;
|
$i:
|
||||||
|
!important
|
||||||
|
;
|
||||||
|
|
||||||
.form-control-custom {
|
.form-control-custom {
|
||||||
background-color: $bg-colour $i;
|
background-color: $bg-colour $i;
|
||||||
|
@ -222,3 +224,7 @@ button.list-group-item:focus {
|
||||||
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
|
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
|
||||||
border-bottom: 6px solid $bg-colour $i;
|
border-bottom: 6px solid $bg-colour $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sidebar-wrapper {
|
||||||
|
background: $bg-colour-disabled;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
(function() {
|
(function() {
|
||||||
module = angular.module('PlexRequests', []);
|
module = angular.module('PlexRequests', []);
|
||||||
|
module.constant("moment", moment);
|
||||||
}());
|
}());
|
|
@ -1,6 +1,6 @@
|
||||||
(function () {
|
(function () {
|
||||||
|
|
||||||
var controller = function ($scope, userManagementService) {
|
var controller = function ($scope, userManagementService, moment) {
|
||||||
|
|
||||||
$scope.user = {}; // The local user
|
$scope.user = {}; // The local user
|
||||||
$scope.users = []; // list of users
|
$scope.users = []; // list of users
|
||||||
|
@ -9,6 +9,7 @@
|
||||||
$scope.selectedUser = {}; // User on the right side
|
$scope.selectedUser = {}; // User on the right side
|
||||||
$scope.selectedClaims = {};
|
$scope.selectedClaims = {};
|
||||||
|
|
||||||
|
$scope.minDate = "0001-01-01T00:00:00.0000000+00:00";
|
||||||
|
|
||||||
$scope.sortType = "username";
|
$scope.sortType = "username";
|
||||||
$scope.sortReverse = false;
|
$scope.sortReverse = false;
|
||||||
|
@ -20,9 +21,19 @@
|
||||||
errorMessage: ""
|
errorMessage: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var open = false;
|
||||||
|
|
||||||
// Select a user to populate on the right side
|
// Select a user to populate on the right side
|
||||||
$scope.selectUser = function (id) {
|
$scope.selectUser = function (id) {
|
||||||
$scope.selectedUser = $scope.users.find(x => x.id === id);
|
var user = $scope.users.filter(function (item) {
|
||||||
|
return item.id === id;
|
||||||
|
});
|
||||||
|
$scope.selectedUser = user[0];
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
$("#wrapper").toggleClass("toggled");
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all users in the system
|
// Get all users in the system
|
||||||
|
@ -51,19 +62,38 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$scope.selectedClaims) {
|
||||||
|
$scope.error.error = true;
|
||||||
|
$scope.error.errorMessage = "Please select a permission";
|
||||||
|
generateNotify($scope.error.errorMessage, 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
userManagementService.addUser($scope.user, $scope.selectedClaims)
|
userManagementService.addUser($scope.user, $scope.selectedClaims)
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
$scope.error.error = true;
|
$scope.error.error = true;
|
||||||
$scope.error.errorMessage = data.message;
|
$scope.error.errorMessage = data.message;
|
||||||
} else {
|
} else {
|
||||||
$scope.users.push(data); // Push the new user into the array to update the DOM
|
$scope.users.push(data.data); // Push the new user into the array to update the DOM
|
||||||
$scope.user = {};
|
$scope.user = {};
|
||||||
$scope.selectedClaims = {};
|
$scope.selectedClaims = {};
|
||||||
|
$scope.claims.forEach(function (entry) {
|
||||||
|
entry.selected = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.hasClaim = function (claim) {
|
||||||
|
var claims = $scope.selectedUser.claimsArray;
|
||||||
|
|
||||||
|
var result = claims.some(function (item) {
|
||||||
|
return item === claim.name;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.$watch('claims|filter:{selected:true}',
|
$scope.$watch('claims|filter:{selected:true}',
|
||||||
function (nv) {
|
function (nv) {
|
||||||
$scope.selectedClaims = nv.map(function (claim) {
|
$scope.selectedClaims = nv.map(function (claim) {
|
||||||
|
@ -74,13 +104,36 @@
|
||||||
|
|
||||||
|
|
||||||
$scope.updateUser = function () {
|
$scope.updateUser = function () {
|
||||||
|
var u = $scope.selectedUser;
|
||||||
|
userManagementService.updateUser(u.id, u.claimsItem, u.alias, u.emailAddress)
|
||||||
|
.then(function (data) {
|
||||||
|
if (data) {
|
||||||
|
$scope.selectedUser = data;
|
||||||
|
return successCallback("Updated User", "success");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.deleteUser = function () {
|
||||||
|
var u = $scope.selectedUser;
|
||||||
|
var result = userManagementService.deleteUser(u.id);
|
||||||
|
|
||||||
|
result.success(function(data) {
|
||||||
|
if (data.result) {
|
||||||
|
removeUser(u.id, true);
|
||||||
|
return successCallback("Deleted User", "success");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
return $('#baseUrl').val();
|
return $('#baseUrl').val();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.formatDate = function (utcDate) {
|
||||||
|
return moment.utc(utcDate).local().format('lll');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// On page load
|
// On page load
|
||||||
$scope.init = function () {
|
$scope.init = function () {
|
||||||
|
@ -88,7 +141,21 @@
|
||||||
$scope.getClaims();
|
$scope.getClaims();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeUser(id, current) {
|
||||||
|
$scope.users = $scope.users.filter(function (user) {
|
||||||
|
return user.id !== id;
|
||||||
|
});
|
||||||
|
if (current) {
|
||||||
|
$scope.selectedUser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService", controller]);
|
function successCallback(message, type) {
|
||||||
|
generateNotify(message, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService","moment", controller]);
|
||||||
}());
|
}());
|
|
@ -24,10 +24,28 @@
|
||||||
return $http.get('/usermanagement/claims');
|
return $http.get('/usermanagement/claims');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updateUser = function (id, claims, alias, email) {
|
||||||
|
return $http({
|
||||||
|
url: '/usermanagement/updateUser',
|
||||||
|
method: "POST",
|
||||||
|
data: { id: id, claims: claims, alias: alias, emailAddress: email }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteUser = function (id) {
|
||||||
|
return $http({
|
||||||
|
url: '/usermanagement/deleteUser',
|
||||||
|
method: "POST",
|
||||||
|
data: { id: id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getUsers: getUsers,
|
getUsers: getUsers,
|
||||||
addUser: addUser,
|
addUser: addUser,
|
||||||
getClaims: getClaims
|
getClaims: getClaims,
|
||||||
|
updateUser: updateUser,
|
||||||
|
deleteUser: deleteUser
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
98
PlexRequests.UI/Content/base.css
vendored
98
PlexRequests.UI/Content/base.css
vendored
|
@ -344,11 +344,95 @@ label {
|
||||||
.img-circle {
|
.img-circle {
|
||||||
border-radius: 50%; }
|
border-radius: 50%; }
|
||||||
|
|
||||||
.user-management-menu {
|
#wrapper {
|
||||||
border-style: ridge;
|
padding-left: 0;
|
||||||
height: 100%;
|
-webkit-transition: all 0.5s ease;
|
||||||
left: 60%;
|
-moz-transition: all 0.5s ease;
|
||||||
position: absolute;
|
-o-transition: all 0.5s ease;
|
||||||
width: 40%;
|
transition: all 0.5s ease; }
|
||||||
top: 7%; }
|
|
||||||
|
#wrapper.toggled {
|
||||||
|
padding-right: 250px; }
|
||||||
|
|
||||||
|
#sidebar-wrapper {
|
||||||
|
z-index: 1000;
|
||||||
|
position: fixed;
|
||||||
|
right: 250px;
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: -250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #4e5d6c;
|
||||||
|
padding-left: 15px;
|
||||||
|
-webkit-transition: all 0.5s ease;
|
||||||
|
-moz-transition: all 0.5s ease;
|
||||||
|
-o-transition: all 0.5s ease;
|
||||||
|
transition: all 0.5s ease; }
|
||||||
|
|
||||||
|
#wrapper.toggled #sidebar-wrapper {
|
||||||
|
width: 500px; }
|
||||||
|
|
||||||
|
#page-content-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
padding: 15px; }
|
||||||
|
|
||||||
|
#wrapper.toggled #page-content-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -250px; }
|
||||||
|
|
||||||
|
/* Sidebar Styles */
|
||||||
|
.sidebar-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none; }
|
||||||
|
|
||||||
|
.sidebar-nav li {
|
||||||
|
text-indent: 20px;
|
||||||
|
line-height: 40px; }
|
||||||
|
|
||||||
|
.sidebar-nav li a {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #999999; }
|
||||||
|
|
||||||
|
.sidebar-nav li a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(255, 255, 255, 0.2); }
|
||||||
|
|
||||||
|
.sidebar-nav li a:active,
|
||||||
|
.sidebar-nav li a:focus {
|
||||||
|
text-decoration: none; }
|
||||||
|
|
||||||
|
.sidebar-nav > .sidebar-brand {
|
||||||
|
height: 65px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 60px; }
|
||||||
|
|
||||||
|
.sidebar-nav > .sidebar-brand a {
|
||||||
|
color: #999999; }
|
||||||
|
|
||||||
|
.sidebar-nav > .sidebar-brand a:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: none; }
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#wrapper {
|
||||||
|
padding-right: 250px; }
|
||||||
|
#wrapper.toggled {
|
||||||
|
padding-right: 0; }
|
||||||
|
#sidebar-wrapper {
|
||||||
|
width: 500px; }
|
||||||
|
#wrapper.toggled #sidebar-wrapper {
|
||||||
|
width: 0; }
|
||||||
|
#page-content-wrapper {
|
||||||
|
padding: 20px;
|
||||||
|
position: relative; }
|
||||||
|
#wrapper.toggled #page-content-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 0; } }
|
||||||
|
|
||||||
|
|
2
PlexRequests.UI/Content/base.min.css
vendored
2
PlexRequests.UI/Content/base.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -434,11 +434,121 @@ $border-radius: 10px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-management-menu {
|
#wrapper {
|
||||||
border-style: ridge;
|
padding-left: 0;
|
||||||
height: 100%;
|
-webkit-transition: all 0.5s ease;
|
||||||
left: 60%;
|
-moz-transition: all 0.5s ease;
|
||||||
position: absolute;
|
-o-transition: all 0.5s ease;
|
||||||
width: 40%;
|
transition: all 0.5s ease;
|
||||||
top: 7%;
|
}
|
||||||
|
|
||||||
|
#wrapper.toggled {
|
||||||
|
padding-right: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-wrapper {
|
||||||
|
z-index: 1000;
|
||||||
|
position: fixed;
|
||||||
|
right: 250px;
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: -250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #4e5d6c;
|
||||||
|
padding-left:15px;
|
||||||
|
-webkit-transition: all 0.5s ease;
|
||||||
|
-moz-transition: all 0.5s ease;
|
||||||
|
-o-transition: all 0.5s ease;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper.toggled #sidebar-wrapper {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-content-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper.toggled #page-content-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Styles */
|
||||||
|
|
||||||
|
.sidebar-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav li {
|
||||||
|
text-indent: 20px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav li a {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav li a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav li a:active,
|
||||||
|
.sidebar-nav li a:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > .sidebar-brand {
|
||||||
|
height: 65px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > .sidebar-brand a {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > .sidebar-brand a:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width:768px) {
|
||||||
|
#wrapper {
|
||||||
|
padding-right: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper.toggled {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-wrapper {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper.toggled #sidebar-wrapper {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-content-wrapper {
|
||||||
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper.toggled #page-content-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
22
PlexRequests.UI/Content/bootstrap-switch.min.css
vendored
Normal file
22
PlexRequests.UI/Content/bootstrap-switch.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
22
PlexRequests.UI/Content/bootstrap-switch.min.js
vendored
Normal file
22
PlexRequests.UI/Content/bootstrap-switch.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PlexRequests.UI/Content/requests.js
vendored
1
PlexRequests.UI/Content/requests.js
vendored
|
@ -5,6 +5,7 @@
|
||||||
return !opts ? null : opts.inverse(this);
|
return !opts ? null : opts.inverse(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var searchSource = $("#search-template").html();
|
var searchSource = $("#search-template").html();
|
||||||
var albumSource = $("#album-template").html();
|
var albumSource = $("#album-template").html();
|
||||||
var searchTemplate = Handlebars.compile(searchSource);
|
var searchTemplate = Handlebars.compile(searchSource);
|
||||||
|
|
|
@ -133,14 +133,6 @@ namespace PlexRequests.UI.Helpers
|
||||||
var assetLocation = GetBaseUrl();
|
var assetLocation = GetBaseUrl();
|
||||||
|
|
||||||
var content = GetContentUrl(assetLocation);
|
var content = GetContentUrl(assetLocation);
|
||||||
var settings = GetSettings();
|
|
||||||
if (string.IsNullOrEmpty(settings.ThemeName))
|
|
||||||
{
|
|
||||||
settings.ThemeName = Themes.PlexTheme;
|
|
||||||
}
|
|
||||||
if (settings.ThemeName == "PlexBootstrap.css") settings.ThemeName = Themes.PlexTheme;
|
|
||||||
if (settings.ThemeName == "OriginalBootstrap.css") settings.ThemeName = Themes.OriginalTheme;
|
|
||||||
|
|
||||||
var startUrl = $"{content}/Content";
|
var startUrl = $"{content}/Content";
|
||||||
|
|
||||||
sb.AppendLine($"<script src=\"{startUrl}/angular.min.js\"></script>"); // Load angular first
|
sb.AppendLine($"<script src=\"{startUrl}/angular.min.js\"></script>"); // Load angular first
|
||||||
|
@ -161,6 +153,20 @@ namespace PlexRequests.UI.Helpers
|
||||||
return helper.Raw(sb.ToString());
|
return helper.Raw(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IHtmlString LoadSettingsAssets(this HtmlHelpers helper)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var assetLocation = GetBaseUrl();
|
||||||
|
|
||||||
|
var content = GetContentUrl(assetLocation);
|
||||||
|
|
||||||
|
sb.AppendLine($"<script src=\"{content}/Content/bootstrap-switch.min.js\" type=\"text/javascript\"></script>");
|
||||||
|
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/bootstrap-switch.min.css\" type=\"text/css\"/>");
|
||||||
|
|
||||||
|
return helper.Raw(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static IHtmlString LoadRequestAssets(this HtmlHelpers helper)
|
public static IHtmlString LoadRequestAssets(this HtmlHelpers helper)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
@ -212,12 +218,14 @@ namespace PlexRequests.UI.Helpers
|
||||||
{
|
{
|
||||||
var assetLocation = GetBaseUrl();
|
var assetLocation = GetBaseUrl();
|
||||||
var content = GetContentUrl(assetLocation);
|
var content = GetContentUrl(assetLocation);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
var controller = $"<script src=\"{content}/Content/app/userManagement/userManagementController.js?v={Assembly}\" type=\"text/javascript\"></script>";
|
sb.Append($"<script src=\"{content}/Content/app/userManagement/userManagementController.js?v={Assembly}\" type=\"text/javascript\"></script>");
|
||||||
controller += $"<script src=\"{content}/Content/app/userManagement/userManagementService.js?v={Assembly}\" type=\"text/javascript\"></script>";
|
sb.Append($"<script src=\"{content}/Content/app/userManagement/userManagementService.js?v={Assembly}\" type=\"text/javascript\"></script>");
|
||||||
|
sb.Append($"<script src=\"{content}/Content/moment.min.js\"></script>");
|
||||||
|
|
||||||
|
|
||||||
return helper.Raw(controller);
|
return helper.Raw(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
49
PlexRequests.UI/Helpers/HtmlSecurityHelper.cs
Normal file
49
PlexRequests.UI/Helpers/HtmlSecurityHelper.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: HtmlSecurityHelper.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 Nancy.Security;
|
||||||
|
using Nancy.ViewEngines.Razor;
|
||||||
|
|
||||||
|
namespace PlexRequests.UI.Helpers
|
||||||
|
{
|
||||||
|
public static class HtmlSecurityHelper
|
||||||
|
{
|
||||||
|
public static bool HasAnyPermission(this HtmlHelpers helper, params string[] claims)
|
||||||
|
{
|
||||||
|
if (!helper.CurrentUser.IsAuthenticated())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return helper.CurrentUser.HasAnyClaim(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DoesNotHaveAnyPermission(this HtmlHelpers helper, params string[] claims)
|
||||||
|
{
|
||||||
|
return SecurityExtensions.DoesNotHaveClaims(claims, helper.CurrentUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
PlexRequests.UI/Helpers/SecurityExtensions.cs
Normal file
170
PlexRequests.UI/Helpers/SecurityExtensions.cs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: SecurityExtensions.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 Nancy;
|
||||||
|
using Nancy.Extensions;
|
||||||
|
using Nancy.Security;
|
||||||
|
using PlexRequests.UI.Models;
|
||||||
|
|
||||||
|
namespace PlexRequests.UI.Helpers
|
||||||
|
{
|
||||||
|
public static class SecurityExtensions
|
||||||
|
{
|
||||||
|
|
||||||
|
public static bool IsLoggedIn(this NancyContext context)
|
||||||
|
{
|
||||||
|
var userName = context.Request.Session[SessionKeys.UsernameKey];
|
||||||
|
var realUser = false;
|
||||||
|
var plexUser = userName != null;
|
||||||
|
|
||||||
|
if (context.CurrentUser?.IsAuthenticated() ?? false)
|
||||||
|
{
|
||||||
|
realUser = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realUser || plexUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsPlexUser(this NancyContext context)
|
||||||
|
{
|
||||||
|
var userName = context.Request.Session[SessionKeys.UsernameKey];
|
||||||
|
var plexUser = userName != null;
|
||||||
|
|
||||||
|
var isAuth = context.CurrentUser?.IsAuthenticated() ?? false;
|
||||||
|
|
||||||
|
return plexUser && !isAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsNormalUser(this NancyContext context)
|
||||||
|
{
|
||||||
|
var userName = context.Request.Session[SessionKeys.UsernameKey];
|
||||||
|
var plexUser = userName != null;
|
||||||
|
|
||||||
|
var isAuth = context.CurrentUser?.IsAuthenticated() ?? false;
|
||||||
|
|
||||||
|
return isAuth && !plexUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This module requires authentication and NO certain claims to be present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="module">Module to enable</param>
|
||||||
|
/// <param name="requiredClaims">Claim(s) required</param>
|
||||||
|
public static void DoesNotHaveClaim(this INancyModule module, params string[] bannedClaims)
|
||||||
|
{
|
||||||
|
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication");
|
||||||
|
module.AddBeforeHookOrExecute(DoesNotHaveClaims(bannedClaims), "Has Banned Claims");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DoesNotHaveClaimCheck(this INancyModule module, params string[] bannedClaims)
|
||||||
|
{
|
||||||
|
if (!module.Context?.CurrentUser?.IsAuthenticated() ?? false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (DoesNotHaveClaims(bannedClaims, module.Context))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DoesNotHaveClaimCheck(this NancyContext context, params string[] bannedClaims)
|
||||||
|
{
|
||||||
|
if (!context?.CurrentUser?.IsAuthenticated() ?? false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (DoesNotHaveClaims(bannedClaims, context))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a hook to be used in a pipeline before a route handler to ensure
|
||||||
|
/// that the request was made by an authenticated user does not have the claims.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="claims">Claims the authenticated user needs to have</param>
|
||||||
|
/// <returns>Hook that returns an Unauthorized response if the user is not
|
||||||
|
/// authenticated or does have the claims, null otherwise</returns>
|
||||||
|
private static Func<NancyContext, Response> DoesNotHaveClaims(IEnumerable<string> claims)
|
||||||
|
{
|
||||||
|
return ForbiddenIfNot(ctx => !ctx.CurrentUser.HasAnyClaim(claims));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DoesNotHaveClaims(IEnumerable<string> claims, NancyContext ctx)
|
||||||
|
{
|
||||||
|
return !ctx.CurrentUser.HasAnyClaim(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DoesNotHaveClaims(IEnumerable<string> claims, IUserIdentity identity)
|
||||||
|
{
|
||||||
|
return !identity?.HasAnyClaim(claims) ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// BELOW IS A COPY FROM THE SecurityHooks CLASS!
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a hook to be used in a pipeline before a route handler to ensure that
|
||||||
|
/// the request satisfies a specific test.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="test">Test that must return true for the request to continue</param>
|
||||||
|
/// <returns>Hook that returns an Forbidden response if the test fails, null otherwise</returns>
|
||||||
|
private static Func<NancyContext, Response> ForbiddenIfNot(Func<NancyContext, bool> test)
|
||||||
|
{
|
||||||
|
return HttpStatusCodeIfNot(HttpStatusCode.Forbidden, test);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a hook to be used in a pipeline before a route handler to ensure that
|
||||||
|
/// the request satisfies a specific test.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="statusCode">HttpStatusCode to use for the response</param>
|
||||||
|
/// <param name="test">Test that must return true for the request to continue</param>
|
||||||
|
/// <returns>Hook that returns a response with a specific HttpStatusCode if the test fails, null otherwise</returns>
|
||||||
|
private static Func<NancyContext, Response> HttpStatusCodeIfNot(HttpStatusCode statusCode, Func<NancyContext, bool> test)
|
||||||
|
{
|
||||||
|
return ctx =>
|
||||||
|
{
|
||||||
|
Response response = null;
|
||||||
|
if (!test(ctx))
|
||||||
|
response = new Response
|
||||||
|
{
|
||||||
|
StatusCode = statusCode
|
||||||
|
};
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,8 +26,6 @@
|
||||||
#endregion
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
using PlexRequests.Api.Models.SickRage;
|
using PlexRequests.Api.Models.SickRage;
|
||||||
|
@ -35,11 +33,8 @@ using PlexRequests.Api.Models.Sonarr;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Store;
|
using PlexRequests.Store;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using PlexRequests.Helpers.Exceptions;
|
|
||||||
|
|
||||||
namespace PlexRequests.UI.Helpers
|
namespace PlexRequests.UI.Helpers
|
||||||
{
|
{
|
||||||
public class TvSender
|
public class TvSender
|
||||||
|
@ -58,6 +53,13 @@ namespace PlexRequests.UI.Helpers
|
||||||
return await SendToSonarr(sonarrSettings, model, string.Empty);
|
return await SendToSonarr(sonarrSettings, model, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broken Way
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sonarrSettings"></param>
|
||||||
|
/// <param name="model"></param>
|
||||||
|
/// <param name="qualityId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<SonarrAddSeries> SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId)
|
public async Task<SonarrAddSeries> SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId)
|
||||||
{
|
{
|
||||||
var qualityProfile = 0;
|
var qualityProfile = 0;
|
||||||
|
@ -121,13 +123,21 @@ namespace PlexRequests.UI.Helpers
|
||||||
if (series == null)
|
if (series == null)
|
||||||
{
|
{
|
||||||
// Set the series as monitored with a season count as 0 so it doesn't search for anything
|
// Set the series as monitored with a season count as 0 so it doesn't search for anything
|
||||||
SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
|
SonarrApi.AddSeriesNew(model.ProviderId, model.Title, qualityProfile,
|
||||||
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, model.SeasonList, sonarrSettings.ApiKey,
|
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13}, sonarrSettings.ApiKey,
|
||||||
sonarrSettings.FullUri);
|
sonarrSettings.FullUri);
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
series = await GetSonarrSeries(sonarrSettings, model.ProviderId);
|
series = await GetSonarrSeries(sonarrSettings, model.ProviderId);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var s in series.seasons)
|
||||||
|
{
|
||||||
|
s.monitored = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestAll ?? false)
|
if (requestAll ?? false)
|
||||||
|
@ -138,11 +148,21 @@ namespace PlexRequests.UI.Helpers
|
||||||
season.monitored = true;
|
season.monitored = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
SonarrApi.SearchForSeries(series.id, sonarrSettings.ApiKey, sonarrSettings.FullUri); // Search For all episodes!"
|
SonarrApi.SearchForSeries(series.id, sonarrSettings.ApiKey, sonarrSettings.FullUri); // Search For all episodes!"
|
||||||
|
|
||||||
|
|
||||||
|
//// This is a work around for this issue: https://github.com/Sonarr/Sonarr/issues/1507
|
||||||
|
//// The above is the previous code.
|
||||||
|
//SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
|
||||||
|
// sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, model.SeasonList, sonarrSettings.ApiKey,
|
||||||
|
// sonarrSettings.FullUri, true, true);
|
||||||
return new SonarrAddSeries { title = series.title }; // We have updated it
|
return new SonarrAddSeries { title = series.title }; // We have updated it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (first ?? false)
|
if (first ?? false)
|
||||||
{
|
{
|
||||||
var firstSeries = (series?.seasons?.OrderBy(x => x.seasonNumber)).FirstOrDefault(x => x.seasonNumber > 0) ?? new Season();
|
var firstSeries = (series?.seasons?.OrderBy(x => x.seasonNumber)).FirstOrDefault(x => x.seasonNumber > 0) ?? new Season();
|
||||||
|
|
302
PlexRequests.UI/Helpers/TvSenderOld.cs
Normal file
302
PlexRequests.UI/Helpers/TvSenderOld.cs
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: TvSenderOld.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 PlexRequests.Api.Interfaces;
|
||||||
|
using PlexRequests.Api.Models.SickRage;
|
||||||
|
using PlexRequests.Api.Models.Sonarr;
|
||||||
|
using PlexRequests.Core.SettingModels;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
|
||||||
|
namespace PlexRequests.UI.Helpers
|
||||||
|
{
|
||||||
|
public class TvSenderOld
|
||||||
|
{
|
||||||
|
public TvSenderOld(ISonarrApi sonarrApi, ISickRageApi srApi)
|
||||||
|
{
|
||||||
|
SonarrApi = sonarrApi;
|
||||||
|
SickrageApi = srApi;
|
||||||
|
}
|
||||||
|
private ISonarrApi SonarrApi { get; }
|
||||||
|
private ISickRageApi SickrageApi { get; }
|
||||||
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public async Task<SonarrAddSeries> SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model)
|
||||||
|
{
|
||||||
|
return await SendToSonarr(sonarrSettings, model, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SonarrAddSeries> SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId)
|
||||||
|
{
|
||||||
|
var qualityProfile = 0;
|
||||||
|
var episodeRequest = model.Episodes.Any();
|
||||||
|
if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
|
||||||
|
{
|
||||||
|
int.TryParse(qualityId, out qualityProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualityProfile <= 0)
|
||||||
|
{
|
||||||
|
int.TryParse(sonarrSettings.QualityProfile, out qualityProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
var series = await GetSonarrSeries(sonarrSettings, model.ProviderId);
|
||||||
|
|
||||||
|
if (episodeRequest)
|
||||||
|
{
|
||||||
|
// Does series exist?
|
||||||
|
if (series != null)
|
||||||
|
{
|
||||||
|
// Series Exists
|
||||||
|
// Request the episodes in the existing series
|
||||||
|
await RequestEpisodesWithExistingSeries(model, series, sonarrSettings);
|
||||||
|
return new SonarrAddSeries { title = series.title };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Series doesn't exist, need to add it as unmonitored.
|
||||||
|
var addResult = await Task.Run(() => SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
|
||||||
|
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, new int[0], sonarrSettings.ApiKey,
|
||||||
|
sonarrSettings.FullUri, false));
|
||||||
|
|
||||||
|
|
||||||
|
// Get the series that was just added
|
||||||
|
series = await GetSonarrSeries(sonarrSettings, model.ProviderId);
|
||||||
|
series.monitored = true; // We want to make sure we are monitoring the series
|
||||||
|
|
||||||
|
// Un-monitor all seasons
|
||||||
|
foreach (var season in series.seasons)
|
||||||
|
{
|
||||||
|
season.monitored = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the series, Since we cannot add as un-monitored due to the following bug: https://github.com/Sonarr/Sonarr/issues/1404
|
||||||
|
SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
|
||||||
|
|
||||||
|
// We now have the series in Sonarr, update it to request the episodes.
|
||||||
|
await RequestAllEpisodesInASeasonWithExistingSeries(model, series, sonarrSettings);
|
||||||
|
|
||||||
|
return addResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series != null)
|
||||||
|
{
|
||||||
|
var requestAll = model.SeasonsRequested.Equals("All", StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
var first = model.SeasonsRequested.Equals("First", StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
var latest = model.SeasonsRequested.Equals("Latest", StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
|
||||||
|
if (model.SeasonList.Any())
|
||||||
|
{
|
||||||
|
// Monitor the seasons that we have chosen
|
||||||
|
foreach (var season in series.seasons)
|
||||||
|
{
|
||||||
|
if (model.SeasonList.Contains(season.seasonNumber))
|
||||||
|
{
|
||||||
|
season.monitored = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestAll)
|
||||||
|
{
|
||||||
|
// Monitor all seasons
|
||||||
|
foreach (var season in series.seasons)
|
||||||
|
{
|
||||||
|
season.monitored = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
var firstSeries = series?.seasons?.OrderBy(x => x.seasonNumber)?.FirstOrDefault() ?? new Season();
|
||||||
|
firstSeries.monitored = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latest)
|
||||||
|
{
|
||||||
|
var lastSeries = series?.seasons?.OrderByDescending(x => x.seasonNumber)?.FirstOrDefault() ?? new Season();
|
||||||
|
lastSeries.monitored = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Update the series in sonarr with the new monitored status
|
||||||
|
SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
await RequestAllEpisodesInASeasonWithExistingSeries(model, series, sonarrSettings);
|
||||||
|
return new SonarrAddSeries { title = series.title }; // We have updated it
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
|
||||||
|
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey,
|
||||||
|
sonarrSettings.FullUri, true, true);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model)
|
||||||
|
{
|
||||||
|
return SendToSickRage(sickRageSettings, model, sickRageSettings.QualityProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model, string qualityId)
|
||||||
|
{
|
||||||
|
Log.Info("Sending to SickRage {0}", model.Title);
|
||||||
|
if (sickRageSettings.Qualities.All(x => x.Key != qualityId))
|
||||||
|
{
|
||||||
|
qualityId = sickRageSettings.QualityProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResult = SickrageApi.AddSeries(model.ProviderId, model.SeasonCount, model.SeasonList, qualityId,
|
||||||
|
sickRageSettings.ApiKey, sickRageSettings.FullUri);
|
||||||
|
|
||||||
|
var result = apiResult.Result;
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task RequestEpisodesWithExistingSeries(RequestedModel model, Series selectedSeries, SonarrSettings sonarrSettings)
|
||||||
|
{
|
||||||
|
// Show Exists
|
||||||
|
// Look up all episodes
|
||||||
|
var ep = SonarrApi.GetEpisodes(selectedSeries.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
var episodes = ep?.ToList() ?? new List<SonarrEpisodes>();
|
||||||
|
|
||||||
|
var internalEpisodeIds = new List<int>();
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
foreach (var r in model.Episodes)
|
||||||
|
{
|
||||||
|
// Match the episode and season number.
|
||||||
|
// If the episode is monitored we might not be searching for it.
|
||||||
|
var episode =
|
||||||
|
episodes.FirstOrDefault(
|
||||||
|
x => x.episodeNumber == r.EpisodeNumber && x.seasonNumber == r.SeasonNumber);
|
||||||
|
if (episode == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var episodeInfo = SonarrApi.GetEpisode(episode.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
episodeInfo.monitored = true; // Set the episode to monitored
|
||||||
|
tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey,
|
||||||
|
sonarrSettings.FullUri)));
|
||||||
|
internalEpisodeIds.Add(episode.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks.ToArray());
|
||||||
|
|
||||||
|
SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task RequestAllEpisodesWithExistingSeries(RequestedModel model, Series selectedSeries, SonarrSettings sonarrSettings)
|
||||||
|
{
|
||||||
|
// Show Exists
|
||||||
|
// Look up all episodes
|
||||||
|
var ep = SonarrApi.GetEpisodes(selectedSeries.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
var episodes = ep?.ToList() ?? new List<SonarrEpisodes>();
|
||||||
|
|
||||||
|
var internalEpisodeIds = new List<int>();
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
foreach (var r in episodes)
|
||||||
|
{
|
||||||
|
if (r.monitored || r.hasFile) // If it's already montiored or has the file, there is no point in updating it
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the individual episode details
|
||||||
|
var episodeInfo = SonarrApi.GetEpisode(r.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
episodeInfo.monitored = true; // Set the episode to monitored
|
||||||
|
|
||||||
|
tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey,
|
||||||
|
sonarrSettings.FullUri)));
|
||||||
|
internalEpisodeIds.Add(r.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks.ToArray());
|
||||||
|
|
||||||
|
SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal async Task RequestAllEpisodesInASeasonWithExistingSeries(RequestedModel model, Series selectedSeries, SonarrSettings sonarrSettings)
|
||||||
|
{
|
||||||
|
// Show Exists
|
||||||
|
// Look up all episodes
|
||||||
|
var ep = SonarrApi.GetEpisodes(selectedSeries.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
var episodes = ep?.ToList() ?? new List<SonarrEpisodes>();
|
||||||
|
|
||||||
|
var internalEpisodeIds = new List<int>();
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
var requestedEpisodes = model.Episodes;
|
||||||
|
|
||||||
|
foreach (var r in episodes)
|
||||||
|
{
|
||||||
|
if (r.hasFile) // If it already has the file, there is no point in updating it
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var epComparison = new EpisodesModel
|
||||||
|
{
|
||||||
|
EpisodeNumber = r.episodeNumber,
|
||||||
|
SeasonNumber = r.seasonNumber
|
||||||
|
};
|
||||||
|
// Make sure we are looking for the right episode and season
|
||||||
|
if (!requestedEpisodes.Contains(epComparison))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the individual episode details
|
||||||
|
var episodeInfo = SonarrApi.GetEpisode(r.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
// If the season is not in thr
|
||||||
|
|
||||||
|
episodeInfo.monitored = true; // Set the episode to monitored
|
||||||
|
|
||||||
|
tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey,
|
||||||
|
sonarrSettings.FullUri)));
|
||||||
|
internalEpisodeIds.Add(r.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks.ToArray());
|
||||||
|
|
||||||
|
SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
}
|
||||||
|
private async Task<Series> GetSonarrSeries(SonarrSettings sonarrSettings, int showId)
|
||||||
|
{
|
||||||
|
var task = await Task.Run(() => SonarrApi.GetSeries(sonarrSettings.ApiKey, sonarrSettings.FullUri)).ConfigureAwait(false);
|
||||||
|
var selectedSeries = task.FirstOrDefault(series => series.tvdbId == showId);
|
||||||
|
|
||||||
|
return selectedSeries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,9 @@ namespace PlexRequests.UI.Jobs
|
||||||
|
|
||||||
private IEnumerable<IJobDetail> CreateJobs()
|
private IEnumerable<IJobDetail> CreateJobs()
|
||||||
{
|
{
|
||||||
|
var settingsService = Service.Resolve<ISettingsService<ScheduledJobsSettings>>();
|
||||||
|
var s = settingsService.GetSettings();
|
||||||
|
|
||||||
var jobs = new List<IJobDetail>();
|
var jobs = new List<IJobDetail>();
|
||||||
|
|
||||||
var jobList = new List<IJobDetail>
|
var jobList = new List<IJobDetail>
|
||||||
|
@ -66,9 +69,13 @@ namespace PlexRequests.UI.Jobs
|
||||||
JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build(),
|
JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build(),
|
||||||
JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build(),
|
JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build(),
|
||||||
JobBuilder.Create<UserRequestLimitResetter>().WithIdentity("UserRequestLimiter", "Request").Build(),
|
JobBuilder.Create<UserRequestLimitResetter>().WithIdentity("UserRequestLimiter", "Request").Build(),
|
||||||
JobBuilder.Create<RecentlyAdded>().WithIdentity("RecentlyAdded", "Email").Build()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(s.RecentlyAddedCron))
|
||||||
|
{
|
||||||
|
jobList.Add(JobBuilder.Create<RecentlyAdded>().WithIdentity("RecentlyAddedModel", "Email").Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
jobs.AddRange(jobList);
|
jobs.AddRange(jobList);
|
||||||
|
|
||||||
|
@ -155,24 +162,34 @@ namespace PlexRequests.UI.Jobs
|
||||||
var userRequestLimiter =
|
var userRequestLimiter =
|
||||||
TriggerBuilder.Create()
|
TriggerBuilder.Create()
|
||||||
.WithIdentity("UserRequestLimiter", "Request")
|
.WithIdentity("UserRequestLimiter", "Request")
|
||||||
.StartAt(DateTimeOffset.Now.AddMinutes(5)) // Everything has started on application start, lets wait 5 minutes
|
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute))
|
||||||
|
// Everything has started on application start, lets wait 5 minutes
|
||||||
.WithSimpleSchedule(x => x.WithIntervalInHours(s.UserRequestLimitResetter).RepeatForever())
|
.WithSimpleSchedule(x => x.WithIntervalInHours(s.UserRequestLimitResetter).RepeatForever())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var plexEpCacher =
|
var plexEpCacher =
|
||||||
TriggerBuilder.Create()
|
TriggerBuilder.Create()
|
||||||
.WithIdentity("PlexEpisodeCacher", "Cache")
|
.WithIdentity("PlexEpisodeCacher", "Cache")
|
||||||
.StartAt(DateTimeOffset.Now.AddMinutes(5))
|
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute))
|
||||||
.WithSimpleSchedule(x => x.WithIntervalInHours(s.PlexEpisodeCacher).RepeatForever())
|
.WithSimpleSchedule(x => x.WithIntervalInHours(s.PlexEpisodeCacher).RepeatForever())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
|
var cronJob = string.IsNullOrEmpty(s.RecentlyAddedCron);
|
||||||
|
if (!cronJob)
|
||||||
|
{
|
||||||
var rencentlyAdded =
|
var rencentlyAdded =
|
||||||
TriggerBuilder.Create()
|
TriggerBuilder.Create()
|
||||||
.WithIdentity("RecentlyAdded", "Email")
|
.WithIdentity("RecentlyAddedModel", "Email")
|
||||||
.StartNow()
|
.StartNow()
|
||||||
|
.WithCronSchedule(s.RecentlyAddedCron)
|
||||||
.WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever())
|
.WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
triggers.Add(rencentlyAdded);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
triggers.Add(plexAvailabilityChecker);
|
triggers.Add(plexAvailabilityChecker);
|
||||||
triggers.Add(srCacher);
|
triggers.Add(srCacher);
|
||||||
|
@ -182,7 +199,6 @@ namespace PlexRequests.UI.Jobs
|
||||||
triggers.Add(storeCleanup);
|
triggers.Add(storeCleanup);
|
||||||
triggers.Add(userRequestLimiter);
|
triggers.Add(userRequestLimiter);
|
||||||
triggers.Add(plexEpCacher);
|
triggers.Add(plexEpCacher);
|
||||||
triggers.Add(rencentlyAdded);
|
|
||||||
|
|
||||||
return triggers;
|
return triggers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,5 +46,6 @@ namespace PlexRequests.UI.Models
|
||||||
public bool Video { get; set; }
|
public bool Video { get; set; }
|
||||||
public double VoteAverage { get; set; }
|
public double VoteAverage { get; set; }
|
||||||
public int VoteCount { get; set; }
|
public int VoteCount { get; set; }
|
||||||
|
public bool AlreadyInCp { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
33
PlexRequests.UI/Models/UserManagement/DeleteUserViewModel.cs
Normal file
33
PlexRequests.UI/Models/UserManagement/DeleteUserViewModel.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2016 Jamie Rees
|
||||||
|
// File: DeleteUserViewModel.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 PlexRequests.UI.Models
|
||||||
|
{
|
||||||
|
public class DeleteUserViewModel
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using PlexRequests.Helpers;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Models
|
namespace PlexRequests.UI.Models
|
||||||
{
|
{
|
||||||
|
@ -17,6 +19,8 @@ namespace PlexRequests.UI.Models
|
||||||
public string EmailAddress { get; set; }
|
public string EmailAddress { get; set; }
|
||||||
public UserManagementPlexInformation PlexInfo { get; set; }
|
public UserManagementPlexInformation PlexInfo { get; set; }
|
||||||
public string[] ClaimsArray { get; set; }
|
public string[] ClaimsArray { get; set; }
|
||||||
|
public List<UserManagementUpdateModel.ClaimsModel> ClaimsItem { get; set; }
|
||||||
|
public DateTime LastLoggedIn { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UserManagementPlexInformation
|
public class UserManagementPlexInformation
|
||||||
|
@ -39,11 +43,6 @@ namespace PlexRequests.UI.Models
|
||||||
public string NumLibraries { get; set; }
|
public string NumLibraries { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum UserType
|
|
||||||
{
|
|
||||||
PlexUser,
|
|
||||||
LocalUser
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserManagementCreateModel
|
public class UserManagementCreateModel
|
||||||
{
|
{
|
||||||
|
@ -57,5 +56,25 @@ namespace PlexRequests.UI.Models
|
||||||
[JsonProperty("email")]
|
[JsonProperty("email")]
|
||||||
public string EmailAddress { get; set; }
|
public string EmailAddress { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UserManagementUpdateModel
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("claims")]
|
||||||
|
public List<ClaimsModel> Claims { get; set; }
|
||||||
|
|
||||||
|
public string Alias { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
|
||||||
|
public class ClaimsModel
|
||||||
|
{
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonProperty("selected")]
|
||||||
|
public bool Selected { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ using PlexRequests.Store.Models;
|
||||||
using PlexRequests.Store.Repository;
|
using PlexRequests.Store.Repository;
|
||||||
using PlexRequests.UI.Helpers;
|
using PlexRequests.UI.Helpers;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
using Quartz;
|
||||||
using Action = PlexRequests.Helpers.Analytics.Action;
|
using Action = PlexRequests.Helpers.Analytics.Action;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Modules
|
namespace PlexRequests.UI.Modules
|
||||||
|
@ -80,6 +80,7 @@ namespace PlexRequests.UI.Modules
|
||||||
private ISettingsService<PushbulletNotificationSettings> PushbulletService { get; }
|
private ISettingsService<PushbulletNotificationSettings> PushbulletService { get; }
|
||||||
private ISettingsService<PushoverNotificationSettings> PushoverService { get; }
|
private ISettingsService<PushoverNotificationSettings> PushoverService { get; }
|
||||||
private ISettingsService<HeadphonesSettings> HeadphonesService { get; }
|
private ISettingsService<HeadphonesSettings> HeadphonesService { get; }
|
||||||
|
private ISettingsService<NewletterSettings> NewsLetterService { get; }
|
||||||
private ISettingsService<LogSettings> LogService { get; }
|
private ISettingsService<LogSettings> LogService { get; }
|
||||||
private IPlexApi PlexApi { get; }
|
private IPlexApi PlexApi { get; }
|
||||||
private ISonarrApi SonarrApi { get; }
|
private ISonarrApi SonarrApi { get; }
|
||||||
|
@ -112,6 +113,7 @@ namespace PlexRequests.UI.Modules
|
||||||
PushbulletApi pbApi,
|
PushbulletApi pbApi,
|
||||||
ICouchPotatoApi cpApi,
|
ICouchPotatoApi cpApi,
|
||||||
ISettingsService<PushoverNotificationSettings> pushoverSettings,
|
ISettingsService<PushoverNotificationSettings> pushoverSettings,
|
||||||
|
ISettingsService<NewletterSettings> newsletter,
|
||||||
IPushoverApi pushoverApi,
|
IPushoverApi pushoverApi,
|
||||||
IRepository<LogEntity> logsRepo,
|
IRepository<LogEntity> logsRepo,
|
||||||
INotificationService notify,
|
INotificationService notify,
|
||||||
|
@ -139,6 +141,7 @@ namespace PlexRequests.UI.Modules
|
||||||
PushoverApi = pushoverApi;
|
PushoverApi = pushoverApi;
|
||||||
NotificationService = notify;
|
NotificationService = notify;
|
||||||
HeadphonesService = headphones;
|
HeadphonesService = headphones;
|
||||||
|
NewsLetterService = newsletter;
|
||||||
LogService = logs;
|
LogService = logs;
|
||||||
Cache = cache;
|
Cache = cache;
|
||||||
SlackSettings = slackSettings;
|
SlackSettings = slackSettings;
|
||||||
|
@ -200,11 +203,14 @@ namespace PlexRequests.UI.Modules
|
||||||
Get["/headphones"] = _ => Headphones();
|
Get["/headphones"] = _ => Headphones();
|
||||||
Post["/headphones"] = _ => SaveHeadphones();
|
Post["/headphones"] = _ => SaveHeadphones();
|
||||||
|
|
||||||
|
Get["/newsletter"] = _ => Newsletter();
|
||||||
|
Post["/newsletter"] = _ => SaveNewsletter();
|
||||||
|
|
||||||
Post["/createapikey"] = x => CreateApiKey();
|
Post["/createapikey"] = x => CreateApiKey();
|
||||||
|
|
||||||
Post["/autoupdate"] = x => AutoUpdate();
|
Post["/autoupdate"] = x => AutoUpdate();
|
||||||
|
|
||||||
Post["/testslacknotification", true] = async (x,ct) => await TestSlackNotification();
|
Post["/testslacknotification", true] = async (x, ct) => await TestSlackNotification();
|
||||||
|
|
||||||
Get["/slacknotification"] = _ => SlackNotifications();
|
Get["/slacknotification"] = _ => SlackNotifications();
|
||||||
Post["/slacknotification"] = _ => SaveSlackNotifications();
|
Post["/slacknotification"] = _ => SaveSlackNotifications();
|
||||||
|
@ -814,6 +820,33 @@ namespace PlexRequests.UI.Modules
|
||||||
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Negotiator Newsletter()
|
||||||
|
{
|
||||||
|
var settings = NewsLetterService.GetSettings();
|
||||||
|
return View["NewsletterSettings", settings];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response SaveNewsletter()
|
||||||
|
{
|
||||||
|
var settings = this.Bind<NewletterSettings>();
|
||||||
|
|
||||||
|
var valid = this.Validate(settings);
|
||||||
|
if (!valid.IsValid)
|
||||||
|
{
|
||||||
|
var error = valid.SendJsonError();
|
||||||
|
Log.Info("Error validating Headphones settings, message: {0}", error.Message);
|
||||||
|
return Response.AsJson(error);
|
||||||
|
}
|
||||||
|
settings.SendRecentlyAddedEmail = settings.SendRecentlyAddedEmail;
|
||||||
|
var result = NewsLetterService.SaveSettings(settings);
|
||||||
|
|
||||||
|
Log.Info("Saved headphones settings, result: {0}", result);
|
||||||
|
return Response.AsJson(result
|
||||||
|
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Newsletter!" }
|
||||||
|
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Response CreateApiKey()
|
private Response CreateApiKey()
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresClaims(UserClaims.Admin);
|
||||||
|
@ -942,7 +975,8 @@ namespace PlexRequests.UI.Modules
|
||||||
SonarrCacher = s.SonarrCacher,
|
SonarrCacher = s.SonarrCacher,
|
||||||
StoreBackup = s.StoreBackup,
|
StoreBackup = s.StoreBackup,
|
||||||
StoreCleanup = s.StoreCleanup,
|
StoreCleanup = s.StoreCleanup,
|
||||||
JobRecorder = jobsDict
|
JobRecorder = jobsDict,
|
||||||
|
RecentlyAddedCron = s.RecentlyAddedCron
|
||||||
};
|
};
|
||||||
return View["SchedulerSettings", model];
|
return View["SchedulerSettings", model];
|
||||||
}
|
}
|
||||||
|
@ -953,6 +987,21 @@ namespace PlexRequests.UI.Modules
|
||||||
Analytics.TrackEventAsync(Category.Admin, Action.Update, "Update ScheduledJobs", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
Analytics.TrackEventAsync(Category.Admin, Action.Update, "Update ScheduledJobs", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
||||||
var settings = this.Bind<ScheduledJobsSettings>();
|
var settings = this.Bind<ScheduledJobsSettings>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(settings.RecentlyAddedCron))
|
||||||
|
{
|
||||||
|
// Validate CRON
|
||||||
|
var isValid = CronExpression.IsValidExpression(settings.RecentlyAddedCron);
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message =
|
||||||
|
$"CRON {settings.RecentlyAddedCron} is not valid. Please ensure you are using a valid CRON."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
var result = await ScheduledJobSettings.SaveSettingsAsync(settings);
|
var result = await ScheduledJobSettings.SaveSettingsAsync(settings);
|
||||||
|
|
||||||
return Response.AsJson(result
|
return Response.AsJson(result
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
// ************************************************************************/
|
// ************************************************************************/
|
||||||
#endregion
|
#endregion
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Mono.Data.Sqlite;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.ModelBinding;
|
using Nancy.ModelBinding;
|
||||||
using Nancy.Security;
|
using Nancy.Security;
|
||||||
|
@ -35,6 +36,8 @@ using NLog;
|
||||||
using PlexRequests.Api.Interfaces;
|
using PlexRequests.Api.Interfaces;
|
||||||
using PlexRequests.Core;
|
using PlexRequests.Core;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
using PlexRequests.Store.Repository;
|
||||||
using PlexRequests.UI.Helpers;
|
using PlexRequests.UI.Helpers;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ namespace PlexRequests.UI.Modules
|
||||||
Post["/plex"] = _ => PlexTest();
|
Post["/plex"] = _ => PlexTest();
|
||||||
Post["/sickrage"] = _ => SickRageTest();
|
Post["/sickrage"] = _ => SickRageTest();
|
||||||
Post["/headphones"] = _ => HeadphonesTest();
|
Post["/headphones"] = _ => HeadphonesTest();
|
||||||
|
Post["/plexdb"] = _ => TestPlexDb();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
@ -223,5 +226,65 @@ namespace PlexRequests.UI.Modules
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); ;
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response TestPlexDb()
|
||||||
|
{
|
||||||
|
var settings = this.Bind<PlexSettings>();
|
||||||
|
var valid = this.Validate(settings);
|
||||||
|
if (!valid.IsValid)
|
||||||
|
{
|
||||||
|
return Response.AsJson(valid.SendJsonError());
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var location = string.Empty;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(settings.PlexDatabaseLocationOverride))
|
||||||
|
{
|
||||||
|
if (Type.GetType("Mono.Runtime") != null)
|
||||||
|
{
|
||||||
|
// Mono
|
||||||
|
location = Path.Combine("/var/lib/plexmediaserver/Library/Application Support/",
|
||||||
|
"Plex Media Server", "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default Windows
|
||||||
|
location = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"),
|
||||||
|
"Plex Media Server", "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
location = Path.Combine(settings.PlexDatabaseLocationOverride, "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(location))
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = true,
|
||||||
|
Message = "Found the database!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = $"Could not find the database at the following full location : {location}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Warn("Exception thrown when attempting to find the plex database: ");
|
||||||
|
Log.Warn(e);
|
||||||
|
var message = $"Could not find Plex's DB, please check your settings. <strong>Exception Message:</strong> {e.Message}";
|
||||||
|
if (e.InnerException != null)
|
||||||
|
{
|
||||||
|
message = $"Could not find Plex's DB, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
|
||||||
|
}
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); ;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -52,7 +52,7 @@ namespace PlexRequests.UI.Modules
|
||||||
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
|
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
|
||||||
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr) : base("approval", pr)
|
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr) : base("approval", pr)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
|
|
||||||
Service = service;
|
Service = service;
|
||||||
CpService = cpService;
|
CpService = cpService;
|
||||||
|
@ -119,7 +119,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> RequestTvAndUpdateStatus(RequestedModel request, string qualityId)
|
private async Task<Response> RequestTvAndUpdateStatus(RequestedModel request, string qualityId)
|
||||||
{
|
{
|
||||||
var sender = new TvSender(SonarrApi, SickRageApi);
|
var sender = new TvSenderOld(SonarrApi, SickRageApi); // TODO put back
|
||||||
|
|
||||||
var sonarrSettings = await SonarrSettings.GetSettingsAsync();
|
var sonarrSettings = await SonarrSettings.GetSettingsAsync();
|
||||||
if (sonarrSettings.Enabled)
|
if (sonarrSettings.Enabled)
|
||||||
|
@ -439,7 +439,7 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
if (r.Type == RequestType.TvShow)
|
if (r.Type == RequestType.TvShow)
|
||||||
{
|
{
|
||||||
var sender = new TvSender(SonarrApi, SickRageApi);
|
var sender = new TvSenderOld(SonarrApi, SickRageApi); // TODO put back
|
||||||
var sr = await SickRageSettings.GetSettingsAsync();
|
var sr = await SickRageSettings.GetSettingsAsync();
|
||||||
var sonarr = await SonarrSettings.GetSettingsAsync();
|
var sonarr = await SonarrSettings.GetSettingsAsync();
|
||||||
if (sr.Enabled)
|
if (sr.Enabled)
|
||||||
|
|
|
@ -366,7 +366,7 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
var issue = await IssuesService.GetAsync(issueId);
|
var issue = await IssuesService.GetAsync(issueId);
|
||||||
var request = await RequestService.GetAsync(issue.RequestId);
|
var request = await RequestService.GetAsync(issue.RequestId);
|
||||||
if (request.Id > 0)
|
if (request.Id > 0)
|
||||||
|
@ -399,7 +399,7 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
|
|
||||||
var issue = await IssuesService.GetAsync(issueId);
|
var issue = await IssuesService.GetAsync(issueId);
|
||||||
issue.IssueStatus = status;
|
issue.IssueStatus = status;
|
||||||
|
@ -417,7 +417,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Negotiator> ClearIssue(int issueId, IssueState state)
|
private async Task<Negotiator> ClearIssue(int issueId, IssueState state)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
var issue = await IssuesService.GetAsync(issueId);
|
var issue = await IssuesService.GetAsync(issueId);
|
||||||
|
|
||||||
var toRemove = issue.Issues.FirstOrDefault(x => x.Issue == state);
|
var toRemove = issue.Issues.FirstOrDefault(x => x.Issue == state);
|
||||||
|
@ -430,7 +430,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> AddNote(int requestId, string noteArea, IssueState state)
|
private async Task<Response> AddNote(int requestId, string noteArea, IssueState state)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
var issue = await IssuesService.GetAsync(requestId);
|
var issue = await IssuesService.GetAsync(requestId);
|
||||||
if (issue == null)
|
if (issue == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,13 +40,15 @@ using Nancy.Security;
|
||||||
using PlexRequests.Core;
|
using PlexRequests.Core;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
using PlexRequests.Store.Repository;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Modules
|
namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
public class LoginModule : BaseModule
|
public class LoginModule : BaseModule
|
||||||
{
|
{
|
||||||
public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker)
|
public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker, IRepository<UserLogins> userLoginRepo)
|
||||||
: base(pr)
|
: base(pr)
|
||||||
{
|
{
|
||||||
UserMapper = m;
|
UserMapper = m;
|
||||||
|
@ -101,6 +103,14 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search";
|
redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userLoginRepo.Insert(new UserLogins
|
||||||
|
{
|
||||||
|
LastLoggedIn = DateTime.UtcNow,
|
||||||
|
Type = UserType.LocalUser,
|
||||||
|
UserId = userId.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
return this.LoginAndRedirect(userId.Value, expiry, redirect);
|
return this.LoginAndRedirect(userId.Value, expiry, redirect);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -260,7 +260,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> DeleteRequest(int requestid)
|
private async Task<Response> DeleteRequest(int requestid)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
||||||
|
|
||||||
var currentEntity = await Service.GetAsync(requestid);
|
var currentEntity = await Service.GetAsync(requestid);
|
||||||
|
@ -308,7 +308,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> ClearIssue(int requestId)
|
private async Task<Response> ClearIssue(int requestId)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
|
|
||||||
var originalRequest = await Service.GetAsync(requestId);
|
var originalRequest = await Service.GetAsync(requestId);
|
||||||
if (originalRequest == null)
|
if (originalRequest == null)
|
||||||
|
@ -326,7 +326,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
|
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
||||||
var originalRequest = await Service.GetAsync(requestId);
|
var originalRequest = await Service.GetAsync(requestId);
|
||||||
if (originalRequest == null)
|
if (originalRequest == null)
|
||||||
|
|
|
@ -308,7 +308,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> DeleteRequest(int requestid)
|
private async Task<Response> DeleteRequest(int requestid)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
||||||
|
|
||||||
var currentEntity = await Service.GetAsync(requestid);
|
var currentEntity = await Service.GetAsync(requestid);
|
||||||
|
@ -356,7 +356,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> ClearIssue(int requestId)
|
private async Task<Response> ClearIssue(int requestId)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
|
|
||||||
var originalRequest = await Service.GetAsync(requestId);
|
var originalRequest = await Service.GetAsync(requestId);
|
||||||
if (originalRequest == null)
|
if (originalRequest == null)
|
||||||
|
@ -374,7 +374,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
|
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
|
||||||
{
|
{
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||||
Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
|
||||||
var originalRequest = await Service.GetAsync(requestId);
|
var originalRequest = await Service.GetAsync(requestId);
|
||||||
if (originalRequest == null)
|
if (originalRequest == null)
|
||||||
|
|
|
@ -189,7 +189,7 @@ namespace PlexRequests.UI.Modules
|
||||||
switch (searchType)
|
switch (searchType)
|
||||||
{
|
{
|
||||||
case MovieSearchType.Search:
|
case MovieSearchType.Search:
|
||||||
var movies = await MovieApi.SearchMovie(searchTerm);
|
var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false);
|
||||||
apiMovies = movies.Select(x =>
|
apiMovies = movies.Select(x =>
|
||||||
new MovieResult
|
new MovieResult
|
||||||
{
|
{
|
||||||
|
@ -232,8 +232,18 @@ namespace PlexRequests.UI.Modules
|
||||||
var plexMovies = Checker.GetPlexMovies();
|
var plexMovies = Checker.GetPlexMovies();
|
||||||
var settings = await PrService.GetSettingsAsync();
|
var settings = await PrService.GetSettingsAsync();
|
||||||
var viewMovies = new List<SearchMovieViewModel>();
|
var viewMovies = new List<SearchMovieViewModel>();
|
||||||
|
var counter = 0;
|
||||||
foreach (var movie in apiMovies)
|
foreach (var movie in apiMovies)
|
||||||
{
|
{
|
||||||
|
var imdbId = string.Empty;
|
||||||
|
if (counter <= 5) // Let's only do it for the first 5 items
|
||||||
|
{
|
||||||
|
var movieInfoTask = await MovieApi.GetMovieInformation(movie.Id).ConfigureAwait(false); // TODO needs to be careful about this, it's adding extra time to search...
|
||||||
|
// https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2
|
||||||
|
imdbId = movieInfoTask.ImdbId;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
var viewMovie = new SearchMovieViewModel
|
var viewMovie = new SearchMovieViewModel
|
||||||
{
|
{
|
||||||
Adult = movie.Adult,
|
Adult = movie.Adult,
|
||||||
|
@ -252,7 +262,7 @@ namespace PlexRequests.UI.Modules
|
||||||
VoteCount = movie.VoteCount
|
VoteCount = movie.VoteCount
|
||||||
};
|
};
|
||||||
var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies);
|
var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies);
|
||||||
var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString());
|
var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), imdbId);
|
||||||
if (plexMovie != null)
|
if (plexMovie != null)
|
||||||
{
|
{
|
||||||
viewMovie.Available = true;
|
viewMovie.Available = true;
|
||||||
|
@ -349,8 +359,7 @@ namespace PlexRequests.UI.Modules
|
||||||
providerId = viewT.Id.ToString();
|
providerId = viewT.Id.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4),
|
var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId);
|
||||||
providerId);
|
|
||||||
if (plexShow != null)
|
if (plexShow != null)
|
||||||
{
|
{
|
||||||
viewT.Available = true;
|
viewT.Available = true;
|
||||||
|
@ -435,6 +444,15 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
private async Task<Response> RequestMovie(int movieId)
|
private async Task<Response> RequestMovie(int movieId)
|
||||||
{
|
{
|
||||||
|
if (this.DoesNotHaveClaimCheck(UserClaims.ReadOnlyUser))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
Response.AsJson(new JsonResponseModel()
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = "Sorry, you do not have the correct permissions to request a movie!"
|
||||||
|
});
|
||||||
|
}
|
||||||
var settings = await PrService.GetSettingsAsync();
|
var settings = await PrService.GetSettingsAsync();
|
||||||
if (!await CheckRequestLimit(settings, RequestType.Movie))
|
if (!await CheckRequestLimit(settings, RequestType.Movie))
|
||||||
{
|
{
|
||||||
|
@ -479,7 +497,7 @@ namespace PlexRequests.UI.Modules
|
||||||
Type = RequestType.Movie,
|
Type = RequestType.Movie,
|
||||||
Overview = movieInfo.Overview,
|
Overview = movieInfo.Overview,
|
||||||
ImdbId = movieInfo.ImdbId,
|
ImdbId = movieInfo.ImdbId,
|
||||||
PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath,
|
PosterPath = movieInfo.PosterPath,
|
||||||
Title = movieInfo.Title,
|
Title = movieInfo.Title,
|
||||||
ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue,
|
ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue,
|
||||||
Status = movieInfo.Status,
|
Status = movieInfo.Status,
|
||||||
|
@ -535,6 +553,15 @@ namespace PlexRequests.UI.Modules
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task<Response> RequestTvShow(int showId, string seasons)
|
private async Task<Response> RequestTvShow(int showId, string seasons)
|
||||||
{
|
{
|
||||||
|
if (this.DoesNotHaveClaimCheck(UserClaims.ReadOnlyUser))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
Response.AsJson(new JsonResponseModel()
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = "Sorry, you do not have the correct permissions to request a TV Show!"
|
||||||
|
});
|
||||||
|
}
|
||||||
// Get the JSON from the request
|
// Get the JSON from the request
|
||||||
var req = (Dictionary<string, object>.ValueCollection)Request.Form.Values;
|
var req = (Dictionary<string, object>.ValueCollection)Request.Form.Values;
|
||||||
EpisodeRequestModel episodeModel = null;
|
EpisodeRequestModel episodeModel = null;
|
||||||
|
@ -590,7 +617,7 @@ namespace PlexRequests.UI.Modules
|
||||||
RequestedUsers = new List<string> { Username },
|
RequestedUsers = new List<string> { Username },
|
||||||
Issues = IssueState.None,
|
Issues = IssueState.None,
|
||||||
ImdbId = showInfo.externals?.imdb ?? string.Empty,
|
ImdbId = showInfo.externals?.imdb ?? string.Empty,
|
||||||
SeasonCount = showInfo.seasonCount,
|
SeasonCount = showInfo.Season.Count,
|
||||||
TvDbId = showId.ToString()
|
TvDbId = showId.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -703,7 +730,18 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId, model.SeasonList))
|
if (plexSettings.EnableTvEpisodeSearching)
|
||||||
|
{
|
||||||
|
foreach (var s in showInfo.Season)
|
||||||
|
{
|
||||||
|
var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId, model.SeasonList))
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" });
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" });
|
||||||
}
|
}
|
||||||
|
@ -719,7 +757,7 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
model.Approved = true;
|
model.Approved = true;
|
||||||
var s = await sonarrSettings;
|
var s = await sonarrSettings;
|
||||||
var sender = new TvSender(SonarrApi, SickrageApi);
|
var sender = new TvSenderOld(SonarrApi, SickrageApi); // TODO put back
|
||||||
if (s.Enabled)
|
if (s.Enabled)
|
||||||
{
|
{
|
||||||
var result = await sender.SendToSonarr(s, model);
|
var result = await sender.SendToSonarr(s, model);
|
||||||
|
|
|
@ -59,7 +59,9 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false });
|
return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false });
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
|
return Response.AsJson(new JsonUpdateAvailableModel {UpdateAvailable = false});
|
||||||
|
#endif
|
||||||
var checker = new StatusChecker();
|
var checker = new StatusChecker();
|
||||||
var release = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async() => await checker.GetStatus(), 30);
|
var release = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async() => await checker.GetStatus(), 30);
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,8 @@ using PlexRequests.Core;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
using PlexRequests.Helpers.Analytics;
|
using PlexRequests.Helpers.Analytics;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
using PlexRequests.Store.Repository;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ namespace PlexRequests.UI.Modules
|
||||||
public class UserLoginModule : BaseModule
|
public class UserLoginModule : BaseModule
|
||||||
{
|
{
|
||||||
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> pr,
|
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> pr,
|
||||||
ISettingsService<LandingPageSettings> lp, IAnalytics a, IResourceLinker linker) : base("userlogin", pr)
|
ISettingsService<LandingPageSettings> lp, IAnalytics a, IResourceLinker linker, IRepository<UserLogins> userLogins) : base("userlogin", pr)
|
||||||
{
|
{
|
||||||
AuthService = auth;
|
AuthService = auth;
|
||||||
LandingPageSettings = lp;
|
LandingPageSettings = lp;
|
||||||
|
@ -60,6 +62,7 @@ namespace PlexRequests.UI.Modules
|
||||||
Api = api;
|
Api = api;
|
||||||
PlexSettings = plexSettings;
|
PlexSettings = plexSettings;
|
||||||
Linker = linker;
|
Linker = linker;
|
||||||
|
UserLogins = userLogins;
|
||||||
|
|
||||||
Get["UserLoginIndex", "/", true] = async (x, ct) =>
|
Get["UserLoginIndex", "/", true] = async (x, ct) =>
|
||||||
{
|
{
|
||||||
|
@ -82,11 +85,13 @@ namespace PlexRequests.UI.Modules
|
||||||
private IPlexApi Api { get; }
|
private IPlexApi Api { get; }
|
||||||
private IResourceLinker Linker { get; }
|
private IResourceLinker Linker { get; }
|
||||||
private IAnalytics Analytics { get; }
|
private IAnalytics Analytics { get; }
|
||||||
|
private IRepository<UserLogins> UserLogins { get; }
|
||||||
|
|
||||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private async Task<Response> LoginUser()
|
private async Task<Response> LoginUser()
|
||||||
{
|
{
|
||||||
|
var userId = string.Empty;
|
||||||
var dateTimeOffset = Request.Form.DateTimeOffset;
|
var dateTimeOffset = Request.Form.DateTimeOffset;
|
||||||
var username = Request.Form.username.Value;
|
var username = Request.Form.username.Value;
|
||||||
Log.Debug("Username \"{0}\" attempting to login", username);
|
Log.Debug("Username \"{0}\" attempting to login", username);
|
||||||
|
@ -135,6 +140,7 @@ namespace PlexRequests.UI.Modules
|
||||||
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
|
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
|
||||||
Log.Debug("Friends list result = {0}", authenticated);
|
Log.Debug("Friends list result = {0}", authenticated);
|
||||||
}
|
}
|
||||||
|
userId = signedIn.user.uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (settings.UserAuthentication) // Check against the users in Plex
|
else if (settings.UserAuthentication) // Check against the users in Plex
|
||||||
|
@ -147,6 +153,11 @@ namespace PlexRequests.UI.Modules
|
||||||
authenticated = true;
|
authenticated = true;
|
||||||
}
|
}
|
||||||
Log.Debug("Friends list result = {0}", authenticated);
|
Log.Debug("Friends list result = {0}", authenticated);
|
||||||
|
if (authenticated)
|
||||||
|
{
|
||||||
|
// Get the user that is authenticated to store in the UserLogins
|
||||||
|
userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!settings.UserAuthentication) // No auth, let them pass!
|
else if (!settings.UserAuthentication) // No auth, let them pass!
|
||||||
{
|
{
|
||||||
|
@ -156,12 +167,12 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
if (authenticated)
|
if (authenticated)
|
||||||
{
|
{
|
||||||
|
UserLogins.Insert(new UserLogins { UserId = userId, Type = UserType.PlexUser, LastLoggedIn = DateTime.UtcNow });
|
||||||
Log.Debug("We are authenticated! Setting session.");
|
Log.Debug("We are authenticated! Setting session.");
|
||||||
// Add to the session (Used in the BaseModules)
|
// Add to the session (Used in the BaseModules)
|
||||||
Session[SessionKeys.UsernameKey] = (string)username;
|
Session[SessionKeys.UsernameKey] = (string)username;
|
||||||
}
|
|
||||||
|
|
||||||
Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset;
|
Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
if (!authenticated)
|
if (!authenticated)
|
||||||
{
|
{
|
||||||
|
@ -214,6 +225,14 @@ namespace PlexRequests.UI.Modules
|
||||||
return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase));
|
return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private string GetUserIdIsInPlexFriends(string username, string authToken)
|
||||||
|
{
|
||||||
|
var users = Api.GetUsers(authToken);
|
||||||
|
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title));
|
||||||
|
return allUsers?.Where(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)).Select(x => x.Id).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsUserInDeniedList(string username, AuthenticationSettings settings)
|
private bool IsUserInDeniedList(string username, AuthenticationSettings settings)
|
||||||
{
|
{
|
||||||
return settings.DeniedUserList.Any(x => x.Equals(username, StringComparison.CurrentCultureIgnoreCase));
|
return settings.DeniedUserList.Any(x => x.Equals(username, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
|
@ -13,13 +13,15 @@ using PlexRequests.Core;
|
||||||
using PlexRequests.Core.Models;
|
using PlexRequests.Core.Models;
|
||||||
using PlexRequests.Core.SettingModels;
|
using PlexRequests.Core.SettingModels;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
|
using PlexRequests.Store;
|
||||||
|
using PlexRequests.Store.Repository;
|
||||||
using PlexRequests.UI.Models;
|
using PlexRequests.UI.Models;
|
||||||
|
|
||||||
namespace PlexRequests.UI.Modules
|
namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
public class UserManagementModule : BaseModule
|
public class UserManagementModule : BaseModule
|
||||||
{
|
{
|
||||||
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex) : base("usermanagement", pr)
|
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins) : base("usermanagement", pr)
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
this.RequiresClaims(UserClaims.Admin);
|
this.RequiresClaims(UserClaims.Admin);
|
||||||
|
@ -27,6 +29,7 @@ namespace PlexRequests.UI.Modules
|
||||||
UserMapper = m;
|
UserMapper = m;
|
||||||
PlexApi = plexApi;
|
PlexApi = plexApi;
|
||||||
PlexSettings = plex;
|
PlexSettings = plex;
|
||||||
|
UserLoginsRepo = userLogins;
|
||||||
|
|
||||||
Get["/"] = x => Load();
|
Get["/"] = x => Load();
|
||||||
|
|
||||||
|
@ -35,11 +38,14 @@ namespace PlexRequests.UI.Modules
|
||||||
Get["/local/{id}"] = x => LocalDetails((Guid)x.id);
|
Get["/local/{id}"] = x => LocalDetails((Guid)x.id);
|
||||||
Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id);
|
Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id);
|
||||||
Get["/claims"] = x => GetClaims();
|
Get["/claims"] = x => GetClaims();
|
||||||
|
Post["/updateuser"] = x => UpdateUser();
|
||||||
|
Post["/deleteuser"] = x => DeleteUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICustomUserMapper UserMapper { get; }
|
private ICustomUserMapper UserMapper { get; }
|
||||||
private IPlexApi PlexApi { get; }
|
private IPlexApi PlexApi { get; }
|
||||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||||
|
private IRepository<UserLogins> UserLoginsRepo { get; }
|
||||||
|
|
||||||
private Negotiator Load()
|
private Negotiator Load()
|
||||||
{
|
{
|
||||||
|
@ -50,22 +56,13 @@ namespace PlexRequests.UI.Modules
|
||||||
{
|
{
|
||||||
var localUsers = await UserMapper.GetUsersAsync();
|
var localUsers = await UserMapper.GetUsersAsync();
|
||||||
var model = new List<UserManagementUsersViewModel>();
|
var model = new List<UserManagementUsersViewModel>();
|
||||||
|
|
||||||
|
var usersDb = UserLoginsRepo.GetAll().ToList();
|
||||||
|
|
||||||
foreach (var user in localUsers)
|
foreach (var user in localUsers)
|
||||||
{
|
{
|
||||||
var claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims);
|
var userDb = usersDb.FirstOrDefault(x => x.UserId == user.UserGuid);
|
||||||
var claimsString = string.Join(", ", claims);
|
model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue));
|
||||||
|
|
||||||
var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties);
|
|
||||||
|
|
||||||
model.Add(new UserManagementUsersViewModel
|
|
||||||
{
|
|
||||||
Id = user.UserGuid,
|
|
||||||
Claims = claimsString,
|
|
||||||
Username = user.UserName,
|
|
||||||
Type = UserType.LocalUser,
|
|
||||||
EmailAddress = userProps.EmailAddress,
|
|
||||||
ClaimsArray = claims
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var plexSettings = await PlexSettings.GetSettingsAsync();
|
var plexSettings = await PlexSettings.GetSettingsAsync();
|
||||||
|
@ -76,7 +73,7 @@ namespace PlexRequests.UI.Modules
|
||||||
|
|
||||||
foreach (var u in plexUsers.User)
|
foreach (var u in plexUsers.User)
|
||||||
{
|
{
|
||||||
|
var userDb = usersDb.FirstOrDefault(x => x.UserId == u.Id);
|
||||||
model.Add(new UserManagementUsersViewModel
|
model.Add(new UserManagementUsersViewModel
|
||||||
{
|
{
|
||||||
Username = u.Username,
|
Username = u.Username,
|
||||||
|
@ -87,7 +84,8 @@ namespace PlexRequests.UI.Modules
|
||||||
PlexInfo = new UserManagementPlexInformation
|
PlexInfo = new UserManagementPlexInformation
|
||||||
{
|
{
|
||||||
Thumb = u.Thumb
|
Thumb = u.Thumb
|
||||||
}
|
},
|
||||||
|
LastLoggedIn = userDb?.LastLoggedIn ?? DateTime.MinValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,12 +113,80 @@ namespace PlexRequests.UI.Modules
|
||||||
var user = UserMapper.CreateUser(model.Username, model.Password, model.Claims, new UserProperties { EmailAddress = model.EmailAddress });
|
var user = UserMapper.CreateUser(model.Username, model.Password, model.Claims, new UserProperties { EmailAddress = model.EmailAddress });
|
||||||
if (user.HasValue)
|
if (user.HasValue)
|
||||||
{
|
{
|
||||||
return Response.AsJson(user);
|
return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" });
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response UpdateUser()
|
||||||
|
{
|
||||||
|
var body = Request.Body.AsString();
|
||||||
|
if (string.IsNullOrEmpty(body))
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = JsonConvert.DeserializeObject<UserManagementUpdateModel>(body);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(model.Id))
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = true,
|
||||||
|
Message = "Couldn't find the user"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims = new List<string>();
|
||||||
|
|
||||||
|
foreach (var c in model.Claims)
|
||||||
|
{
|
||||||
|
if (c.Selected)
|
||||||
|
{
|
||||||
|
claims.Add(c.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userFound = UserMapper.GetUser(new Guid(model.Id));
|
||||||
|
|
||||||
|
userFound.Claims = ByteConverterHelper.ReturnBytes(claims.ToArray());
|
||||||
|
var currentProps = ByteConverterHelper.ReturnObject<UserProperties>(userFound.UserProperties);
|
||||||
|
currentProps.UserAlias = model.Alias;
|
||||||
|
currentProps.EmailAddress = model.EmailAddress;
|
||||||
|
|
||||||
|
userFound.UserProperties = ByteConverterHelper.ReturnBytes(currentProps);
|
||||||
|
|
||||||
|
var user = UserMapper.EditUser(userFound);
|
||||||
|
var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid);
|
||||||
|
var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue);
|
||||||
|
return Response.AsJson(retUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response DeleteUser()
|
||||||
|
{
|
||||||
|
var body = Request.Body.AsString();
|
||||||
|
if (string.IsNullOrEmpty(body))
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = JsonConvert.DeserializeObject<DeleteUserViewModel>(body);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(model.Id))
|
||||||
|
{
|
||||||
|
return Response.AsJson(new JsonResponseModel
|
||||||
|
{
|
||||||
|
Result = true,
|
||||||
|
Message = "Couldn't find the user"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
UserMapper.DeleteUser(model.Id);
|
||||||
|
|
||||||
|
return Response.AsJson(new JsonResponseModel {Result = true});
|
||||||
|
}
|
||||||
|
|
||||||
private Response LocalDetails(Guid id)
|
private Response LocalDetails(Guid id)
|
||||||
{
|
{
|
||||||
var localUser = UserMapper.GetUser(id);
|
var localUser = UserMapper.GetUser(id);
|
||||||
|
@ -165,6 +231,45 @@ namespace PlexRequests.UI.Modules
|
||||||
}
|
}
|
||||||
return Response.AsJson(retVal);
|
return Response.AsJson(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn)
|
||||||
|
{
|
||||||
|
var claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims);
|
||||||
|
var claimsString = string.Join(", ", claims);
|
||||||
|
|
||||||
|
var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties);
|
||||||
|
|
||||||
|
var m = new UserManagementUsersViewModel
|
||||||
|
{
|
||||||
|
Id = user.UserGuid,
|
||||||
|
Claims = claimsString,
|
||||||
|
Username = user.UserName,
|
||||||
|
Type = UserType.LocalUser,
|
||||||
|
EmailAddress = userProps.EmailAddress,
|
||||||
|
Alias = userProps.UserAlias,
|
||||||
|
ClaimsArray = claims,
|
||||||
|
ClaimsItem = new List<UserManagementUpdateModel.ClaimsModel>(),
|
||||||
|
LastLoggedIn = lastLoggedIn
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add all of the current claims
|
||||||
|
foreach (var c in claims)
|
||||||
|
{
|
||||||
|
m.ClaimsItem.Add(new UserManagementUpdateModel.ClaimsModel { Name = c, Selected = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
var allClaims = UserMapper.GetAllClaims();
|
||||||
|
|
||||||
|
// Get me the current claims that the user does not have
|
||||||
|
var missingClaims = allClaims.Except(claims);
|
||||||
|
|
||||||
|
// Add them into the view
|
||||||
|
foreach (var missingClaim in missingClaims)
|
||||||
|
{
|
||||||
|
m.ClaimsItem.Add(new UserManagementUpdateModel.ClaimsModel { Name = missingClaim, Selected = false });
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ using Nancy.Authentication.Forms;
|
||||||
using Ninject.Modules;
|
using Ninject.Modules;
|
||||||
|
|
||||||
using PlexRequests.Core;
|
using PlexRequests.Core;
|
||||||
|
using PlexRequests.Core.Migration;
|
||||||
using PlexRequests.Helpers;
|
using PlexRequests.Helpers;
|
||||||
using PlexRequests.Services.Interfaces;
|
using PlexRequests.Services.Interfaces;
|
||||||
using PlexRequests.Services.Notification;
|
using PlexRequests.Services.Notification;
|
||||||
|
@ -45,6 +46,10 @@ namespace PlexRequests.UI.NinjectModules
|
||||||
{
|
{
|
||||||
Bind<ICacheProvider>().To<MemoryCacheProvider>().InSingletonScope();
|
Bind<ICacheProvider>().To<MemoryCacheProvider>().InSingletonScope();
|
||||||
Bind<ISqliteConfiguration>().To<DbConfiguration>().WithConstructorArgument("provider", new SqliteFactory());
|
Bind<ISqliteConfiguration>().To<DbConfiguration>().WithConstructorArgument("provider", new SqliteFactory());
|
||||||
|
Bind<IPlexDatabase>().To<PlexDatabase>().WithConstructorArgument("provider", new SqliteFactory());
|
||||||
|
Bind<IPlexReadOnlyDatabase>().To<PlexReadOnlyDatabase>();
|
||||||
|
Bind<IMigrationRunner>().To<MigrationRunner>();
|
||||||
|
|
||||||
|
|
||||||
Bind<IUserMapper>().To<UserMapper>();
|
Bind<IUserMapper>().To<UserMapper>();
|
||||||
Bind<ICustomUserMapper>().To<UserMapper>();
|
Bind<ICustomUserMapper>().To<UserMapper>();
|
||||||
|
|
|
@ -117,6 +117,7 @@
|
||||||
<Reference Include="System.Configuration" />
|
<Reference Include="System.Configuration" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
<Reference Include="System.Web" />
|
<Reference Include="System.Web" />
|
||||||
<Reference Include="System.Web.Extensions" />
|
<Reference Include="System.Web.Extensions" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
@ -196,8 +197,9 @@
|
||||||
<Reference Include="System.Web.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
|
<Reference Include="System.Web.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
|
||||||
<HintPath>..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll</HintPath>
|
<HintPath>..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, PublicKeyToken=null">
|
<Reference Include="TMDbLib, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
<HintPath>..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -210,9 +212,12 @@
|
||||||
<Compile Include="Helpers\EmptyViewBase.cs" />
|
<Compile Include="Helpers\EmptyViewBase.cs" />
|
||||||
<Compile Include="Helpers\HeadphonesSender.cs" />
|
<Compile Include="Helpers\HeadphonesSender.cs" />
|
||||||
<Compile Include="Helpers\AngularViewBase.cs" />
|
<Compile Include="Helpers\AngularViewBase.cs" />
|
||||||
|
<Compile Include="Helpers\HtmlSecurityHelper.cs" />
|
||||||
|
<Compile Include="Helpers\SecurityExtensions.cs" />
|
||||||
<Compile Include="Helpers\ServiceLocator.cs" />
|
<Compile Include="Helpers\ServiceLocator.cs" />
|
||||||
<Compile Include="Helpers\Themes.cs" />
|
<Compile Include="Helpers\Themes.cs" />
|
||||||
<Compile Include="Helpers\TvSender.cs" />
|
<Compile Include="Helpers\TvSender.cs" />
|
||||||
|
<Compile Include="Helpers\TvSenderOld.cs" />
|
||||||
<Compile Include="Helpers\ValidationHelper.cs" />
|
<Compile Include="Helpers\ValidationHelper.cs" />
|
||||||
<Compile Include="Jobs\CustomJobFactory.cs" />
|
<Compile Include="Jobs\CustomJobFactory.cs" />
|
||||||
<Compile Include="Jobs\Scheduler.cs" />
|
<Compile Include="Jobs\Scheduler.cs" />
|
||||||
|
@ -237,7 +242,8 @@
|
||||||
<Compile Include="Models\SearchViewModel.cs" />
|
<Compile Include="Models\SearchViewModel.cs" />
|
||||||
<Compile Include="Models\SearchMusicViewModel.cs" />
|
<Compile Include="Models\SearchMusicViewModel.cs" />
|
||||||
<Compile Include="Models\SearchMovieViewModel.cs" />
|
<Compile Include="Models\SearchMovieViewModel.cs" />
|
||||||
<Compile Include="Models\UserUpdateViewModel.cs" />
|
<Compile Include="Models\UserManagement\DeleteUserViewModel.cs" />
|
||||||
|
<Compile Include="Models\UserManagement\UserUpdateViewModel.cs" />
|
||||||
<Compile Include="Modules\ApiDocsModule.cs" />
|
<Compile Include="Modules\ApiDocsModule.cs" />
|
||||||
<Compile Include="Modules\ApiSettingsMetadataModule.cs" />
|
<Compile Include="Modules\ApiSettingsMetadataModule.cs" />
|
||||||
<Compile Include="Modules\ApiUserMetadataModule.cs" />
|
<Compile Include="Modules\ApiUserMetadataModule.cs" />
|
||||||
|
@ -304,6 +310,12 @@
|
||||||
<DependentUpon>base.css</DependentUpon>
|
<DependentUpon>base.css</DependentUpon>
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Content\bootstrap-switch.min.css">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Content\bootstrap-switch.min.js">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="Content\bootstrap.css">
|
<Content Include="Content\bootstrap.css">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -487,7 +499,7 @@
|
||||||
</Content>
|
</Content>
|
||||||
<Compile Include="Modules\ApiRequestModule.cs" />
|
<Compile Include="Modules\ApiRequestModule.cs" />
|
||||||
<Compile Include="Models\ApiModel.cs" />
|
<Compile Include="Models\ApiModel.cs" />
|
||||||
<Compile Include="Models\UserManagementUsersViewModel.cs" />
|
<Compile Include="Models\UserManagement\UserManagementUsersViewModel.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Content\bootstrap.min.js">
|
<Content Include="Content\bootstrap.min.js">
|
||||||
|
@ -711,6 +723,9 @@
|
||||||
<Content Include="Views\Admin\NotificationSettings.cshtml">
|
<Content Include="Views\Admin\NotificationSettings.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<None Include="Views\Admin\NewsletterSettings.cshtml">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Include="Web.Debug.config">
|
<None Include="Web.Debug.config">
|
||||||
<DependentUpon>web.config</DependentUpon>
|
<DependentUpon>web.config</DependentUpon>
|
||||||
</None>
|
</None>
|
||||||
|
@ -745,6 +760,10 @@
|
||||||
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
|
<Project>{8CB8D235-2674-442D-9C6A-35FCAEEB160D}</Project>
|
||||||
<Name>PlexRequests.Api</Name>
|
<Name>PlexRequests.Api</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\PlexRequests.Core.Migration\PlexRequests.Core.Migration.csproj">
|
||||||
|
<Project>{8406EE57-D533-47C0-9302-C6B5F8C31E55}</Project>
|
||||||
|
<Name>PlexRequests.Core.Migration</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
|
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
|
||||||
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
|
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
|
||||||
<Name>PlexRequests.Core</Name>
|
<Name>PlexRequests.Core</Name>
|
||||||
|
|
|
@ -446,4 +446,25 @@
|
||||||
<data name="Custom_Donation_Default" xml:space="preserve">
|
<data name="Custom_Donation_Default" xml:space="preserve">
|
||||||
<value>Donate to Library Maintainer</value>
|
<value>Donate to Library Maintainer</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Search_Available_on_plex" xml:space="preserve">
|
||||||
|
<value>Available on Plex</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search_Movie_Status" xml:space="preserve">
|
||||||
|
<value>Movie status</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search_Not_Requested_Yet" xml:space="preserve">
|
||||||
|
<value>Not Requested yet</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search_Pending_approval" xml:space="preserve">
|
||||||
|
<value>Pending approval</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search_Processing_Request" xml:space="preserve">
|
||||||
|
<value>Processing request</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search_Request_denied" xml:space="preserve">
|
||||||
|
<value>Request denied</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search_TV_Show_Status" xml:space="preserve">
|
||||||
|
<value>TV show status</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
63
PlexRequests.UI/Resources/UI1.Designer.cs
generated
63
PlexRequests.UI/Resources/UI1.Designer.cs
generated
|
@ -744,6 +744,15 @@ namespace PlexRequests.UI.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Available on Plex.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_Available_on_plex {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_Available_on_plex", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Coming Soon.
|
/// Looks up a localized string similar to Coming Soon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -834,6 +843,15 @@ namespace PlexRequests.UI.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Movie status.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_Movie_Status {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_Movie_Status", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Movies.
|
/// Looks up a localized string similar to Movies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -852,6 +870,15 @@ namespace PlexRequests.UI.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Not Requested yet.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_Not_Requested_Yet {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_Not_Requested_Yet", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to We could not remove this notification because you never had it!.
|
/// Looks up a localized string similar to We could not remove this notification because you never had it!.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -870,6 +897,24 @@ namespace PlexRequests.UI.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Pending approval.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_Pending_approval {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_Pending_approval", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Processing request.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_Processing_Request {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_Processing_Request", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Report Issue.
|
/// Looks up a localized string similar to Report Issue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -888,6 +933,15 @@ namespace PlexRequests.UI.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Request denied.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_Request_denied {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_Request_denied", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Requested.
|
/// Looks up a localized string similar to Requested.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -978,6 +1032,15 @@ namespace PlexRequests.UI.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to TV show status.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search_TV_Show_Status {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search_TV_Show_Status", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to The request of TV Shows is not correctly set up. Please contact your admin..
|
/// Looks up a localized string similar to The request of TV Shows is not correctly set up. Please contact your admin..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -32,7 +32,8 @@ using Ninject.Planning.Bindings.Resolvers;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
using Owin;
|
using Owin;
|
||||||
|
using PlexRequests.Core.Migration;
|
||||||
|
using PlexRequests.Services.Jobs;
|
||||||
using PlexRequests.UI.Helpers;
|
using PlexRequests.UI.Helpers;
|
||||||
using PlexRequests.UI.Jobs;
|
using PlexRequests.UI.Jobs;
|
||||||
using PlexRequests.UI.NinjectModules;
|
using PlexRequests.UI.NinjectModules;
|
||||||
|
@ -62,11 +63,26 @@ namespace PlexRequests.UI
|
||||||
Debug.WriteLine("Added Contravariant Binder");
|
Debug.WriteLine("Added Contravariant Binder");
|
||||||
kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();
|
kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();
|
||||||
|
|
||||||
Debug.WriteLine("Start the bootstrapper with the Kernel.ı");
|
Debug.WriteLine("Start the bootstrapper with the Kernel.");
|
||||||
app.UseNancy(options => options.Bootstrapper = new Bootstrapper(kernel));
|
app.UseNancy(options => options.Bootstrapper = new Bootstrapper(kernel));
|
||||||
Debug.WriteLine("Finished bootstrapper");
|
Debug.WriteLine("Finished bootstrapper");
|
||||||
|
|
||||||
|
|
||||||
|
Debug.WriteLine("Migrating DB Now");
|
||||||
|
var runner = kernel.Get<IMigrationRunner>();
|
||||||
|
runner.MigrateToLatest();
|
||||||
|
|
||||||
|
|
||||||
|
Debug.WriteLine("Settings up Scheduler");
|
||||||
var scheduler = new Scheduler();
|
var scheduler = new Scheduler();
|
||||||
scheduler.StartScheduler();
|
scheduler.StartScheduler();
|
||||||
|
|
||||||
|
//var c = kernel.Get<IRecentlyAdded>();
|
||||||
|
//c.Test();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
<p class="form-group">Notice Message</p>
|
<p class="form-group">Notice Message</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. Plex will be down for maintaince (HTML is allowed)" value="@Model.NoticeMessage"></textarea>
|
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. Plex will be down for maintaince (HTML is allowed)" value="@Model.NoticeMessage">@Model.NoticeMessage</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -121,6 +121,8 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#save').click(function (e) {
|
$('#save').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
var start = '';
|
var start = '';
|
||||||
var end = '';
|
var end = '';
|
||||||
if ($startDate.data("DateTimePicker").date()) {
|
if ($startDate.data("DateTimePicker").date()) {
|
||||||
|
@ -130,8 +132,6 @@
|
||||||
end = $endDate.data("DateTimePicker").date().toISOString();
|
end = $endDate.data("DateTimePicker").date().toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var $form = $("#mainForm");
|
var $form = $("#mainForm");
|
||||||
|
|
||||||
var data = $form.serialize();
|
var data = $form.serialize();
|
||||||
|
|
130
PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml
Normal file
130
PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
@using System.Linq
|
||||||
|
@using PlexRequests.Core.Models
|
||||||
|
@using PlexRequests.UI.Helpers
|
||||||
|
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<PlexRequests.Core.SettingModels.NewletterSettings>
|
||||||
|
@Html.Partial("_Sidebar")
|
||||||
|
|
||||||
|
<div class="col-sm-8 col-sm-push-1">
|
||||||
|
<form class="form-horizontal" method="POST" id="mainForm">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Newsletter Settings</legend>
|
||||||
|
|
||||||
|
<!-- Email Nofication Section -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
|
||||||
|
<small>Note: This will require you to setup your email notifications</small>
|
||||||
|
<br />
|
||||||
|
@if (Model.SendRecentlyAddedEmail)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="SendRecentlyAddedEmail" name="SendRecentlyAddedEmail" checked="checked"><label for="SendRecentlyAddedEmail">Enable the newsletter of recently added content</label>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="SendRecentlyAddedEmail" name="SendRecentlyAddedEmail"><label for="SendRecentlyAddedEmail">Enable the newsletter of recently added content</label>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
@if (Model.SendToPlexUsers)
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="SendToPlexUsers" name="SendToPlexUsers" checked="checked"><label for="SendToPlexUsers">Send to all of your Plex 'Friends'</label>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" id="SendToPlexUsers" name="SendToPlexUsers"><label for="SendToPlexUsers">Send to all of your Plex 'Friends'</label>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="StoreCleanup" class="control-label">Email Addresses to Send to (For users that are not in your Plex Friends)</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" class="form-control form-control-custom " placeholder="email@address.com;second@address.com" id="StoreCleanup" name="StoreCleanup" value="@Model.CustomUsers">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button id="recentlyAddedBtn" class="btn btn-primary-outline">Send test email to Admin <div id="spinner"></div></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Email Nofication Section -->
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
|
||||||
|
var base = '@Html.GetBaseUrl()';
|
||||||
|
$('#save').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
var data = $form.serialize();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
data: data,
|
||||||
|
url: $form.prop("action"),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (response) {
|
||||||
|
if (response.result === true) {
|
||||||
|
generateNotify(response.message, "success");
|
||||||
|
} else {
|
||||||
|
generateNotify(response.message, "warning");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
console.log(e);
|
||||||
|
generateNotify("Something went wrong!", "danger");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#recentlyAddedBtn').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var base = '@Html.GetBaseUrl()';
|
||||||
|
var url = createBaseUrl(base, '/admin/recentlyAddedTest');
|
||||||
|
$('#spinner').attr("class", "fa fa-spinner fa-spin");
|
||||||
|
$.ajax({
|
||||||
|
type: "post",
|
||||||
|
url: url,
|
||||||
|
dataType: "json",
|
||||||
|
success: function (response) {
|
||||||
|
if (response) {
|
||||||
|
generateNotify(response.message, "success");
|
||||||
|
$('#spinner').attr("class", "fa fa-check");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
generateNotify(response.message, "danger");
|
||||||
|
$('#spinner').attr("class", "fa fa-times");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
console.log(e);
|
||||||
|
generateNotify("Something went wrong!", "danger");
|
||||||
|
$('#spinner').attr("class", "fa fa-times");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -16,7 +16,7 @@
|
||||||
<form class="form-horizontal" method="POST" id="mainForm">
|
<form class="form-horizontal" method="POST" id="mainForm">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Plex Settings</legend>
|
<legend>Plex Settings</legend>
|
||||||
|
@*<input id="advancedToggle" type="checkbox"/>*@ @*TODO*@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="Ip" class="control-label">Plex Hostname or IP</label>
|
<label for="Ip" class="control-label">Plex Hostname or IP</label>
|
||||||
<div>
|
<div>
|
||||||
|
@ -88,6 +88,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@*<div class="form-group">
|
||||||
|
<label for="PlexDatabaseLocationOverride" class="control-label">Plex Database Override</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" class="form-control form-control-custom " id="PlexDatabaseLocationOverride" name="PlexDatabaseLocationOverride" placeholder="%LOCALAPPDATA%\Plex Media Server\" value="@Model.PlexDatabaseLocationOverride">
|
||||||
|
</div>
|
||||||
|
<small>
|
||||||
|
This is your Plex data directory location, if we cannot manually find it then you need to specify the location! See <a href="https://support.plex.tv/hc/en-us/articles/202915258-Where-is-the-Plex-Media-Server-data-directory-located-">Here</a>.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="">
|
||||||
|
<button id="dbTest" class="btn btn-primary-outline">Test Database Directory <i class="fa fa-database"></i> <div id="dbSpinner"></div></button>
|
||||||
|
</div>
|
||||||
|
</div>*@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="authToken" class="control-label">Plex Authorization Token</label>
|
<label for="authToken" class="control-label">Plex Authorization Token</label>
|
||||||
<div class="">
|
<div class="">
|
||||||
|
@ -95,6 +110,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="control-label">Username and Password</label>
|
<label for="username" class="control-label">Username and Password</label>
|
||||||
<div>
|
<div>
|
||||||
|
@ -129,6 +146,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
|
|
||||||
|
$("#advancedToggle").bootstrapSwitch();
|
||||||
|
|
||||||
var base = '@Html.GetBaseUrl()';
|
var base = '@Html.GetBaseUrl()';
|
||||||
|
|
||||||
$('#testPlex').click(function (e) {
|
$('#testPlex').click(function (e) {
|
||||||
|
@ -163,6 +183,38 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#dbTest').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var url = createBaseUrl(base, '/test/plexdb');
|
||||||
|
var $form = $("#mainForm");
|
||||||
|
|
||||||
|
$('#dbSpinner').attr("class", "fa fa-spinner fa-spin");
|
||||||
|
$.ajax({
|
||||||
|
type: $form.prop("method"),
|
||||||
|
url: url,
|
||||||
|
data: $form.serialize(),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (response) {
|
||||||
|
$('#dbSpinner').attr("class", "");
|
||||||
|
console.log(response);
|
||||||
|
if (response.result === true) {
|
||||||
|
generateNotify(response.message, "success");
|
||||||
|
|
||||||
|
$('#dbSpinner').attr("class", "fa fa-check");
|
||||||
|
} else {
|
||||||
|
generateNotify(response.message, "warning");
|
||||||
|
$('#dbSpinner').attr("class", "fa fa-times");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
|
||||||
|
$('#spinner').attr("class", "fa fa-times");
|
||||||
|
console.log(e);
|
||||||
|
generateNotify("Something went wrong!", "danger");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('#requestToken').click(function (e) {
|
$('#requestToken').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var $form = $("#mainForm");
|
var $form = $("#mainForm");
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">@record.Key</div>
|
<div class="col-md-4">@record.Key</div>
|
||||||
<div class="col-md-5 col-md-push-3 date">@record.Value.ToString("O")</div>
|
<div class="col-md-5 col-md-push-3 date">@record.Value.ToString("R")</div>
|
||||||
</div>
|
</div>
|
||||||
<hr style="margin-top: 4px; margin-bottom: 4px"/>
|
<hr style="margin-top: 4px; margin-bottom: 4px"/>
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<small>Please note, this uses a Quartz CRON job, you can build a CRON <a href="http://www.cronmaker.com/">Here</a></small>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="RecentlyAdded" class="control-label">Recently Added Email (hours)</label>
|
<label for="RecentlyAddedCron" class="control-label">Recently Added Email (CRON)</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" class="form-control form-control-custom " id="RecentlyAdded" name="RecentlyAdded" value="@Model.RecentlyAdded">
|
<input type="text" class="form-control form-control-custom " id="RecentlyAddedCron" name="RecentlyAddedCron" value="@Model.RecentlyAddedCron">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -74,27 +74,6 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
|
|
||||||
<small>Note: This will require you to setup your email notifications</small>
|
|
||||||
@if (Model.SendRecentlyAddedEmail)
|
|
||||||
{
|
|
||||||
<input type="checkbox" id="SendRecentlyAddedEmail" name="SendRecentlyAddedEmail" checked="checked"><label for="SendRecentlyAddedEmail">Send out a weekly email of recently added content to all your Plex 'Friends'</label>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input type="checkbox" id="SendRecentlyAddedEmail" name="SendRecentlyAddedEmail"><label for="SendRecentlyAddedEmail">Send out a weekly email of recently added content to all your Plex 'Friends'</label>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button id="recentlyAddedBtn" class="btn btn-primary-outline">Send test email to Admin</button>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
|
||||||
|
@ -382,7 +361,7 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#refreshKey').click(function (e) {
|
$('#refreshKey').click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var base = '@Html.GetBaseUrl()';
|
var base = '@Html.GetBaseUrl()';
|
||||||
var url = createBaseUrl(base, '/admin/createapikey');
|
var url = createBaseUrl(base, '/admin/createapikey');
|
||||||
|
@ -391,37 +370,13 @@
|
||||||
type: "post",
|
type: "post",
|
||||||
url: url,
|
url: url,
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (response) {
|
success: function(response) {
|
||||||
if (response) {
|
if (response) {
|
||||||
generateNotify("Success!", "success");
|
generateNotify("Success!", "success");
|
||||||
$('#apiKey').val(response);
|
$('#apiKey').val(response);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (e) {
|
error: function(e) {
|
||||||
console.log(e);
|
|
||||||
generateNotify("Something went wrong!", "danger");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#recentlyAddedBtn').click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var base = '@Html.GetBaseUrl()';
|
|
||||||
var url = createBaseUrl(base, '/admin/recentlyAddedTest');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: "post",
|
|
||||||
url: url,
|
|
||||||
dataType: "json",
|
|
||||||
success: function (response) {
|
|
||||||
if (response) {
|
|
||||||
generateNotify(response.message, "success");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
generateNotify(response.message, "danger");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function (e) {
|
|
||||||
console.log(e);
|
console.log(e);
|
||||||
generateNotify("Something went wrong!", "danger");
|
generateNotify("Something went wrong!", "danger");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@using PlexRequests.UI.Helpers
|
@using PlexRequests.UI.Helpers
|
||||||
|
@Html.LoadSettingsAssets()
|
||||||
<div class="col-lg-3 col-md-3 col-sm-4">
|
<div class="col-lg-3 col-md-3 col-sm-4">
|
||||||
<div class="list-group table-of-contents">
|
<div class="list-group table-of-contents">
|
||||||
@Html.GetSidebarUrl(Context, "/admin", "Plex Request")
|
@Html.GetSidebarUrl(Context, "/admin", "Plex Request")
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
@Html.GetSidebarUrl(Context, "/admin/sonarr", "Sonarr")
|
@Html.GetSidebarUrl(Context, "/admin/sonarr", "Sonarr")
|
||||||
@Html.GetSidebarUrl(Context, "/admin/sickrage", "SickRage")
|
@Html.GetSidebarUrl(Context, "/admin/sickrage", "SickRage")
|
||||||
@Html.GetSidebarUrl(Context, "/admin/headphones", "Headphones (Beta)")
|
@Html.GetSidebarUrl(Context, "/admin/headphones", "Headphones (Beta)")
|
||||||
|
@Html.GetSidebarUrl(Context, "/admin/newsletter", "Newsletter Settings")
|
||||||
@Html.GetSidebarUrl(Context, "/admin/emailnotification", "Email Notifications")
|
@Html.GetSidebarUrl(Context, "/admin/emailnotification", "Email Notifications")
|
||||||
@Html.GetSidebarUrl(Context, "/admin/pushbulletnotification", "Pushbullet Notifications")
|
@Html.GetSidebarUrl(Context, "/admin/pushbulletnotification", "Pushbullet Notifications")
|
||||||
@Html.GetSidebarUrl(Context, "/admin/pushovernotification", "Pushover Notifications")
|
@Html.GetSidebarUrl(Context, "/admin/pushovernotification", "Pushover Notifications")
|
||||||
|
|
|
@ -168,15 +168,38 @@
|
||||||
<a href="http://www.imdb.com/title/{{imdb}}/" target="_blank">
|
<a href="http://www.imdb.com/title/{{imdb}}/" target="_blank">
|
||||||
<h4 class="request-title">{{title}} ({{year}})</h4>
|
<h4 class="request-title">{{title}} ({{year}})</h4>
|
||||||
</a>
|
</a>
|
||||||
|
<div>
|
||||||
|
{{#if_eq type "tv"}}
|
||||||
|
<span>@UI.Search_TV_Show_Status: </span>
|
||||||
|
{{else}}
|
||||||
|
<span>@UI.Search_Movie_Status: </span>
|
||||||
|
{{/if_eq}}
|
||||||
<span class="label label-success">{{status}}</span>
|
<span class="label label-success">{{status}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>Request status: </span>
|
||||||
|
{{#if available}}
|
||||||
|
<span class="label label-success">@UI.Search_Available_on_plex</span>
|
||||||
|
{{else}}
|
||||||
|
{{#if approved}}
|
||||||
|
<span class="label label-info">@UI.Search_Processing_Request</span>
|
||||||
|
{{else if denied}}
|
||||||
|
<span class="label label-danger">@UI.Search_Request_denied</span>
|
||||||
|
{{#if deniedReason}}
|
||||||
|
<span class="customTooltip" title="{{deniedReason}}"><i class="fa fa-info-circle"></i></span>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<span class="label label-warning">@UI.Search_Pending_approval</span>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
{{#if denied}}
|
{{#if denied}}
|
||||||
<div>
|
<div>
|
||||||
Denied: <i style="color:red;" class="fa fa-check"></i>
|
Denied: <i style="color:red;" class="fa fa-check"></i>
|
||||||
{{#if deniedReason}}
|
|
||||||
<span class="customTooltip" title="{{deniedReason}}"><i class="fa fa-info-circle"></i></span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -185,31 +208,14 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
|
<div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
{{#unless denied}}
|
<br />
|
||||||
<div>
|
|
||||||
@UI.Common_Approved:
|
|
||||||
{{#if_eq approved false}}
|
|
||||||
<i id="{{requestId}}notapproved" class="fa fa-times"></i>
|
|
||||||
{{/if_eq}}
|
|
||||||
{{#if_eq approved true}}
|
|
||||||
<i class="fa fa-check"></i>
|
|
||||||
{{/if_eq}}
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
<div>
|
|
||||||
@UI.Requests_Available
|
|
||||||
{{#if_eq available false}}
|
|
||||||
<i id="availableIcon{{requestId}}" class="fa fa-times"></i>
|
|
||||||
{{/if_eq}}
|
|
||||||
{{#if_eq available true}}
|
|
||||||
<i id="availableIcon{{requestId}}" class="fa fa-check"></i>
|
|
||||||
{{/if_eq}}
|
|
||||||
</div>
|
|
||||||
{{#if_eq type "tv"}}
|
{{#if_eq type "tv"}}
|
||||||
{{#if episodes}}
|
{{#if episodes}}
|
||||||
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
|
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
|
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
{{#if requestedUsers}}
|
{{#if requestedUsers}}
|
||||||
|
@ -217,11 +223,10 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
|
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
|
||||||
<div>
|
<div>
|
||||||
@UI.Issues_Issue:
|
|
||||||
{{#if_eq issueId 0}}
|
{{#if_eq issueId 0}}
|
||||||
<i class="fa fa-times"></i>
|
@*Nothing*@
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
|
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue