Files
FATrace/FATrace.OEMApp/MainApp.cs
2025-12-02 17:27:09 +08:00

858 lines
32 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using FATrace.Com;
using FATrace.HKNetLib.Hardware;
using FATrace.HKNetLib.Wrapper;
using FATrace.Model;
using FATrace.OEMApp.Services;
using LibVLCSharp.Shared;
using NLog;
using ReaLTaiizor.Forms;
using System.Collections.Concurrent;
using System.ComponentModel;
using TaskStatus = FATrace.Model.TaskStatus;
namespace FATrace.OEMApp
{
public partial class MainApp : MaterialForm
{
/// <summary>
/// 日志
/// </summary>
private Logger Logger { get; set; } = LogManager.GetCurrentClassLogger();
/// <summary>
/// 海康Camera 客户端
/// </summary>
public HkCamera HkCameraClient { get; set; }
// 历史表列头中文映射
private readonly Dictionary<string, string> _historyHeaderMap = new Dictionary<string, string>
{
{ nameof(VideoAction.Id), "编号" },
{ nameof(VideoAction.Code), "条码" },
{ nameof(VideoAction.User), "用户" },
{ nameof(VideoAction.VideoFilePath), "视频路径" },
{ nameof(VideoAction.VideoName), "视频名称" },
{ nameof(VideoAction.StartTime), "开始时间" },
{ nameof(VideoAction.EndTime), "结束时间" },
{ nameof(VideoAction.CreateTime), "创建时间" }
};
public MainApp()
{
InitializeComponent();
}
/// <summary>
/// PLC数据服务
/// </summary>
private PLCDataService PLCDataService { get; set; }
/// <summary>
/// 主窗体加载:
/// - 初始化海康客户端与基础配置(日志目录与保存路径)
/// - 初始化播放器与历史记录网格绑定
/// - 启动后台任务服务(顺序下载队列 + Jellyfin 批量监听)
/// - 初始化并启动 gridRULogDataGridView定时刷新实时展示下载/监听任务状态
/// </summary>
private void MainApp_Load(object sender, EventArgs e)
{
HkCameraClient = new HkCamera();
//保存SDK日志
CHCNetSDK.NET_DVR_SetLogToFile(3, "C:\\SdkLog\\", true);
//读取配置
//HkCameraClient.NVR_IP = ConfigHelper.GetValue("NVRIP");
//HkCameraClient.NVR_Port = ConfigHelper.GetValue("NVRPort");
//HkCameraClient.NVR_UserName = ConfigHelper.GetValue("NVRUserName");
PLCDataService = new PLCDataService();
PLCDataService.PlcConnectedEventHandler += PLCDataService_PlcConnectedEventHandler;
PLCDataService.ScanCodeEventHandler += PLCDataService_ScanCodeEventHandler;
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
{
var systemName = Program.SystemName;
var authText = Program.IsActive ? "(已授权)" : "(未授权)";
this.Text = string.IsNullOrWhiteSpace(systemName)
? $"添加剂追溯系统 {authText}"
: $"{systemName} {authText}";
}
catch (Exception)
{
// 安静失败,不影响主界面显示
}
try
{
}
catch (Exception ex)
{
MessageBox.Show($"初始化界面失败: {ex.Message}");
}
}
/// <summary>
/// PLC数据服务扫描条码
/// </summary>
/// <param name="sender"></param>
/// <param name="Code"></param>
private void PLCDataService_ScanCodeEventHandler(object? sender, string Code)
{
}
/// <summary>
/// PLC数据服务PLC连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PLCDataService_PlcConnectedEventHandler(object? sender, string e)
{
}
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>
public string CurUserName { get; set; } = "Admin";
#region
/// <summary>
/// 当前播放的视频
/// </summary>
public string CurrentVideoPath { get; set; }
/// <summary>
/// 按文件名/路径去重,避免同一下载完成事件被重复处理
/// key: 文件名或路径(小写) value: 对应插入的 OEMRawUse.Id0 表示尚未完成插入)
/// </summary>
private readonly ConcurrentDictionary<string, long> _downloadProcessingKeys = new();
#endregion
#region
/// <summary>
/// 内包条码
/// </summary>
public string CurInBagCode { get; set; } = "AAAAAA";
/// <summary>
/// 内包条码 原料名称
/// </summary>
public string CurInBagRawName { get; set; } = "添加剂Test";
#endregion
private void btnNVRLogin_Click(object sender, EventArgs e)
{
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>
/// 下载按钮:仅将请求入队为 DownloadTask由后台 DownloadTaskWorker 顺序处理。
/// 注意:不会在此处直接调用 SDK 下载,也不会订阅 SDK 事件,避免并发与重复。
/// </summary>
private void btnLoadVideo_Click(object sender, EventArgs e)
{
// 仅入队下载任务,由后台 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)
{
this.BeginInvoke(new Action(() =>
{
DownloadProgressBarMain.Value = value;
}));
}
private void btnDownloadName_Click(object sender, EventArgs e)
{
//if (m_lDownHandle >= 0)
//{
// MessageBox.Show("Downloading, please stop firstly!");//正在下载,请先停止下载
// return;
//}
//string sVideoFileName; //录像文件保存路径和文件名 the path and file name to save
//sVideoFileName = "D:\\Downtest1111_" + sPlayBackFileName + ".mp4";
////按文件名下载 Download by file name
//m_lDownHandle = CHCNetSDK.NET_DVR_GetFileByName(m_lUserID, sPlayBackFileName, sVideoFileName);
//if (m_lDownHandle < 0)
//{
// iLastErr = CHCNetSDK.NET_DVR_GetLastError();
// str = "NET_DVR_GetFileByName failed, error code= " + iLastErr;
// MessageBox.Show(str);
// return;
//}
//uint iOutValue = 0;
////设置转封装格式
////UInt32 iInValue = 5;
////IntPtr lpInValue = Marshal.AllocHGlobal(4);
////Marshal.StructureToPtr(iInValue, lpInValue, false);
////if (!CHCNetSDK.NET_DVR_PlayBackControl_V40(m_lDownHandle, CHCNetSDK.NET_DVR_SET_TRANS_TYPE, lpInValue, 4, IntPtr.Zero, ref iOutValue))
////{
//// iLastErr = CHCNetSDK.NET_DVR_GetLastError();
//// str = "NET_DVR_PLAYSTART failed, error code= " + iLastErr; //下载控制失败,输出错误号
//// MessageBox.Show(str);
//// return;
////}
//if (!CHCNetSDK.NET_DVR_PlayBackControl_V40(m_lDownHandle, CHCNetSDK.NET_DVR_PLAYSTART, IntPtr.Zero, 0, IntPtr.Zero, ref iOutValue))
//{
// iLastErr = CHCNetSDK.NET_DVR_GetLastError();
// str = "NET_DVR_PLAYSTART failed, error code= " + iLastErr; //下载控制失败,输出错误号
// MessageBox.Show(str);
// return;
//}
//timerDownload.Interval = 1000;
//timerDownload.Enabled = true;
//btnStopDownload.Enabled = true;
}
private void btnStopLoadVideo_Click(object sender, EventArgs e)
{
var Result = HkCameraClient.Sdk_NET_DVR_StopGetFile();
if (Result.Result)
{
MessageBox.Show($"[暂停成功] {Result.Msg}");
}
else
{
MessageBox.Show($"[暂停失败] {Result.Msg}");
}
}
#region
private LibVLC _libVLC;
private MediaPlayer _mediaPlayer;
/// <summary>
/// 初始化MediaPlay
/// </summary>
private void InitMediaPlayer()
{
_libVLC = new LibVLC();
_mediaPlayer = new MediaPlayer(_libVLC);
videoView1.MediaPlayer = _mediaPlayer;
}
#endregion
/// <summary>
/// 测试手动播放
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPlayHistoryVideo_Click(object sender, EventArgs e)
{
string path = "D:\\Downtest_Channel2025-09-12 102454.mp4";
_mediaPlayer.Play(new Media(_libVLC, path));
}
/// <summary>
/// 停止播放当前的视频文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStopHistoryPlay_Click(object sender, EventArgs e)
{
_mediaPlayer.Stop();
}
/// <summary>
/// 清理播放资源
/// </summary>
private void CleanMediaPlay()
{
_mediaPlayer.Stop();
_mediaPlayer.Dispose();
_libVLC.Dispose();
}
#region
/// <summary>
/// 采用 BindingList + BindingSource使数据变化自动触发 UI 刷新
/// </summary>
private BindingSource historyBindingSource { get; set; }
private BindingList<VideoAction> historyVideoBindingList { get; set; }
/// <summary>
/// 初始化历史记录表格的数据绑定
/// </summary>
private void InitHistoryGridBinding()
{
historyBindingSource = new BindingSource();
historyVideoBindingList = new BindingList<VideoAction>();
historyBindingSource.DataSource = historyVideoBindingList;
// 绑定到 WinForms 原生 DataGridView
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = historyBindingSource;
// 绑定完成后统一调整列头与可见性
dataGridView1.DataBindingComplete -= DataGridView1_DataBindingComplete;
dataGridView1.DataBindingComplete += DataGridView1_DataBindingComplete;
// 双击行播放对应视频
dataGridView1.CellDoubleClick -= DataGridView1_CellDoubleClick;
dataGridView1.CellDoubleClick += DataGridView1_CellDoubleClick;
// 若此时已存在列,立即调整一次
ConfigureHistoryGridColumns();
}
private void btnHistoryVideoSearch_Click(object sender, EventArgs e)
{
var query = FSqlContext.FDb.Select<VideoAction>();
if (!string.IsNullOrEmpty(txtSearchCode.Text.Trim()))
{
query = query.Where(a => a.Code!.Contains(txtSearchCode.Text.Trim()));
}
query.Where(a => a.CreateTime >= PdtHistorySearchStart.Value && a.CreateTime <= PdtHistorySearchEnd.Value);
// 拉取结果并刷新绑定列表
var resultList = query.ToList();
// 暂停变更通知,批量更新提高效率
historyVideoBindingList.RaiseListChangedEvents = false;
historyVideoBindingList.Clear();
foreach (var item in resultList)
{
historyVideoBindingList.Add(item);
}
historyVideoBindingList.RaiseListChangedEvents = true;
// 通知 UI 刷新
historyVideoBindingList.ResetBindings();
}
// 与历史记录表格列配置相关的成员WinForms DataGridView
private void DataGridView1_DataBindingComplete(object? sender, DataGridViewBindingCompleteEventArgs e)
{
try
{
ConfigureHistoryGridColumns();
}
catch (Exception ex)
{
// 仅记录,不中断界面
System.Diagnostics.Debug.WriteLine($"[DataBindingComplete] 列配置失败: {ex.Message}");
}
}
/// <summary>
/// 双击行,播放该行对应的视频文件
/// </summary>
private void DataGridView1_CellDoubleClick(object? sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0) return; // 双击列头等
try
{
var row = dataGridView1.Rows[e.RowIndex];
if (row?.DataBoundItem is VideoAction va)
{
var path = va.VideoFilePath;
if (string.IsNullOrWhiteSpace(path))
{
MessageBox.Show("该记录没有可播放的视频路径。", "播放提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (!File.Exists(path))
{
MessageBox.Show($"未找到视频文件:\n{path}", "播放失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
_mediaPlayer?.Stop();
_mediaPlayer?.Play(new LibVLCSharp.Shared.Media(_libVLC, path));
}
}
catch (Exception ex)
{
MessageBox.Show($"播放失败: {ex.Message}", "播放失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 按照 _historyHeaderMap 配置列头、隐藏不需要的列,并做常用格式化
/// </summary>
private void ConfigureHistoryGridColumns()
{
if (dataGridView1 == null || dataGridView1.Columns.Count == 0) return;
foreach (DataGridViewColumn col in dataGridView1.Columns)
{
var propName = col.DataPropertyName;
// 设置列头中文
if (!string.IsNullOrWhiteSpace(propName) && _historyHeaderMap.TryGetValue(propName, out var headerText))
{
col.HeaderText = headerText;
}
// 隐藏 Id 列
if (propName == nameof(VideoAction.Id))
{
col.Visible = false;
continue;
}
//// 常用列宽与格式
//if (propName is nameof(VideoAction.Code))
//{
// col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
//}
//else if (propName is nameof(VideoAction.VideoFilePath))
//{
// col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
//}
// 时间列格式化
if (propName is nameof(VideoAction.StartTime) or nameof(VideoAction.EndTime) or nameof(VideoAction.CreateTime))
{
col.DefaultCellStyle.Format = "yyyy-MM-dd HH:mm:ss";
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
}
}
// 选中整行,易于操作
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridView1.MultiSelect = false;
dataGridView1.ReadOnly = true;
dataGridView1.AllowUserToAddRows = false;
}
#endregion
/// <summary>
/// 测试/结束操作按钮:同样仅入队一条下载任务,方便快速验证下载->监听全流程。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnTestAction_Click(object sender, EventArgs e)
{
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>
/// 下载完成事件:触发后入库并启动 Jellyfin 轮询匹配
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HkCameraClient_NVRLoadVideoCompleteEventHandler(object? sender, string e)
{
// 计算用于去重的 key文件路径或文件名小写
var localNameOrPath = (!string.IsNullOrWhiteSpace(e) && (e.Contains("\\") || e.Contains("/") || e.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase)))
? e
: this.CurrentVideoPath;
var key = (System.IO.Path.GetFileName(localNameOrPath) ?? localNameOrPath).ToLowerInvariant();
// 若同一 key 已在处理中,则忽略本次回调
if (!_downloadProcessingKeys.TryAdd(key, 0))
{
System.Diagnostics.Debug.WriteLine($"[NVRLoadVideoComplete] 正在处理相同文件,忽略: {key}");
return;
}
// 先保存当前的信息,记录主键 Id
var rawUse = new OEMRawUse()
{
InBagCode = CurInBagCode,
RawName = CurInBagRawName,
User = CurUserName,
UrlState = false,
VideoUrl = string.Empty
};
long rawUseId = 0;
try
{
rawUseId = FSqlContext.FDb.Insert<OEMRawUse>(rawUse).ExecuteIdentity();
_downloadProcessingKeys[key] = rawUseId;
}
catch (Exception ex)
{
_downloadProcessingKeys.TryRemove(key, out _);
System.Diagnostics.Debug.WriteLine($"[NVRLoadVideoComplete] 插入 OEMRawUse 失败: {ex.Message}");
return;
}
// 后台执行,避免阻塞 UI 线程
Task.Run(async () =>
{
try
{
var monitor = new JellyfinMonitorService();
await monitor.MonitorAndUpdateAfterDownloadAsync(
oemRawUseId: rawUseId,
videoLocalPathOrName: localNameOrPath,
code: CurInBagCode,
rawName: CurInBagRawName,
userName: CurUserName,
cancellationToken: CancellationToken.None
).ConfigureAwait(false);
}
catch (Exception ex1)
{
System.Diagnostics.Debug.WriteLine($"[JellyfinMonitor] 异常: {ex1.Message}");
}
finally
{
// 移除去重 key允许后续相同文件再次处理如需重试
_downloadProcessingKeys.TryRemove(key, out _);
}
});
}
#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();
}
}
}