diff --git a/src/Ombi.Api.Emby/EmbyApiFactory.cs b/src/Ombi.Api.Emby/EmbyApiFactory.cs
index c5c6e1c02..6f71938bf 100644
--- a/src/Ombi.Api.Emby/EmbyApiFactory.cs
+++ b/src/Ombi.Api.Emby/EmbyApiFactory.cs
@@ -25,10 +25,6 @@ namespace Ombi.Api.Emby
public IEmbyApi CreateClient(EmbySettings settings)
{
- if (settings.IsJellyfin)
- {
- return new JellyfinApi(_api);
- }
return new EmbyApi(_api);
}
}
diff --git a/src/Ombi.Api.Emby/Models/PublicInfo.cs b/src/Ombi.Api.Emby/Models/PublicInfo.cs
index 01432d3c5..23db412b5 100644
--- a/src/Ombi.Api.Emby/Models/PublicInfo.cs
+++ b/src/Ombi.Api.Emby/Models/PublicInfo.cs
@@ -5,15 +5,8 @@
public string LocalAddress { get; set; }
public string ServerName { get; set; }
public string Version { get; set; }
- ///
- /// Only populated for Jellyfin
- ///
- public string ProductName { get; set; }
-
- public bool IsJellyfin => !string.IsNullOrEmpty(ProductName) && ProductName.Contains("Jellyfin");
-
public string OperatingSystem { get; set; }
public string Id { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs b/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs
new file mode 100644
index 000000000..7a9ee8a5a
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/IBaseJellyfinApi.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Ombi.Api.Jellyfin.Models;
+using Ombi.Api.Jellyfin.Models.Media.Tv;
+using Ombi.Api.Jellyfin.Models.Movie;
+
+namespace Ombi.Api.Jellyfin
+{
+ public interface IBaseJellyfinApi
+ {
+ Task GetSystemInformation(string apiKey, string baseUrl);
+ Task> GetUsers(string baseUri, string apiKey);
+ Task LogIn(string username, string password, string apiKey, string baseUri);
+
+ Task> GetAllMovies(string apiKey, int startIndex, int count, string userId,
+ string baseUri);
+
+ Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
+ string baseUri);
+
+ Task> GetAllShows(string apiKey, int startIndex, int count, string userId,
+ string baseUri);
+
+ Task> GetCollection(string mediaId,
+ string apiKey, string userId, string baseUrl);
+
+ Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
+ Task GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);
+ Task GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl);
+ Task GetPublicInformation(string baseUrl);
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/IJellyfinApi.cs b/src/Ombi.Api.Jellyfin/IJellyfinApi.cs
new file mode 100644
index 000000000..72d45877c
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/IJellyfinApi.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Ombi.Api.Jellyfin.Models;
+
+namespace Ombi.Api.Jellyfin
+{
+ public interface IJellyfinApi : IBaseJellyfinApi
+ {
+ Task LoginConnectUser(string username, string password);
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Emby/JellyfinApi.cs b/src/Ombi.Api.Jellyfin/JellyfinApi.cs
similarity index 66%
rename from src/Ombi.Api.Emby/JellyfinApi.cs
rename to src/Ombi.Api.Jellyfin/JellyfinApi.cs
index 197fba684..a8d94eca6 100644
--- a/src/Ombi.Api.Emby/JellyfinApi.cs
+++ b/src/Ombi.Api.Jellyfin/JellyfinApi.cs
@@ -3,14 +3,14 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json;
-using Ombi.Api.Emby.Models;
-using Ombi.Api.Emby.Models.Media.Tv;
-using Ombi.Api.Emby.Models.Movie;
+using Ombi.Api.Jellyfin.Models;
+using Ombi.Api.Jellyfin.Models.Media.Tv;
+using Ombi.Api.Jellyfin.Models.Movie;
using Ombi.Helpers;
-namespace Ombi.Api.Emby
+namespace Ombi.Api.Jellyfin
{
- public class JellyfinApi : IEmbyApi
+ public class JellyfinApi : IJellyfinApi
{
public JellyfinApi(IApi api)
{
@@ -20,27 +20,27 @@ namespace Ombi.Api.Emby
private IApi Api { get; }
///
- /// Returns all users from the Emby Instance
+ /// Returns all users from the Jellyfin Instance
///
///
///
- public async Task> GetUsers(string baseUri, string apiKey)
+ public async Task> GetUsers(string baseUri, string apiKey)
{
var request = new Request("users", baseUri, HttpMethod.Get);
AddHeaders(request, apiKey);
- var obj = await Api.Request>(request);
+ var obj = await Api.Request>(request);
return obj;
}
- public async Task GetSystemInformation(string apiKey, string baseUrl)
+ public async Task GetSystemInformation(string apiKey, string baseUrl)
{
var request = new Request("System/Info", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
- var obj = await Api.Request(request);
+ var obj = await Api.Request(request);
return obj;
}
@@ -56,7 +56,7 @@ namespace Ombi.Api.Emby
return obj;
}
- public async Task LogIn(string username, string password, string apiKey, string baseUri)
+ public async Task LogIn(string username, string password, string apiKey, string baseUri)
{
var request = new Request("users/authenticatebyname", baseUri, HttpMethod.Post);
var body = new
@@ -71,11 +71,11 @@ namespace Ombi.Api.Emby
$"MediaBrowser Client=\"Ombi\", Device=\"Ombi\", DeviceId=\"v3\", Version=\"v3\"");
AddHeaders(request, apiKey);
- var obj = await Api.Request(request);
+ var obj = await Api.Request(request);
return obj;
}
- public async Task> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
+ public async Task> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
{
var request = new Request($"users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
@@ -84,22 +84,22 @@ namespace Ombi.Api.Emby
request.AddQueryString("IsVirtualItem", "False");
- return await Api.Request>(request);
+ return await Api.Request>(request);
}
- public async Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
+ public async Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
{
- return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count);
+ return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count);
}
- public async Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
+ public async Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
{
- return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count);
+ return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count);
}
- public async Task> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
+ public async Task> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
{
- return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count);
+ return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@@ -126,7 +126,7 @@ namespace Ombi.Api.Emby
return JsonConvert.DeserializeObject(response);
}
- private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview = false)
+ private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview = false)
{
var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get);
@@ -139,10 +139,10 @@ namespace Ombi.Api.Emby
AddHeaders(request, apiKey);
- var obj = await Api.Request>(request);
+ var obj = await Api.Request>(request);
return obj;
}
- private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
+ private async Task> GetAll(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
{
var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get);
@@ -157,7 +157,7 @@ namespace Ombi.Api.Emby
AddHeaders(request, apiKey);
- var obj = await Api.Request>(request);
+ var obj = await Api.Request>(request);
return obj;
}
@@ -172,7 +172,7 @@ namespace Ombi.Api.Emby
req.AddHeader("Device", "Ombi");
}
- public Task LoginConnectUser(string username, string password)
+ public Task LoginConnectUser(string username, string password)
{
throw new System.NotImplementedException();
}
diff --git a/src/Ombi.Api.Jellyfin/JellyfinApiFactory.cs b/src/Ombi.Api.Jellyfin/JellyfinApiFactory.cs
new file mode 100644
index 000000000..cc604b871
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/JellyfinApiFactory.cs
@@ -0,0 +1,37 @@
+using Ombi.Api;
+using Ombi.Core.Settings;
+using Ombi.Core.Settings.Models.External;
+using System.Threading.Tasks;
+
+namespace Ombi.Api.Jellyfin
+{
+ public class JellyfinApiFactory : IJellyfinApiFactory
+ {
+ private readonly ISettingsService _jellyfinSettings;
+ private readonly IApi _api;
+
+ // TODO, if we need to derive futher, need to rework
+ public JellyfinApiFactory(ISettingsService jellyfinSettings, IApi api)
+ {
+ _jellyfinSettings = jellyfinSettings;
+ _api = api;
+ }
+
+ public async Task CreateClient()
+ {
+ var settings = await _jellyfinSettings.GetSettingsAsync();
+ return CreateClient(settings);
+ }
+
+ public IJellyfinApi CreateClient(JellyfinSettings settings)
+ {
+ return new JellyfinApi(_api);
+ }
+ }
+
+ public interface IJellyfinApiFactory
+ {
+ Task CreateClient();
+ IJellyfinApi CreateClient(JellyfinSettings settings);
+ }
+}
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinConfiguration.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinConfiguration.cs
new file mode 100644
index 000000000..b7d2e39cb
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinConfiguration.cs
@@ -0,0 +1,45 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinConfiguration.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinConfiguration
+ {
+ public bool PlayDefaultAudioTrack { get; set; }
+ public bool DisplayMissingEpisodes { get; set; }
+ public bool DisplayUnairedEpisodes { get; set; }
+ public object[] GroupedFolders { get; set; }
+ public string SubtitleMode { get; set; }
+ public bool DisplayCollectionsView { get; set; }
+ public bool EnableLocalPassword { get; set; }
+ public object[] OrderedViews { get; set; }
+ public object[] LatestItemsExcludes { get; set; }
+ public bool HidePlayedInLatest { get; set; }
+ public bool RememberAudioSelections { get; set; }
+ public bool RememberSubtitleSelections { get; set; }
+ public bool EnableNextEpisodeAutoPlay { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinConnectUser.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinConnectUser.cs
new file mode 100644
index 000000000..a19418e3d
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinConnectUser.cs
@@ -0,0 +1,47 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinConnectUser.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinConnectUser
+ {
+ public string AccessToken { get; set; }
+ public User User { get; set; }
+ }
+
+ public class User
+ {
+ public string Id { get; set; }
+ public string Name { get; set; }
+ public string DisplayName { get; set; }
+ public string Email { get; set; }
+ public string IsActive { get; set; }
+ public string ImageUrl { get; set; }
+ public object IsSupporter { get; set; }
+ public object ExpDate { get; set; }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinItemContainer.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinItemContainer.cs
new file mode 100644
index 000000000..940d568fa
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinItemContainer.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinItemContainer
+ {
+ public List Items { get; set; }
+ public int TotalRecordCount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinMediaType.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinMediaType.cs
new file mode 100644
index 000000000..2c4f75be0
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinMediaType.cs
@@ -0,0 +1,10 @@
+namespace Ombi.Api.Jellyfin.Models
+{
+ public enum JellyfinMediaType
+ {
+ Movie = 0,
+ Series = 1,
+ Music = 2,
+ Episode = 3
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinPolicy.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinPolicy.cs
new file mode 100644
index 000000000..8091b8b34
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinPolicy.cs
@@ -0,0 +1,59 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinPolicy.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinPolicy
+ {
+ public bool IsAdministrator { get; set; }
+ public bool IsHidden { get; set; }
+ public bool IsDisabled { get; set; }
+ public object[] BlockedTags { get; set; }
+ public bool EnableUserPreferenceAccess { get; set; }
+ public object[] AccessSchedules { get; set; }
+ public object[] BlockUnratedItems { get; set; }
+ public bool EnableRemoteControlOfOtherUsers { get; set; }
+ public bool EnableSharedDeviceControl { get; set; }
+ public bool EnableLiveTvManagement { get; set; }
+ public bool EnableLiveTvAccess { get; set; }
+ public bool EnableMediaPlayback { get; set; }
+ public bool EnableAudioPlaybackTranscoding { get; set; }
+ public bool EnableVideoPlaybackTranscoding { get; set; }
+ public bool EnablePlaybackRemuxing { get; set; }
+ public bool EnableContentDeletion { get; set; }
+ public bool EnableContentDownloading { get; set; }
+ public bool EnableSync { get; set; }
+ public bool EnableSyncTranscoding { get; set; }
+ public object[] EnabledDevices { get; set; }
+ public bool EnableAllDevices { get; set; }
+ public object[] EnabledChannels { get; set; }
+ public bool EnableAllChannels { get; set; }
+ public object[] EnabledFolders { get; set; }
+ public bool EnableAllFolders { get; set; }
+ public int InvalidLoginAttemptCount { get; set; }
+ public bool EnablePublicSharing { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinSystemInfo.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinSystemInfo.cs
new file mode 100644
index 000000000..1cedee722
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinSystemInfo.cs
@@ -0,0 +1,63 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinSystemInfo.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinSystemInfo
+ {
+ public string SystemUpdateLevel { get; set; }
+ public string OperatingSystemDisplayName { get; set; }
+ public bool SupportsRunningAsService { get; set; }
+ public string MacAddress { get; set; }
+ public bool HasPendingRestart { get; set; }
+ public bool SupportsLibraryMonitor { get; set; }
+ public object[] InProgressInstallations { get; set; }
+ public int WebSocketPortNumber { get; set; }
+ public object[] CompletedInstallations { get; set; }
+ public bool CanSelfRestart { get; set; }
+ public bool CanSelfUpdate { get; set; }
+ public object[] FailedPluginAssemblies { get; set; }
+ public string ProgramDataPath { get; set; }
+ public string ItemsByNamePath { get; set; }
+ public string CachePath { get; set; }
+ public string LogPath { get; set; }
+ public string InternalMetadataPath { get; set; }
+ public string TranscodingTempPath { get; set; }
+ public int HttpServerPortNumber { get; set; }
+ public bool SupportsHttps { get; set; }
+ public int HttpsPortNumber { get; set; }
+ public bool HasUpdateAvailable { get; set; }
+ public bool SupportsAutoRunAtStartup { get; set; }
+ public string EncoderLocationType { get; set; }
+ public string SystemArchitecture { get; set; }
+ public string LocalAddress { get; set; }
+ public string WanAddress { get; set; }
+ public string ServerName { get; set; }
+ public string Version { get; set; }
+ public string OperatingSystem { get; set; }
+ public string Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinUser.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinUser.cs
new file mode 100644
index 000000000..51078f36c
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinUser.cs
@@ -0,0 +1,47 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinUser.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+
+using System;
+
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinUser
+ {
+ public string Name { get; set; }
+ public string ServerId { get; set; }
+ public string ConnectUserName { get; set; }
+ public string ConnectLinkType { get; set; }
+ public string Id { get; set; }
+ public bool HasPassword { get; set; }
+ public bool HasConfiguredPassword { get; set; }
+ public bool HasConfiguredEasyPassword { get; set; }
+ public DateTime LastLoginDate { get; set; }
+ public DateTime LastActivityDate { get; set; }
+ public JellyfinConfiguration Configuration { get; set; }
+ public JellyfinPolicy Policy { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/JellyfinUserLogin.cs b/src/Ombi.Api.Jellyfin/Models/JellyfinUserLogin.cs
new file mode 100644
index 000000000..580cc45af
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/JellyfinUserLogin.cs
@@ -0,0 +1,7 @@
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class JellyfinUserLogin
+ {
+ public JellyfinUser User { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinChapter.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinChapter.cs
new file mode 100644
index 000000000..332268ae3
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinChapter.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinChapter
+ {
+ public long StartPositionTicks { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinExternalurl.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinExternalurl.cs
new file mode 100644
index 000000000..c53ec3cdf
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinExternalurl.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinExternalurl
+ {
+ public string Name { get; set; }
+ public string Url { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinImagetags.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinImagetags.cs
new file mode 100644
index 000000000..d2dcc0f8a
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinImagetags.cs
@@ -0,0 +1,10 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinImagetags
+ {
+ public string Primary { get; set; }
+ public string Logo { get; set; }
+ public string Thumb { get; set; }
+ public string Banner { get; set; }
+ }
+}
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediasource.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediasource.cs
new file mode 100644
index 000000000..3cdec7c4c
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediasource.cs
@@ -0,0 +1,30 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinMediasource
+ {
+ public string Protocol { get; set; }
+ public string Id { get; set; }
+ public string Path { get; set; }
+ public string Type { get; set; }
+ public string Container { get; set; }
+ public string Name { get; set; }
+ public bool IsRemote { get; set; }
+ public string ETag { get; set; }
+ public long RunTimeTicks { get; set; }
+ public bool ReadAtNativeFramerate { get; set; }
+ public bool SupportsTranscoding { get; set; }
+ public bool SupportsDirectStream { get; set; }
+ public bool SupportsDirectPlay { get; set; }
+ public bool IsInfiniteStream { get; set; }
+ public bool RequiresOpening { get; set; }
+ public bool RequiresClosing { get; set; }
+ public bool SupportsProbing { get; set; }
+ public string VideoType { get; set; }
+ public JellyfinMediastream[] MediaStreams { get; set; }
+ public object[] PlayableStreamFileNames { get; set; }
+ public object[] Formats { get; set; }
+ public int Bitrate { get; set; }
+ public int DefaultAudioStreamIndex { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs
new file mode 100644
index 000000000..89da2651a
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs
@@ -0,0 +1,36 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinMediastream
+ {
+ public string Codec { get; set; }
+ public string Language { get; set; }
+ public string TimeBase { get; set; }
+ public string CodecTimeBase { get; set; }
+ public string NalLengthSize { get; set; }
+ public bool IsInterlaced { get; set; }
+ public bool IsAVC { get; set; }
+ public int BitRate { get; set; }
+ public int BitDepth { get; set; }
+ public int RefFrames { get; set; }
+ public bool IsDefault { get; set; }
+ public bool IsForced { get; set; }
+ public int Height { get; set; }
+ public int Width { get; set; }
+ public float AverageFrameRate { get; set; }
+ public float RealFrameRate { get; set; }
+ public string Profile { get; set; }
+ public string Type { get; set; }
+ public string AspectRatio { get; set; }
+ public int Index { get; set; }
+ public bool IsExternal { get; set; }
+ public bool IsTextSubtitleStream { get; set; }
+ public bool SupportsExternalStream { get; set; }
+ public string PixelFormat { get; set; }
+ public int Level { get; set; }
+ public bool IsAnamorphic { get; set; }
+ public string DisplayTitle { get; set; }
+ public string ChannelLayout { get; set; }
+ public int Channels { get; set; }
+ public int SampleRate { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinPerson.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinPerson.cs
new file mode 100644
index 000000000..19bdd3f81
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinPerson.cs
@@ -0,0 +1,11 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinPerson
+ {
+ public string Name { get; set; }
+ public string Id { get; set; }
+ public string Role { get; set; }
+ public string Type { get; set; }
+ public string PrimaryImageTag { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinProviderids.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinProviderids.cs
new file mode 100644
index 000000000..9b47f9a1a
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinProviderids.cs
@@ -0,0 +1,13 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinProviderids
+ {
+ public string Tmdb { get; set; }
+ public string Imdb { get; set; }
+ public string TmdbCollection { get; set; }
+
+ public string Tvdb { get; set; }
+ public string Zap2It { get; set; }
+ public string TvRage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinRemotetrailer.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinRemotetrailer.cs
new file mode 100644
index 000000000..325ef8519
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinRemotetrailer.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinRemotetrailer
+ {
+ public string Url { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinStudio.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinStudio.cs
new file mode 100644
index 000000000..c0d561102
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinStudio.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinStudio
+ {
+ public string Name { get; set; }
+ public string Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinUserdata.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinUserdata.cs
new file mode 100644
index 000000000..502c67822
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinUserdata.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinUserdata
+ {
+ public double PlaybackPositionTicks { get; set; }
+ public int PlayCount { get; set; }
+ public bool IsFavorite { get; set; }
+ public bool Played { get; set; }
+ public string Key { get; set; }
+ public DateTime LastPlayedDate { get; set; }
+ public int UnplayedItemCount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs b/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs
new file mode 100644
index 000000000..d86bf5047
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class JellyfinMovie
+ {
+ public string Name { get; set; }
+ public string ServerId { get; set; }
+ public string Id { get; set; }
+ public string Container { get; set; }
+ public DateTime PremiereDate { get; set; }
+ public object[] ProductionLocations { get; set; }
+ public string OfficialRating { get; set; }
+ public float CommunityRating { get; set; }
+ public long RunTimeTicks { get; set; }
+ public string PlayAccess { get; set; }
+ public int ProductionYear { get; set; }
+ public bool IsPlaceHolder { get; set; }
+ public bool IsHD { get; set; }
+ public bool IsFolder { get; set; }
+ public string Type { get; set; }
+ public int LocalTrailerCount { get; set; }
+ public JellyfinUserdata UserData { get; set; }
+ public string VideoType { get; set; }
+ public JellyfinImagetags ImageTags { get; set; }
+ public string[] BackdropImageTags { get; set; }
+ public string LocationType { get; set; }
+ public string MediaType { get; set; }
+ public bool HasSubtitles { get; set; }
+ public int CriticRating { get; set; }
+ public string Overview { get; set; }
+ public JellyfinProviderids ProviderIds { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Movie/MovieInformation.cs b/src/Ombi.Api.Jellyfin/Models/Media/Movie/MovieInformation.cs
new file mode 100644
index 000000000..3dba49bea
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Movie/MovieInformation.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace Ombi.Api.Jellyfin.Models.Movie
+{
+ public class MovieInformation
+ {
+ public string Name { get; set; }
+ public string OriginalTitle { get; set; }
+ public string ServerId { get; set; }
+ public string Id { get; set; }
+ public string Etag { get; set; }
+ public DateTime DateCreated { get; set; }
+ public bool CanDelete { get; set; }
+ public bool CanDownload { get; set; }
+ public bool SupportsSync { get; set; }
+ public string Container { get; set; }
+ public string SortName { get; set; }
+ public DateTime PremiereDate { get; set; }
+ public JellyfinExternalurl[] ExternalUrls { get; set; }
+ public JellyfinMediasource[] MediaSources { get; set; }
+ public string[] ProductionLocations { get; set; }
+ public string Path { get; set; }
+ public string OfficialRating { get; set; }
+ public string Overview { get; set; }
+ public string[] Taglines { get; set; }
+ public string[] Genres { get; set; }
+ public float CommunityRating { get; set; }
+ public int VoteCount { get; set; }
+ public long RunTimeTicks { get; set; }
+ public string PlayAccess { get; set; }
+ public int ProductionYear { get; set; }
+ public bool IsPlaceHolder { get; set; }
+ public JellyfinRemotetrailer[] RemoteTrailers { get; set; }
+ public JellyfinProviderids ProviderIds { get; set; }
+ public bool IsHD { get; set; }
+ public bool IsFolder { get; set; }
+ public string ParentId { get; set; }
+ public string Type { get; set; }
+ public JellyfinPerson[] People { get; set; }
+ public JellyfinStudio[] Studios { get; set; }
+ public int LocalTrailerCount { get; set; }
+ public JellyfinUserdata UserData { get; set; }
+ public string DisplayPreferencesId { get; set; }
+ public object[] Tags { get; set; }
+ public string[] Keywords { get; set; }
+ public JellyfinMediastream[] MediaStreams { get; set; }
+ public string VideoType { get; set; }
+ public JellyfinImagetags ImageTags { get; set; }
+ public string[] BackdropImageTags { get; set; }
+ public object[] ScreenshotImageTags { get; set; }
+ public JellyfinChapter[] Chapters { get; set; }
+ public string LocationType { get; set; }
+ public string MediaType { get; set; }
+ public string HomePageUrl { get; set; }
+ public int Budget { get; set; }
+ public float Revenue { get; set; }
+ public object[] LockedFields { get; set; }
+ public bool LockData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Tv/EpisodeInformation.cs b/src/Ombi.Api.Jellyfin/Models/Media/Tv/EpisodeInformation.cs
new file mode 100644
index 000000000..4df1e9bcc
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Tv/EpisodeInformation.cs
@@ -0,0 +1,71 @@
+using System;
+using Ombi.Api.Jellyfin.Models.Movie;
+
+namespace Ombi.Api.Jellyfin.Models.Media.Tv
+{
+ public class EpisodeInformation
+ {
+ public string Name { get; set; }
+ public string ServerId { get; set; }
+ public string Id { get; set; }
+ public string Etag { get; set; }
+ public DateTime DateCreated { get; set; }
+ public bool CanDelete { get; set; }
+ public bool CanDownload { get; set; }
+ public bool SupportsSync { get; set; }
+ public string Container { get; set; }
+ public string SortName { get; set; }
+ public DateTime PremiereDate { get; set; }
+ public JellyfinExternalurl[] ExternalUrls { get; set; }
+ public JellyfinMediasource[] MediaSources { get; set; }
+ public string Path { get; set; }
+ public string Overview { get; set; }
+ public object[] Taglines { get; set; }
+ public object[] Genres { get; set; }
+ public string[] SeriesGenres { get; set; }
+ public float CommunityRating { get; set; }
+ public int VoteCount { get; set; }
+ public long RunTimeTicks { get; set; }
+ public string PlayAccess { get; set; }
+ public int ProductionYear { get; set; }
+ public bool IsPlaceHolder { get; set; }
+ public int IndexNumber { get; set; }
+ public int ParentIndexNumber { get; set; }
+ public object[] RemoteTrailers { get; set; }
+ public JellyfinProviderids ProviderIds { get; set; }
+ public bool IsHD { get; set; }
+ public bool IsFolder { get; set; }
+ public string ParentId { get; set; }
+ public string Type { get; set; }
+ public object[] People { get; set; }
+ public object[] Studios { get; set; }
+ public string ParentLogoItemId { get; set; }
+ public string ParentBackdropItemId { get; set; }
+ public string[] ParentBackdropImageTags { get; set; }
+ public int LocalTrailerCount { get; set; }
+ public JellyfinUserdata UserData { get; set; }
+ public string SeriesName { get; set; }
+ public string SeriesId { get; set; }
+ public string SeasonId { get; set; }
+ public string DisplayPreferencesId { get; set; }
+ public object[] Tags { get; set; }
+ public object[] Keywords { get; set; }
+ public string SeriesPrimaryImageTag { get; set; }
+ public string SeasonName { get; set; }
+ public JellyfinMediastream[] MediaStreams { get; set; }
+ public string VideoType { get; set; }
+ public JellyfinImagetags ImageTags { get; set; }
+ public object[] BackdropImageTags { get; set; }
+ public object[] ScreenshotImageTags { get; set; }
+ public string ParentLogoImageTag { get; set; }
+ public string SeriesStudio { get; set; }
+ public JellyfinSeriesstudioinfo SeriesStudioInfo { get; set; }
+ public string ParentThumbItemId { get; set; }
+ public string ParentThumbImageTag { get; set; }
+ public JellyfinChapter[] Chapters { get; set; }
+ public string LocationType { get; set; }
+ public string MediaType { get; set; }
+ public object[] LockedFields { get; set; }
+ public bool LockData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinEpisodes.cs b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinEpisodes.cs
new file mode 100644
index 000000000..a2769138e
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinEpisodes.cs
@@ -0,0 +1,45 @@
+using Ombi.Api.Jellyfin.Models.Movie;
+using System;
+
+namespace Ombi.Api.Jellyfin.Models.Media.Tv
+{
+ public class JellyfinEpisodes
+ {
+ public string Name { get; set; }
+ public string ServerId { get; set; }
+ public string Id { get; set; }
+ public string Container { get; set; }
+ public DateTime PremiereDate { get; set; }
+ public float CommunityRating { get; set; }
+ public long RunTimeTicks { get; set; }
+ public string PlayAccess { get; set; }
+ public int ProductionYear { get; set; }
+ public bool IsPlaceHolder { get; set; }
+ public int IndexNumber { get; set; }
+ public int? IndexNumberEnd { get; set; }
+ public int ParentIndexNumber { get; set; }
+ public bool IsHD { get; set; }
+ public bool IsFolder { get; set; }
+ public string Type { get; set; }
+ public string ParentLogoItemId { get; set; }
+ public string ParentBackdropItemId { get; set; }
+ public string[] ParentBackdropImageTags { get; set; }
+ public int LocalTrailerCount { get; set; }
+ public JellyfinUserdata UserData { get; set; }
+ public string SeriesName { get; set; }
+ public string SeriesId { get; set; }
+ public string SeasonId { get; set; }
+ public string SeriesPrimaryImageTag { get; set; }
+ public string SeasonName { get; set; }
+ public string VideoType { get; set; }
+ public JellyfinImagetags ImageTags { get; set; }
+ public object[] BackdropImageTags { get; set; }
+ public string ParentLogoImageTag { get; set; }
+ public string ParentThumbItemId { get; set; }
+ public string ParentThumbImageTag { get; set; }
+ public string LocationType { get; set; }
+ public string MediaType { get; set; }
+ public bool HasSubtitles { get; set; }
+ public JellyfinProviderids ProviderIds { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinRemotetrailer.cs b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinRemotetrailer.cs
new file mode 100644
index 000000000..663dc4ce3
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinRemotetrailer.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.Jellyfin.Models.Media.Tv
+{
+ public class JellyfinRemotetrailer
+ {
+ public string Url { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeries.cs b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeries.cs
new file mode 100644
index 000000000..0d0fb21be
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeries.cs
@@ -0,0 +1,32 @@
+using Ombi.Api.Jellyfin.Models.Movie;
+using System;
+
+namespace Ombi.Api.Jellyfin.Models.Media.Tv
+{
+ public class JellyfinSeries
+ {
+ public string Name { get; set; }
+ public string ServerId { get; set; }
+ public string Id { get; set; }
+ public DateTime PremiereDate { get; set; }
+ public string OfficialRating { get; set; }
+ public float CommunityRating { get; set; }
+ public long RunTimeTicks { get; set; }
+ public string PlayAccess { get; set; }
+ public int ProductionYear { get; set; }
+ public bool IsFolder { get; set; }
+ public string Type { get; set; }
+ public int LocalTrailerCount { get; set; }
+ public JellyfinUserdata UserData { get; set; }
+ public int ChildCount { get; set; }
+ public string Status { get; set; }
+ public string AirTime { get; set; }
+ public string[] AirDays { get; set; }
+ public JellyfinImagetags ImageTags { get; set; }
+ public string[] BackdropImageTags { get; set; }
+ public string LocationType { get; set; }
+ public DateTime EndDate { get; set; }
+
+ public JellyfinProviderids ProviderIds { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeriesstudioinfo.cs b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeriesstudioinfo.cs
new file mode 100644
index 000000000..ada6be68d
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Tv/JellyfinSeriesstudioinfo.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.Jellyfin.Models.Media.Tv
+{
+ public class JellyfinSeriesstudioinfo
+ {
+ public string Name { get; set; }
+ public string Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Tv/SeriesInformation.cs b/src/Ombi.Api.Jellyfin/Models/Media/Tv/SeriesInformation.cs
new file mode 100644
index 000000000..efb48560e
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/Media/Tv/SeriesInformation.cs
@@ -0,0 +1,59 @@
+using System;
+using Ombi.Api.Jellyfin.Models.Movie;
+
+namespace Ombi.Api.Jellyfin.Models.Media.Tv
+{
+ public class SeriesInformation
+ {
+
+ public string Name { get; set; }
+ public string ServerId { get; set; }
+ public string Id { get; set; }
+ public string Etag { get; set; }
+ public DateTime DateCreated { get; set; }
+ public DateTime DateLastMediaAdded { get; set; }
+ public bool CanDelete { get; set; }
+ public bool CanDownload { get; set; }
+ public bool SupportsSync { get; set; }
+ public string SortName { get; set; }
+ public DateTime PremiereDate { get; set; }
+ public JellyfinExternalurl[] ExternalUrls { get; set; }
+ public string Path { get; set; }
+ public string OfficialRating { get; set; }
+ public string Overview { get; set; }
+ public string ShortOverview { get; set; }
+ public object[] Taglines { get; set; }
+ public string[] Genres { get; set; }
+ public float CommunityRating { get; set; }
+ public int VoteCount { get; set; }
+ public long CumulativeRunTimeTicks { get; set; }
+ public long RunTimeTicks { get; set; }
+ public string PlayAccess { get; set; }
+ public int ProductionYear { get; set; }
+ public JellyfinRemotetrailer[] RemoteTrailers { get; set; }
+ public JellyfinProviderids ProviderIds { get; set; }
+ public bool IsFolder { get; set; }
+ public string ParentId { get; set; }
+ public string Type { get; set; }
+ public JellyfinPerson[] People { get; set; }
+ public JellyfinStudio[] Studios { get; set; }
+ public int LocalTrailerCount { get; set; }
+ public JellyfinUserdata UserData { get; set; }
+ public int RecursiveItemCount { get; set; }
+ public int ChildCount { get; set; }
+ public string DisplayPreferencesId { get; set; }
+ public string Status { get; set; }
+ public string AirTime { get; set; }
+ public string[] AirDays { get; set; }
+ public object[] Tags { get; set; }
+ public object[] Keywords { get; set; }
+ public JellyfinImagetags ImageTags { get; set; }
+ public string[] BackdropImageTags { get; set; }
+ public object[] ScreenshotImageTags { get; set; }
+ public string LocationType { get; set; }
+ public string HomePageUrl { get; set; }
+ public object[] LockedFields { get; set; }
+ public bool LockData { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Api.Jellyfin/Models/PublicInfo.cs b/src/Ombi.Api.Jellyfin/Models/PublicInfo.cs
new file mode 100644
index 000000000..6687cf3c9
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Models/PublicInfo.cs
@@ -0,0 +1,12 @@
+namespace Ombi.Api.Jellyfin.Models
+{
+ public class PublicInfo
+ {
+ public string LocalAddress { get; set; }
+ public string ServerName { get; set; }
+ public string Version { get; set; }
+ public string OperatingSystem { get; set; }
+ public string Id { get; set; }
+ }
+
+}
diff --git a/src/Ombi.Api.Jellyfin/Ombi.Api.Jellyfin.csproj b/src/Ombi.Api.Jellyfin/Ombi.Api.Jellyfin.csproj
new file mode 100644
index 000000000..5457b0290
--- /dev/null
+++ b/src/Ombi.Api.Jellyfin/Ombi.Api.Jellyfin.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net5.0
+ 3.0.0.0
+ 3.0.0.0
+
+
+ 8.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs b/src/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs
index 69d8231ad..3b47cdd28 100644
--- a/src/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs
+++ b/src/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs
@@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Hqub.MusicBrainz.API.Entities;
+using Hqub.MusicBrainz.API.Entities.Collections;
using Ombi.Api.MusicBrainz.Models;
namespace Ombi.Api.MusicBrainz
@@ -11,6 +12,7 @@ namespace Ombi.Api.MusicBrainz
Task> SearchArtist(string artistQuery);
Task> GetReleaseForArtist(string artistId);
Task GetArtistInformation(string artistId);
+ Task GetAlbumInformation(string albumId);
Task GetCoverArtForReleaseGroup(string musicBrainzId, CancellationToken token);
}
}
\ No newline at end of file
diff --git a/src/Ombi.Api.MusicBrainz/MusicBrainzApi.cs b/src/Ombi.Api.MusicBrainz/MusicBrainzApi.cs
index 3c86d7882..e870eb07f 100644
--- a/src/Ombi.Api.MusicBrainz/MusicBrainzApi.cs
+++ b/src/Ombi.Api.MusicBrainz/MusicBrainzApi.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Hqub.MusicBrainz.API;
using Hqub.MusicBrainz.API.Entities;
+using Hqub.MusicBrainz.API.Entities.Collections;
using Newtonsoft.Json;
using Ombi.Api.MusicBrainz.Models;
@@ -20,6 +21,12 @@ namespace Ombi.Api.MusicBrainz
_api = api;
}
+ public Task GetAlbumInformation(string albumId)
+ {
+ var album = Release.GetAsync(albumId);
+ return album;
+ }
+
public async Task> SearchArtist(string artistQuery)
{
var artist = await Artist.SearchAsync(artistQuery, 10);
diff --git a/src/Ombi.Api.RottenTomatoes/IRottenTomatoesApi.cs b/src/Ombi.Api.RottenTomatoes/IRottenTomatoesApi.cs
new file mode 100644
index 000000000..4466832b1
--- /dev/null
+++ b/src/Ombi.Api.RottenTomatoes/IRottenTomatoesApi.cs
@@ -0,0 +1,14 @@
+using Ombi.Api.RottenTomatoes.Models;
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Ombi.Api.RottenTomatoes
+{
+ public interface IRottenTomatoesApi
+ {
+ Task GetMovieRatings(string movieName, int movieYear);
+ Task GetTvRatings(string showName, int showYear);
+ }
+}
diff --git a/src/Ombi.Api.RottenTomatoes/Models/RottenTomatoesMovieResponse.cs b/src/Ombi.Api.RottenTomatoes/Models/RottenTomatoesMovieResponse.cs
new file mode 100644
index 000000000..ceea5000b
--- /dev/null
+++ b/src/Ombi.Api.RottenTomatoes/Models/RottenTomatoesMovieResponse.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+
+namespace Ombi.Api.RottenTomatoes.Models
+{
+ public class RottenTomatoesMovieResponse
+ {
+ public int total { get; set; }
+ public List movies { get; set; }
+ }
+
+ public class Movie
+ {
+ public string id { get; set; }
+ public string title { get; set; }
+ public int year { get; set; }
+ public string mpaa_rating { get; set; }
+ public object runtime { get; set; }
+ public string critics_consensus { get; set; }
+ public MovieRatings ratings { get; set; }
+ public Links links { get; set; }
+ }
+
+ public class MovieRatings
+ {
+ public string critics_rating { get; set; }
+ public int critics_score { get; set; }
+ public string audience_rating { get; set; }
+ public int audience_score { get; set; }
+ }
+
+ public class Links
+ {
+ public string alternate { get; set; }
+ }
+}
diff --git a/src/Ombi.Api.RottenTomatoes/Models/RottenTomatoesTvResponse.cs b/src/Ombi.Api.RottenTomatoes/Models/RottenTomatoesTvResponse.cs
new file mode 100644
index 000000000..234e958be
--- /dev/null
+++ b/src/Ombi.Api.RottenTomatoes/Models/RottenTomatoesTvResponse.cs
@@ -0,0 +1,20 @@
+namespace Ombi.Api.RottenTomatoes.Models
+{
+ public class RottenTomatoesTvResponse
+ {
+ public int tvCount { get; set; }
+ public TvSeries[] tvSeries { get; set; }
+ }
+
+ public class TvSeries
+ {
+ public string title { get; set; }
+ public int startYear { get; set; }
+ public int endYear { get; set; }
+ public string url { get; set; }
+ public string meterClass { get; set; }
+ public int meterScore { get; set; }
+ public string image { get; set; }
+ }
+
+}
diff --git a/src/Ombi.Api.RottenTomatoes/Models/TvRatings.cs b/src/Ombi.Api.RottenTomatoes/Models/TvRatings.cs
new file mode 100644
index 000000000..902d532d7
--- /dev/null
+++ b/src/Ombi.Api.RottenTomatoes/Models/TvRatings.cs
@@ -0,0 +1,8 @@
+namespace Ombi.Api.RottenTomatoes.Models
+{
+ public class TvRatings
+ {
+ public string Class { get; set; }
+ public int Score { get; set; }
+ }
+}
diff --git a/src/Ombi.Api.RottenTomatoes/Ombi.Api.RottenTomatoes.csproj b/src/Ombi.Api.RottenTomatoes/Ombi.Api.RottenTomatoes.csproj
new file mode 100644
index 000000000..8cb4799bc
--- /dev/null
+++ b/src/Ombi.Api.RottenTomatoes/Ombi.Api.RottenTomatoes.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net5.0
+ 8.0
+
+
+
+
+
+
+
diff --git a/src/Ombi.Api.RottenTomatoes/RottenTomatoesApi.cs b/src/Ombi.Api.RottenTomatoes/RottenTomatoesApi.cs
new file mode 100644
index 000000000..88dfa2f79
--- /dev/null
+++ b/src/Ombi.Api.RottenTomatoes/RottenTomatoesApi.cs
@@ -0,0 +1,56 @@
+using Ombi.Api.RottenTomatoes.Models;
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Ombi.Api.RottenTomatoes
+{
+ public class RottenTomatoesApi : IRottenTomatoesApi
+ {
+ public RottenTomatoesApi(IApi api)
+ {
+ _api = api;
+ }
+
+ private string Endpoint => "https://www.rottentomatoes.com/api/private";
+ private IApi _api { get; }
+
+ public async Task GetMovieRatings(string movieName, int movieYear)
+ {
+ var request = new Request("/v1.0/movies", Endpoint, HttpMethod.Get);
+ request.AddHeader("Accept", "application/json");
+ request.AddQueryString("q", movieName);
+ var result = await _api.Request(request);
+
+ var movieFound = result.movies.FirstOrDefault(x => x.year == movieYear);
+ if (movieFound == null)
+ {
+ return null;
+ }
+
+ return movieFound.ratings;
+ }
+
+ public async Task GetTvRatings(string showName, int showYear)
+ {
+ var request = new Request("/v2.0/search/", Endpoint, HttpMethod.Get);
+ request.AddHeader("Accept", "application/json");
+ request.AddQueryString("q", showName);
+ request.AddQueryString("limit", 10.ToString());
+ var result = await _api.Request(request);
+
+ var showFound = result.tvSeries.FirstOrDefault(x => x.startYear == showYear);
+ if (showFound == null)
+ {
+ return null;
+ }
+
+ return new TvRatings
+ {
+ Class = showFound.meterClass,
+ Score = showFound.meterScore
+ };
+ }
+ }
+}
diff --git a/src/Ombi.Core.Tests/Authentication/OmbiUserManagerTests.cs b/src/Ombi.Core.Tests/Authentication/OmbiUserManagerTests.cs
index bd6e8d5a2..f4e6f59a3 100644
--- a/src/Ombi.Core.Tests/Authentication/OmbiUserManagerTests.cs
+++ b/src/Ombi.Core.Tests/Authentication/OmbiUserManagerTests.cs
@@ -30,7 +30,7 @@ namespace Ombi.Core.Tests.Authentication
AuthenticationSettings.Setup(x => x.GetSettingsAsync())
.ReturnsAsync(new AuthenticationSettings());
_um = new OmbiUserManager(UserStore.Object, null, null, null, null, null, null, null, null,
- PlexApi.Object, null, null, AuthenticationSettings.Object);
+ PlexApi.Object, null, null, null, null, AuthenticationSettings.Object);
}
public OmbiUserManager _um { get; set; }
diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs
index 8e5c57d67..132f51e49 100644
--- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs
+++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs
@@ -115,4 +115,4 @@ namespace Ombi.Core.Tests.Rule.Search
Assert.False(search.Available);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
new file mode 100644
index 000000000..1b838f102
--- /dev/null
+++ b/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
@@ -0,0 +1,119 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Moq;
+using NUnit.Framework;
+using Ombi.Core.Models.Search;
+using Ombi.Core.Rule.Rules.Search;
+using Ombi.Core.Settings;
+using Ombi.Core.Settings.Models.External;
+using Ombi.Store.Entities;
+using Ombi.Store.Repository;
+using Ombi.Store.Repository.Requests;
+
+namespace Ombi.Core.Tests.Rule.Search
+{
+ public class JellyfinAvailabilityRuleTests
+ {
+ [SetUp]
+ public void Setup()
+ {
+ ContextMock = new Mock();
+ SettingsMock = new Mock>();
+ Rule = new JellyfinAvailabilityRule(ContextMock.Object, SettingsMock.Object);
+ }
+
+ private JellyfinAvailabilityRule Rule { get; set; }
+ private Mock ContextMock { get; set; }
+ private Mock> SettingsMock { get; set; }
+
+ [Test]
+ public async Task Movie_ShouldBe_Available_WhenFoundInJellyfin()
+ {
+ SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings());
+ ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent
+ {
+ ProviderId = "123"
+ });
+ var search = new SearchMovieViewModel()
+ {
+ TheMovieDbId = "123",
+ };
+ var result = await Rule.Execute(search);
+
+ Assert.True(result.Success);
+ Assert.True(search.Available);
+ }
+
+ [Test]
+ public async Task Movie_Has_Custom_Url_When_Specified_In_Settings()
+ {
+ SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings
+ {
+ Enable = true,
+ Servers = new List
+ {
+ new JellyfinServers
+ {
+ ServerHostname = "http://test.com/",
+ ServerId = "8"
+ }
+ }
+ });
+ ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent
+ {
+ ProviderId = "123",
+ JellyfinId = 1.ToString(),
+ });
+ var search = new SearchMovieViewModel()
+ {
+ TheMovieDbId = "123",
+ };
+ var result = await Rule.Execute(search);
+
+ Assert.True(result.Success);
+ Assert.That(search.JellyfinUrl, Is.EqualTo("http://test.com/web/index.html#!/details?id=1&serverId=8"));
+ }
+
+ [Test]
+ public async Task Movie_Uses_Default_Url_When()
+ {
+ SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings
+ {
+ Enable = true,
+ Servers = new List
+ {
+ new JellyfinServers
+ {
+ Ip = "8080",
+ Port = 9090,
+ ServerHostname = string.Empty,
+ ServerId = "8"
+ }
+ }
+ });
+ ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent
+ {
+ ProviderId = "123",
+ JellyfinId = 1.ToString()
+ });
+ var search = new SearchMovieViewModel()
+ {
+ TheMovieDbId = "123",
+ };
+ var result = await Rule.Execute(search);
+
+ Assert.True(result.Success);
+ }
+
+ [Test]
+ public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInJellyfin()
+ {
+ ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).Returns(Task.FromResult(default(JellyfinContent)));
+ var search = new SearchMovieViewModel();
+ var result = await Rule.Execute(search);
+
+ Assert.True(result.Success);
+ Assert.False(search.Available);
+ }
+ }
+}
diff --git a/src/Ombi.Core.Tests/WatchProviderParserTests.cs b/src/Ombi.Core.Tests/WatchProviderParserTests.cs
new file mode 100644
index 000000000..c56f64946
--- /dev/null
+++ b/src/Ombi.Core.Tests/WatchProviderParserTests.cs
@@ -0,0 +1,94 @@
+using NUnit.Framework;
+using Ombi.Api.TheMovieDb.Models;
+using Ombi.Core.Helpers;
+using Ombi.Store.Entities;
+using System.Collections.Generic;
+
+namespace Ombi.Core.Tests
+{
+ [TestFixture]
+ public class WatchProviderParserTests
+ {
+ [TestCase("GB", TestName = "UpperCase")]
+ [TestCase("gb", TestName = "LowerCase")]
+ [TestCase("gB", TestName = "MixedCase")]
+ public void GetValidStreamData(string streamingCountry)
+ {
+ var result = WatchProviderParser.GetUserWatchProviders(new WatchProviders
+ {
+ Results = new Results
+ {
+ GB = new WatchProviderData()
+ {
+ StreamInformation = new List
+ {
+ new StreamData
+ {
+ provider_name = "Netflix",
+ display_priority = 0,
+ logo_path = "logo",
+ provider_id = 8
+ }
+ }
+ }
+ }
+ }, new OmbiUser { StreamingCountry = streamingCountry });
+
+ Assert.That(result[0].provider_name, Is.EqualTo("Netflix"));
+ }
+
+ [TestCase("GB", TestName = "Missing_UpperCase")]
+ [TestCase("gb", TestName = "Missing_LowerCase")]
+ [TestCase("gB", TestName = "Missing_MixedCase")]
+ public void GetMissingStreamData(string streamingCountry)
+ {
+ var result = WatchProviderParser.GetUserWatchProviders(new WatchProviders
+ {
+ Results = new Results
+ {
+ AR = new WatchProviderData()
+ {
+ StreamInformation = new List
+ {
+ new StreamData
+ {
+ provider_name = "Netflix",
+ display_priority = 0,
+ logo_path = "logo",
+ provider_id = 8
+ }
+ }
+ }
+ }
+ }, new OmbiUser { StreamingCountry = streamingCountry });
+
+ Assert.That(result, Is.Empty);
+ }
+
+ [Test]
+ public void GetInvalidStreamData()
+ {
+ var result = WatchProviderParser.GetUserWatchProviders(new WatchProviders
+ {
+ Results = new Results
+ {
+ AR = new WatchProviderData()
+ {
+ StreamInformation = new List
+ {
+ new StreamData
+ {
+ provider_name = "Netflix",
+ display_priority = 0,
+ logo_path = "logo",
+ provider_id = 8
+ }
+ }
+ }
+ }
+ }, new OmbiUser { StreamingCountry = "BLAH" });
+
+ Assert.That(result, Is.Empty);
+ }
+ }
+}
diff --git a/src/Ombi.Core/Authentication/OmbiUserManager.cs b/src/Ombi.Core/Authentication/OmbiUserManager.cs
index 8313f359b..87f82c1de 100644
--- a/src/Ombi.Core/Authentication/OmbiUserManager.cs
+++ b/src/Ombi.Core/Authentication/OmbiUserManager.cs
@@ -33,6 +33,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Ombi.Api.Emby;
+using Ombi.Api.Jellyfin;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Core.Settings;
@@ -49,18 +50,24 @@ namespace Ombi.Core.Authentication
IPasswordHasher passwordHasher, IEnumerable> userValidators,
IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger, IPlexApi plexApi,
- IEmbyApiFactory embyApi, ISettingsService embySettings, ISettingsService auth)
+ IEmbyApiFactory embyApi, ISettingsService embySettings,
+ IJellyfinApiFactory jellyfinApi, ISettingsService jellyfinSettings,
+ ISettingsService auth)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
_plexApi = plexApi;
_embyApi = embyApi;
+ _jellyfinApi = jellyfinApi;
_embySettings = embySettings;
+ _jellyfinSettings = jellyfinSettings;
_authSettings = auth;
}
private readonly IPlexApi _plexApi;
private readonly IEmbyApiFactory _embyApi;
+ private readonly IJellyfinApiFactory _jellyfinApi;
private readonly ISettingsService _embySettings;
+ private readonly ISettingsService _jellyfinSettings;
private readonly ISettingsService _authSettings;
public override async Task CheckPasswordAsync(OmbiUser user, string password)
@@ -83,6 +90,10 @@ namespace Ombi.Core.Authentication
{
return await CheckEmbyPasswordAsync(user, password);
}
+ if (user.UserType == UserType.JellyfinUser)
+ {
+ return await CheckJellyfinPasswordAsync(user, password);
+ }
return false;
}
@@ -185,5 +196,36 @@ namespace Ombi.Core.Authentication
}
return false;
}
+
+ ///
+ /// Sign the user into Jellyfin
+ /// We do not check if the user is in the owners "friends" since they must have a local user account to get this far.
+ /// We also have to try and authenticate them with every server, the first server that work we just say it was a success
+ ///
+ ///
+ ///
+ ///
+ private async Task CheckJellyfinPasswordAsync(OmbiUser user, string password)
+ {
+ var jellyfinSettings = await _jellyfinSettings.GetSettingsAsync();
+ var client = _jellyfinApi.CreateClient(jellyfinSettings);
+
+ foreach (var server in jellyfinSettings.Servers)
+ {
+ try
+ {
+ var result = await client.LogIn(user.UserName, password, server.ApiKey, server.FullUri);
+ if (result != null)
+ {
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e, "Jellyfin Login Failed");
+ }
+ }
+ return false;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs
index fc9847c7d..66e60767a 100644
--- a/src/Ombi.Core/Engine/BaseMediaEngine.cs
+++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs
@@ -15,6 +15,8 @@ using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
+using Ombi.Api.TheMovieDb.Models;
+using Ombi.Core.Helpers;
namespace Ombi.Core.Engine
{
@@ -179,6 +181,12 @@ namespace Ombi.Core.Engine
return user.Language;
}
+ protected async Task> GetUserWatchProvider(WatchProviders providers)
+ {
+ var user = await GetUser();
+ return WatchProviderParser.GetUserWatchProviders(providers, user);
+ }
+
private OmbiSettings ombiSettings;
protected async Task GetOmbiSettings()
{
diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs b/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs
index 3b8d97dc0..746045ef3 100644
--- a/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs
+++ b/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs
@@ -26,5 +26,6 @@ namespace Ombi.Core.Engine.Interfaces
int ResultLimit { get; set; }
Task GetMovieInfoByImdbId(string imdbId, CancellationToken requestAborted);
+ Task> GetStreamInformation(int movieDbId, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs
index f9889ec9c..efd42dc68 100644
--- a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs
+++ b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngineV2.cs
@@ -9,5 +9,6 @@ namespace Ombi.Core.Engine.Interfaces
Task GetArtistInformation(string artistId);
Task GetArtistInformationByRequestId(int requestId);
Task GetReleaseGroupArt(string musicBrainzId, CancellationToken token);
+ Task GetAlbum(string albumId);
}
}
\ No newline at end of file
diff --git a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs
index a8a27aa19..d2201825f 100644
--- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs
+++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs
@@ -1,4 +1,6 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Ombi.Core.Models.Search.V2;
namespace Ombi.Core
@@ -7,5 +9,6 @@ namespace Ombi.Core
{
Task GetShowInformation(int tvdbid);
Task GetShowByRequest(int requestId);
+ Task> GetStreamInformation(int tvDbId, int tvMazeId, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs
index 741503795..c9dbde067 100644
--- a/src/Ombi.Core/Engine/MovieRequestEngine.cs
+++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs
@@ -67,6 +67,22 @@ namespace Ombi.Core.Engine
$"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}";
var userDetails = await GetUser();
+ var canRequestOnBehalf = false;
+
+ if (model.RequestOnBehalf.HasValue())
+ {
+ canRequestOnBehalf = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
+
+ if (!canRequestOnBehalf)
+ {
+ return new RequestEngineResult
+ {
+ Result = false,
+ Message = "You do not have the correct permissions to request on behalf of users!",
+ ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
+ };
+ }
+ }
var requestModel = new MovieRequests
{
@@ -82,7 +98,7 @@ namespace Ombi.Core.Engine
Status = movieInfo.Status,
RequestedDate = DateTime.UtcNow,
Approved = false,
- RequestedUserId = userDetails.Id,
+ RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
Background = movieInfo.BackdropPath,
LangCode = model.LanguageCode,
RequestedByAlias = model.RequestedByAlias
@@ -103,7 +119,7 @@ namespace Ombi.Core.Engine
if (requestModel.Approved) // The rules have auto approved this
{
- var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName);
+ var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
if (requestEngineResult.Result)
{
var result = await ApproveMovie(requestModel);
@@ -124,7 +140,7 @@ namespace Ombi.Core.Engine
// If there are no providers then it's successful but movie has not been sent
}
- return await AddMovieRequest(requestModel, fullMovieName);
+ return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
}
@@ -270,7 +286,7 @@ namespace Ombi.Core.Engine
allRequests = allRequests.Where(x => x.Available);
break;
case RequestStatus.Denied:
- allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
+ allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
break;
default:
break;
@@ -429,7 +445,7 @@ namespace Ombi.Core.Engine
public async Task GetRequest(int requestId)
{
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
- await CheckForSubscription(new HideResult(), new List{request });
+ await CheckForSubscription(new HideResult(), new List { request });
return request;
}
@@ -654,19 +670,19 @@ namespace Ombi.Core.Engine
};
}
- private async Task AddMovieRequest(MovieRequests model, string movieName)
+ private async Task AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf)
{
await MovieRepository.Add(model);
var result = await RunSpecificRule(model, SpecificRules.CanSendNotification);
if (result.Success)
- {
+ {
await NotificationHelper.NewRequest(model);
}
await _requestLog.Add(new RequestLog
{
- UserId = (await GetUser()).Id,
+ UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.Movie,
diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs
index e7491ff39..d597ec80b 100644
--- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs
+++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs
@@ -13,15 +13,17 @@ namespace Ombi.Core.Engine
{
public class RecentlyAddedEngine : IRecentlyAddedEngine
{
- public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository recentlyAdded)
+ public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IJellyfinContentRepository jellyfin, IRepository recentlyAdded)
{
_plex = plex;
_emby = emby;
+ _jellyfin = jellyfin;
_recentlyAddedLog = recentlyAdded;
}
private readonly IPlexContentRepository _plex;
private readonly IEmbyContentRepository _emby;
+ private readonly IJellyfinContentRepository _jellyfin;
private readonly IRepository _recentlyAddedLog;
@@ -30,23 +32,26 @@ namespace Ombi.Core.Engine
{
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt > from && x.AddedAt < to);
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
+ var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == JellyfinMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
- return GetRecentlyAddedMovies(plexMovies, embyMovies).Take(30);
+ return GetRecentlyAddedMovies(plexMovies, embyMovies, jellyfinMovies).Take(30);
}
public IEnumerable GetRecentlyAddedMovies()
{
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie);
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie);
- return GetRecentlyAddedMovies(plexMovies, embyMovies);
+ var jellyfinMovies = _jellyfin.GetAll().Where(x => x.Type == JellyfinMediaType.Movie);
+ return GetRecentlyAddedMovies(plexMovies, embyMovies, jellyfinMovies);
}
public IEnumerable GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason)
{
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to);
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to);
+ var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == JellyfinMediaType.Series && x.AddedAt > from && x.AddedAt < to);
- return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason).Take(30);
+ return GetRecentlyAddedTv(plexTv, embyTv, jellyfinTv, groupBySeason).Take(30);
}
@@ -54,14 +59,16 @@ namespace Ombi.Core.Engine
{
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series);
+ var jellyfinTv = _jellyfin.GetAll().Include(x => x.Episodes).Where(x => x.Type == JellyfinMediaType.Series);
- return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason);
+ return GetRecentlyAddedTv(plexTv, embyTv, jellyfinTv, groupBySeason);
}
public async Task UpdateRecentlyAddedDatabase()
{
var plexContent = _plex.GetAll().Include(x => x.Episodes);
var embyContent = _emby.GetAll().Include(x => x.Episodes);
+ var jellyfinContent = _jellyfin.GetAll().Include(x => x.Episodes);
var recentlyAddedLog = new HashSet();
foreach (var p in plexContent)
{
@@ -138,17 +145,56 @@ namespace Ombi.Core.Engine
}
}
}
+
+ foreach (var e in jellyfinContent)
+ {
+ if (e.TheMovieDbId.IsNullOrEmpty())
+ {
+ continue;
+ }
+ if (e.Type == JellyfinMediaType.Movie)
+ {
+ recentlyAddedLog.Add(new RecentlyAddedLog
+ {
+ AddedAt = DateTime.Now,
+ Type = RecentlyAddedType.Jellyfin,
+ ContentId = int.Parse(e.TheMovieDbId),
+ ContentType = ContentType.Parent
+ });
+ }
+ else
+ {
+ // Add the episodes
+ foreach (var ep in e.Episodes)
+ {
+ if (ep.Series.TvDbId.IsNullOrEmpty())
+ {
+ continue;
+ }
+ recentlyAddedLog.Add(new RecentlyAddedLog
+ {
+ AddedAt = DateTime.Now,
+ Type = RecentlyAddedType.Jellyfin,
+ ContentId = int.Parse(ep.Series.TvDbId),
+ ContentType = ContentType.Episode,
+ EpisodeNumber = ep.EpisodeNumber,
+ SeasonNumber = ep.SeasonNumber
+ });
+ }
+ }
+ }
await _recentlyAddedLog.AddRange(recentlyAddedLog);
return true;
}
- private IEnumerable GetRecentlyAddedTv(IQueryable plexTv, IQueryable embyTv,
+ private IEnumerable GetRecentlyAddedTv(IQueryable plexTv, IQueryable embyTv, IQueryable jellyfinTv,
bool groupBySeason)
{
var model = new HashSet();
TransformPlexShows(plexTv, model);
TransformEmbyShows(embyTv, model);
+ TransformJellyfinShows(jellyfinTv, model);
if (groupBySeason)
{
@@ -158,11 +204,12 @@ namespace Ombi.Core.Engine
return model;
}
- private IEnumerable GetRecentlyAddedMovies(IQueryable plexMovies, IQueryable embyMovies)
+ private IEnumerable GetRecentlyAddedMovies(IQueryable plexMovies, IQueryable embyMovies, IQueryable jellyfinMovies)
{
var model = new HashSet();
TransformPlexMovies(plexMovies, model);
TransformEmbyMovies(embyMovies, model);
+ TransformJellyfinMovies(jellyfinMovies, model);
return model;
}
@@ -183,6 +230,22 @@ namespace Ombi.Core.Engine
}
}
+ private static void TransformJellyfinMovies(IQueryable jellyfinMovies, HashSet model)
+ {
+ foreach (var jellyfin in jellyfinMovies)
+ {
+ model.Add(new RecentlyAddedMovieModel
+ {
+ Id = jellyfin.Id,
+ ImdbId = jellyfin.ImdbId,
+ TheMovieDbId = jellyfin.TheMovieDbId,
+ TvDbId = jellyfin.TvDbId,
+ AddedAt = jellyfin.AddedAt,
+ Title = jellyfin.Title,
+ });
+ }
+ }
+
private static void TransformPlexMovies(IQueryable plexMovies, HashSet model)
{
foreach (var plex in plexMovies)
@@ -246,5 +309,26 @@ namespace Ombi.Core.Engine
}
}
}
+
+ private static void TransformJellyfinShows(IQueryable jellyfinShows, HashSet model)
+ {
+ foreach (var jellyfin in jellyfinShows)
+ {
+ foreach (var episode in jellyfin.Episodes)
+ {
+ model.Add(new RecentlyAddedTvModel
+ {
+ Id = jellyfin.Id,
+ ImdbId = jellyfin.ImdbId,
+ TvDbId = jellyfin.TvDbId,
+ TheMovieDbId = jellyfin.TheMovieDbId,
+ AddedAt = jellyfin.AddedAt,
+ Title = jellyfin.Title,
+ EpisodeNumber = episode.EpisodeNumber,
+ SeasonNumber = episode.SeasonNumber
+ });
+ }
+ }
+ }
}
}
diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs
index e066261f4..b06c99d49 100644
--- a/src/Ombi.Core/Engine/TvRequestEngine.cs
+++ b/src/Ombi.Core/Engine/TvRequestEngine.cs
@@ -51,12 +51,28 @@ namespace Ombi.Core.Engine
public async Task RequestTvShow(TvRequestViewModel tv)
{
var user = await GetUser();
+ var canRequestOnBehalf = false;
+
+ if (tv.RequestOnBehalf.HasValue())
+ {
+ canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
+
+ if (!canRequestOnBehalf)
+ {
+ return new RequestEngineResult
+ {
+ Result = false,
+ Message = "You do not have the correct permissions to request on behalf of users!",
+ ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
+ };
+ }
+ }
var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi);
(await tvBuilder
.GetShowInfo(tv.TvDbId))
.CreateTvList(tv)
- .CreateChild(tv, user.Id);
+ .CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id);
await tvBuilder.BuildEpisodes(tv);
@@ -124,12 +140,12 @@ namespace Ombi.Core.Engine
ErrorMessage = "This has already been requested"
};
}
- return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest);
+ return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf);
}
// This is a new request
var newRequest = tvBuilder.CreateNewRequest(tv);
- return await AddRequest(newRequest.NewRequest);
+ return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
}
public async Task> GetRequests(int count, int position, OrderFilterModel type)
@@ -736,21 +752,21 @@ namespace Ombi.Core.Engine
}
}
- private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest)
+ private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf)
{
// Add the child
existingRequest.ChildRequests.Add(newRequest);
await TvRepository.Update(existingRequest);
- return await AfterRequest(newRequest);
+ return await AfterRequest(newRequest, requestOnBehalf);
}
- private async Task AddRequest(TvRequests model)
+ private async Task AddRequest(TvRequests model, string requestOnBehalf)
{
await TvRepository.Add(model);
// This is a new request so we should only have 1 child
- return await AfterRequest(model.ChildRequests.FirstOrDefault());
+ return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf);
}
private static List SortEpisodes(List items)
@@ -766,7 +782,7 @@ namespace Ombi.Core.Engine
}
- private async Task AfterRequest(ChildRequests model)
+ private async Task AfterRequest(ChildRequests model, string requestOnBehalf)
{
var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification);
if (sendRuleResult.Success)
@@ -776,7 +792,7 @@ namespace Ombi.Core.Engine
await _requestLog.Add(new RequestLog
{
- UserId = (await GetUser()).Id,
+ UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.TvShow,
diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs
index b0a78fbb3..dc009371a 100644
--- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs
+++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs
@@ -249,6 +249,26 @@ namespace Ombi.Core.Engine.V2
return result;
}
+ public async Task> GetStreamInformation(int movieDbId, CancellationToken cancellationToken)
+ {
+ var providers = await MovieApi.GetMovieWatchProviders(movieDbId, cancellationToken);
+ var results = await GetUserWatchProvider(providers);
+
+ var data = new List();
+
+ foreach (var result in results)
+ {
+ data.Add(new StreamingData
+ {
+ Logo = result.logo_path,
+ Order = result.display_priority,
+ StreamingProvider = result.provider_name
+ });
+ }
+
+ return data;
+ }
+
protected async Task> TransformMovieResultsToResponse(
IEnumerable movies)
{
@@ -287,6 +307,7 @@ namespace Ombi.Core.Engine.V2
mapped.Requested = viewMovie.Requested;
mapped.PlexUrl = viewMovie.PlexUrl;
mapped.EmbyUrl = viewMovie.EmbyUrl;
+ mapped.JellyfinUrl = viewMovie.JellyfinUrl;
mapped.Subscribed = viewMovie.Subscribed;
mapped.ShowSubscribe = viewMovie.ShowSubscribe;
diff --git a/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs
index 13256be0a..b7d9575db 100644
--- a/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs
+++ b/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
+using Newtonsoft.Json;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
using Ombi.Api.MusicBrainz;
@@ -41,6 +42,21 @@ namespace Ombi.Core.Engine.V2
_lidarrApi = lidarrApi;
}
+ public async Task GetAlbum(string albumId)
+ {
+ var g = await _musicBrainzApi.GetAlbumInformation(albumId);
+ var release = new ReleaseGroup
+ {
+ ReleaseType = g.ReleaseGroup.PrimaryType,
+ Id = g.Id,
+ Title = g.Title,
+ ReleaseDate = g.ReleaseGroup.FirstReleaseDate,
+ };
+
+ await RunSearchRules(release);
+ return release;
+ }
+
public async Task GetArtistInformation(string artistId)
{
var artist = await _musicBrainzApi.GetArtistInformation(artistId);
@@ -84,12 +100,19 @@ namespace Ombi.Core.Engine.V2
if (lidarrArtistTask != null)
{
- var artistResult = await lidarrArtistTask;
- info.Banner = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("banner", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
- info.Logo = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("logo", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
- info.Poster = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("poster", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
- info.FanArt = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("fanart", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
- info.Overview = artistResult.overview;
+ try
+ {
+ var artistResult = await lidarrArtistTask;
+ info.Banner = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("banner", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
+ info.Logo = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("logo", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
+ info.Poster = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("poster", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
+ info.FanArt = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("fanart", StringComparison.InvariantCultureIgnoreCase))?.url.ToHttpsUrl();
+ info.Overview = artistResult.overview;
+ }
+ catch (JsonSerializationException)
+ {
+ // swallow, Lidarr probably doesn't have this artist
+ }
}
return info;
@@ -118,7 +141,7 @@ namespace Ombi.Core.Engine.V2
return new AlbumArt();
}
-
+
public async Task GetArtistInformationByRequestId(int requestId)
{
var request = await RequestService.MusicRequestRepository.Find(requestId);
diff --git a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs
index dd2ce22aa..29ea01879 100644
--- a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs
+++ b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs
@@ -19,6 +19,8 @@ using Ombi.Core.Settings;
using Ombi.Store.Repository;
using TraktSharp.Entities;
using Microsoft.EntityFrameworkCore;
+using System.Threading;
+using Ombi.Api.TheMovieDb;
namespace Ombi.Core.Engine.V2
{
@@ -27,15 +29,17 @@ namespace Ombi.Core.Engine.V2
private readonly ITvMazeApi _tvMaze;
private readonly IMapper _mapper;
private readonly ITraktApi _traktApi;
+ private readonly IMovieDbApi _movieApi;
public TvSearchEngineV2(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, ISettingsService s,
- IRepository sub)
+ IRepository sub, IMovieDbApi movieApi)
: base(identity, service, r, um, memCache, s, sub)
{
_tvMaze = tvMaze;
_mapper = mapper;
_traktApi = trakt;
+ _movieApi = movieApi;
}
@@ -106,6 +110,39 @@ namespace Ombi.Core.Engine.V2
return await ProcessResult(mapped, traktInfoTask);
}
+ public async Task> GetStreamInformation(int tvDbId, int tvMazeId, CancellationToken cancellationToken)
+ {
+ var tvdbshow = await Cache.GetOrAdd(nameof(GetShowInformation) + tvMazeId,
+ async () => await _tvMaze.ShowLookupByTheTvDbId(tvMazeId), DateTime.Now.AddHours(12));
+ if (tvdbshow == null)
+ {
+ return null;
+ }
+
+ /// this is a best effort guess since TV maze do not provide the TheMovieDbId
+ var movieDbResults = await _movieApi.SearchTv(tvdbshow.name, tvdbshow.premiered.Substring(0, 4));
+ var potential = movieDbResults.FirstOrDefault();
+ tvDbId = potential.Id;
+ // end guess
+
+ var providers = await _movieApi.GetTvWatchProviders(tvDbId, cancellationToken);
+ var results = await GetUserWatchProvider(providers);
+
+ var data = new List();
+
+ foreach (var result in results)
+ {
+ data.Add(new StreamingData
+ {
+ Logo = result.logo_path,
+ Order = result.display_priority,
+ StreamingProvider = result.provider_name
+ });
+ }
+
+ return data;
+ }
+
private IEnumerable ProcessResults(IEnumerable items)
{
var retVal = new List();
@@ -141,7 +178,7 @@ namespace Ombi.Core.Engine.V2
{
item.Images.Medium = item.Images.Medium.ToHttpsUrl();
}
-
+
if (item.Cast?.Any() ?? false)
{
foreach (var cast in item.Cast)
diff --git a/src/Ombi.Core/Helpers/WatchProviderParser.cs b/src/Ombi.Core/Helpers/WatchProviderParser.cs
new file mode 100644
index 000000000..68f3c26dd
--- /dev/null
+++ b/src/Ombi.Core/Helpers/WatchProviderParser.cs
@@ -0,0 +1,35 @@
+using Ombi.Api.TheMovieDb.Models;
+using Ombi.Store.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ombi.Core.Helpers
+{
+ public static class WatchProviderParser
+ {
+ public static List GetUserWatchProviders(WatchProviders providers, OmbiUser user)
+ {
+ var data = new List();
+
+ if (providers?.Results == null)
+ {
+ return data;
+ }
+
+ var resultsProp = providers.Results.GetType().GetProperties();
+ var matchingStreamingCountry = resultsProp.FirstOrDefault(x => x.Name.Equals(user.StreamingCountry, StringComparison.InvariantCultureIgnoreCase));
+ if (matchingStreamingCountry == null)
+ {
+ return data;
+ }
+
+ var result = (WatchProviderData)matchingStreamingCountry.GetValue(providers.Results);
+ if (result == null || result.StreamInformation == null)
+ {
+ return data;
+ }
+ return result.StreamInformation;
+ }
+ }
+}
diff --git a/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs b/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
index c63ea98d3..4ed713453 100644
--- a/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
+++ b/src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
@@ -18,6 +18,7 @@ namespace Ombi.Core.Models
public enum RecentlyAddedType
{
Plex,
- Emby
+ Emby,
+ Jellyfin
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs
index 5a79d2982..22d1cc449 100644
--- a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs
+++ b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs
@@ -33,6 +33,7 @@ namespace Ombi.Core.Models.Requests
{
public int TheMovieDbId { get; set; }
public string LanguageCode { get; set; } = "en";
+ public string RequestOnBehalf { get; set; }
///
/// This is only set from a HTTP Header
diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs
index c17925b1b..15349462b 100644
--- a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs
+++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs
@@ -12,6 +12,8 @@ namespace Ombi.Core.Models.Requests
public List Seasons { get; set; } = new List();
[JsonIgnore]
public string RequestedByAlias { get; set; }
+
+ public string RequestOnBehalf { get; set; }
}
public class SeasonsViewModel
diff --git a/src/Ombi.Core/Models/Search/SearchViewModel.cs b/src/Ombi.Core/Models/Search/SearchViewModel.cs
index 2f951c97e..4cf812982 100644
--- a/src/Ombi.Core/Models/Search/SearchViewModel.cs
+++ b/src/Ombi.Core/Models/Search/SearchViewModel.cs
@@ -14,11 +14,12 @@ namespace Ombi.Core.Models.Search
public bool Available { get; set; }
public string PlexUrl { get; set; }
public string EmbyUrl { get; set; }
+ public string JellyfinUrl { get; set; }
public string Quality { get; set; }
public abstract RequestType Type { get; }
///
- /// This is used for the PlexAvailabilityCheck/EmbyAvailabilityRule rule
+ /// This is used for the PlexAvailabilityCheck/EmbyAvailabilityRule/JellyfinAvailabilityRule rule
///
///
/// The custom identifier.
@@ -35,4 +36,4 @@ namespace Ombi.Core.Models.Search
[NotMapped]
public bool ShowSubscribe { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core/Models/Search/V2/StreamingData.cs b/src/Ombi.Core/Models/Search/V2/StreamingData.cs
new file mode 100644
index 000000000..d9444c2ce
--- /dev/null
+++ b/src/Ombi.Core/Models/Search/V2/StreamingData.cs
@@ -0,0 +1,9 @@
+namespace Ombi.Core.Models.Search.V2
+{
+ public class StreamingData
+ {
+ public int Order { get; set; }
+ public string StreamingProvider { get; set; }
+ public string Logo { get; set; }
+ }
+}
diff --git a/src/Ombi.Core/Models/UI/UserViewModel.cs b/src/Ombi.Core/Models/UI/UserViewModel.cs
index e74e5037d..0c9be846a 100644
--- a/src/Ombi.Core/Models/UI/UserViewModel.cs
+++ b/src/Ombi.Core/Models/UI/UserViewModel.cs
@@ -18,6 +18,7 @@ namespace Ombi.Core.Models.UI
public UserType UserType { get; set; }
public int MovieRequestLimit { get; set; }
public int EpisodeRequestLimit { get; set; }
+ public string StreamingCountry { get; set; }
public RequestQuotaCountModel EpisodeRequestQuota { get; set; }
public RequestQuotaCountModel MovieRequestQuota { get; set; }
public RequestQuotaCountModel MusicRequestQuota { get; set; }
@@ -30,4 +31,10 @@ namespace Ombi.Core.Models.UI
public string Value { get; set; }
public bool Enabled { get; set; }
}
+
+ public class UserViewModelDropdown
+ {
+ public string Id { get; set; }
+ public string Username { get; set; }
+ }
}
\ No newline at end of file
diff --git a/src/Ombi.Core/Models/UserDto.cs b/src/Ombi.Core/Models/UserDto.cs
index 5c629fb39..7fbdb3465 100644
--- a/src/Ombi.Core/Models/UserDto.cs
+++ b/src/Ombi.Core/Models/UserDto.cs
@@ -19,6 +19,7 @@ namespace Ombi.Core.Models
{
LocalUser = 1,
PlexUser = 2,
- EmbyUser = 3
+ EmbyUser = 3,
+ JellyfinUser = 5
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj
index 8acbaceb0..8fa0f976f 100644
--- a/src/Ombi.Core/Ombi.Core.csproj
+++ b/src/Ombi.Core/Ombi.Core.csproj
@@ -24,6 +24,7 @@
+
@@ -40,4 +41,4 @@
-
\ No newline at end of file
+
diff --git a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs
index 24faa3a97..9076cd232 100644
--- a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs
+++ b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs
@@ -108,10 +108,40 @@ namespace Ombi.Core.Rule.Rules.Search
x.Series.TvDbId == item.TvDbId);
}
+ if (epExists != null)
+ {
+ episode.Available = true;
+ }
+ }
+ public static async Task SingleEpisodeCheck(bool useImdb, IQueryable allEpisodes, EpisodeRequests episode,
+ SeasonRequests season, JellyfinContent item, bool useTheMovieDb, bool useTvDb)
+ {
+ JellyfinEpisode epExists = null;
+ if (useImdb)
+ {
+ epExists = await allEpisodes.FirstOrDefaultAsync(x =>
+ x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
+ x.Series.ImdbId == item.ImdbId);
+ }
+
+ if (useTheMovieDb)
+ {
+ epExists = await allEpisodes.FirstOrDefaultAsync(x =>
+ x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
+ x.Series.TheMovieDbId == item.TheMovieDbId);
+ }
+
+ if (useTvDb)
+ {
+ epExists = await allEpisodes.FirstOrDefaultAsync(x =>
+ x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
+ x.Series.TvDbId == item.TvDbId);
+ }
+
if (epExists != null)
{
episode.Available = true;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs
index 75b6633bb..3fe11cbc4 100644
--- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs
+++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs
@@ -70,11 +70,11 @@ namespace Ombi.Core.Rule.Rules.Search
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
- obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname, s.IsJellyfin);
+ obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname);
}
else
{
- obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, null, s.IsJellyfin);
+ obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, null);
}
}
@@ -100,4 +100,4 @@ namespace Ombi.Core.Rule.Rules.Search
return Success();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs
new file mode 100644
index 000000000..0447458d9
--- /dev/null
+++ b/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs
@@ -0,0 +1,104 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Ombi.Core.Models.Search;
+using Ombi.Core.Rule.Interfaces;
+using Ombi.Core.Settings;
+using Ombi.Core.Settings.Models.External;
+using Ombi.Helpers;
+using Ombi.Store.Entities;
+using Ombi.Store.Repository;
+
+namespace Ombi.Core.Rule.Rules.Search
+{
+ public class JellyfinAvailabilityRule : BaseSearchRule, IRules
+ {
+ public JellyfinAvailabilityRule(IJellyfinContentRepository repo, ISettingsService s)
+ {
+ JellyfinContentRepository = repo;
+ JellyfinSettings = s;
+ }
+
+ private IJellyfinContentRepository JellyfinContentRepository { get; }
+ private ISettingsService JellyfinSettings { get; }
+
+ public async Task Execute(SearchViewModel obj)
+ {
+ JellyfinContent item = null;
+ var useImdb = false;
+ var useTheMovieDb = false;
+ var useTvDb = false;
+
+ if (obj.ImdbId.HasValue())
+ {
+ item = await JellyfinContentRepository.GetByImdbId(obj.ImdbId);
+ if (item != null)
+ {
+ useImdb = true;
+ }
+ }
+ if (item == null)
+ {
+ if (obj.TheMovieDbId.HasValue())
+ {
+ item = await JellyfinContentRepository.GetByTheMovieDbId(obj.TheMovieDbId);
+ if (item != null)
+ {
+ useTheMovieDb = true;
+ }
+ }
+
+ if (item == null)
+ {
+ if (obj.TheTvDbId.HasValue())
+ {
+ item = await JellyfinContentRepository.GetByTvDbId(obj.TheTvDbId);
+ if (item != null)
+ {
+ useTvDb = true;
+ }
+ }
+ }
+ }
+
+ if (item != null)
+ {
+ obj.Available = true;
+ var s = await JellyfinSettings.GetSettingsAsync();
+ if (s.Enable)
+ {
+ var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
+ if ((server?.ServerHostname ?? string.Empty).HasValue())
+ {
+ obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, server?.ServerId, server?.ServerHostname);
+ }
+ else
+ {
+ var firstServer = s.Servers?.FirstOrDefault();
+ obj.JellyfinUrl = JellyfinHelper.GetJellyfinMediaUrl(item.JellyfinId, firstServer.ServerId, firstServer.FullUri);
+ }
+ }
+
+ if (obj.Type == RequestType.TvShow)
+ {
+ var search = (SearchTvShowViewModel)obj;
+ // Let's go through the episodes now
+ if (search.SeasonRequests.Any())
+ {
+ var allEpisodes = JellyfinContentRepository.GetAllEpisodes().Include(x => x.Series);
+ foreach (var season in search.SeasonRequests)
+ {
+ foreach (var episode in season.Episodes)
+ {
+ await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
+ }
+ }
+ }
+
+ AvailabilityRuleHelper.CheckForUnairedEpisodes(search);
+ }
+ }
+ return Success();
+ }
+ }
+}
diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs
index 76f4879ce..d5e577d84 100644
--- a/src/Ombi.DependencyInjection/IocExtensions.cs
+++ b/src/Ombi.DependencyInjection/IocExtensions.cs
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Ombi.Api.Discord;
using Ombi.Api.Emby;
+using Ombi.Api.Jellyfin;
using Ombi.Api.Plex;
using Ombi.Api.Radarr;
using Ombi.Api.Sonarr;
@@ -47,6 +48,7 @@ using Ombi.Core.Senders;
using Ombi.Helpers;
using Ombi.Schedule.Jobs.Couchpotato;
using Ombi.Schedule.Jobs.Emby;
+using Ombi.Schedule.Jobs.Jellyfin;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Sonarr;
@@ -65,6 +67,7 @@ using Quartz.Spi;
using Ombi.Api.MusicBrainz;
using Ombi.Api.Twilio;
using Ombi.Api.CloudService;
+using Ombi.Api.RottenTomatoes;
namespace Ombi.DependencyInjection
{
@@ -126,6 +129,7 @@ namespace Ombi.DependencyInjection
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
@@ -153,8 +157,9 @@ namespace Ombi.DependencyInjection
services.AddTransient();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
}
public static void RegisterStore(this IServiceCollection services) {
@@ -169,6 +174,7 @@ namespace Ombi.DependencyInjection
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
@@ -213,6 +219,9 @@ namespace Ombi.DependencyInjection
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
@@ -220,6 +229,7 @@ namespace Ombi.DependencyInjection
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj
index bbfe532eb..ed1e9e4a2 100644
--- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj
+++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj
@@ -32,6 +32,7 @@
+
diff --git a/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs b/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs
index a68ce327b..cb41ab31d 100644
--- a/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs
+++ b/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs
@@ -3,6 +3,8 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
using Ombi.Api.CouchPotato;
using Ombi.Api.Emby;
using Ombi.Api.Emby.Models;
+using Ombi.Api.Jellyfin;
+using Ombi.Api.Jellyfin.Models;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models.Status;
using Ombi.Core.Settings;
diff --git a/src/Ombi.HealthChecks/Checks/JellyfinHealthCheck.cs b/src/Ombi.HealthChecks/Checks/JellyfinHealthCheck.cs
new file mode 100644
index 000000000..2f758961c
--- /dev/null
+++ b/src/Ombi.HealthChecks/Checks/JellyfinHealthCheck.cs
@@ -0,0 +1,54 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Ombi.Api.Jellyfin;
+using Ombi.Api.Jellyfin.Models;
+using Ombi.Api.Plex;
+using Ombi.Api.Plex.Models.Status;
+using Ombi.Core.Settings;
+using Ombi.Core.Settings.Models.External;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Ombi.HealthChecks.Checks
+{
+ public class JellyfinHealthCheck : BaseHealthCheck
+ {
+ public JellyfinHealthCheck(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
+ {
+ }
+ public override async Task CheckHealthAsync(
+ HealthCheckContext context,
+ CancellationToken cancellationToken = default)
+ {
+ using (var scope = CreateScope())
+ {
+ var settingsProvider = scope.ServiceProvider.GetRequiredService>();
+ var api = scope.ServiceProvider.GetRequiredService();
+ var settings = await settingsProvider.GetSettingsAsync();
+ if (settings == null)
+ {
+ return HealthCheckResult.Healthy("Jellyfin is not configured.");
+ }
+
+ var client = api.CreateClient(settings);
+ var taskResult = new List>();
+ foreach (var server in settings.Servers)
+ {
+ taskResult.Add(client.GetSystemInformation(server.ApiKey, server.FullUri));
+ }
+
+ try
+ {
+ var result = await Task.WhenAll(taskResult.ToArray());
+ return HealthCheckResult.Healthy();
+ }
+ catch (Exception e)
+ {
+ return HealthCheckResult.Unhealthy("Could not communicate with Jellyfin", e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs b/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs
index c348e8edf..68fb5fa84 100644
--- a/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs
+++ b/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs
@@ -3,6 +3,8 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
using Ombi.Api.CouchPotato;
using Ombi.Api.Emby;
using Ombi.Api.Emby.Models;
+using Ombi.Api.Jellyfin;
+using Ombi.Api.Jellyfin.Models;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models.Status;
using Ombi.Api.SickRage;
diff --git a/src/Ombi.HealthChecks/HealthCheckExtensions.cs b/src/Ombi.HealthChecks/HealthCheckExtensions.cs
index e608d5ec0..2f80378ff 100644
--- a/src/Ombi.HealthChecks/HealthCheckExtensions.cs
+++ b/src/Ombi.HealthChecks/HealthCheckExtensions.cs
@@ -12,6 +12,7 @@ namespace Ombi.HealthChecks
{
builder.AddCheck("Plex", tags: new string[] { "MediaServer" });
builder.AddCheck("Emby", tags: new string[] { "MediaServer" });
+ builder.AddCheck("Jellyfin", tags: new string[] { "MediaServer" });
builder.AddCheck("Lidarr", tags: new string[] { "DVR" });
builder.AddCheck("Sonarr", tags: new string[] { "DVR" });
builder.AddCheck("Radarr", tags: new string[] { "DVR" });
diff --git a/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj b/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj
index 5171c9c36..20cb07609 100644
--- a/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj
+++ b/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Ombi.Helpers.Tests/EmbyHelperTests.cs b/src/Ombi.Helpers.Tests/EmbyHelperTests.cs
index 261ba87cc..50ac9c48f 100644
--- a/src/Ombi.Helpers.Tests/EmbyHelperTests.cs
+++ b/src/Ombi.Helpers.Tests/EmbyHelperTests.cs
@@ -15,13 +15,6 @@ namespace Ombi.Helpers.Tests
return EmbyHelper.GetEmbyMediaUrl(mediaId, serverId, url);
}
- [TestCaseSource(nameof(JellyfinUrlData))]
- public string TestJellyfinUrl(string mediaId, string url, string serverId)
- {
- // http://192.168.68.X:8097/web/index.html#!/details?id=7ffe222498445d5ebfddb31bc4fa9a6d&serverId=50cce67f0baa425093d189b3017331fb
- return EmbyHelper.GetEmbyMediaUrl(mediaId, serverId, url, true);
- }
-
public static IEnumerable UrlData
{
get
@@ -33,16 +26,5 @@ namespace Ombi.Helpers.Tests
yield return new TestCaseData(mediaId.ToString(), string.Empty, "1").Returns($"https://app.emby.media/web/index.html#!/item?id={mediaId}&serverId=1").SetName("EmbyHelper_GetMediaUrl_WithOutCustomDomain");
}
}
-
- public static IEnumerable JellyfinUrlData
- {
- get
- {
- var mediaId = 1;
- yield return new TestCaseData(mediaId.ToString(), "http://google.com", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("EmbyHelperJellyfin_GetMediaUrl_WithCustomDomain_WithoutTrailingSlash");
- yield return new TestCaseData(mediaId.ToString(), "http://google.com/", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("EmbyHelperJellyfin_GetMediaUrl_WithCustomDomain");
- yield return new TestCaseData(mediaId.ToString(), "https://google.com/", "1").Returns($"https://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("EmbyHelperJellyfin_GetMediaUrl_WithCustomDomain_Https");
- }
- }
}
}
diff --git a/src/Ombi.Helpers.Tests/JellyfinHelperTests.cs b/src/Ombi.Helpers.Tests/JellyfinHelperTests.cs
new file mode 100644
index 000000000..df3f960d4
--- /dev/null
+++ b/src/Ombi.Helpers.Tests/JellyfinHelperTests.cs
@@ -0,0 +1,29 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ombi.Helpers.Tests
+{
+ [TestFixture]
+ public class JellyfinHelperTests
+ {
+ [TestCaseSource(nameof(UrlData))]
+ public string TestUrl(string mediaId, string url, string serverId)
+ {
+ // http://192.168.68.X:8097/web/index.html#!/details?id=7ffe222498445d5ebfddb31bc4fa9a6d&serverId=50cce67f0baa425093d189b3017331fb
+ return JellyfinHelper.GetJellyfinMediaUrl(mediaId, serverId, url);
+ }
+
+ public static IEnumerable UrlData
+ {
+ get
+ {
+ var mediaId = 1;
+ yield return new TestCaseData(mediaId.ToString(), "http://google.com", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("JellyfinHelper_GetMediaUrl_WithCustomDomain_WithoutTrailingSlash");
+ yield return new TestCaseData(mediaId.ToString(), "http://google.com/", "1").Returns($"http://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("JellyfinHelper_GetMediaUrl_WithCustomDomain");
+ yield return new TestCaseData(mediaId.ToString(), "https://google.com/", "1").Returns($"https://google.com/web/index.html#!/details?id={mediaId}&serverId=1").SetName("JellyfinHelper_GetMediaUrl_WithCustomDomain_Https");
+ }
+ }
+ }
+}
diff --git a/src/Ombi.Helpers/EmbyHelper.cs b/src/Ombi.Helpers/EmbyHelper.cs
index 785ca47d4..db739b375 100644
--- a/src/Ombi.Helpers/EmbyHelper.cs
+++ b/src/Ombi.Helpers/EmbyHelper.cs
@@ -2,14 +2,10 @@
{
public static class EmbyHelper
{
- public static string GetEmbyMediaUrl(string mediaId, string serverId, string customerServerUrl = null, bool isJellyfin = false)
+ public static string GetEmbyMediaUrl(string mediaId, string serverId, string customerServerUrl = null)
{
//web/index.html#!/details|item
string path = "item";
- if (isJellyfin)
- {
- path = "details";
- }
if (customerServerUrl.HasValue())
{
if (!customerServerUrl.EndsWith("/"))
diff --git a/src/Ombi.Helpers/JellyfinHelper.cs b/src/Ombi.Helpers/JellyfinHelper.cs
new file mode 100644
index 000000000..506341d7d
--- /dev/null
+++ b/src/Ombi.Helpers/JellyfinHelper.cs
@@ -0,0 +1,23 @@
+namespace Ombi.Helpers
+{
+ public static class JellyfinHelper
+ {
+ public static string GetJellyfinMediaUrl(string mediaId, string serverId, string customerServerUrl = null)
+ {
+ //web/index.html#!/details|item
+ string path = "details";
+ if (customerServerUrl.HasValue())
+ {
+ if (!customerServerUrl.EndsWith("/"))
+ {
+ return $"{customerServerUrl}/web/index.html#!/{path}?id={mediaId}&serverId={serverId}";
+ }
+ return $"{customerServerUrl}web/index.html#!/{path}?id={mediaId}&serverId={serverId}";
+ }
+ else
+ {
+ return $"http://localhost:8096/web/index.html#!/{path}?id={mediaId}&serverId={serverId}";
+ }
+ }
+ }
+}
diff --git a/src/Ombi.Helpers/LoggingEvents.cs b/src/Ombi.Helpers/LoggingEvents.cs
index a7c61d9d2..ed6a7bc2a 100644
--- a/src/Ombi.Helpers/LoggingEvents.cs
+++ b/src/Ombi.Helpers/LoggingEvents.cs
@@ -14,8 +14,10 @@ namespace Ombi.Helpers
public static EventId RadarrCacher => new EventId(2001);
public static EventId PlexEpisodeCacher => new EventId(2002);
public static EventId EmbyContentCacher => new EventId(2003);
+ public static EventId JellyfinContentCacher => new EventId(2012);
public static EventId PlexUserImporter => new EventId(2004);
public static EventId EmbyUserImporter => new EventId(2005);
+ public static EventId JellyfinUserImporter => new EventId(2013);
public static EventId SonarrCacher => new EventId(2006);
public static EventId CouchPotatoCacher => new EventId(2007);
public static EventId PlexContentCacher => new EventId(2008);
@@ -43,4 +45,4 @@ namespace Ombi.Helpers
public static EventId Updater => new EventId(6000);
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Helpers/StartupSingleton.cs b/src/Ombi.Helpers/StartupSingleton.cs
index 41fd1a9ab..c9df7665a 100644
--- a/src/Ombi.Helpers/StartupSingleton.cs
+++ b/src/Ombi.Helpers/StartupSingleton.cs
@@ -11,5 +11,8 @@
public string StoragePath { get; set; }
public string SecurityKey { get; set; }
+#if DEBUG
+ = "test";
+#endif
}
}
\ No newline at end of file
diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs
index f8bde2755..866216fe4 100644
--- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs
+++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs
@@ -58,7 +58,7 @@ namespace Ombi.Schedule.Jobs.Emby
{
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Emby Content Sync Failed");
- _logger.LogError(e, "Exception when caching {1} for server {0}", server.Name, embySettings.IsJellyfin ? "Jellyfin" : "Emby");
+ _logger.LogError(e, "Exception when caching Emby for server {0}", server.Name);
}
}
@@ -145,7 +145,7 @@ namespace Ombi.Schedule.Jobs.Emby
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
- Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname, settings.IsJellyfin),
+ Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
});
}
@@ -228,4 +228,4 @@ namespace Ombi.Schedule.Jobs.Emby
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs
index 1207e1f42..4b684b8ab 100644
--- a/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs
+++ b/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs
@@ -80,7 +80,7 @@ namespace Ombi.Schedule.Jobs.Emby
Api = _apiFactory.CreateClient(settings);
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
- .SendAsync(NotificationHub.NotificationEvent, $"{(settings.IsJellyfin ? "Jellyfin" : "Emby")} User Importer Started");
+ .SendAsync(NotificationHub.NotificationEvent, $"Emby User Importer Started");
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.EmbyUser || x.UserType == UserType.EmbyConnectUser).ToListAsync();
foreach (var server in settings.Servers)
{
@@ -117,7 +117,8 @@ namespace Ombi.Schedule.Jobs.Emby
ProviderUserId = embyUser.Id,
Alias = isConnectUser ? embyUser.Name : string.Empty,
MovieRequestLimit = userManagementSettings.MovieRequestLimit,
- EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit
+ EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit,
+ StreamingCountry = userManagementSettings.DefaultStreamingCountry
};
var result = await _userManager.CreateAsync(newUser);
if (!result.Succeeded)
@@ -180,4 +181,4 @@ namespace Ombi.Schedule.Jobs.Emby
GC.SuppressFinalize(this);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinAvaliabilityChecker.cs
new file mode 100644
index 000000000..7b89fa3f2
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinAvaliabilityChecker.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public interface IJellyfinAvaliabilityChecker : IBaseJob
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinContentSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinContentSync.cs
new file mode 100644
index 000000000..e9ef28cc6
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinContentSync.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public interface IJellyfinContentSync : IBaseJob
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinEpisodeSync.cs
new file mode 100644
index 000000000..53392cc6a
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinEpisodeSync.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public interface IJellyfinEpisodeSync : IBaseJob
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinUserImporter.cs b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinUserImporter.cs
new file mode 100644
index 000000000..06ff204f8
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/IJellyfinUserImporter.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public interface IJellyfinUserImporter : IBaseJob
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
new file mode 100644
index 000000000..60d017d93
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
@@ -0,0 +1,235 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinAvaliabilityCheker.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Ombi.Core;
+using Ombi.Core.Notifications;
+using Ombi.Helpers;
+using Ombi.Hubs;
+using Ombi.Notifications.Models;
+using Ombi.Schedule.Jobs.Ombi;
+using Ombi.Store.Entities;
+using Ombi.Store.Repository;
+using Ombi.Store.Repository.Requests;
+using Quartz;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public class JellyfinAvaliabilityChecker : IJellyfinAvaliabilityChecker
+ {
+ public JellyfinAvaliabilityChecker(IJellyfinContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
+ INotificationHelper n, ILogger log, IHubContext notification)
+ {
+ _repo = repo;
+ _tvRepo = t;
+ _movieRepo = m;
+ _notificationService = n;
+ _log = log;
+ _notification = notification;
+ }
+
+ private readonly ITvRequestRepository _tvRepo;
+ private readonly IMovieRequestRepository _movieRepo;
+ private readonly IJellyfinContentRepository _repo;
+ private readonly INotificationHelper _notificationService;
+ private readonly ILogger _log;
+ private readonly IHubContext _notification;
+
+ public async Task Execute(IJobExecutionContext job)
+ {
+ _log.LogInformation("Starting Jellyfin Availability Check");
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Started");
+ await ProcessMovies();
+ await ProcessTv();
+
+ _log.LogInformation("Finished Jellyfin Availability Check");
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Availability Checker Finished");
+ }
+
+ private async Task ProcessMovies()
+ {
+ var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
+
+ foreach (var movie in movies)
+ {
+ JellyfinContent jellyfinContent = null;
+ if (movie.TheMovieDbId > 0)
+ {
+ jellyfinContent = await _repo.GetByTheMovieDbId(movie.TheMovieDbId.ToString());
+ }
+ else if(movie.ImdbId.HasValue())
+ {
+ jellyfinContent = await _repo.GetByImdbId(movie.ImdbId);
+ }
+
+ if (jellyfinContent == null)
+ {
+ // We don't have this yet
+ continue;
+ }
+
+ _log.LogInformation("We have found the request {0} on Jellyfin, sending the notification", movie?.Title ?? string.Empty);
+
+ movie.Available = true;
+ movie.MarkedAsAvailable = DateTime.Now;
+ if (movie.Available)
+ {
+ var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty;
+
+ _log.LogDebug("MovieId: {0}, RequestUser: {1}", movie.Id, recipient);
+
+ await _notificationService.Notify(new NotificationOptions
+ {
+ DateTime = DateTime.Now,
+ NotificationType = NotificationType.RequestAvailable,
+ RequestId = movie.Id,
+ RequestType = RequestType.Movie,
+ Recipient = recipient,
+ });
+ }
+ }
+ await _movieRepo.Save();
+ }
+
+
+
+ ///
+ /// TODO This is EXCATLY the same as the PlexAvailabilityChecker. Refactor Please future Jamie
+ ///
+ ///
+ private async Task ProcessTv()
+ {
+ var tv = _tvRepo.GetChild().Where(x => !x.Available);
+ var jellyfinEpisodes = _repo.GetAllEpisodes().Include(x => x.Series);
+
+ foreach (var child in tv)
+ {
+
+ var useImdb = false;
+ var useTvDb = false;
+ if (child.ParentRequest.ImdbId.HasValue())
+ {
+ useImdb = true;
+ }
+
+ if (child.ParentRequest.TvDbId.ToString().HasValue())
+ {
+ useTvDb = true;
+ }
+
+ var tvDbId = child.ParentRequest.TvDbId;
+ var imdbId = child.ParentRequest.ImdbId;
+ IQueryable seriesEpisodes = null;
+ if (useImdb)
+ {
+ seriesEpisodes = jellyfinEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
+ }
+
+ if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()))
+ {
+ seriesEpisodes = jellyfinEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
+ }
+
+ if (seriesEpisodes == null)
+ {
+ continue;
+ }
+
+ if (!seriesEpisodes.Any())
+ {
+ // Let's try and match the series by name
+ seriesEpisodes = jellyfinEpisodes.Where(x =>
+ x.Series.Title == child.Title);
+ }
+
+ foreach (var season in child.SeasonRequests)
+ {
+ foreach (var episode in season.Episodes)
+ {
+ if (episode.Available)
+ {
+ continue;
+ }
+
+ var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
+ x => x.EpisodeNumber == episode.EpisodeNumber &&
+ x.SeasonNumber == episode.Season.SeasonNumber);
+
+ if (foundEp != null)
+ {
+ episode.Available = true;
+ }
+ }
+ }
+
+ // Check to see if all of the episodes in all seasons are available for this request
+ var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available));
+ if (allAvailable)
+ {
+ // We have fulfulled this request!
+ child.Available = true;
+ child.MarkedAsAvailable = DateTime.Now;
+ await _notificationService.Notify(new NotificationOptions
+ {
+ DateTime = DateTime.Now,
+ NotificationType = NotificationType.RequestAvailable,
+ RequestId = child.Id,
+ RequestType = RequestType.TvShow,
+ Recipient = child.RequestedUser.Email
+ });
+ }
+ }
+
+ await _tvRepo.Save();
+ }
+
+ private bool _disposed;
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ }
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs
new file mode 100644
index 000000000..ff96e2130
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using Ombi.Api.Jellyfin;
+using Ombi.Api.Jellyfin.Models.Movie;
+using Ombi.Core.Settings;
+using Ombi.Core.Settings.Models.External;
+using Ombi.Helpers;
+using Ombi.Hubs;
+using Ombi.Schedule.Jobs.Ombi;
+using Ombi.Store.Entities;
+using Ombi.Store.Repository;
+using Quartz;
+using JellyfinMediaType = Ombi.Store.Entities.JellyfinMediaType;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public class JellyfinContentSync : IJellyfinContentSync
+ {
+ public JellyfinContentSync(ISettingsService settings, IJellyfinApiFactory api, ILogger logger,
+ IJellyfinContentRepository repo, IHubContext notification)
+ {
+ _logger = logger;
+ _settings = settings;
+ _apiFactory = api;
+ _repo = repo;
+ _notification = notification;
+ }
+
+ private readonly ILogger _logger;
+ private readonly ISettingsService _settings;
+ private readonly IJellyfinApiFactory _apiFactory;
+ private readonly IJellyfinContentRepository _repo;
+ private readonly IHubContext _notification;
+ private IJellyfinApi Api { get; set; }
+
+ public async Task Execute(IJobExecutionContext job)
+ {
+ var jellyfinSettings = await _settings.GetSettingsAsync();
+ if (!jellyfinSettings.Enable)
+ return;
+
+ Api = _apiFactory.CreateClient(jellyfinSettings);
+
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Started");
+
+ foreach (var server in jellyfinSettings.Servers)
+ {
+ try
+ {
+ await StartServerCache(server, jellyfinSettings);
+ }
+ catch (Exception e)
+ {
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Failed");
+ _logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name);
+ }
+ }
+
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished");
+ // Episodes
+
+ await OmbiQuartz.TriggerJob(nameof(IJellyfinEpisodeSync), "Jellyfin");
+ }
+
+
+ private async Task StartServerCache(JellyfinServers server, JellyfinSettings settings)
+ {
+ if (!ValidateSettings(server))
+ return;
+
+ //await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
+ //await _repo.ExecuteSql("DELETE FROM JellyfinContent");
+
+ var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
+ var totalCount = movies.TotalRecordCount;
+ var processed = 1;
+
+ var mediaToAdd = new HashSet();
+
+ while (processed < totalCount)
+ {
+ foreach (var movie in movies.Items)
+ {
+ if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
+ {
+ var movieInfo =
+ await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
+ foreach (var item in movieInfo.Items)
+ {
+ await ProcessMovies(item, mediaToAdd, server);
+ }
+
+ processed++;
+ }
+ else
+ {
+ processed++;
+ // Regular movie
+ await ProcessMovies(movie, mediaToAdd, server);
+ }
+ }
+
+ // Get the next batch
+ movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
+ await _repo.AddRange(mediaToAdd);
+ mediaToAdd.Clear();
+
+ }
+
+
+ // TV Time
+ var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
+ var totalTv = tv.TotalRecordCount;
+ processed = 1;
+ while (processed < totalTv)
+ {
+ foreach (var tvShow in tv.Items)
+ {
+ try
+ {
+
+ processed++;
+ if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
+ {
+ _logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
+ continue;
+ }
+
+ var existingTv = await _repo.GetByJellyfinId(tvShow.Id);
+ if (existingTv == null)
+ {
+ _logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
+ mediaToAdd.Add(new JellyfinContent
+ {
+ TvDbId = tvShow.ProviderIds?.Tvdb,
+ ImdbId = tvShow.ProviderIds?.Imdb,
+ TheMovieDbId = tvShow.ProviderIds?.Tmdb,
+ Title = tvShow.Name,
+ Type = JellyfinMediaType.Series,
+ JellyfinId = tvShow.Id,
+ Url = JellyfinHelper.GetJellyfinMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
+ AddedAt = DateTime.UtcNow
+ });
+ }
+ else
+ {
+ _logger.LogDebug("We already have TV Show {0}", tvShow.Name);
+ }
+
+ }
+ catch (Exception)
+ {
+
+ throw;
+ }
+ }
+ // Get the next batch
+ tv = await Api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
+ await _repo.AddRange(mediaToAdd);
+ mediaToAdd.Clear();
+ }
+
+ if (mediaToAdd.Any())
+ await _repo.AddRange(mediaToAdd);
+ }
+
+ private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection content, JellyfinServers server)
+ {
+ // Check if it exists
+ var existingMovie = await _repo.GetByJellyfinId(movieInfo.Id);
+ var alreadyGoingToAdd = content.Any(x => x.JellyfinId == movieInfo.Id);
+ if (existingMovie == null && !alreadyGoingToAdd)
+ {
+ _logger.LogDebug("Adding new movie {0}", movieInfo.Name);
+ content.Add(new JellyfinContent
+ {
+ ImdbId = movieInfo.ProviderIds.Imdb,
+ TheMovieDbId = movieInfo.ProviderIds?.Tmdb,
+ Title = movieInfo.Name,
+ Type = JellyfinMediaType.Movie,
+ JellyfinId = movieInfo.Id,
+ Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname),
+ AddedAt = DateTime.UtcNow,
+ });
+ }
+ else
+ {
+ // we have this
+ _logger.LogDebug("We already have movie {0}", movieInfo.Name);
+ }
+ }
+
+ private bool ValidateSettings(JellyfinServers server)
+ {
+ if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey))
+ {
+ _logger.LogInformation(LoggingEvents.JellyfinContentCacher, $"Server {server?.Name} is not configured correctly");
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool _disposed;
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ //_settings?.Dispose();
+ }
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+
+}
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs
new file mode 100644
index 000000000..11c7ec7af
--- /dev/null
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs
@@ -0,0 +1,181 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2017 Jamie Rees
+// File: JellyfinEpisodeCacher.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 Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using Ombi.Api.Jellyfin;
+using Ombi.Core.Settings;
+using Ombi.Core.Settings.Models.External;
+using Ombi.Hubs;
+using Ombi.Helpers;
+using Ombi.Store.Entities;
+using Ombi.Store.Repository;
+using Quartz;
+using Ombi.Schedule.Jobs.Ombi;
+
+namespace Ombi.Schedule.Jobs.Jellyfin
+{
+ public class JellyfinEpisodeSync : IJellyfinEpisodeSync
+ {
+ public JellyfinEpisodeSync(ISettingsService s, IJellyfinApiFactory api, ILogger l, IJellyfinContentRepository repo
+ , IHubContext notification)
+ {
+ _apiFactory = api;
+ _logger = l;
+ _settings = s;
+ _repo = repo;
+ _notification = notification;
+ }
+
+ private readonly ISettingsService _settings;
+ private readonly IJellyfinApiFactory _apiFactory;
+ private readonly ILogger _logger;
+ private readonly IJellyfinContentRepository _repo;
+ private readonly IHubContext _notification;
+ private IJellyfinApi Api { get; set; }
+
+
+ public async Task Execute(IJobExecutionContext job)
+ {
+ var settings = await _settings.GetSettingsAsync();
+
+ Api = _apiFactory.CreateClient(settings);
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Started");
+ foreach (var server in settings.Servers)
+ {
+ await CacheEpisodes(server);
+ }
+
+ await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
+ .SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Finished");
+ _logger.LogInformation("Jellyfin Episode Sync Finished - Triggering Metadata refresh");
+ await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
+ }
+
+ private async Task CacheEpisodes(JellyfinServers server)
+ {
+ var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
+ var total = allEpisodes.TotalRecordCount;
+ var processed = 1;
+ var epToAdd = new HashSet