using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using MediaBrowser.Common.Configuration; 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; private readonly string _imagePath; public OverlayImageController(IApplicationPaths applicationPaths) { _applicationPaths = applicationPaths; // We use the plugin's data folder to store the image _imageDirectory = MediaBarEnhancedPlugin.Instance?.DataFolderPath ?? Path.Combine(applicationPaths.DataPath, "plugins", "MediaBarEnhanced"); // We'll just overwrite this single file each time _imagePath = Path.Combine(_imageDirectory, "custom_overlay_image.dat"); } /// /// Uploads a new custom overlay image. /// [HttpPost("OverlayImage")] [Consumes("multipart/form-data")] public async Task UploadImage([FromForm] IFormFile file) { if (file == null || file.Length == 0) { return BadRequest("No file uploaded."); } try { if (!Directory.Exists(_imageDirectory)) { Directory.CreateDirectory(_imageDirectory); } // Delete is not strictly necessary and can cause locking issues if someone is currently reading it. // FileMode.Create will truncate the file if it exists, effectively overwriting it. // We use FileShare.None to ensure we have exclusive write access, but handle potential IOExceptions gracefully. using (var stream = new FileStream(_imagePath, FileMode.Create, FileAccess.Write, FileShare.None)) { await file.CopyToAsync(stream).ConfigureAwait(false); } // Return the GET URL that the frontend can use var getUrl = "/MediaBarEnhanced/OverlayImage?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() { if (!System.IO.File.Exists(_imagePath)) { return NotFound(); } // Read the file and return as a generic octet stream. // We use FileShare.ReadWrite so that if someone is currently overwriting the file (uploading), we don't block them, // and we also don't get blocked by other readers. var stream = new FileStream(_imagePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // "image/*" works reliably as browsers will sniff the exact image mime type (jpeg, png, webp). return File(stream, "image/*"); } } }