diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
new file mode 100644
index 0000000000..7407c44878
--- /dev/null
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -0,0 +1,260 @@
+#nullable enable
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Security;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Devices;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Jellyfin.Api.Controllers
+{
+ ///
+ /// Devices Controller.
+ ///
+ [Authenticated]
+ public class DevicesController : BaseJellyfinApiController
+ {
+ private readonly IDeviceManager _deviceManager;
+ private readonly IAuthenticationRepository _authenticationRepository;
+ private readonly ISessionManager _sessionManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of interface.
+ /// Instance of interface.
+ /// Instance of interface.
+ public DevicesController(
+ IDeviceManager deviceManager,
+ IAuthenticationRepository authenticationRepository,
+ ISessionManager sessionManager)
+ {
+ _deviceManager = deviceManager;
+ _authenticationRepository = authenticationRepository;
+ _sessionManager = sessionManager;
+ }
+
+ ///
+ /// Get Devices.
+ ///
+ /// /// Gets or sets a value indicating whether [supports synchronize].
+ /// /// Gets or sets the user identifier.
+ /// Device Infos.
+ [HttpGet]
+ [ProducesResponseType(typeof(DeviceInfo[]), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public IActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
+ {
+ try
+ {
+ var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
+ var devices = _deviceManager.GetDevices(deviceQuery);
+ return Ok(devices);
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+
+ ///
+ /// Get info for a device.
+ ///
+ /// Device Id.
+ /// Device Info.
+ [HttpGet("Info")]
+ [ProducesResponseType(typeof(DeviceInfo), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public IActionResult GetDeviceInfo([FromQuery, BindRequired] string id)
+ {
+ try
+ {
+ var deviceInfo = _deviceManager.GetDevice(id);
+ if (deviceInfo == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(deviceInfo);
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+
+ ///
+ /// Get options for a device.
+ ///
+ /// Device Id.
+ /// Device Info.
+ [HttpGet("Options")]
+ [ProducesResponseType(typeof(DeviceOptions), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public IActionResult GetDeviceOptions([FromQuery, BindRequired] string id)
+ {
+ try
+ {
+ var deviceInfo = _deviceManager.GetDeviceOptions(id);
+ if (deviceInfo == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(deviceInfo);
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+
+ ///
+ /// Update device options.
+ ///
+ /// Device Id.
+ /// Device Options.
+ /// Status.
+ [HttpPost("Options")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public IActionResult UpdateDeviceOptions(
+ [FromQuery, BindRequired] string id,
+ [FromBody, BindRequired] DeviceOptions deviceOptions)
+ {
+ try
+ {
+ var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
+ if (existingDeviceOptions == null)
+ {
+ return NotFound();
+ }
+
+ _deviceManager.UpdateDeviceOptions(id, deviceOptions);
+ return Ok();
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+
+ ///
+ /// Deletes a device.
+ ///
+ /// Device Id.
+ /// Status.
+ [HttpDelete]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public IActionResult DeleteDevice([FromQuery, BindRequired] string id)
+ {
+ try
+ {
+ var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items;
+
+ foreach (var session in sessions)
+ {
+ _sessionManager.Logout(session);
+ }
+
+ return Ok();
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+
+ ///
+ /// Gets camera upload history for a device.
+ ///
+ /// Device Id.
+ /// Content Upload History.
+ [HttpGet("CameraUploads")]
+ [ProducesResponseType(typeof(ContentUploadHistory), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public IActionResult GetCameraUploads([FromQuery, BindRequired] string id)
+ {
+ try
+ {
+ var uploadHistory = _deviceManager.GetCameraUploadHistory(id);
+ return Ok(uploadHistory);
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+
+ ///
+ /// Uploads content.
+ ///
+ /// Device Id.
+ /// Album.
+ /// Name.
+ /// Id.
+ /// Status.
+ [HttpPost("CameraUploads")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
+ public async Task PostCameraUploadAsync(
+ [FromQuery, BindRequired] string deviceId,
+ [FromQuery, BindRequired] string album,
+ [FromQuery, BindRequired] string name,
+ [FromQuery, BindRequired] string id)
+ {
+ try
+ {
+ Stream fileStream;
+ string contentType;
+
+ if (Request.HasFormContentType)
+ {
+ if (Request.Form.Files.Any())
+ {
+ fileStream = Request.Form.Files[0].OpenReadStream();
+ contentType = Request.Form.Files[0].ContentType;
+ }
+ else
+ {
+ return BadRequest();
+ }
+ }
+ else
+ {
+ fileStream = Request.Body;
+ contentType = Request.ContentType;
+ }
+
+ await _deviceManager.AcceptCameraUpload(
+ deviceId,
+ fileStream,
+ new LocalFileInfo
+ {
+ MimeType = contentType,
+ Album = album,
+ Name = name,
+ Id = id
+ }).ConfigureAwait(false);
+
+ return Ok();
+ }
+ catch (Exception e)
+ {
+ return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs
deleted file mode 100644
index 7004a2559e..0000000000
--- a/MediaBrowser.Api/Devices/DeviceService.cs
+++ /dev/null
@@ -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>
- {
- }
-
- [Route("/Devices/Info", "GET", Summary = "Gets info for a device")]
- [Authenticated(Roles = "Admin")]
- public class GetDeviceInfo : IReturn
- {
- [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
- {
- [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
- {
- [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 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
- });
- }
- }
-}