using System;
using System.Drawing;
using System.IO;
using System.Net;
namespace HanvonF710XAttendanceService
{
internal sealed class HrmsEmployeePhotoDownloadResult
{
public bool Success { get; set; }
public int StatusCode { get; set; }
public string ContentType { get; set; }
public string Url { get; set; }
public byte[] ImageBytes { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Base64Length { get; set; }
public string Error { get; set; }
}
internal static class HrmsEmployeePhotoClient
{
/// employees.id used in the portal photo path. Not the device serial.
public static string BuildPhotoUrl(string baseUrl, string hrmsEmployeeId)
{
if (string.IsNullOrWhiteSpace(baseUrl))
{
throw new ArgumentException("Employee photo base URL is required.");
}
if (string.IsNullOrWhiteSpace(hrmsEmployeeId))
{
throw new ArgumentException("HRMS employee ID (employees.id) is required.");
}
string trimmedBase = baseUrl.Trim();
if (!trimmedBase.EndsWith("/", StringComparison.Ordinal))
{
trimmedBase += "/";
}
return trimmedBase + hrmsEmployeeId.Trim() + ".jpeg";
}
/// employees.id for EmployeePhotoBaseUrl + id + ".jpeg".
public static bool TryDownloadEmployeePhoto(string hrmsEmployeeId, out HrmsEmployeePhotoDownloadResult result)
{
result = new HrmsEmployeePhotoDownloadResult();
if (!EmployeePhotoSourceSettings.IsEnabled())
{
result.Error = "employee_photo_source_disabled";
return false;
}
string baseUrl = EmployeePhotoSourceSettings.GetBaseUrl();
if (string.IsNullOrWhiteSpace(baseUrl))
{
result.Error = "employee_photo_base_url_missing";
return false;
}
if (string.IsNullOrWhiteSpace(hrmsEmployeeId))
{
result.Error = "hrms_employee_id_missing";
return false;
}
result.Url = BuildPhotoUrl(baseUrl, hrmsEmployeeId);
int timeoutMs = EmployeePhotoSourceSettings.GetTimeoutMs();
try
{
var request = (HttpWebRequest)WebRequest.Create(result.Url);
request.Method = "GET";
request.Timeout = timeoutMs;
request.ReadWriteTimeout = timeoutMs;
request.AllowAutoRedirect = true;
request.UserAgent = "HanvonF710XAttendanceService/1.0";
using (var response = (HttpWebResponse)request.GetResponse())
using (var stream = response.GetResponseStream())
using (var memory = new MemoryStream())
{
result.StatusCode = (int)response.StatusCode;
result.ContentType = response.ContentType;
if (response.StatusCode != HttpStatusCode.OK)
{
result.Error = "http_status_" + result.StatusCode;
return false;
}
if (stream == null)
{
result.Error = "empty_response_stream";
return false;
}
stream.CopyTo(memory);
result.ImageBytes = memory.ToArray();
}
}
catch (WebException wex)
{
if (wex.Response is HttpWebResponse errResp)
{
result.StatusCode = (int)errResp.StatusCode;
result.ContentType = errResp.ContentType;
}
result.Error = wex.Message;
return false;
}
catch (Exception ex)
{
result.Error = ex.Message;
return false;
}
if (!TryValidateDownloadedImage(result, out string validationError))
{
result.Error = validationError;
result.ImageBytes = null;
return false;
}
result.Base64Length = Convert.ToBase64String(result.ImageBytes).Length;
result.Success = true;
return true;
}
public static bool TryValidateDownloadedImage(HrmsEmployeePhotoDownloadResult result, out string error)
{
error = null;
byte[] bytes = result?.ImageBytes;
if (bytes == null || bytes.Length == 0)
{
error = "empty_image";
return false;
}
if (bytes.Length < 4 || bytes[0] != 0xFF || bytes[1] != 0xD8)
{
error = "not_jpeg";
return false;
}
if (!NedoPhotoPreprocessor.IsCompleteJpeg(bytes))
{
error = "incomplete_jpeg";
return false;
}
string contentType = result.ContentType ?? "";
if (!string.IsNullOrWhiteSpace(contentType)
&& contentType.IndexOf("image", StringComparison.OrdinalIgnoreCase) < 0
&& contentType.IndexOf("jpeg", StringComparison.OrdinalIgnoreCase) < 0
&& contentType.IndexOf("octet-stream", StringComparison.OrdinalIgnoreCase) < 0)
{
error = "invalid_content_type";
return false;
}
try
{
using (var input = new MemoryStream(bytes))
using (var image = Image.FromStream(input))
{
result.Width = image.Width;
result.Height = image.Height;
}
}
catch (Exception ex)
{
error = "invalid_image_data:" + ex.Message;
return false;
}
int minShort = EmployeePhotoSourceSettings.GetMinShortSide();
int maxShort = EmployeePhotoSourceSettings.GetMaxShortSide();
int shortSide = Math.Min(result.Width, result.Height);
int longSide = Math.Max(result.Width, result.Height);
if (shortSide < minShort)
{
error = "image_too_small";
return false;
}
if (longSide > maxShort)
{
error = "image_too_large";
return false;
}
return true;
}
}
}