mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 12:59:39 -07:00
Added the option to select libaries to monitor in Jellyfin and Emby #2389
This commit is contained in:
parent
fc7df0e11b
commit
6d70010777
20 changed files with 249 additions and 85 deletions
|
@ -1,9 +1,12 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore.Internal;
|
using Microsoft.EntityFrameworkCore.Internal;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ombi.Api.Emby.Models;
|
using Ombi.Api.Emby.Models;
|
||||||
|
using Ombi.Api.Emby.Models.Media;
|
||||||
using Ombi.Api.Emby.Models.Media.Tv;
|
using Ombi.Api.Emby.Models.Media.Tv;
|
||||||
using Ombi.Api.Emby.Models.Movie;
|
using Ombi.Api.Emby.Models.Movie;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
|
@ -112,19 +115,30 @@ namespace Ombi.Api.Emby
|
||||||
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
|
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
|
public async Task<EmbyItemContainer<MediaFolders>> GetLibraries(string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
|
var request = new Request("library/mediafolders", baseUrl, HttpMethod.Get);
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
|
||||||
|
var response = await Api.Request<EmbyItemContainer<MediaFolders>>(request);
|
||||||
|
response.Items = response.Items.Where(x => !x.CollectionType.Equals("playlists", StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
|
|
||||||
|
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
|
||||||
{
|
{
|
||||||
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
|
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
|
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
|
||||||
{
|
{
|
||||||
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
|
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
|
||||||
|
{
|
||||||
|
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
||||||
|
@ -167,7 +181,7 @@ namespace Ombi.Api.Emby
|
||||||
var obj = await Api.Request<EmbyItemContainer<T>>(request);
|
var obj = await Api.Request<EmbyItemContainer<T>>(request);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
|
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count, string parentIdFilder = default)
|
||||||
{
|
{
|
||||||
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
|
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
|
||||||
|
|
||||||
|
@ -176,6 +190,10 @@ namespace Ombi.Api.Emby
|
||||||
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
|
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
|
||||||
request.AddQueryString("startIndex", startIndex.ToString());
|
request.AddQueryString("startIndex", startIndex.ToString());
|
||||||
request.AddQueryString("limit", count.ToString());
|
request.AddQueryString("limit", count.ToString());
|
||||||
|
if (!string.IsNullOrEmpty(parentIdFilder))
|
||||||
|
{
|
||||||
|
request.AddQueryString("ParentId", parentIdFilder);
|
||||||
|
}
|
||||||
|
|
||||||
request.AddQueryString("IsVirtualItem", "False");
|
request.AddQueryString("IsVirtualItem", "False");
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,13 @@ namespace Ombi.Api.Emby
|
||||||
Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey);
|
Task<List<EmbyUser>> GetUsers(string baseUri, string apiKey);
|
||||||
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
|
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
|
||||||
|
|
||||||
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
|
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
|
||||||
string baseUri);
|
string baseUri);
|
||||||
|
|
||||||
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
|
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
|
||||||
string baseUri);
|
string baseUri);
|
||||||
|
|
||||||
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
|
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
|
||||||
string baseUri);
|
string baseUri);
|
||||||
|
|
||||||
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
|
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ombi.Api.Emby.Models;
|
using Ombi.Api.Emby.Models;
|
||||||
|
using Ombi.Api.Emby.Models.Media;
|
||||||
|
|
||||||
namespace Ombi.Api.Emby
|
namespace Ombi.Api.Emby
|
||||||
{
|
{
|
||||||
public interface IEmbyApi : IBaseEmbyApi
|
public interface IEmbyApi : IBaseEmbyApi
|
||||||
{
|
{
|
||||||
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
|
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
|
||||||
|
Task<EmbyItemContainer<MediaFolders>> GetLibraries(string apiKey, string baseUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
16
src/Ombi.Api.Emby/Models/Media/MediaFolders.cs
Normal file
16
src/Ombi.Api.Emby/Models/Media/MediaFolders.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Emby.Models.Media
|
||||||
|
{
|
||||||
|
public class MediaFolders
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string ServerId { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string CollectionType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,10 @@ namespace Ombi.Helpers
|
||||||
public async Task Purge()
|
public async Task Purge()
|
||||||
{
|
{
|
||||||
var keys = await _memoryCache.GetAsync<List<string>>(CacheKey);
|
var keys = await _memoryCache.GetAsync<List<string>>(CacheKey);
|
||||||
|
if (keys == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach (var key in keys)
|
foreach (var key in keys)
|
||||||
{
|
{
|
||||||
base.Remove(key);
|
base.Remove(key);
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
private readonly IEmbyApiFactory _apiFactory;
|
private readonly IEmbyApiFactory _apiFactory;
|
||||||
private readonly IEmbyContentRepository _repo;
|
private readonly IEmbyContentRepository _repo;
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
private readonly IHubContext<NotificationHub> _notification;
|
||||||
|
|
||||||
private IEmbyApi Api { get; set; }
|
private IEmbyApi Api { get; set; }
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
|
@ -78,47 +79,37 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
//await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
|
//await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
|
||||||
//await _repo.ExecuteSql("DELETE FROM EmbyContent");
|
//await _repo.ExecuteSql("DELETE FROM EmbyContent");
|
||||||
|
|
||||||
var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
|
||||||
var totalCount = movies.TotalRecordCount;
|
|
||||||
var processed = 1;
|
|
||||||
|
|
||||||
var mediaToAdd = new HashSet<EmbyContent>();
|
|
||||||
|
|
||||||
while (processed < totalCount)
|
|
||||||
{
|
{
|
||||||
foreach (var movie in movies.Items)
|
var movieLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
|
||||||
{
|
|
||||||
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++;
|
foreach (var movieParentIdFilder in movieLibsToFilter)
|
||||||
}
|
{
|
||||||
else
|
_logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
|
||||||
{
|
await ProcessMovies(server, movieParentIdFilder.Key);
|
||||||
processed++;
|
|
||||||
// Regular movie
|
|
||||||
await ProcessMovies(movie, mediaToAdd, server);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next batch
|
var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
|
||||||
movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
foreach (var tvParentIdFilter in tvLibsToFilter)
|
||||||
await _repo.AddRange(mediaToAdd);
|
{
|
||||||
mediaToAdd.Clear();
|
_logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'");
|
||||||
|
await ProcessTv(server, tvParentIdFilter.Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ProcessMovies(server);
|
||||||
|
await ProcessTv(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessTv(EmbyServers server, string parentId = default)
|
||||||
|
{
|
||||||
// TV Time
|
// TV Time
|
||||||
var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
var mediaToAdd = new HashSet<EmbyContent>();
|
||||||
|
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
|
||||||
var totalTv = tv.TotalRecordCount;
|
var totalTv = tv.TotalRecordCount;
|
||||||
processed = 1;
|
var processed = 1;
|
||||||
while (processed < totalTv)
|
while (processed < totalTv)
|
||||||
{
|
{
|
||||||
foreach (var tvShow in tv.Items)
|
foreach (var tvShow in tv.Items)
|
||||||
|
@ -162,7 +153,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get the next batch
|
// Get the next batch
|
||||||
tv = await Api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
tv = await Api.GetAllShows(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri);
|
||||||
await _repo.AddRange(mediaToAdd);
|
await _repo.AddRange(mediaToAdd);
|
||||||
mediaToAdd.Clear();
|
mediaToAdd.Clear();
|
||||||
}
|
}
|
||||||
|
@ -171,6 +162,43 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
await _repo.AddRange(mediaToAdd);
|
await _repo.AddRange(mediaToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ProcessMovies(EmbyServers server, string parentId = default)
|
||||||
|
{
|
||||||
|
var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
|
||||||
|
var totalCount = movies.TotalRecordCount;
|
||||||
|
var processed = 1;
|
||||||
|
var mediaToAdd = new HashSet<EmbyContent>();
|
||||||
|
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, parentId, processed, 200, server.AdministratorId, server.FullUri);
|
||||||
|
await _repo.AddRange(mediaToAdd);
|
||||||
|
mediaToAdd.Clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content, EmbyServers server)
|
private async Task ProcessMovies(EmbyMovie movieInfo, ICollection<EmbyContent> content, EmbyServers server)
|
||||||
{
|
{
|
||||||
// Check if it exists
|
// Check if it exists
|
||||||
|
|
|
@ -60,6 +60,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
private readonly ILogger<EmbyEpisodeSync> _logger;
|
private readonly ILogger<EmbyEpisodeSync> _logger;
|
||||||
private readonly IEmbyContentRepository _repo;
|
private readonly IEmbyContentRepository _repo;
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
private readonly IHubContext<NotificationHub> _notification;
|
||||||
|
|
||||||
private IEmbyApi Api { get; set; }
|
private IEmbyApi Api { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +73,19 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Emby Episode Sync Started");
|
.SendAsync(NotificationHub.NotificationEvent, "Emby Episode Sync Started");
|
||||||
foreach (var server in settings.Servers)
|
foreach (var server in settings.Servers)
|
||||||
{
|
{
|
||||||
await CacheEpisodes(server);
|
if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
|
||||||
|
{
|
||||||
|
var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
|
||||||
|
foreach (var tvParentIdFilter in tvLibsToFilter)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Scanning Lib for episodes '{tvParentIdFilter.Title}'");
|
||||||
|
await CacheEpisodes(server, tvParentIdFilter.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await CacheEpisodes(server, string.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
|
@ -81,9 +94,9 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
|
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CacheEpisodes(EmbyServers server)
|
private async Task CacheEpisodes(EmbyServers server, string parentIdFilter)
|
||||||
{
|
{
|
||||||
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri);
|
||||||
var total = allEpisodes.TotalRecordCount;
|
var total = allEpisodes.TotalRecordCount;
|
||||||
var processed = 1;
|
var processed = 1;
|
||||||
var epToAdd = new HashSet<EmbyEpisode>();
|
var epToAdd = new HashSet<EmbyEpisode>();
|
||||||
|
@ -150,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
|
|
||||||
await _repo.AddRange(epToAdd);
|
await _repo.AddRange(epToAdd);
|
||||||
epToAdd.Clear();
|
epToAdd.Clear();
|
||||||
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, processed, 200, server.AdministratorId, server.FullUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (epToAdd.Any())
|
if (epToAdd.Any())
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
||||||
private readonly IJellyfinApiFactory _apiFactory;
|
private readonly IJellyfinApiFactory _apiFactory;
|
||||||
private readonly IJellyfinContentRepository _repo;
|
private readonly IJellyfinContentRepository _repo;
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
private readonly IHubContext<NotificationHub> _notification;
|
||||||
|
|
||||||
private IJellyfinApi Api { get; set; }
|
private IJellyfinApi Api { get; set; }
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
|
@ -61,7 +62,6 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
||||||
_logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name);
|
_logger.LogError(e, "Exception when caching Jellyfin for server {0}", server.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished");
|
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Content Sync Finished");
|
||||||
// Episodes
|
// Episodes
|
||||||
|
@ -80,7 +80,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
||||||
//await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
|
//await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
|
||||||
//await _repo.ExecuteSql("DELETE FROM JellyfinContent");
|
//await _repo.ExecuteSql("DELETE FROM JellyfinContent");
|
||||||
|
|
||||||
if (server.JellyfinSelectedLibraries.Any())
|
if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled))
|
||||||
{
|
{
|
||||||
var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
|
var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
||||||
foreach (var server in settings.Servers)
|
foreach (var server in settings.Servers)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (server.JellyfinSelectedLibraries.Any())
|
if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled))
|
||||||
{
|
{
|
||||||
var tvLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
|
var tvLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
|
||||||
foreach (var tvParentIdFilter in tvLibsToFilter)
|
foreach (var tvParentIdFilter in tvLibsToFilter)
|
||||||
|
|
|
@ -16,21 +16,26 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
|
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
|
||||||
{
|
{
|
||||||
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
|
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
|
||||||
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo)
|
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo,
|
||||||
|
ISettingsService<EmbySettings> embySettings, ISettingsService<JellyfinSettings> jellyfinSettings)
|
||||||
{
|
{
|
||||||
_settings = s;
|
_plexSettings = s;
|
||||||
_log = log;
|
_log = log;
|
||||||
_plexRepo = plexRepo;
|
_plexRepo = plexRepo;
|
||||||
_embyRepo = embyRepo;
|
_embyRepo = embyRepo;
|
||||||
_jellyfinRepo = jellyfinRepo;
|
_jellyfinRepo = jellyfinRepo;
|
||||||
_settings.ClearCache();
|
_embySettings = embySettings;
|
||||||
|
_jellyfinSettings = jellyfinSettings;
|
||||||
|
_plexSettings.ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ISettingsService<PlexSettings> _settings;
|
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||||
private readonly ILogger _log;
|
private readonly ILogger _log;
|
||||||
private readonly IPlexContentRepository _plexRepo;
|
private readonly IPlexContentRepository _plexRepo;
|
||||||
private readonly IEmbyContentRepository _embyRepo;
|
private readonly IEmbyContentRepository _embyRepo;
|
||||||
private readonly IJellyfinContentRepository _jellyfinRepo;
|
private readonly IJellyfinContentRepository _jellyfinRepo;
|
||||||
|
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||||
|
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext job)
|
public async Task Execute(IJobExecutionContext job)
|
||||||
{
|
{
|
||||||
|
@ -51,7 +56,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var s = await _settings.GetSettingsAsync();
|
var s = await _embySettings.GetSettingsAsync();
|
||||||
if (!s.Enable)
|
if (!s.Enable)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -73,7 +78,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var s = await _settings.GetSettingsAsync();
|
var s = await _jellyfinSettings.GetSettingsAsync();
|
||||||
if (!s.Enable)
|
if (!s.Enable)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -95,7 +100,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var s = await _settings.GetSettingsAsync();
|
var s = await _plexSettings.GetSettingsAsync();
|
||||||
if (!s.Enable)
|
if (!s.Enable)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
IMovieDbApi movieApi,
|
IMovieDbApi movieApi,
|
||||||
ISettingsService<EmbySettings> embySettings, IEmbyApiFactory embyApi,
|
ISettingsService<EmbySettings> embySettings, IEmbyApiFactory embyApi,
|
||||||
ISettingsService<JellyfinSettings> jellyfinSettings, IJellyfinApiFactory jellyfinApi,
|
ISettingsService<JellyfinSettings> jellyfinSettings, IJellyfinApiFactory jellyfinApi,
|
||||||
IHubContext<NotificationHub> notification)
|
IHubContext<NotificationHub> notification, IMediaCacheService mediaCacheService)
|
||||||
{
|
{
|
||||||
_plexRepo = plexRepo;
|
_plexRepo = plexRepo;
|
||||||
_embyRepo = embyRepo;
|
_embyRepo = embyRepo;
|
||||||
|
@ -44,6 +44,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
_jellyfinSettings = jellyfinSettings;
|
_jellyfinSettings = jellyfinSettings;
|
||||||
_jellyfinApiFactory = jellyfinApi;
|
_jellyfinApiFactory = jellyfinApi;
|
||||||
_notification = notification;
|
_notification = notification;
|
||||||
|
_mediaCacheService = mediaCacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IPlexContentRepository _plexRepo;
|
private readonly IPlexContentRepository _plexRepo;
|
||||||
|
@ -58,6 +59,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
private readonly IEmbyApiFactory _embyApiFactory;
|
private readonly IEmbyApiFactory _embyApiFactory;
|
||||||
private readonly IJellyfinApiFactory _jellyfinApiFactory;
|
private readonly IJellyfinApiFactory _jellyfinApiFactory;
|
||||||
private readonly IHubContext<NotificationHub> _notification;
|
private readonly IHubContext<NotificationHub> _notification;
|
||||||
|
private readonly IMediaCacheService _mediaCacheService;
|
||||||
|
|
||||||
private IEmbyApi EmbyApi { get; set; }
|
private IEmbyApi EmbyApi { get; set; }
|
||||||
private IJellyfinApi JellyfinApi { get; set; }
|
private IJellyfinApi JellyfinApi { get; set; }
|
||||||
|
|
||||||
|
@ -102,6 +105,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _mediaCacheService.Purge();
|
||||||
|
|
||||||
_log.LogInformation("Metadata refresh finished");
|
_log.LogInformation("Metadata refresh finished");
|
||||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||||
.SendAsync(NotificationHub.NotificationEvent, "Metadata Refresh Finished");
|
.SendAsync(NotificationHub.NotificationEvent, "Metadata Refresh Finished");
|
||||||
|
|
|
@ -52,8 +52,10 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
public class PlexContentSync : IPlexContentSync
|
public class PlexContentSync : IPlexContentSync
|
||||||
{
|
{
|
||||||
private readonly IMovieDbApi _movieApi;
|
private readonly IMovieDbApi _movieApi;
|
||||||
|
private readonly IMediaCacheService _mediaCacheService;
|
||||||
|
|
||||||
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
|
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
|
||||||
IPlexEpisodeSync epsiodeSync, IHubContext<NotificationHub> hub, IMovieDbApi movieDbApi)
|
IPlexEpisodeSync epsiodeSync, IHubContext<NotificationHub> hub, IMovieDbApi movieDbApi, IMediaCacheService mediaCacheService)
|
||||||
{
|
{
|
||||||
Plex = plex;
|
Plex = plex;
|
||||||
PlexApi = plexApi;
|
PlexApi = plexApi;
|
||||||
|
@ -62,6 +64,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
EpisodeSync = epsiodeSync;
|
EpisodeSync = epsiodeSync;
|
||||||
Notification = hub;
|
Notification = hub;
|
||||||
_movieApi = movieDbApi;
|
_movieApi = movieDbApi;
|
||||||
|
_mediaCacheService = mediaCacheService;
|
||||||
Plex.ClearCache();
|
Plex.ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +124,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
{
|
{
|
||||||
await NotifyClient("Plex Sync - Checking if any requests are now available");
|
await NotifyClient("Plex Sync - Checking if any requests are now available");
|
||||||
Logger.LogInformation("Kicking off Plex Availability Checker");
|
Logger.LogInformation("Kicking off Plex Availability Checker");
|
||||||
|
await _mediaCacheService.Purge();
|
||||||
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
|
||||||
}
|
}
|
||||||
var processedCont = processedContent?.Content?.Count() ?? 0;
|
var processedCont = processedContent?.Content?.Count() ?? 0;
|
||||||
|
|
|
@ -22,8 +22,9 @@ namespace Ombi.Core.Settings.Models.External
|
||||||
|
|
||||||
public class EmbySelectedLibraries
|
public class EmbySelectedLibraries
|
||||||
{
|
{
|
||||||
public int Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string Title { get; set; } // Name is for display purposes
|
public string Title { get; set; } // Name is for display purposes
|
||||||
|
public string CollectionType { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ export interface IEmbyServer extends IExternalSettings {
|
||||||
administratorId: string;
|
administratorId: string;
|
||||||
enableEpisodeSearching: boolean;
|
enableEpisodeSearching: boolean;
|
||||||
serverHostname: string;
|
serverHostname: string;
|
||||||
|
embySelectedLibraries: IEmbyLibrariesSettings[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPublicInfo {
|
export interface IPublicInfo {
|
||||||
|
@ -72,8 +73,14 @@ export interface IJellyfinLibrariesSettings {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
collectionType: string;
|
collectionType: string;
|
||||||
}
|
}
|
||||||
|
export interface IEmbyLibrariesSettings {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
enabled: boolean;
|
||||||
|
collectionType: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IJellyfinContainer<T> {
|
export interface IMediaServerMediaContainer<T> {
|
||||||
items: T[];
|
items: T[];
|
||||||
totalRecordCount: number;
|
totalRecordCount: number;
|
||||||
}
|
}
|
||||||
|
@ -84,6 +91,12 @@ export interface IJellyfinLibrary {
|
||||||
id: string;
|
id: string;
|
||||||
collectionType: string;
|
collectionType: string;
|
||||||
}
|
}
|
||||||
|
export interface IEmbyLibrary {
|
||||||
|
name: string;
|
||||||
|
serverId: string;
|
||||||
|
id: string;
|
||||||
|
collectionType: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPublicInfo {
|
export interface IPublicInfo {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { ServiceHelpers } from "../service.helpers";
|
import { ServiceHelpers } from "../service.helpers";
|
||||||
|
|
||||||
import { IEmbyServer, IEmbySettings, IPublicInfo, IUsersModel } from "../../interfaces";
|
import { IEmbyLibrary, IEmbyServer, IEmbySettings, IMediaServerMediaContainer, IPublicInfo, IUsersModel } from "../../interfaces";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmbyService extends ServiceHelpers {
|
export class EmbyService extends ServiceHelpers {
|
||||||
|
@ -25,4 +25,8 @@ export class EmbyService extends ServiceHelpers {
|
||||||
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
|
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getLibraries(settings: IEmbyServer): Observable<IMediaServerMediaContainer<IEmbyLibrary>> {
|
||||||
|
return this.http.post<IMediaServerMediaContainer<IEmbyLibrary>>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { ServiceHelpers } from "../service.helpers";
|
import { ServiceHelpers } from "../service.helpers";
|
||||||
|
|
||||||
import { IEmbyServer, IJellyfinContainer, IJellyfinLibrary, IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces";
|
import { IEmbyServer, IMediaServerMediaContainer, IJellyfinLibrary, IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JellyfinService extends ServiceHelpers {
|
export class JellyfinService extends ServiceHelpers {
|
||||||
|
@ -25,7 +25,7 @@ export class JellyfinService extends ServiceHelpers {
|
||||||
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
|
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLibraries(settings: IJellyfinServer): Observable<IJellyfinContainer<IJellyfinLibrary>> {
|
public getLibraries(settings: IJellyfinServer): Observable<IMediaServerMediaContainer<IJellyfinLibrary>> {
|
||||||
return this.http.post<IJellyfinContainer<IJellyfinLibrary>>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<IMediaServerMediaContainer<IJellyfinLibrary>>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,11 @@
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-6 col-sm-6">
|
<div class="col-md-6 col-6 col-sm-6">
|
||||||
<div style="float:right;text-align:left;">
|
<div class="md-form-field">
|
||||||
<div class="md-form-field">
|
<mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
|
||||||
<mat-slide-toggle [(ngModel)]="settings.enable" (change)="toggle()" [checked]="settings.enable">Enable</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div> </div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;">
|
<mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;">
|
||||||
<mat-tab *ngFor="let server of settings.servers" [label]="server.name">
|
<mat-tab *ngFor="let server of settings.servers" [label]="server.name">
|
||||||
|
@ -74,7 +72,28 @@
|
||||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/item/item.html?id=1</span>
|
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/item/item.html?id=1</span>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
<label>Please select the libraries you want Ombi to look in for content</label>
|
||||||
|
<br />
|
||||||
|
<small>Note: if nothing is selected, we will monitor all libraries</small>
|
||||||
|
<div class="md-form-field">
|
||||||
|
<div>
|
||||||
|
<button mat-raised-button (click)="loadLibraries(server)"
|
||||||
|
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
|
||||||
|
<i class="fas fa-film"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div *ngIf="server.embySelectedLibraries">
|
||||||
|
<div *ngFor="let lib of server.embySelectedLibraries">
|
||||||
|
<div class="md-form-field">
|
||||||
|
<div class="checkbox">
|
||||||
|
<mat-slide-toggle [(ngModel)]="lib.enabled" [checked]="lib.enabled"
|
||||||
|
for="{{lib.title}}">{{lib.title}}</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { IEmbyServer, IEmbySettings } from "../../interfaces";
|
|
||||||
import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services";
|
import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services";
|
||||||
import { MatTabChangeEvent } from "@angular/material/tabs";
|
import { IEmbyLibrariesSettings, IEmbyServer, IEmbySettings } from "../../interfaces";
|
||||||
|
|
||||||
import {FormControl} from '@angular/forms';
|
import {FormControl} from '@angular/forms';
|
||||||
|
import { MatTabChangeEvent } from "@angular/material/tabs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./emby.component.html",
|
templateUrl: "./emby.component.html",
|
||||||
|
@ -100,4 +100,28 @@ export class EmbyComponent implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadLibraries(server: IEmbyServer) {
|
||||||
|
if (server.ip == null) {
|
||||||
|
this.notificationService.error("Emby is not yet configured correctly");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.embyService.getLibraries(server).subscribe(x => {
|
||||||
|
server.embySelectedLibraries = [];
|
||||||
|
if (x.totalRecordCount > 0) {
|
||||||
|
x.items.forEach((item) => {
|
||||||
|
const lib: IEmbyLibrariesSettings = {
|
||||||
|
key: item.id,
|
||||||
|
title: item.name,
|
||||||
|
enabled: false,
|
||||||
|
collectionType: item.collectionType
|
||||||
|
};
|
||||||
|
server.embySelectedLibraries.push(lib);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.notificationService.error("Couldn't find any libraries");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => { this.notificationService.error(err); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { EmbyService } from "../../services";
|
import { EmbyService } from "../../services";
|
||||||
import { NotificationService } from "../../services";
|
|
||||||
|
|
||||||
import { IEmbySettings } from "../../interfaces";
|
import { IEmbySettings } from "../../interfaces";
|
||||||
|
import { NotificationService } from "../../services";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "wizard-emby",
|
selector: "wizard-emby",
|
||||||
|
@ -35,7 +34,8 @@ export class EmbyComponent implements OnInit {
|
||||||
ssl: false,
|
ssl: false,
|
||||||
subDir: "",
|
subDir: "",
|
||||||
serverHostname: "",
|
serverHostname: "",
|
||||||
serverId: undefined
|
serverId: undefined,
|
||||||
|
embySelectedLibraries: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Ombi.Api.Emby;
|
using Ombi.Api.Emby;
|
||||||
using Ombi.Api.Emby.Models;
|
using Ombi.Api.Emby.Models;
|
||||||
using Ombi.Api.Plex;
|
using Ombi.Api.Emby.Models.Media;
|
||||||
using Ombi.Attributes;
|
using Ombi.Attributes;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Core.Settings.Models.External;
|
using Ombi.Core.Settings.Models.External;
|
||||||
|
@ -92,5 +92,13 @@ namespace Ombi.Controllers.V1.External
|
||||||
// Filter out any dupes
|
// Filter out any dupes
|
||||||
return vm.DistinctBy(x => x.Id);
|
return vm.DistinctBy(x => x.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("Library")]
|
||||||
|
public async Task<EmbyItemContainer<MediaFolders>> GetLibaries([FromBody] EmbyServers server)
|
||||||
|
{
|
||||||
|
var client = await EmbyApi.CreateClient();
|
||||||
|
var result = await client.GetLibraries(server.ApiKey, server.FullUri);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue