421 lines
16 KiB
C#
421 lines
16 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 required string DateSample { get; set; }
|
|
public required string 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 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();
|
|
|
|
// Get the specific tarball record with all joins
|
|
var query = @"
|
|
SELECT
|
|
marine.Id,
|
|
marine.StationID,
|
|
marine.Longitude,
|
|
marine.Latitude,
|
|
DATE_FORMAT(marine.DateSample, '%Y%m%d') AS DateSample, -- Format date as string
|
|
DATE_FORMAT(marine.TimeSample, '%H%i%s') AS TimeSample, -- Format time as string
|
|
marine.ClassifyID,
|
|
marine.OptionalName1,
|
|
marine.OptionalName2,
|
|
marine.OptionalName3,
|
|
marine.OptionalName4,
|
|
marine.FirstSampler,
|
|
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 tarball = await _context.Database
|
|
.SqlQueryRaw<TarballPdfDto>(query, new MySqlParameter("@id", id))
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (tarball == null)
|
|
return NotFound($"No record found with ID: {id}");
|
|
|
|
// Get images from path
|
|
var stationFolder = Path.Combine(PhotoBasePath, tarball.StationID);
|
|
string sampleDateTimeString = tarball.DateSample + tarball.TimeSample;
|
|
|
|
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}_{sampleDateTimeString}") &&
|
|
fileName.Contains(type, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
imageTypes[type] = imagePath;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
stationImages = imageTypes;
|
|
}
|
|
|
|
// Validate mandatory images
|
|
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}");
|
|
}
|
|
}
|
|
|
|
// Generate PDF with all required parameters
|
|
var pdf = new TarBallPDF(
|
|
tarball.StateName,
|
|
tarball.StationID,
|
|
tarball.LocationName,
|
|
tarball.Longitude,
|
|
tarball.Latitude,
|
|
tarball.DateSample,
|
|
tarball.TimeSample,
|
|
tarball.ClassifyID,
|
|
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}_{tarball.TimeSample}.pdf";
|
|
|
|
return forceDownload
|
|
? File(pdf, "application/pdf", pdfFileName)
|
|
: File(pdf, "application/pdf");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, $"Error generating PDF: {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;
|
|
}
|
|
}
|
|
}
|
|
}
|