using Microsoft.AspNetCore.Mvc; using PSTW_CentralSystem.DBContext; using PSTW_CentralSystem.Areas.MMS.Models; using System.IO; using System.Linq; using PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator; using QuestPDF.Fluent; using System.Threading.Tasks; using System.Threading; using System.Collections.Generic; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using MySqlConnector; using Org.BouncyCastle.Asn1.Cms; namespace PSTW_CentralSystem.Areas.MMS.Controllers { public class TarballPdfDto { // From tbl_marine_tarball public int Id { get; set; } public required string StationID { get; set; } public required string Longitude { get; set; } public required string Latitude { get; set; } public DateTime DateSample { get; set; } public TimeSpan TimeSample { get; set; } public required string ClassifyID { get; set; } public string? OptionalName1 { get; set; } public string? OptionalName2 { get; set; } public string? OptionalName3 { get; set; } public string? OptionalName4 { get; set; } public required string FirstSampler { get; set; } // From joined tables public required string LocationName { get; set; } // From tbl_marine_station public required string StateName { get; set; } // From tbl_state public required string FullName { get; set; } // From tbl_user public required string LevelName { get; set; } // From tbl_level } public class TarBallGroupViewModel { public List Id { get; set; } = new List(); public required string StationID { get; set; } public required string Date { get; set; } public required int TimeSampleCount { get; set; } public required string LatestTime {get; set; } public List TimeSamples { get; set; } = new List(); } public class TarBallTimeSample { public required int Id { get; set; } public string Time { get; set; } } [Area("MMS")] public class MarineController : Controller { private readonly MMSSystemContext _context;//Used in TarBallForm and GeneratePdfResponse to query the database. private readonly NetworkShareAccess _networkAccessService;//used in GetImage and GeneratePdfResponse private const string PhotoBasePath = @"\\192.168.12.42\images\marine\manual_tarball";//used in GetImage and GeneratePdfResponse public MarineController(MMSSystemContext context, NetworkShareAccess networkAccessService) { _context = context; _networkAccessService = networkAccessService; } public IActionResult Index() { return View(); } public class StationDateGroup { public required string StationID { get; set; } public DateTime DateSample { get; set; } public List TimeSamples { get; set; } = new List(); } public IActionResult TarBallForm() { try { var marineTarballs = _context.MarineTarballs .Select(t => new { t.Id, station = t.StationID, date = t.DateSample.ToString("yyyy/MM/dd"), time = t.TimeSample.ToString(@"hh\:mm\:ss"), fullDate = t.DateSample, // Keep DateTime for sorting t.TimeSample }) .AsEnumerable() .OrderByDescending(t => t.fullDate) .ThenByDescending(t => t.TimeSample) .ToList(); return View(marineTarballs); } catch (Exception ex) { return Content($"Error: {ex.Message}
{ex.StackTrace}", "text/html"); } } [HttpGet] // Explicitly mark as a GET endpoint public IActionResult TestCredentials() { try { // Use the EXACT same path/credentials as in Program.cs var testService = new NetworkShareAccess( @"\\192.168.12.42\images\marine\manual_tarball", "installer", "mms@pstw" ); testService.ConnectToNetworkPath(); testService.DisconnectFromNetworkShare(); return Ok("Network credentials and path are working correctly!"); } catch (Exception ex) { // Log the full error (including stack trace) Console.WriteLine($"TestCredentials failed: {ex}"); return StatusCode(500, $"Credentials test failed: {ex.Message}"); } } public IActionResult GetImage(string fileName) { if (string.IsNullOrEmpty(fileName)) { return BadRequest("Filename cannot be empty"); } // Sanitize filename to prevent path traversal attacks var sanitizedFileName = Path.GetFileName(fileName); if (sanitizedFileName != fileName) { return BadRequest("Invalid filename"); } int retryCount = 0; const int maxRetries = 3; bool connectionSuccess = false; // Retry loop for network connection while (retryCount < maxRetries && !connectionSuccess) { try { Console.WriteLine($"Attempt {retryCount + 1} to connect to network share..."); // Connect to network share _networkAccessService.ConnectToNetworkPath(); connectionSuccess = true; Console.WriteLine("Network share connected successfully"); } catch (Exception ex) { retryCount++; Console.WriteLine($"Connection attempt {retryCount} failed: {ex.Message}"); if (retryCount >= maxRetries) { Console.WriteLine($"Max connection attempts reached. Last error: {ex}"); return StatusCode(503, $"Could not establish connection to image server after {maxRetries} attempts"); } // Wait before retrying (1s, 2s, 3s) Thread.Sleep(1000 * retryCount); } } try { string imagePath = Path.Combine(PhotoBasePath, sanitizedFileName); Console.WriteLine($"Attempting to access image at: {imagePath}"); // Verify file exists if (!System.IO.File.Exists(imagePath)) { Console.WriteLine($"Image not found at path: {imagePath}"); return NotFound($"Image '{sanitizedFileName}' not found on server"); } // Verify file is an image if (!IsImageValid(imagePath)) { Console.WriteLine($"Invalid image file at path: {imagePath}"); return BadRequest("The requested file is not a valid image"); } // Read and return the image byte[] imageBytes = System.IO.File.ReadAllBytes(imagePath); Console.WriteLine($"Successfully read image: {sanitizedFileName} ({imageBytes.Length} bytes)"); // Determine content type based on extension string contentType = "image/jpeg"; // default string extension = Path.GetExtension(sanitizedFileName)?.ToLower(); if (extension == ".png") { contentType = "image/png"; } else if (extension == ".gif") { contentType = "image/gif"; } return File(imageBytes, contentType); } catch (UnauthorizedAccessException ex) { Console.WriteLine($"Access denied to image: {ex}"); return StatusCode(403, "Access to the image was denied"); } catch (IOException ex) { Console.WriteLine($"IO error accessing image: {ex}"); return StatusCode(503, "Error accessing image file"); } catch (Exception ex) { Console.WriteLine($"Unexpected error: {ex}"); return StatusCode(500, "An unexpected error occurred while processing the image"); } finally { try { if (connectionSuccess) { Console.WriteLine("Disconnecting from network share..."); _networkAccessService.DisconnectFromNetworkShare(); } } catch (Exception ex) { Console.WriteLine($"Warning: Error disconnecting from share: {ex.Message}"); // Don't fail the request because of disconnect issues } } } public async Task GenerateReport(int id) { return await GeneratePdfResponse(id, true); } public async Task DownloadPDF(int id) { return await GeneratePdfResponse(id, true); } public IActionResult ViewPDF(int id) { try { // Add timeout for safety var task = Task.Run(() => GeneratePdfResponse(id, false)); if (task.Wait(TimeSpan.FromSeconds(30))) // 30-second timeout { return task.Result; } return StatusCode(500, "PDF generation took too long"); } catch (Exception ex) { Console.WriteLine($"PDF VIEW ERROR: {ex}"); return StatusCode(500, $"Error showing PDF: {ex.Message}"); } } private async Task GeneratePdfResponse(int id, bool forceDownload) { try { _networkAccessService.ConnectToNetworkPath(); var record = await _context.MarineTarballs.FirstOrDefaultAsync(t => t.Id == id); if (record == null) return NotFound($"No record found for Id: {id}"); // Retrieve all entries for the same StationID and DateSample var query = @" SELECT marine.*, station.LocationName, state.StateName, user.FullName, level.LevelName FROM tbl_marine_tarball marine JOIN tbl_marine_station station ON marine.StationID = station.StationID JOIN tbl_state state ON station.StateID = state.StateID JOIN tbl_user user ON marine.FirstSampler = user.FullName JOIN tbl_level level ON user.LevelID = level.LevelID WHERE marine.Id=@id"; var tarballEntries = await _context.Database .SqlQueryRaw(query, new MySqlParameter("@id", id)) .ToListAsync(); if (!tarballEntries.Any()) return NotFound($"No records found for Id: {id}"); var pdfList = new List(); // Iterate over each unique TimeSample within the same DateSample foreach (var timeGroup in tarballEntries.GroupBy(t => t.TimeSample)) { var tarball = timeGroup.First(); // Use first entry per time sample // Classification logic remains unchanged bool tarBallYes = tarball.ClassifyID != "NO"; bool tarBallNo = tarball.ClassifyID == "NO"; bool isSand = tarball.ClassifyID == "SD"; bool isNonSandy = tarball.ClassifyID == "NS"; bool isCoquina = tarball.ClassifyID == "CO"; // Construct image paths ensuring correct naming format var stationFolder = Path.Combine(PhotoBasePath, tarball.StationID); var sampleDateString = tarball.DateSample.ToString("yyyyMMdd"); var sampleTimeString = tarball.TimeSample.ToString(@"hhmmss"); var stationImages = new Dictionary(); if (Directory.Exists(stationFolder)) { var imageTypes = new Dictionary { { "LEFTSIDECOASTALVIEW", null }, { "RIGHTSIDECOASTALVIEW", null }, { "DRAWINGVERTICALLINES", null }, { "DRAWINGHORIZONTALLINES", null }, { "OPTIONAL01", null }, { "OPTIONAL02", null }, { "OPTIONAL03", null }, { "OPTIONAL04", null } }; foreach (var imagePath in Directory.GetFiles(stationFolder)) { var fileName = Path.GetFileNameWithoutExtension(imagePath); foreach (var type in imageTypes.Keys.ToList()) { if (fileName.StartsWith($"{tarball.StationID}_{sampleDateString}_{sampleTimeString}") && fileName.Contains(type, StringComparison.OrdinalIgnoreCase)) { imageTypes[type] = imagePath; break; } } } stationImages = imageTypes; } // Validate mandatory images exist var mandatoryImages = new List { "LEFTSIDECOASTALVIEW", "RIGHTSIDECOASTALVIEW", "DRAWINGVERTICALLINES", "DRAWINGHORIZONTALLINES" }; foreach (var mandatoryType in mandatoryImages) { if (!stationImages.ContainsKey(mandatoryType) || stationImages[mandatoryType] == null) { return StatusCode(400, $"Missing mandatory image: {mandatoryType} for {tarball.StationID} on {tarball.DateSample}"); } } // Generate PDF using existing classification and sorting logic var pdfDocument = new TarBallPDF( tarball.StateName, tarball.StationID, tarball.LocationName, tarball.Longitude, tarball.Latitude, tarball.DateSample, tarball.TimeSample, tarball.ClassifyID, tarBallYes, tarBallNo, isSand, isNonSandy, isCoquina, stationImages["LEFTSIDECOASTALVIEW"], stationImages["RIGHTSIDECOASTALVIEW"], stationImages["DRAWINGVERTICALLINES"], stationImages["DRAWINGHORIZONTALLINES"], stationImages.GetValueOrDefault("OPTIONAL01"), stationImages.GetValueOrDefault("OPTIONAL02"), stationImages.GetValueOrDefault("OPTIONAL03"), stationImages.GetValueOrDefault("OPTIONAL04"), tarball.OptionalName1, tarball.OptionalName2, tarball.OptionalName3, tarball.OptionalName4, tarball.FirstSampler, tarball.FullName, tarball.LevelName ).GeneratePdf(); string pdfFileName = $"{tarball.StationID}_{tarball.DateSample:yyyyMMdd}_{tarball.TimeSample:hhmmss}.pdf"; pdfList.Add( forceDownload ? File(pdfDocument, "application/pdf", pdfFileName) : File(pdfDocument, "application/pdf") ); } return pdfList.Count == 1 ? pdfList.First() : Ok(pdfList); } catch (Exception ex) { return StatusCode(500, $"Error: {ex.Message}"); } finally { _networkAccessService.DisconnectFromNetworkShare(); } } private bool IsImageValid(string imagePath) { try { using (var image = System.Drawing.Image.FromFile(imagePath)) return true; } catch { Console.WriteLine($"Invalid image skipped: {imagePath}"); return false; } } } }