251029
This commit is contained in:
@@ -9,6 +9,10 @@ using FATrace.OEMApp.Services;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using TaskStatus = FATrace.Model.TaskStatus;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace FATrace.OEMApp
|
||||
{
|
||||
@@ -37,6 +41,13 @@ namespace FATrace.OEMApp
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主窗体加载:
|
||||
/// - 初始化海康客户端与基础配置(日志目录与保存路径)
|
||||
/// - 初始化播放器与历史记录网格绑定
|
||||
/// - 启动后台任务服务(顺序下载队列 + Jellyfin 批量监听)
|
||||
/// - 初始化并启动 gridRULog(DataGridView)定时刷新,实时展示下载/监听任务状态
|
||||
/// </summary>
|
||||
private void MainApp_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -50,12 +61,30 @@ namespace FATrace.OEMApp
|
||||
//HkCameraClient.NVR_UserName = ConfigHelper.GetValue("NVRUserName");
|
||||
|
||||
HkCameraClient.NVRVideoSavePath = ConfigHelper.GetValue("NVRVideoSavePath");
|
||||
|
||||
//NVR登录
|
||||
NVRLogin();
|
||||
|
||||
|
||||
InitMediaPlayer();
|
||||
InitHistoryGridBinding();
|
||||
|
||||
// 启动后台任务服务:下载队列(单线程顺序)与 Jellyfin 批量监控(并行匹配)
|
||||
try
|
||||
{
|
||||
DownloadTaskWorker.Instance.Start(HkCameraClient);
|
||||
JellyfinMonitorQueueService.Instance.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[TaskServices] 启动失败: {ex.Message}");
|
||||
}
|
||||
|
||||
// 初始化 gridRULog 并启动 UI 定时刷新
|
||||
InitRuLogGrid();
|
||||
// 立即刷新一次,避免首次 1s 空白
|
||||
RefreshRuLogGrid();
|
||||
StartTaskUiTimer();
|
||||
|
||||
//materialListView1.DataBindings
|
||||
try
|
||||
{
|
||||
@@ -80,6 +109,23 @@ namespace FATrace.OEMApp
|
||||
}
|
||||
}
|
||||
|
||||
private void NVRLogin()
|
||||
{
|
||||
HkCameraClient.NVR_IP = ConfigHelper.GetValue("NVRIP");
|
||||
HkCameraClient.NVR_Port = ushort.Parse(ConfigHelper.GetValue("NVRPort"));
|
||||
HkCameraClient.NVR_UserName = ConfigHelper.GetValue("NVRUserName");
|
||||
HkCameraClient.NVR_Pw = ConfigHelper.GetValue("NVRPw");
|
||||
|
||||
var result = HkCameraClient.Sdk_NET_DVR_Login_V30(HkCameraClient.NVR_IP, HkCameraClient.NVR_Port, HkCameraClient.NVR_UserName, HkCameraClient.NVR_Pw);
|
||||
if (result)
|
||||
{
|
||||
MessageBox.Show("登录成功");
|
||||
return;
|
||||
}
|
||||
MessageBox.Show($"登录失败:{HkCameraClient.LastMsgErr}");
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前登录用户
|
||||
/// </summary>
|
||||
@@ -133,26 +179,21 @@ namespace FATrace.OEMApp
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下载按钮:仅将请求入队为 DownloadTask,由后台 DownloadTaskWorker 顺序处理。
|
||||
/// 注意:不会在此处直接调用 SDK 下载,也不会订阅 SDK 事件,避免并发与重复。
|
||||
/// </summary>
|
||||
private void btnLoadVideo_Click(object sender, EventArgs e)
|
||||
{
|
||||
CurrentVideoPath = NVRCom.GetVideoName(HkCameraClient.NVRVideoSavePath, "CODE");
|
||||
|
||||
var Result = HkCameraClient.Sdk_NET_DVR_GetFileByTime_V40(DateTime.Now.AddMinutes(-5), DateTime.Now, CurrentVideoPath);
|
||||
if (Result.Result)
|
||||
{
|
||||
HkCameraClient.NVRLoadVideoProcessEventHandler -= HkCameraClient_NVRLoadVideoProcessEventHandler;
|
||||
HkCameraClient.NVRLoadVideoProcessEventHandler += HkCameraClient_NVRLoadVideoProcessEventHandler;
|
||||
HkCameraClient.NVRLoadVideoCompleteEventHandler -= HkCameraClient_NVRLoadVideoCompleteEventHandler;
|
||||
HkCameraClient.NVRLoadVideoCompleteEventHandler += HkCameraClient_NVRLoadVideoCompleteEventHandler;
|
||||
// 进度轮询已在库方法内部自动启动,这里无需再次调用
|
||||
HkCameraClient.StartDownloadProgressMonitor();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"[Sdk_NET_DVR_GetFileByTime_V40] 执行失败:{Result.Msg}");
|
||||
}
|
||||
|
||||
//btnStopDownload.Enabled = true;
|
||||
// 仅入队下载任务,由后台 DownloadTaskWorker 顺序处理
|
||||
var taskId = DownloadTaskWorker.Instance.Enqueue(
|
||||
code: CurInBagCode,
|
||||
rawName: CurInBagRawName,
|
||||
user: CurUserName,
|
||||
start: DateTime.Now.AddMinutes(-5),
|
||||
end: DateTime.Now
|
||||
);
|
||||
MessageBox.Show($"已入队下载任务,Id={taskId}");
|
||||
}
|
||||
|
||||
private void HkCameraClient_NVRLoadVideoProcessEventHandler(object? sender, short value)
|
||||
@@ -432,29 +473,20 @@ namespace FATrace.OEMApp
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 操作结束
|
||||
/// 测试/结束操作按钮:同样仅入队一条下载任务,方便快速验证下载->监听全流程。
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void btnTestAction_Click(object sender, EventArgs e)
|
||||
{
|
||||
CurrentVideoPath = NVRCom.GetVideoName(HkCameraClient.NVRVideoSavePath, "CODE");
|
||||
|
||||
var Result = HkCameraClient.Sdk_NET_DVR_GetFileByTime_V40(DateTime.Now.AddMinutes(-5), DateTime.Now, CurrentVideoPath);
|
||||
if (Result.Result)
|
||||
{
|
||||
HkCameraClient.NVRLoadVideoProcessEventHandler -= HkCameraClient_NVRLoadVideoProcessEventHandler;
|
||||
HkCameraClient.NVRLoadVideoProcessEventHandler += HkCameraClient_NVRLoadVideoProcessEventHandler;
|
||||
|
||||
HkCameraClient.NVRLoadVideoCompleteEventHandler -= HkCameraClient_NVRLoadVideoCompleteEventHandler;
|
||||
HkCameraClient.NVRLoadVideoCompleteEventHandler += HkCameraClient_NVRLoadVideoCompleteEventHandler;
|
||||
|
||||
// 进度轮询已在库方法内部自动启动,这里无需再次调用
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"[Sdk_NET_DVR_GetFileByTime_V40] 执行失败:{Result.Msg}");
|
||||
}
|
||||
var taskId = DownloadTaskWorker.Instance.Enqueue(
|
||||
code: CurInBagCode,
|
||||
rawName: CurInBagRawName,
|
||||
user: CurUserName,
|
||||
start: DateTime.Now.AddMinutes(-5),
|
||||
end: DateTime.Now
|
||||
);
|
||||
MessageBox.Show($"[Test] 已入队下载任务,Id={taskId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -526,6 +558,268 @@ namespace FATrace.OEMApp
|
||||
});
|
||||
}
|
||||
|
||||
#region 任务监控(gridRULog / DataGridView)
|
||||
|
||||
private System.Windows.Forms.Timer _taskUiTimer;
|
||||
private BindingSource ruLogBindingSource;
|
||||
private BindingList<RuLogRow> ruLogBindingList;
|
||||
|
||||
/// <summary>
|
||||
/// DataGridView 行模型
|
||||
/// </summary>
|
||||
private sealed class RuLogRow
|
||||
{
|
||||
public string TimeText { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty; // 下载/监听
|
||||
public long TaskId { get; set; }
|
||||
public string StatusText { get; set; } = string.Empty;
|
||||
public string? ProgressText { get; set; }
|
||||
public string Remark { get; set; } = string.Empty; // 错误优先,否则文件路径/ItemId
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 gridRULog:列定义/数据绑定/低闪烁
|
||||
/// </summary>
|
||||
private void InitRuLogGrid()
|
||||
{
|
||||
ruLogBindingSource = new BindingSource();
|
||||
ruLogBindingList = new BindingList<RuLogRow>();
|
||||
ruLogBindingSource.DataSource = ruLogBindingList;
|
||||
|
||||
gridRULog.AutoGenerateColumns = false;
|
||||
gridRULog.Columns.Clear();
|
||||
// 列:时间、类型、任务Id、状态、进度、备注(填充)
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colTime",
|
||||
HeaderText = "时间",
|
||||
DataPropertyName = nameof(RuLogRow.TimeText),
|
||||
Width = 100,
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colType",
|
||||
HeaderText = "类型",
|
||||
DataPropertyName = nameof(RuLogRow.Type),
|
||||
Width = 100,
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colTaskId",
|
||||
HeaderText = "任务Id",
|
||||
DataPropertyName = nameof(RuLogRow.TaskId),
|
||||
Width = 120,
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colStatus",
|
||||
HeaderText = "状态",
|
||||
DataPropertyName = nameof(RuLogRow.StatusText),
|
||||
Width = 120,
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colProgress",
|
||||
HeaderText = "进度",
|
||||
DataPropertyName = nameof(RuLogRow.ProgressText),
|
||||
Width = 80,
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colRemark",
|
||||
HeaderText = "备注",
|
||||
DataPropertyName = nameof(RuLogRow.Remark),
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
||||
});
|
||||
|
||||
// 双缓存减少闪烁
|
||||
SetDataGridViewDoubleBuffered(gridRULog);
|
||||
|
||||
gridRULog.DataSource = ruLogBindingSource;
|
||||
gridRULog.ColumnHeadersVisible = true;
|
||||
gridRULog.EnableHeadersVisualStyles = false;
|
||||
gridRULog.AllowUserToResizeRows = false;
|
||||
gridRULog.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
|
||||
gridRULog.BackgroundColor = Color.White;
|
||||
gridRULog.BorderStyle = BorderStyle.FixedSingle;
|
||||
gridRULog.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single;
|
||||
gridRULog.ColumnHeadersDefaultCellStyle.BackColor = Color.Gainsboro;
|
||||
gridRULog.ColumnHeadersDefaultCellStyle.ForeColor = Color.Black;
|
||||
gridRULog.RowHeadersVisible = false;
|
||||
// 使用 Designer 中的 Location/Size 与 Anchor,不额外 Dock
|
||||
gridRULog.BringToFront();
|
||||
|
||||
// 跟随父容器尺寸变化进行定位与大小调整
|
||||
materialCard2.Resize -= MaterialCard2_Resize;
|
||||
materialCard2.Resize += MaterialCard2_Resize;
|
||||
LayoutRuLogGrid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据左侧控件宽度动态计算 gridRULog 的 Left/Size,确保位置与尺寸正确
|
||||
/// </summary>
|
||||
private void LayoutRuLogGrid()
|
||||
{
|
||||
if (gridRULog == null || materialCard2 == null) return;
|
||||
try
|
||||
{
|
||||
int leftReserved = 540; // 默认左侧操作区宽度
|
||||
try
|
||||
{
|
||||
var candidates = new Control[] { txtRUInBagCode, txtRURawName, btnTestAction, btnRawStopLoadVideo, DownloadProgressBarMain };
|
||||
foreach (var c in candidates)
|
||||
{
|
||||
if (c?.Parent == materialCard2)
|
||||
{
|
||||
leftReserved = Math.Max(leftReserved, c.Right);
|
||||
}
|
||||
}
|
||||
leftReserved += 20; // 预留间距
|
||||
}
|
||||
catch { }
|
||||
|
||||
var client = materialCard2.ClientSize;
|
||||
var left = leftReserved;
|
||||
var top = 9;
|
||||
var width = Math.Max(200, client.Width - left - 14);
|
||||
var height = Math.Max(120, client.Height - top - 9);
|
||||
gridRULog.Location = new Point(left, top);
|
||||
gridRULog.Size = new Size(width, height);
|
||||
gridRULog.BringToFront();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void MaterialCard2_Resize(object? sender, EventArgs e)
|
||||
{
|
||||
LayoutRuLogGrid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动 UI 定时器,定时刷新下载与 Jellyfin 监听任务状态
|
||||
/// </summary>
|
||||
private void StartTaskUiTimer()
|
||||
{
|
||||
_taskUiTimer = new System.Windows.Forms.Timer();
|
||||
_taskUiTimer.Interval = 1000; // 1s 刷新
|
||||
_taskUiTimer.Tick += TaskUiTimer_Tick;
|
||||
_taskUiTimer.Start();
|
||||
}
|
||||
|
||||
private void TaskUiTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
RefreshRuLogGrid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 gridRULog(下载 + 监听)
|
||||
/// </summary>
|
||||
private void RefreshRuLogGrid()
|
||||
{
|
||||
try
|
||||
{
|
||||
var db = FSqlContext.FDb;
|
||||
var downloads = db.Select<DownloadTask>()
|
||||
.OrderByDescending(a => a.UpdateTime)
|
||||
.Limit(50)
|
||||
.ToList();
|
||||
var monitors = db.Select<JellyfinMonitorTask>()
|
||||
.OrderByDescending(a => a.UpdateTime)
|
||||
.Limit(50)
|
||||
.ToList();
|
||||
|
||||
var rows = new List<RuLogRow>(downloads.Count + monitors.Count);
|
||||
|
||||
// 运行中优先
|
||||
foreach (var t in downloads.Where(x => x.Status == TaskStatus.Running))
|
||||
{
|
||||
rows.Add(new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "下载",
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = t.Progress.ToString(),
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.VideoFilePath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
foreach (var t in monitors.Where(x => x.Status == TaskStatus.Running))
|
||||
{
|
||||
rows.Add(new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "监听",
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = string.Empty,
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.FoundItemId ?? t.LocalFileNameOrPath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
|
||||
// 其余
|
||||
foreach (var t in downloads.Where(x => x.Status != TaskStatus.Running))
|
||||
{
|
||||
rows.Add(new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "下载",
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = t.Progress.ToString(),
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.VideoFilePath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
foreach (var t in monitors.Where(x => x.Status != TaskStatus.Running))
|
||||
{
|
||||
rows.Add(new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "监听",
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = string.Empty,
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.FoundItemId ?? t.LocalFileNameOrPath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
|
||||
// 批量更新绑定列表,尽量减少闪烁
|
||||
ruLogBindingList.RaiseListChangedEvents = false;
|
||||
ruLogBindingList.Clear();
|
||||
foreach (var r in rows)
|
||||
{
|
||||
ruLogBindingList.Add(r);
|
||||
}
|
||||
ruLogBindingList.RaiseListChangedEvents = true;
|
||||
ruLogBindingList.ResetBindings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[gridRULog] 刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetDataGridViewDoubleBuffered(DataGridView dgv)
|
||||
{
|
||||
try
|
||||
{
|
||||
typeof(DataGridView).InvokeMember(
|
||||
"DoubleBuffered",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty,
|
||||
null,
|
||||
dgv,
|
||||
new object[] { true }
|
||||
);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void btnRawStopLoadVideo_Click(object sender, EventArgs e)
|
||||
{
|
||||
HkCameraClient.Sdk_NET_DVR_StopGetFile();
|
||||
|
||||
Reference in New Issue
Block a user