mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-14 17:22:54 -07:00
!wip on adding music to v4
This commit is contained in:
parent
b5043d0580
commit
ea24e33d31
22 changed files with 615 additions and 7 deletions
13
src/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs
Normal file
13
src/Ombi.Api.MusicBrainz/IMusicBrainzApi.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Api.MusicBrainz.Models.Lookup;
|
||||||
|
using Ombi.Api.MusicBrainz.Models.Search;
|
||||||
|
|
||||||
|
namespace Ombi.Api.MusicBrainz
|
||||||
|
{
|
||||||
|
public interface IMusicBrainzApi
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Artist>> SearchArtist(string artistQuery);
|
||||||
|
Task<IEnumerable<ReleaseGroups>> GetReleaseGroups(string artistId);
|
||||||
|
}
|
||||||
|
}
|
27
src/Ombi.Api.MusicBrainz/Models/Lookup/ReleaseGroups.cs
Normal file
27
src/Ombi.Api.MusicBrainz/Models/Lookup/ReleaseGroups.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ombi.Api.MusicBrainz.Models.Lookup
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonPluralName("release-groups")]
|
||||||
|
public class ReleaseGroups
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "primary-type-ids")]
|
||||||
|
public string PrimaryTypeId { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "disambiguation")]
|
||||||
|
public string Disambiguation { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "secondary-types")]
|
||||||
|
public string[] SecondaryTypes { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "primary-type")]
|
||||||
|
public string PrimaryType { get; set; } // Album / Single / Live / EP
|
||||||
|
[JsonProperty(PropertyName = "first-release-date")]
|
||||||
|
public string FirstReleaseDate { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "secondary-type-ids")]
|
||||||
|
public string[] SecondaryTypeIds { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "title")]
|
||||||
|
public string Title { get; set; } // Release title
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
15
src/Ombi.Api.MusicBrainz/Models/MusicBrainzResult.cs
Normal file
15
src/Ombi.Api.MusicBrainz/Models/MusicBrainzResult.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ombi.Api.MusicBrainz.Models
|
||||||
|
{
|
||||||
|
public class MusicBrainzResult<T>
|
||||||
|
{
|
||||||
|
public DateTime created { get; set; }
|
||||||
|
public int count { get; set; }
|
||||||
|
public int offset { get; set; }
|
||||||
|
[JsonPropertyNameBasedOnItemClass]
|
||||||
|
public List<T> Data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
83
src/Ombi.Api.MusicBrainz/Models/Search/ArtistSearchResult.cs
Normal file
83
src/Ombi.Api.MusicBrainz/Models/Search/ArtistSearchResult.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ombi.Api.MusicBrainz.Models.Search
|
||||||
|
{
|
||||||
|
|
||||||
|
[JsonPluralName("artists")]
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "type-id")]
|
||||||
|
public string typeid { get; set; }
|
||||||
|
public int score { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "sort-name")]
|
||||||
|
public string sortname { get; set; }
|
||||||
|
public string country { get; set; }
|
||||||
|
public Area area { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "begin-area")]
|
||||||
|
public BeginArea beginarea { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "life-span")]
|
||||||
|
public LifeSpan2 lifespan { get; set; }
|
||||||
|
public Tag[] tags { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "isni-list")]
|
||||||
|
public IsniList[] isnilist { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Area
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "type-id")]
|
||||||
|
public string typeid { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "sort-name")]
|
||||||
|
public string sortname { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "life-span")]
|
||||||
|
public LifeSpan lifespan { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LifeSpan
|
||||||
|
{
|
||||||
|
public object ended { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BeginArea
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "type-id")]
|
||||||
|
public string typeid { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "sort-name")]
|
||||||
|
public string sortname { get; set; }
|
||||||
|
[JsonProperty(PropertyName = "life-span")]
|
||||||
|
public LifeSpan1 lifespan { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LifeSpan1
|
||||||
|
{
|
||||||
|
public object ended { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LifeSpan2
|
||||||
|
{
|
||||||
|
public string begin { get; set; }
|
||||||
|
public object ended { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Tag
|
||||||
|
{
|
||||||
|
public int count { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IsniList
|
||||||
|
{
|
||||||
|
public string isni { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
src/Ombi.Api.MusicBrainz/MusicBrainzApi.cs
Normal file
50
src/Ombi.Api.MusicBrainz/MusicBrainzApi.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Api;
|
||||||
|
using Ombi.Api.MusicBrainz.Models;
|
||||||
|
using Ombi.Api.MusicBrainz.Models.Lookup;
|
||||||
|
using Ombi.Api.MusicBrainz.Models.Search;
|
||||||
|
|
||||||
|
namespace Ombi.Api.MusicBrainz
|
||||||
|
{
|
||||||
|
public class MusicBrainzApi : IMusicBrainzApi
|
||||||
|
{
|
||||||
|
public MusicBrainzApi(IApi api)
|
||||||
|
{
|
||||||
|
_api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IApi _api;
|
||||||
|
private const string _baseUrl = "https://musicbrainz.org/ws/2/";
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Artist>> SearchArtist(string artistQuery)
|
||||||
|
{
|
||||||
|
var request = new Request("artist", _baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
request.AddQueryString("query", artistQuery);
|
||||||
|
AddHeaders(request);
|
||||||
|
var albums = await _api.Request<MusicBrainzResult<Artist>>(request);
|
||||||
|
return albums.Data.Where(x => !x.type.Equals("Person", StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ReleaseGroups>> GetReleaseGroups(string artistId)
|
||||||
|
{
|
||||||
|
var request = new Request("release-group", _baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
request.AddQueryString("artist", artistId);
|
||||||
|
AddHeaders(request);
|
||||||
|
|
||||||
|
// The count properties for release groups is called releasegroupcount... Will sort this out if I need paging
|
||||||
|
var releases = await _api.Request<MusicBrainzResult<ReleaseGroups>>(request);
|
||||||
|
return releases.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddHeaders(Request req)
|
||||||
|
{
|
||||||
|
req.AddHeader("Accept", "application/json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/Ombi.Api.MusicBrainz/Ombi.Api.MusicBrainz.csproj
Normal file
10
src/Ombi.Api.MusicBrainz/Ombi.Api.MusicBrainz.csproj
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -27,7 +27,8 @@ namespace Ombi.Api
|
||||||
|
|
||||||
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
|
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
NullValueHandling = NullValueHandling.Ignore
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
|
ContractResolver = new PluralPropertyContractResolver()
|
||||||
};
|
};
|
||||||
|
|
||||||
public async Task<T> Request<T>(Request request, CancellationToken cancellationToken = default(CancellationToken))
|
public async Task<T> Request<T>(Request request, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
|
17
src/Ombi.Api/Attribute/JsonPluralNameAttribute.cs
Normal file
17
src/Ombi.Api/Attribute/JsonPluralNameAttribute.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api
|
||||||
|
{
|
||||||
|
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JsonPluralNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string PluralName { get; set; }
|
||||||
|
public JsonPluralNameAttribute(string pluralName)
|
||||||
|
{
|
||||||
|
PluralName = pluralName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/Ombi.Api/Attribute/PluralPropertyContractResolver.cs
Normal file
33
src/Ombi.Api/Attribute/PluralPropertyContractResolver.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Ombi.Api
|
||||||
|
{
|
||||||
|
public class PluralPropertyContractResolver : DefaultContractResolver
|
||||||
|
{
|
||||||
|
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||||
|
{
|
||||||
|
JsonProperty prop = base.CreateProperty(member, memberSerialization);
|
||||||
|
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
|
||||||
|
{
|
||||||
|
Type itemType = prop.PropertyType.GetGenericArguments()[0];
|
||||||
|
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
|
||||||
|
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
|
||||||
|
}
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string Pluralize(string name)
|
||||||
|
{
|
||||||
|
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
|
||||||
|
return name.Substring(0, name.Length - 1) + "ies";
|
||||||
|
|
||||||
|
if (name.EndsWith("s"))
|
||||||
|
return name + "es";
|
||||||
|
|
||||||
|
return name + "s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ namespace Ombi.Api
|
||||||
{
|
{
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_settings = s;
|
_settings = s;
|
||||||
|
_runtimeVersion = AssemblyHelper.GetRuntimeVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpClient _client;
|
private static HttpClient _client;
|
||||||
|
@ -54,6 +55,7 @@ namespace Ombi.Api
|
||||||
|
|
||||||
private readonly ICacheService _cache;
|
private readonly ICacheService _cache;
|
||||||
private readonly ISettingsService<OmbiSettings> _settings;
|
private readonly ISettingsService<OmbiSettings> _settings;
|
||||||
|
private readonly string _runtimeVersion;
|
||||||
|
|
||||||
|
|
||||||
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
|
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
|
||||||
|
@ -84,7 +86,7 @@ namespace Ombi.Api
|
||||||
_handler = await GetHandler();
|
_handler = await GetHandler();
|
||||||
}
|
}
|
||||||
_client = new HttpClient(_handler);
|
_client = new HttpClient(_handler);
|
||||||
_client.DefaultRequestHeaders.Add("User-Agent","Ombi");
|
_client.DefaultRequestHeaders.Add("User-Agent",$"Ombi/{_runtimeVersion} (https://ombi.io/)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ using Ombi.Schedule.Jobs.SickRage;
|
||||||
using Ombi.Schedule.Processor;
|
using Ombi.Schedule.Processor;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Quartz.Spi;
|
using Quartz.Spi;
|
||||||
|
using Ombi.Api.MusicBrainz;
|
||||||
|
|
||||||
namespace Ombi.DependencyInjection
|
namespace Ombi.DependencyInjection
|
||||||
{
|
{
|
||||||
|
@ -145,6 +146,7 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<IOneSignalApi, OneSignalApi>();
|
services.AddTransient<IOneSignalApi, OneSignalApi>();
|
||||||
services.AddTransient<ILidarrApi, LidarrApi>();
|
services.AddTransient<ILidarrApi, LidarrApi>();
|
||||||
services.AddTransient<IGroupMeApi, GroupMeApi>();
|
services.AddTransient<IGroupMeApi, GroupMeApi>();
|
||||||
|
services.AddTransient<IMusicBrainzApi, MusicBrainzApi>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterStore(this IServiceCollection services) {
|
public static void RegisterStore(this IServiceCollection services) {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
<ProjectReference Include="..\Ombi.Api.GroupMe\Ombi.Api.GroupMe.csproj" />
|
<ProjectReference Include="..\Ombi.Api.GroupMe\Ombi.Api.GroupMe.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Api.MusicBrainz\Ombi.Api.MusicBrainz.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Ombi.Helpers
|
||||||
var version = Assembly.GetEntryAssembly()
|
var version = Assembly.GetEntryAssembly()
|
||||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||||
.InformationalVersion;
|
.InformationalVersion;
|
||||||
return version.Equals("1.0.0") ? "3.0.0-develop" : version;
|
return version.Equals("1.0.0") ? "4.0.0-develop" : version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -106,7 +106,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Test.Common", "Ombi.Te
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Hubs", "Ombi.Hubs\Ombi.Hubs.csproj", "{67416CC5-13B2-44BB-98CE-39DA93D6F70E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Hubs", "Ombi.Hubs\Ombi.Hubs.csproj", "{67416CC5-13B2-44BB-98CE-39DA93D6F70E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.GroupMe", "Ombi.Api.GroupMe\Ombi.Api.GroupMe.csproj", "{9266403C-B04D-4C0F-AC39-82F12C781949}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.GroupMe", "Ombi.Api.GroupMe\Ombi.Api.GroupMe.csproj", "{9266403C-B04D-4C0F-AC39-82F12C781949}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.MusicBrainz", "Ombi.Api.MusicBrainz\Ombi.Api.MusicBrainz.csproj", "{C5C1769B-4197-4410-A160-0EEF39EDDC98}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -286,6 +288,10 @@ Global
|
||||||
{9266403C-B04D-4C0F-AC39-82F12C781949}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9266403C-B04D-4C0F-AC39-82F12C781949}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9266403C-B04D-4C0F-AC39-82F12C781949}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9266403C-B04D-4C0F-AC39-82F12C781949}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9266403C-B04D-4C0F-AC39-82F12C781949}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9266403C-B04D-4C0F-AC39-82F12C781949}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -327,6 +333,7 @@ Global
|
||||||
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
||||||
{27111E7C-748E-4996-BD71-2117027C6460} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
{27111E7C-748E-4996-BD71-2117027C6460} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
||||||
{9266403C-B04D-4C0F-AC39-82F12C781949} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
{9266403C-B04D-4C0F-AC39-82F12C781949} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||||
|
{C5C1769B-4197-4410-A160-0EEF39EDDC98} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}
|
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}
|
||||||
|
|
|
@ -186,6 +186,7 @@ export interface IAbout {
|
||||||
osDescription: string;
|
osDescription: string;
|
||||||
processArchitecture: string;
|
processArchitecture: string;
|
||||||
applicationBasePath: string;
|
applicationBasePath: string;
|
||||||
|
notSupported: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICouchPotatoSettings extends IExternalSettings {
|
export interface ICouchPotatoSettings extends IExternalSettings {
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
<div *ngIf="!movie" class="justify-content-md-center top-spacing loading-spinner">
|
||||||
|
<mat-spinner [color]="'accent'"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="movie" class="dark-theme">
|
||||||
|
|
||||||
|
<top-banner [background]="movie.background" [available]="movie.available" [title]="movie.title"
|
||||||
|
[releaseDate]="movie.releaseDate" [tagline]="movie.tagline"></top-banner>
|
||||||
|
|
||||||
|
<section id="info-wrapper">
|
||||||
|
<div class="small-middle-container">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<media-poster [posterPath]="'https://image.tmdb.org/t/p/w300/' + movie.posterPath"></media-poster>
|
||||||
|
|
||||||
|
<!--Next to poster-->
|
||||||
|
<div class="col-12 col-lg-3 col-xl-3 media-row">
|
||||||
|
|
||||||
|
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id"
|
||||||
|
[hasTrailer]="movie.videos.results.length > 0" (openTrailer)="openDialog()" [imdbId]="movie.imdbId"
|
||||||
|
[twitter]="movie.externalIds.twitterId" [facebook]="movie.externalIds.facebookId"
|
||||||
|
[instagram]="movie.externalIds.instagramId" [available]="movie.available" [plexUrl]="movie.plexUrl"
|
||||||
|
[embyUrl]="movie.embyUrl"></social-icons>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-lg-6 col-xl-6 media-row">
|
||||||
|
|
||||||
|
<button mat-raised-button class="btn-green btn-spacing" *ngIf="movie.available"> {{
|
||||||
|
'Common.Available' | translate }}</button>
|
||||||
|
<span *ngIf="!movie.available">
|
||||||
|
<span *ngIf="movie.requested || movie.approved; then requestedBtn else notRequestedBtn"></span>
|
||||||
|
|
||||||
|
<ng-template #requestedBtn>
|
||||||
|
<button mat-raised-button *ngIf="!hasRequest || hasRequest && movieRequest && !movieRequest.denied"
|
||||||
|
class="btn-spacing" color="warn" [disabled]><i class="fa fa-check"></i>
|
||||||
|
{{ 'Common.Requested' | translate }}</button>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #notRequestedBtn>
|
||||||
|
<button mat-raised-button class="btn-spacing" color="primary" (click)="request()">
|
||||||
|
<i *ngIf="movie.requestProcessing" class="fa fa-circle-o-notch fa-spin fa-fw"></i> <i
|
||||||
|
*ngIf="!movie.requestProcessing && !movie.processed" class="fa fa-plus"></i>
|
||||||
|
<i *ngIf="movie.processed && !movie.requestProcessing" class="fa fa-check"></i> {{
|
||||||
|
'Common.Request' | translate }}</button>
|
||||||
|
</ng-template>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="isAdmin && hasRequest">
|
||||||
|
<button (click)="approve()" mat-raised-button class="btn-spacing" color="accent">
|
||||||
|
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!movie.available" (click)="markAvailable()" mat-raised-button class="btn-spacing"
|
||||||
|
color="accent">
|
||||||
|
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button *ngIf="movieRequest && !movieRequest.denied" mat-raised-button class="btn-spacing" color="warn"
|
||||||
|
(click)="deny()">
|
||||||
|
<i class="fa fa-times"></i> {{
|
||||||
|
'Requests.Deny' | translate }}</button>
|
||||||
|
<button *ngIf="movieRequest && movieRequest.denied" [matTooltip]="movieRequest.deniedReason"
|
||||||
|
mat-raised-button class="btn-spacing" color="warn">
|
||||||
|
<i class="fa fa-times"></i> {{
|
||||||
|
'MediaDetails.Denied' | translate }}</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button *ngIf="(hasRequest && movieRequest) || movie.available" mat-raised-button class="btn-spacing"
|
||||||
|
color="danger" (click)="issue()">
|
||||||
|
<i class="fa fa-exclamation"></i> {{
|
||||||
|
'Requests.ReportIssue' | translate }}</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-12 col-md-2">
|
||||||
|
<button *ngIf="movie.belongsToCollection"
|
||||||
|
[routerLink]="'/discover/collection/' + movie.belongsToCollection.id" mat-raised-button
|
||||||
|
class="spacing-below full-width mat-elevation-z8">{{movie.belongsToCollection.name}}</button>
|
||||||
|
|
||||||
|
<mat-card class="mat-elevation-z8 spacing-below" *ngIf="isAdmin && movieRequest">
|
||||||
|
<mat-card-content class="medium-font">
|
||||||
|
<movie-admin-panel [movie]="movieRequest" (advancedOptionsChange)="setAdvancedOptions($event)">
|
||||||
|
</movie-admin-panel>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-card class="mat-elevation-z8">
|
||||||
|
<mat-card-content class="medium-font">
|
||||||
|
<movie-information-panel [movie]="movie" [advancedOptions]="advancedOptions"></movie-information-panel>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<mat-card class=" mat-elevation-z8 spacing-below">
|
||||||
|
<mat-card-content>
|
||||||
|
{{movie.overview}}
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<cast-carousel [cast]="movie.credits.cast"></cast-carousel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<mat-accordion class="mat-elevation-z8 spacing-below">
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'MediaDetails.RecommendationsTitle' | translate}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<div class="row card-spacer" *ngIf="movie.recommendations.results.length > 0">
|
||||||
|
|
||||||
|
<div class="col-md-2" *ngFor="let r of movie.recommendations.results">
|
||||||
|
<div class="sidebar affixable affix-top preview-poster">
|
||||||
|
<div class="poster">
|
||||||
|
<a [routerLink]="'/details/movie/'+r.id">
|
||||||
|
<img class="real grow" matTooltip="{{r.title}}"
|
||||||
|
src="https://image.tmdb.org/t/p/w300/{{r.poster_path}}" alt="Poster"
|
||||||
|
style="display: block;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'MediaDetails.SimilarTitle' | translate}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<div class="row card-spacer" *ngIf="movie.similar.results.length > 0">
|
||||||
|
|
||||||
|
<div class="col-md-2" *ngFor="let r of movie.similar.results">
|
||||||
|
<div class="sidebar affixable affix-top preview-poster">
|
||||||
|
<div class="poster ">
|
||||||
|
<a [routerLink]="'/details/movie/'+r.id">
|
||||||
|
<img class="real grow" matTooltip="{{r.title}}"
|
||||||
|
src="https://image.tmdb.org/t/p/w300/{{r.poster_path}}" alt="Poster"
|
||||||
|
style="display: block;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{'MediaDetails.VideosTitle' | translate}}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<div class="row card-spacer" *ngIf="movie.videos.results.length > 0">
|
||||||
|
|
||||||
|
<div class="col-md-6" *ngFor="let video of movie.videos.results">
|
||||||
|
<iframe width="100%" height="315px" [src]="'https://www.youtube.com/embed/' + video.key | safe"
|
||||||
|
frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="bottom-page-gap">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,115 @@
|
||||||
|
import { Component, ViewEncapsulation } from "@angular/core";
|
||||||
|
import { ImageService, SearchV2Service, RequestService, MessageService } from "../../../services";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
||||||
|
import { MatDialog } from "@angular/material";
|
||||||
|
import { YoutubeTrailerComponent } from "../shared/youtube-trailer.component";
|
||||||
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
|
import { IMovieRequests, RequestType, IAdvancedData } from "../../../interfaces";
|
||||||
|
import { DenyDialogComponent } from "../shared/deny-dialog/deny-dialog.component";
|
||||||
|
import { NewIssueComponent } from "../shared/new-issue/new-issue.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./artist-details.component.html",
|
||||||
|
styleUrls: ["../../media-details.component.scss"],
|
||||||
|
})
|
||||||
|
export class ArtistDetailsComponent {
|
||||||
|
public movie: ISearchMovieResultV2;
|
||||||
|
public hasRequest: boolean;
|
||||||
|
public movieRequest: IMovieRequests;
|
||||||
|
public isAdmin: boolean;
|
||||||
|
public advancedOptions: IAdvancedData;
|
||||||
|
|
||||||
|
private theMovidDbId: number;
|
||||||
|
|
||||||
|
constructor(private searchService: SearchV2Service, private route: ActivatedRoute,
|
||||||
|
private sanitizer: DomSanitizer, private imageService: ImageService,
|
||||||
|
public dialog: MatDialog, private requestService: RequestService,
|
||||||
|
public messageService: MessageService, private auth: AuthService) {
|
||||||
|
this.route.params.subscribe((params: any) => {
|
||||||
|
this.theMovidDbId = params.movieDbId;
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public load() {
|
||||||
|
|
||||||
|
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||||
|
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async x => {
|
||||||
|
this.movie = x;
|
||||||
|
if (this.movie.requestId > 0) {
|
||||||
|
// Load up this request
|
||||||
|
this.hasRequest = true;
|
||||||
|
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
|
||||||
|
}
|
||||||
|
this.imageService.getMovieBanner(this.theMovidDbId.toString()).subscribe(x => {
|
||||||
|
this.movie.background = this.sanitizer.bypassSecurityTrustStyle
|
||||||
|
("url(" + x + ")");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async request() {
|
||||||
|
const result = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: null }).toPromise();
|
||||||
|
if (result.result) {
|
||||||
|
this.movie.requested = true;
|
||||||
|
this.messageService.send(result.message, "Ok");
|
||||||
|
} else {
|
||||||
|
this.messageService.send(result.errorMessage, "Ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public openDialog() {
|
||||||
|
this.dialog.open(YoutubeTrailerComponent, {
|
||||||
|
width: '560px',
|
||||||
|
data: this.movie.videos.results[0].key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deny() {
|
||||||
|
const dialogRef = this.dialog.open(DenyDialogComponent, {
|
||||||
|
width: '250px',
|
||||||
|
data: {requestId: this.movieRequest.id, requestType: RequestType.movie}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
this.movieRequest.denied = result;
|
||||||
|
if(this.movieRequest.denied) {
|
||||||
|
this.movie.approved = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async issue() {
|
||||||
|
const dialogRef = this.dialog.open(NewIssueComponent, {
|
||||||
|
width: '500px',
|
||||||
|
data: {requestId: this.movieRequest ? this.movieRequest.id : null, requestType: RequestType.movie, imdbid: this.movie.imdbId}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async approve() {
|
||||||
|
const result = await this.requestService.approveMovie({ id: this.movieRequest.id }).toPromise();
|
||||||
|
if (result.result) {
|
||||||
|
this.movie.approved = false;
|
||||||
|
this.messageService.send("Successfully Approved", "Ok");
|
||||||
|
} else {
|
||||||
|
this.messageService.send(result.errorMessage, "Ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async markAvailable() {
|
||||||
|
const result = await this.requestService.markMovieAvailable({id: this.movieRequest.id}).toPromise();
|
||||||
|
if (result.result) {
|
||||||
|
this.movie.available = true;
|
||||||
|
this.messageService.send(result.message, "Ok");
|
||||||
|
} else {
|
||||||
|
this.messageService.send(result.errorMessage, "Ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAdvancedOptions(data: any) {
|
||||||
|
this.advancedOptions = data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import { MovieAdvancedOptionsComponent } from "./movie/panels/movie-advanced-opt
|
||||||
import { SearchService, RequestService, RadarrService } from "../../services";
|
import { SearchService, RequestService, RadarrService } from "../../services";
|
||||||
import { RequestServiceV2 } from "../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../services/requestV2.service";
|
||||||
import { NewIssueComponent } from "./shared/new-issue/new-issue.component";
|
import { NewIssueComponent } from "./shared/new-issue/new-issue.component";
|
||||||
|
import { ArtistDetailsComponent } from "./artist/artist-details.component";
|
||||||
|
|
||||||
export const components: any[] = [
|
export const components: any[] = [
|
||||||
MovieDetailsComponent,
|
MovieDetailsComponent,
|
||||||
|
@ -30,6 +31,7 @@ export const components: any[] = [
|
||||||
MovieAdminPanelComponent,
|
MovieAdminPanelComponent,
|
||||||
MovieAdvancedOptionsComponent,
|
MovieAdvancedOptionsComponent,
|
||||||
NewIssueComponent,
|
NewIssueComponent,
|
||||||
|
ArtistDetailsComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const entryComponents: any[] = [
|
export const entryComponents: any[] = [
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
<legend>About</legend>
|
<legend>About</legend>
|
||||||
|
|
||||||
<div class="mat-table">
|
<div class="mat-table">
|
||||||
|
<div class="mat-row" style="background:red;" *ngIf="!notSupported">
|
||||||
|
<div class="mat-cell" style="text-align: center;"><b>NOT SUPPORTED OS. Please use the docker image available on the Container Station</b></div>
|
||||||
|
</div>
|
||||||
<div class="mat-row" *ngIf="connectedUsers">
|
<div class="mat-row" *ngIf="connectedUsers">
|
||||||
<div class="mat-cell">Users Online</div>
|
<div class="mat-cell">Users Online</div>
|
||||||
<div class="mat-cell">{{connectedUsers.length}}</div>
|
<div class="mat-cell">{{connectedUsers.length}}</div>
|
||||||
|
|
|
@ -122,7 +122,8 @@ namespace Ombi.Controllers.V1
|
||||||
OsArchitecture = RuntimeInformation.OSArchitecture.ToString(),
|
OsArchitecture = RuntimeInformation.OSArchitecture.ToString(),
|
||||||
OsDescription = RuntimeInformation.OSDescription,
|
OsDescription = RuntimeInformation.OSDescription,
|
||||||
ProcessArchitecture = RuntimeInformation.ProcessArchitecture.ToString(),
|
ProcessArchitecture = RuntimeInformation.ProcessArchitecture.ToString(),
|
||||||
ApplicationBasePath =Directory.GetCurrentDirectory()
|
ApplicationBasePath = Directory.GetCurrentDirectory(),
|
||||||
|
NotSupported = Directory.GetCurrentDirectory().Contains("qpkg")
|
||||||
};
|
};
|
||||||
|
|
||||||
var version = AssemblyHelper.GetRuntimeVersion();
|
var version = AssemblyHelper.GetRuntimeVersion();
|
||||||
|
|
|
@ -5,7 +5,8 @@ using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Ombi.Api.MusicBrainz;
|
||||||
|
using Ombi.Api.MusicBrainz.Models.Search;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
using Ombi.Api.TheMovieDb.Models;
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
using Ombi.Core.Engine.V2;
|
using Ombi.Core.Engine.V2;
|
||||||
|
@ -22,7 +23,7 @@ namespace Ombi.Controllers.V2
|
||||||
public class SearchController : ControllerBase
|
public class SearchController : ControllerBase
|
||||||
{
|
{
|
||||||
public SearchController(IMultiSearchEngine multiSearchEngine, ITvSearchEngine tvSearchEngine,
|
public SearchController(IMultiSearchEngine multiSearchEngine, ITvSearchEngine tvSearchEngine,
|
||||||
IMovieEngineV2 v2Movie, ITVSearchEngineV2 v2Tv)
|
IMovieEngineV2 v2Movie, ITVSearchEngineV2 v2Tv, IMusicBrainzApi musicApi)
|
||||||
{
|
{
|
||||||
_multiSearchEngine = multiSearchEngine;
|
_multiSearchEngine = multiSearchEngine;
|
||||||
_tvSearchEngine = tvSearchEngine;
|
_tvSearchEngine = tvSearchEngine;
|
||||||
|
@ -30,12 +31,14 @@ namespace Ombi.Controllers.V2
|
||||||
_movieEngineV2 = v2Movie;
|
_movieEngineV2 = v2Movie;
|
||||||
_movieEngineV2.ResultLimit = 12;
|
_movieEngineV2.ResultLimit = 12;
|
||||||
_tvEngineV2 = v2Tv;
|
_tvEngineV2 = v2Tv;
|
||||||
|
music = musicApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IMultiSearchEngine _multiSearchEngine;
|
private readonly IMultiSearchEngine _multiSearchEngine;
|
||||||
private readonly IMovieEngineV2 _movieEngineV2;
|
private readonly IMovieEngineV2 _movieEngineV2;
|
||||||
private readonly ITVSearchEngineV2 _tvEngineV2;
|
private readonly ITVSearchEngineV2 _tvEngineV2;
|
||||||
private readonly ITvSearchEngine _tvSearchEngine;
|
private readonly ITvSearchEngine _tvSearchEngine;
|
||||||
|
private readonly IMusicBrainzApi music;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns search results for both TV and Movies
|
/// Returns search results for both TV and Movies
|
||||||
|
@ -334,5 +337,15 @@ namespace Ombi.Controllers.V2
|
||||||
{
|
{
|
||||||
return await _movieEngineV2.GetMoviesByActor(actorId, null);
|
return await _movieEngineV2.GetMoviesByActor(actorId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("artist/{name}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesDefaultResponseType]
|
||||||
|
public async Task<IEnumerable<Artist>> GetArtists(string name)
|
||||||
|
{
|
||||||
|
return await music.SearchArtist(name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,5 +9,6 @@
|
||||||
public string OsDescription { get; set; }
|
public string OsDescription { get; set; }
|
||||||
public string ProcessArchitecture { get; set; }
|
public string ProcessArchitecture { get; set; }
|
||||||
public string ApplicationBasePath { get; set; }
|
public string ApplicationBasePath { get; set; }
|
||||||
|
public bool NotSupported { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue