commit
a787efc660
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
|
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||||
|
|
||||||
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
|
"recommendations": [
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"editorconfig.editorconfig"
|
||||||
|
],
|
||||||
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
@ -1,189 +0,0 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1402
|
|
||||||
#pragma warning disable SA1649
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Controller.Notifications;
|
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
using MediaBrowser.Model.Notifications;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
|
|
||||||
namespace Emby.Notifications.Api
|
|
||||||
{
|
|
||||||
[Route("/Notifications/{UserId}", "GET", Summary = "Gets notifications")]
|
|
||||||
public class GetNotifications : IReturn<NotificationResult>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
|
||||||
public bool? IsRead { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int? StartIndex { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int? Limit { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Notification
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
|
|
||||||
public bool IsRead { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string Description { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string Url { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public NotificationLevel Level { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NotificationResult
|
|
||||||
{
|
|
||||||
public IReadOnlyList<Notification> Notifications { get; set; } = Array.Empty<Notification>();
|
|
||||||
|
|
||||||
public int TotalRecordCount { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NotificationsSummary
|
|
||||||
{
|
|
||||||
public int UnreadCount { get; set; }
|
|
||||||
|
|
||||||
public NotificationLevel MaxUnreadNotificationLevel { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/{UserId}/Summary", "GET", Summary = "Gets a notification summary for a user")]
|
|
||||||
public class GetNotificationsSummary : IReturn<NotificationsSummary>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/Types", "GET", Summary = "Gets notification types")]
|
|
||||||
public class GetNotificationTypes : IReturn<List<NotificationTypeInfo>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/Services", "GET", Summary = "Gets notification types")]
|
|
||||||
public class GetNotificationServices : IReturn<List<NameIdPair>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/Admin", "POST", Summary = "Sends a notification to all admin users")]
|
|
||||||
public class AddAdminNotification : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Description { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string? ImageUrl { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string? Url { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public NotificationLevel Level { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/{UserId}/Read", "POST", Summary = "Marks notifications as read")]
|
|
||||||
public class MarkRead : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
|
||||||
public string Ids { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")]
|
|
||||||
public class MarkUnread : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
|
||||||
public string Ids { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authenticated]
|
|
||||||
public class NotificationsService : IService
|
|
||||||
{
|
|
||||||
private readonly INotificationManager _notificationManager;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
|
|
||||||
public NotificationsService(INotificationManager notificationManager, IUserManager userManager)
|
|
||||||
{
|
|
||||||
_notificationManager = notificationManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotificationTypes request)
|
|
||||||
{
|
|
||||||
return _notificationManager.GetNotificationTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotificationServices request)
|
|
||||||
{
|
|
||||||
return _notificationManager.GetNotificationServices().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotificationsSummary request)
|
|
||||||
{
|
|
||||||
return new NotificationsSummary
|
|
||||||
{
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Post(AddAdminNotification request)
|
|
||||||
{
|
|
||||||
// This endpoint really just exists as post of a real with sickbeard
|
|
||||||
var notification = new NotificationRequest
|
|
||||||
{
|
|
||||||
Date = DateTime.UtcNow,
|
|
||||||
Description = request.Description,
|
|
||||||
Level = request.Level,
|
|
||||||
Name = request.Name,
|
|
||||||
Url = request.Url,
|
|
||||||
UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public void Post(MarkRead request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public void Post(MarkUnread request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotifications request)
|
|
||||||
{
|
|
||||||
return new NotificationResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,125 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Constants;
|
||||||
|
using Jellyfin.Api.Models.ConfigurationDtos;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration Controller.
|
||||||
|
/// </summary>
|
||||||
|
[Route("System")]
|
||||||
|
[Authorize]
|
||||||
|
public class ConfigurationController : BaseJellyfinApiController
|
||||||
|
{
|
||||||
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ConfigurationController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
|
public ConfigurationController(
|
||||||
|
IServerConfigurationManager configurationManager,
|
||||||
|
IMediaEncoder mediaEncoder)
|
||||||
|
{
|
||||||
|
_configurationManager = configurationManager;
|
||||||
|
_mediaEncoder = mediaEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets application configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">Application configuration returned.</response>
|
||||||
|
/// <returns>Application configuration.</returns>
|
||||||
|
[HttpGet("Configuration")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<ServerConfiguration> GetConfiguration()
|
||||||
|
{
|
||||||
|
return _configurationManager.Configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates application configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configuration">Configuration.</param>
|
||||||
|
/// <response code="200">Configuration updated.</response>
|
||||||
|
/// <returns>Update status.</returns>
|
||||||
|
[HttpPost("Configuration")]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configurationManager.ReplaceConfiguration(configuration);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a named configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Configuration key.</param>
|
||||||
|
/// <response code="200">Configuration returned.</response>
|
||||||
|
/// <returns>Configuration.</returns>
|
||||||
|
[HttpGet("Configuration/{Key}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<object> GetNamedConfiguration([FromRoute] string key)
|
||||||
|
{
|
||||||
|
return _configurationManager.GetConfiguration(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates named configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Configuration key.</param>
|
||||||
|
/// <response code="200">Named configuration updated.</response>
|
||||||
|
/// <returns>Update status.</returns>
|
||||||
|
[HttpPost("Configuration/{Key}")]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string key)
|
||||||
|
{
|
||||||
|
var configurationType = _configurationManager.GetConfigurationType(key);
|
||||||
|
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType);
|
||||||
|
_configurationManager.SaveConfiguration(key, configuration);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a default MetadataOptions object.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">Metadata options returned.</response>
|
||||||
|
/// <returns>Default MetadataOptions.</returns>
|
||||||
|
[HttpGet("Configuration/MetadataOptions/Default")]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<MetadataOptions> GetDefaultMetadataOptions()
|
||||||
|
{
|
||||||
|
return new MetadataOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the path to the media encoder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaEncoderPath">Media encoder path form body.</param>
|
||||||
|
/// <response code="200">Media encoder path updated.</response>
|
||||||
|
/// <returns>Status.</returns>
|
||||||
|
[HttpPost("MediaEncoder/Path")]
|
||||||
|
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath)
|
||||||
|
{
|
||||||
|
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Jellyfin.Api.Constants;
|
||||||
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Security;
|
||||||
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Devices;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Devices Controller.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize]
|
||||||
|
public class DevicesController : BaseJellyfinApiController
|
||||||
|
{
|
||||||
|
private readonly IDeviceManager _deviceManager;
|
||||||
|
private readonly IAuthenticationRepository _authenticationRepository;
|
||||||
|
private readonly ISessionManager _sessionManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DevicesController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
|
||||||
|
/// <param name="authenticationRepository">Instance of <see cref="IAuthenticationRepository"/> interface.</param>
|
||||||
|
/// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
|
||||||
|
public DevicesController(
|
||||||
|
IDeviceManager deviceManager,
|
||||||
|
IAuthenticationRepository authenticationRepository,
|
||||||
|
ISessionManager sessionManager)
|
||||||
|
{
|
||||||
|
_deviceManager = deviceManager;
|
||||||
|
_authenticationRepository = authenticationRepository;
|
||||||
|
_sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Devices.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="supportsSync">Gets or sets a value indicating whether [supports synchronize].</param>
|
||||||
|
/// <param name="userId">Gets or sets the user identifier.</param>
|
||||||
|
/// <response code="200">Devices retrieved.</response>
|
||||||
|
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
|
||||||
|
{
|
||||||
|
var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
|
||||||
|
return _deviceManager.GetDevices(deviceQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get info for a device.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Device Id.</param>
|
||||||
|
/// <response code="200">Device info retrieved.</response>
|
||||||
|
/// <response code="404">Device not found.</response>
|
||||||
|
/// <returns>An <see cref="OkResult"/> containing the device info on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||||
|
[HttpGet("Info")]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, BindRequired] string id)
|
||||||
|
{
|
||||||
|
var deviceInfo = _deviceManager.GetDevice(id);
|
||||||
|
if (deviceInfo == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get options for a device.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Device Id.</param>
|
||||||
|
/// <response code="200">Device options retrieved.</response>
|
||||||
|
/// <response code="404">Device not found.</response>
|
||||||
|
/// <returns>An <see cref="OkResult"/> containing the device info on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||||
|
[HttpGet("Options")]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, BindRequired] string id)
|
||||||
|
{
|
||||||
|
var deviceInfo = _deviceManager.GetDeviceOptions(id);
|
||||||
|
if (deviceInfo == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update device options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Device Id.</param>
|
||||||
|
/// <param name="deviceOptions">Device Options.</param>
|
||||||
|
/// <response code="200">Device options updated.</response>
|
||||||
|
/// <response code="404">Device not found.</response>
|
||||||
|
/// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||||
|
[HttpPost("Options")]
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public ActionResult UpdateDeviceOptions(
|
||||||
|
[FromQuery, BindRequired] string id,
|
||||||
|
[FromBody, BindRequired] DeviceOptions deviceOptions)
|
||||||
|
{
|
||||||
|
var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
|
||||||
|
if (existingDeviceOptions == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceManager.UpdateDeviceOptions(id, deviceOptions);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a device.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Device Id.</param>
|
||||||
|
/// <response code="200">Device deleted.</response>
|
||||||
|
/// <response code="404">Device not found.</response>
|
||||||
|
/// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||||
|
[HttpDelete]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult DeleteDevice([FromQuery, BindRequired] string id)
|
||||||
|
{
|
||||||
|
var existingDevice = _deviceManager.GetDevice(id);
|
||||||
|
if (existingDevice == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items;
|
||||||
|
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
_sessionManager.Logout(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
#nullable enable
|
||||||
|
#pragma warning disable CA1801
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Jellyfin.Api.Models.NotificationDtos;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Notifications;
|
||||||
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Notifications;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The notification controller.
|
||||||
|
/// </summary>
|
||||||
|
public class NotificationsController : BaseJellyfinApiController
|
||||||
|
{
|
||||||
|
private readonly INotificationManager _notificationManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NotificationsController" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notificationManager">The notification manager.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
public NotificationsController(INotificationManager notificationManager, IUserManager userManager)
|
||||||
|
{
|
||||||
|
_notificationManager = notificationManager;
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a user's notifications.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's ID.</param>
|
||||||
|
/// <param name="isRead">An optional filter by notification read state.</param>
|
||||||
|
/// <param name="startIndex">The optional index to start at. All notifications with a lower index will be omitted from the results.</param>
|
||||||
|
/// <param name="limit">An optional limit on the number of notifications returned.</param>
|
||||||
|
/// <response code="200">Notifications returned.</response>
|
||||||
|
/// <returns>An <see cref="OkResult"/> containing a list of notifications.</returns>
|
||||||
|
[HttpGet("{UserID}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<NotificationResultDto> GetNotifications(
|
||||||
|
[FromRoute] string userId,
|
||||||
|
[FromQuery] bool? isRead,
|
||||||
|
[FromQuery] int? startIndex,
|
||||||
|
[FromQuery] int? limit)
|
||||||
|
{
|
||||||
|
return new NotificationResultDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a user's notification summary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's ID.</param>
|
||||||
|
/// <response code="200">Summary of user's notifications returned.</response>
|
||||||
|
/// <returns>An <cref see="OkResult"/> containing a summary of the users notifications.</returns>
|
||||||
|
[HttpGet("{UserID}/Summary")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<NotificationsSummaryDto> GetNotificationsSummary(
|
||||||
|
[FromRoute] string userId)
|
||||||
|
{
|
||||||
|
return new NotificationsSummaryDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets notification types.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">All notification types returned.</response>
|
||||||
|
/// <returns>An <cref see="OkResult"/> containing a list of all notification types.</returns>
|
||||||
|
[HttpGet("Types")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
|
||||||
|
{
|
||||||
|
return _notificationManager.GetNotificationTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets notification services.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">All notification services returned.</response>
|
||||||
|
/// <returns>An <cref see="OkResult"/> containing a list of all notification services.</returns>
|
||||||
|
[HttpGet("Services")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public IEnumerable<NameIdPair> GetNotificationServices()
|
||||||
|
{
|
||||||
|
return _notificationManager.GetNotificationServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a notification to all admins.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the notification.</param>
|
||||||
|
/// <param name="description">The description of the notification.</param>
|
||||||
|
/// <param name="url">The URL of the notification.</param>
|
||||||
|
/// <param name="level">The level of the notification.</param>
|
||||||
|
/// <response code="200">Notification sent.</response>
|
||||||
|
/// <returns>An <cref see="OkResult"/>.</returns>
|
||||||
|
[HttpPost("Admin")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult CreateAdminNotification(
|
||||||
|
[FromQuery] string name,
|
||||||
|
[FromQuery] string description,
|
||||||
|
[FromQuery] string? url,
|
||||||
|
[FromQuery] NotificationLevel? level)
|
||||||
|
{
|
||||||
|
var notification = new NotificationRequest
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Description = description,
|
||||||
|
Url = url,
|
||||||
|
Level = level ?? NotificationLevel.Normal,
|
||||||
|
UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(),
|
||||||
|
Date = DateTime.UtcNow,
|
||||||
|
};
|
||||||
|
|
||||||
|
_notificationManager.SendNotification(notification, CancellationToken.None);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets notifications as read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The userID.</param>
|
||||||
|
/// <param name="ids">A comma-separated list of the IDs of notifications which should be set as read.</param>
|
||||||
|
/// <response code="200">Notifications set as read.</response>
|
||||||
|
/// <returns>An <cref see="OkResult"/>.</returns>
|
||||||
|
[HttpPost("{UserID}/Read")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult SetRead(
|
||||||
|
[FromRoute] string userId,
|
||||||
|
[FromQuery] string ids)
|
||||||
|
{
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets notifications as unread.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The userID.</param>
|
||||||
|
/// <param name="ids">A comma-separated list of the IDs of notifications which should be set as unread.</param>
|
||||||
|
/// <response code="200">Notifications set as unread.</response>
|
||||||
|
/// <returns>An <cref see="OkResult"/>.</returns>
|
||||||
|
[HttpPost("{UserID}/Unread")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult SetUnread(
|
||||||
|
[FromRoute] string userId,
|
||||||
|
[FromQuery] string ids)
|
||||||
|
{
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
namespace Jellyfin.Api.Models.ConfigurationDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Media Encoder Path Dto.
|
||||||
|
/// </summary>
|
||||||
|
public class MediaEncoderPathDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets media encoder path.
|
||||||
|
/// </summary>
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets media encoder path type.
|
||||||
|
/// </summary>
|
||||||
|
public string PathType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MediaBrowser.Model.Notifications;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Models.NotificationDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The notification DTO.
|
||||||
|
/// </summary>
|
||||||
|
public class NotificationDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification ID. Defaults to an empty string.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification's user ID. Defaults to an empty string.
|
||||||
|
/// </summary>
|
||||||
|
public string UserId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification date.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the notification has been read. Defaults to false.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRead { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification's name. Defaults to an empty string.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification's description. Defaults to an empty string.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification's URL. Defaults to an empty string.
|
||||||
|
/// </summary>
|
||||||
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the notification level.
|
||||||
|
/// </summary>
|
||||||
|
public NotificationLevel Level { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Models.NotificationDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A list of notifications with the total record count for pagination.
|
||||||
|
/// </summary>
|
||||||
|
public class NotificationResultDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current page of notifications.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<NotificationDto> Notifications { get; set; } = Array.Empty<NotificationDto>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the total number of notifications.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalRecordCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using MediaBrowser.Model.Notifications;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Models.NotificationDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The notification summary DTO.
|
||||||
|
/// </summary>
|
||||||
|
public class NotificationsSummaryDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of unread notifications.
|
||||||
|
/// </summary>
|
||||||
|
public int UnreadCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum unread notification level.
|
||||||
|
/// </summary>
|
||||||
|
public NotificationLevel? MaxUnreadNotificationLevel { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using MediaBrowser.Common.Json;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Formatters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Camel Case Json Profile Formatter.
|
||||||
|
/// </summary>
|
||||||
|
public class CamelCaseJsonProfileFormatter : SystemTextJsonOutputFormatter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCase)
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Clear();
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
using MediaBrowser.Common.Json;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Formatters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Pascal Case Json Profile Formatter.
|
||||||
|
/// </summary>
|
||||||
|
public class PascalCaseJsonProfileFormatter : SystemTextJsonOutputFormatter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCase)
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Clear();
|
||||||
|
// Add application/json for default formatter
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Controller.Authentication;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Middleware
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exception Middleware.
|
||||||
|
/// </summary>
|
||||||
|
public class ExceptionMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<ExceptionMiddleware> _logger;
|
||||||
|
private readonly IServerConfigurationManager _configuration;
|
||||||
|
private readonly IWebHostEnvironment _hostEnvironment;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ExceptionMiddleware"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">Next request delegate.</param>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{ExceptionMiddleware}"/> interface.</param>
|
||||||
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
|
/// <param name="hostEnvironment">Instance of the <see cref="IWebHostEnvironment"/> interface.</param>
|
||||||
|
public ExceptionMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
ILogger<ExceptionMiddleware> logger,
|
||||||
|
IServerConfigurationManager serverConfigurationManager,
|
||||||
|
IWebHostEnvironment hostEnvironment)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = serverConfigurationManager;
|
||||||
|
_hostEnvironment = hostEnvironment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Request context.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (context.Response.HasStarted)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("The response has already started, the exception middleware will not be executed.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
ex = GetActualException(ex);
|
||||||
|
|
||||||
|
bool ignoreStackTrace =
|
||||||
|
ex is SocketException
|
||||||
|
|| ex is IOException
|
||||||
|
|| ex is OperationCanceledException
|
||||||
|
|| ex is SecurityException
|
||||||
|
|| ex is AuthenticationException
|
||||||
|
|| ex is FileNotFoundException;
|
||||||
|
|
||||||
|
if (ignoreStackTrace)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"Error processing request: {ExceptionMessage}. URL {Method} {Url}.",
|
||||||
|
ex.Message.TrimEnd('.'),
|
||||||
|
context.Request.Method,
|
||||||
|
context.Request.Path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error processing request. URL {Method} {Url}.",
|
||||||
|
context.Request.Method,
|
||||||
|
context.Request.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = GetStatusCode(ex);
|
||||||
|
context.Response.ContentType = MediaTypeNames.Text.Plain;
|
||||||
|
|
||||||
|
// Don't send exception unless the server is in a Development environment
|
||||||
|
var errorContent = _hostEnvironment.IsDevelopment()
|
||||||
|
? NormalizeExceptionMessage(ex.Message)
|
||||||
|
: "Error processing request.";
|
||||||
|
await context.Response.WriteAsync(errorContent).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Exception GetActualException(Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is AggregateException agg)
|
||||||
|
{
|
||||||
|
var inner = agg.InnerException;
|
||||||
|
if (inner != null)
|
||||||
|
{
|
||||||
|
return GetActualException(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
var inners = agg.InnerExceptions;
|
||||||
|
if (inners.Count > 0)
|
||||||
|
{
|
||||||
|
return GetActualException(inners[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetStatusCode(Exception ex)
|
||||||
|
{
|
||||||
|
switch (ex)
|
||||||
|
{
|
||||||
|
case ArgumentException _: return StatusCodes.Status400BadRequest;
|
||||||
|
case SecurityException _: return StatusCodes.Status401Unauthorized;
|
||||||
|
case DirectoryNotFoundException _:
|
||||||
|
case FileNotFoundException _:
|
||||||
|
case ResourceNotFoundException _: return StatusCodes.Status404NotFound;
|
||||||
|
case MethodNotAllowedException _: return StatusCodes.Status405MethodNotAllowed;
|
||||||
|
default: return StatusCodes.Status500InternalServerError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string NormalizeExceptionMessage(string msg)
|
||||||
|
{
|
||||||
|
if (msg == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip any information we don't want to reveal
|
||||||
|
return msg.Replace(
|
||||||
|
_configuration.ApplicationPaths.ProgramSystemPath,
|
||||||
|
string.Empty,
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace(
|
||||||
|
_configuration.ApplicationPaths.ProgramDataPath,
|
||||||
|
string.Empty,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,146 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class GetConfiguration
|
|
||||||
/// </summary>
|
|
||||||
[Route("/System/Configuration", "GET", Summary = "Gets application configuration")]
|
|
||||||
[Authenticated]
|
|
||||||
public class GetConfiguration : IReturn<ServerConfiguration>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")]
|
|
||||||
[Authenticated(AllowBeforeStartupWizard = true)]
|
|
||||||
public class GetNamedConfiguration
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Key { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class UpdateConfiguration
|
|
||||||
/// </summary>
|
|
||||||
[Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class UpdateConfiguration : ServerConfiguration, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Key { get; set; }
|
|
||||||
|
|
||||||
public Stream RequestStream { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")]
|
|
||||||
[Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
|
|
||||||
public class UpdateMediaEncoderPath : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string Path { get; set; }
|
|
||||||
[ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string PathType { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConfigurationService : BaseApiService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The _json serializer
|
|
||||||
/// </summary>
|
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _configuration manager
|
|
||||||
/// </summary>
|
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
|
||||||
|
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
|
||||||
|
|
||||||
public ConfigurationService(
|
|
||||||
ILogger<ConfigurationService> logger,
|
|
||||||
IServerConfigurationManager serverConfigurationManager,
|
|
||||||
IHttpResultFactory httpResultFactory,
|
|
||||||
IJsonSerializer jsonSerializer,
|
|
||||||
IServerConfigurationManager configurationManager,
|
|
||||||
IMediaEncoder mediaEncoder)
|
|
||||||
: base(logger, serverConfigurationManager, httpResultFactory)
|
|
||||||
{
|
|
||||||
_jsonSerializer = jsonSerializer;
|
|
||||||
_configurationManager = configurationManager;
|
|
||||||
_mediaEncoder = mediaEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(UpdateMediaEncoderPath request)
|
|
||||||
{
|
|
||||||
_mediaEncoder.UpdateEncoderPath(request.Path, request.PathType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the specified request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
/// <returns>System.Object.</returns>
|
|
||||||
public object Get(GetConfiguration request)
|
|
||||||
{
|
|
||||||
return ToOptimizedResult(_configurationManager.Configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetNamedConfiguration request)
|
|
||||||
{
|
|
||||||
var result = _configurationManager.GetConfiguration(request.Key);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Posts the specified configuraiton.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
public void Post(UpdateConfiguration request)
|
|
||||||
{
|
|
||||||
// Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
|
|
||||||
var json = _jsonSerializer.SerializeToString(request);
|
|
||||||
|
|
||||||
var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
|
|
||||||
|
|
||||||
_configurationManager.ReplaceConfiguration(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Post(UpdateNamedConfiguration request)
|
|
||||||
{
|
|
||||||
var key = GetPathValue(2).ToString();
|
|
||||||
|
|
||||||
var configurationType = _configurationManager.GetConfigurationType(key);
|
|
||||||
var configuration = await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, configurationType).ConfigureAwait(false);
|
|
||||||
|
|
||||||
_configurationManager.SaveConfiguration(key, configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetDefaultMetadataOptions request)
|
|
||||||
{
|
|
||||||
return ToOptimizedResult(new MetadataOptions());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,168 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Devices;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Controller.Security;
|
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Model.Devices;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Devices
|
|
||||||
{
|
|
||||||
[Route("/Devices", "GET", Summary = "Gets all devices")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class GetDevices : DeviceQuery, IReturn<QueryResult<DeviceInfo>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Devices/Info", "GET", Summary = "Gets info for a device")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class GetDeviceInfo : IReturn<DeviceInfo>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Devices/Options", "GET", Summary = "Gets options for a device")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class GetDeviceOptions : IReturn<DeviceOptions>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Devices", "DELETE", Summary = "Deletes a device")]
|
|
||||||
public class DeleteDevice
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Devices/CameraUploads", "GET", Summary = "Gets camera upload history for a device")]
|
|
||||||
[Authenticated]
|
|
||||||
public class GetCameraUploads : IReturn<ContentUploadHistory>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string DeviceId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Devices/CameraUploads", "POST", Summary = "Uploads content")]
|
|
||||||
[Authenticated]
|
|
||||||
public class PostCameraUpload : IRequiresRequestStream, IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string DeviceId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Album", Description = "Album", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Album { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public Stream RequestStream { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Devices/Options", "POST", Summary = "Updates device options")]
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class PostDeviceOptions : DeviceOptions, IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DeviceService : BaseApiService
|
|
||||||
{
|
|
||||||
private readonly IDeviceManager _deviceManager;
|
|
||||||
private readonly IAuthenticationRepository _authRepo;
|
|
||||||
private readonly ISessionManager _sessionManager;
|
|
||||||
|
|
||||||
public DeviceService(
|
|
||||||
ILogger<DeviceService> logger,
|
|
||||||
IServerConfigurationManager serverConfigurationManager,
|
|
||||||
IHttpResultFactory httpResultFactory,
|
|
||||||
IDeviceManager deviceManager,
|
|
||||||
IAuthenticationRepository authRepo,
|
|
||||||
ISessionManager sessionManager)
|
|
||||||
: base(logger, serverConfigurationManager, httpResultFactory)
|
|
||||||
{
|
|
||||||
_deviceManager = deviceManager;
|
|
||||||
_authRepo = authRepo;
|
|
||||||
_sessionManager = sessionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(PostDeviceOptions request)
|
|
||||||
{
|
|
||||||
_deviceManager.UpdateDeviceOptions(request.Id, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetDevices request)
|
|
||||||
{
|
|
||||||
return ToOptimizedResult(_deviceManager.GetDevices(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetDeviceInfo request)
|
|
||||||
{
|
|
||||||
return _deviceManager.GetDevice(request.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetDeviceOptions request)
|
|
||||||
{
|
|
||||||
return _deviceManager.GetDeviceOptions(request.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetCameraUploads request)
|
|
||||||
{
|
|
||||||
return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(DeleteDevice request)
|
|
||||||
{
|
|
||||||
var sessions = _authRepo.Get(new AuthenticationInfoQuery
|
|
||||||
{
|
|
||||||
DeviceId = request.Id
|
|
||||||
|
|
||||||
}).Items;
|
|
||||||
|
|
||||||
foreach (var session in sessions)
|
|
||||||
{
|
|
||||||
_sessionManager.Logout(session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Post(PostCameraUpload request)
|
|
||||||
{
|
|
||||||
var deviceId = Request.QueryString["DeviceId"];
|
|
||||||
var album = Request.QueryString["Album"];
|
|
||||||
var id = Request.QueryString["Id"];
|
|
||||||
var name = Request.QueryString["Name"];
|
|
||||||
var req = Request.Response.HttpContext.Request;
|
|
||||||
|
|
||||||
if (req.HasFormContentType)
|
|
||||||
{
|
|
||||||
var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0];
|
|
||||||
|
|
||||||
return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo
|
|
||||||
{
|
|
||||||
MimeType = file.ContentType,
|
|
||||||
Album = album,
|
|
||||||
Name = name,
|
|
||||||
Id = id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
|
||||||
{
|
|
||||||
MimeType = Request.ContentType,
|
|
||||||
Album = album,
|
|
||||||
Name = name,
|
|
||||||
Id = id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class CheckForUpdateResult.
|
|
||||||
/// </summary>
|
|
||||||
public class CheckForUpdateResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance is update available.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is update available; otherwise, <c>false</c>.</value>
|
|
||||||
public bool IsUpdateAvailable { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the available version.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The available version.</value>
|
|
||||||
public string AvailableVersion
|
|
||||||
{
|
|
||||||
get => Package != null ? Package.versionStr : "0.0.0.1";
|
|
||||||
set { } // need this for the serializer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get or sets package information for an available update
|
|
||||||
/// </summary>
|
|
||||||
public PackageVersionInfo Package { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enum PackageType.
|
|
||||||
/// </summary>
|
|
||||||
public enum PackageTargetSystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Server.
|
|
||||||
/// </summary>
|
|
||||||
Server,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MB Theater.
|
|
||||||
/// </summary>
|
|
||||||
MBTheater,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MB Classic.
|
|
||||||
/// </summary>
|
|
||||||
MBClassic
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enum PackageVersionClass.
|
|
||||||
/// </summary>
|
|
||||||
public enum PackageVersionClass
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The release.
|
|
||||||
/// </summary>
|
|
||||||
Release = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The beta.
|
|
||||||
/// </summary>
|
|
||||||
Beta = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The dev.
|
|
||||||
/// </summary>
|
|
||||||
Dev = 2
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Updates
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class PackageVersionInfo.
|
|
||||||
/// </summary>
|
|
||||||
public class PackageVersionInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the guid.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The guid.</value>
|
|
||||||
public string guid { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the version STR.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The version STR.</value>
|
|
||||||
public string versionStr { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _version
|
|
||||||
/// </summary>
|
|
||||||
private Version _version;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the version.
|
|
||||||
/// Had to make this an interpreted property since Protobuf can't handle Version
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The version.</value>
|
|
||||||
[JsonIgnore]
|
|
||||||
public Version Version
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_version == null)
|
|
||||||
{
|
|
||||||
var ver = versionStr;
|
|
||||||
_version = new Version(string.IsNullOrEmpty(ver) ? "0.0.0.1" : ver);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the classification.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The classification.</value>
|
|
||||||
public PackageVersionClass classification { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the description.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The description.</value>
|
|
||||||
public string description { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the required version STR.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The required version STR.</value>
|
|
||||||
public string requiredVersionStr { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the source URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The source URL.</value>
|
|
||||||
public string sourceUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the source URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The source URL.</value>
|
|
||||||
public string checksum { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the target filename.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The target filename.</value>
|
|
||||||
public string targetFilename { get; set; }
|
|
||||||
|
|
||||||
public string infoUrl { get; set; }
|
|
||||||
|
|
||||||
public string runtimes { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Updates
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class PackageVersionInfo.
|
||||||
|
/// </summary>
|
||||||
|
public class VersionInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The guid.</value>
|
||||||
|
public string guid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The version.</value>
|
||||||
|
public Version version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the changelog for this version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The changelog.</value>
|
||||||
|
public string changelog { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ABI that this version was built against.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The target ABI version.</value>
|
||||||
|
public string targetAbi { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the source URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The source URL.</value>
|
||||||
|
public string sourceUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a checksum for the binary.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The checksum.</value>
|
||||||
|
public string checksum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the target filename for the downloaded binary.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The target filename.</value>
|
||||||
|
public string filename { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue