using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using MediaBrowser.Common.Configuration; using Microsoft.AspNetCore.Authorization; namespace Jellyfin.Plugin.MediaBarEnhanced.Api { /// /// Controller for handling custom overlay image uploads and retrieval. /// [ApiController] [Route("MediaBarEnhanced")] public class OverlayImageController : ControllerBase { private readonly IApplicationPaths _applicationPaths; private readonly string _imageDirectory; public OverlayImageController(IApplicationPaths applicationPaths) { _applicationPaths = applicationPaths; _imageDirectory = Path.Combine(applicationPaths.PluginConfigurationsPath, "Jellyfin.Plugin.MediaBarEnhanced", "Assets"); } /// /// Uploads a new custom overlay image. /// [Authorize(Policy = "RequiresElevation")] [HttpPost("OverlayImage")] [Consumes("multipart/form-data")] public async Task UploadImage([FromForm] IFormFile file, [FromQuery] string? filename = null) { if (file == null || file.Length == 0) { return BadRequest("No file uploaded."); } // Extract original extension or fallback to .jpg string extension = Path.GetExtension(file.FileName); if (string.IsNullOrWhiteSpace(extension)) extension = ".jpg"; // Delete any existing file with this prefix before saving the new one string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}"; try { if (!Directory.Exists(_imageDirectory)) { Directory.CreateDirectory(_imageDirectory); } // Remove existing var existingFiles = Directory.GetFiles(_imageDirectory, $"{prefix}.*"); foreach(var extFile in existingFiles) { System.IO.File.Delete(extFile); } string targetFileName = $"{prefix}{extension}"; string targetPath = Path.Combine(_imageDirectory, targetFileName); using (var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None)) { await file.CopyToAsync(stream).ConfigureAwait(false); } // Return the GET URL that the frontend can use var qs = string.IsNullOrWhiteSpace(filename) ? "" : $"?filename={Uri.EscapeDataString(filename)}&"; var getUrl = $"/MediaBarEnhanced/OverlayImage{qs}{(qs == "" ? "?" : "")}t={DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; return Ok(new { url = getUrl }); } catch (Exception ex) { return StatusCode(500, $"Internal server error: {ex.Message}"); } } /// /// Retrieves the custom overlay image. /// [HttpGet("OverlayImage")] public IActionResult GetImage([FromQuery] string? filename = null) { string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}"; if (!Directory.Exists(_imageDirectory)) return NotFound(); var existingFiles = Directory.GetFiles(_imageDirectory, $"{prefix}.*"); if (existingFiles.Length == 0) return NotFound(); string targetPath = existingFiles[0]; // Read the file and return with appropriate MIME type var stream = new FileStream(targetPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); string mimeType = "application/octet-stream"; string ext = Path.GetExtension(targetPath).ToLowerInvariant(); switch (ext) { case ".jpg": case ".jpeg": mimeType = "image/jpeg"; break; case ".png": mimeType = "image/png"; break; case ".gif": mimeType = "image/gif"; break; case ".webp": mimeType = "image/webp"; break; } return File(stream, mimeType); } /// /// Deletes a custom overlay image. /// [Authorize(Policy = "RequiresElevation")] [HttpDelete("OverlayImage")] public IActionResult DeleteImage([FromQuery] string? filename = null) { string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}"; if (Directory.Exists(_imageDirectory)) { var existingFiles = Directory.GetFiles(_imageDirectory, $"{prefix}.*"); foreach(var file in existingFiles) { try { System.IO.File.Delete(file); } catch (Exception ex) { return StatusCode(500, $"Error deleting file: {ex.Message}"); } } return Ok(); } return NotFound(); } /// /// Renames a custom overlay image (used when a seasonal section is renamed). /// [HttpPut("OverlayImage/Rename")] public IActionResult RenameImage([FromQuery] string oldName, [FromQuery] string newName) { if (string.IsNullOrWhiteSpace(oldName) || string.IsNullOrWhiteSpace(newName)) { return BadRequest("Both oldName and newName must be provided."); } if (!Directory.Exists(_imageDirectory)) return Ok(); var oldFiles = Directory.GetFiles(_imageDirectory, $"custom_overlay_image_{oldName}.*"); if (oldFiles.Length == 0) return Ok(); try { string oldPath = oldFiles[0]; string extension = Path.GetExtension(oldPath); string newPath = Path.Combine(_imageDirectory, $"custom_overlay_image_{newName}{extension}"); // If a file with the new name already exists, delete it first to avoid conflicts var existingNewFiles = Directory.GetFiles(_imageDirectory, $"custom_overlay_image_{newName}.*"); foreach(var existing in existingNewFiles) { System.IO.File.Delete(existing); } System.IO.File.Move(oldPath, newPath); var qs = $"?filename={Uri.EscapeDataString(newName)}&"; var getUrl = $"/MediaBarEnhanced/OverlayImage{qs}t={DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; return Ok(new { url = getUrl }); } catch (Exception ex) { return StatusCode(500, $"Error renaming file: {ex.Message}"); } } } }