PSTW_CentralizeSystem/Areas/MMS/Controllers/MarineController.cs
2025-05-29 09:23:13 +08:00

447 lines
18 KiB
C#

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<int> Id { get; set; } = new List<int>();
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<TarBallTimeSample> TimeSamples { get; set; } = new List<TarBallTimeSample>();
}
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<TarballPdfDto> TimeSamples { get; set; } = new List<TarballPdfDto>();
}
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}<br/>{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<IActionResult> GenerateReport(int id)
{
return await GeneratePdfResponse(id, true);
}
public async Task<IActionResult> 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<IActionResult> 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<TarballPdfDto>(query, new MySqlParameter("@id", id))
.ToListAsync();
if (!tarballEntries.Any())
return NotFound($"No records found for Id: {id}");
var pdfList = new List<IActionResult>();
// 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<string, string>();
if (Directory.Exists(stationFolder))
{
var imageTypes = new Dictionary<string, string>
{
{ "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<string>
{
"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;
}
}
}
}