367 lines
17 KiB
C#
367 lines
17 KiB
C#
using System;
|
||
using System.Net;
|
||
using System.Net.Sockets;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using System.Drawing;
|
||
using System.Threading.Tasks;
|
||
using System.IO; // 為 Path 和 File 提供支持
|
||
using System.Windows.Forms; // 為 Invoke 和 Form 控件提供支持
|
||
using System.Collections.Generic;
|
||
using DBObj;
|
||
using OverlayFormObj;
|
||
namespace DualScreenDemo
|
||
{
|
||
public class TCPServer
|
||
{
|
||
private TcpListener listener;
|
||
private const int Port = 1000;
|
||
private readonly string hostNameSuffix;
|
||
//private bool isProcessingCommand = false;
|
||
|
||
|
||
public TCPServer()
|
||
{
|
||
listener = new TcpListener(IPAddress.Any, Port);
|
||
hostNameSuffix = GetHostNameSuffix();
|
||
}
|
||
|
||
private bool IsFormReady(Form form)
|
||
{
|
||
if (form == null) return false;
|
||
bool isReady = false;
|
||
try
|
||
{
|
||
if (form.IsHandleCreated && !form.IsDisposed)
|
||
{
|
||
if (form.InvokeRequired)
|
||
{
|
||
form.Invoke(new Action(() => isReady = true));
|
||
}
|
||
else
|
||
{
|
||
isReady = true;
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
isReady = false;
|
||
}
|
||
return isReady;
|
||
}
|
||
|
||
private async Task SafeInvoke(Form form, Action action, int maxRetries = 10)
|
||
{
|
||
if (form == null) return;
|
||
|
||
for (int i = 0; i < maxRetries; i++)
|
||
{
|
||
try
|
||
{
|
||
if (IsFormReady(form))
|
||
{
|
||
if (form.InvokeRequired)
|
||
{
|
||
form.Invoke(action);
|
||
}
|
||
else
|
||
{
|
||
action();
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Invoke attempt {i + 1} failed: {ex.Message}");
|
||
}
|
||
|
||
await Task.Delay(500); // 等待500毫秒后重试
|
||
}
|
||
Console.WriteLine("Failed to invoke action after maximum retries");
|
||
}
|
||
|
||
public void Start()
|
||
{
|
||
// 啟動 TCP 監聽器
|
||
|
||
listener.Start();
|
||
Console.WriteLine("Server started on port " + Port + ".");
|
||
|
||
try {
|
||
// 讀取初始狀態檔案
|
||
string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "states.txt");
|
||
string initialState = ReadStateFile(stateFilePath);
|
||
|
||
// 若初始狀態為 "CLOSE",則顯示送客畫面,並禁用所有主畫面的控制項
|
||
if (initialState.Equals("CLOSE", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
_ = SafeInvoke(PrimaryForm.Instance, () =>
|
||
{
|
||
try {
|
||
foreach (Control ctrl in PrimaryForm.Instance.Controls)
|
||
{
|
||
ctrl.Enabled = false;
|
||
}
|
||
PrimaryForm.Instance.ShowSendOffScreen();
|
||
}
|
||
catch (Exception ex) {
|
||
Console.WriteLine($"顯示送客畫面時發生錯誤: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
|
||
// 不斷等待並處理 TCP 連線
|
||
while (true)
|
||
{
|
||
Console.WriteLine("Waiting for connections...");
|
||
|
||
using (TcpClient client = listener.AcceptTcpClient())
|
||
{
|
||
Console.WriteLine("Connected!");
|
||
NetworkStream stream = client.GetStream();
|
||
|
||
// 處理來自 client 的指令
|
||
while (client.Connected)
|
||
{
|
||
byte[] buffer = new byte[1024];
|
||
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||
if (bytesRead == 0)
|
||
break;
|
||
|
||
string request = Encoding.UTF8.GetString(buffer, 0, bytesRead);
|
||
Console.WriteLine("Received: " + request.Trim());
|
||
|
||
// 忽略長度太短的請求
|
||
if (request.Length < 5)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 解析 host 名稱字尾與指令本體
|
||
string requestHostSuffix = request.Substring(0, 3);
|
||
string command = request.Substring(4);
|
||
|
||
// 比對主機名稱是否符合
|
||
if (requestHostSuffix.Equals(hostNameSuffix, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
// 指令為 "X":播放 CLOSE.MPG,並顯示送客畫面
|
||
if (command.Trim().Equals("X", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
PrimaryForm.Instance.pictureBoxQRCode.Visible = false;
|
||
PrimaryForm.Instance.closeQRCodeButton.Visible = false;
|
||
_ = SafeInvoke(VideoPlayerForm.Instance, async () =>
|
||
{
|
||
if (IsFormReady(PrimaryForm.Instance))
|
||
{
|
||
await SafeInvoke(PrimaryForm.Instance, async () =>
|
||
{
|
||
PrimaryForm.Instance.ShowSendOffScreen();
|
||
PrimaryForm.Instance.HideFireScreen();
|
||
string marqueeMessage= "歡迎使用網路版系統,與你共度美好時光。";
|
||
OverlayForm.MainForm.UpdateMarqueeText(marqueeMessage, OverlayForm.MarqueeStartPosition.Middle, Color.White);
|
||
Console.WriteLine("開始設置新的播放列表");
|
||
|
||
string closePath = @"C:\video\CLOSE.MPG";
|
||
|
||
if (File.Exists(closePath))
|
||
{
|
||
// 建立結束播放用的 SongData 實例
|
||
SongData closeSong = new SongData(
|
||
"", "", "結束播放", 0, "", "", "", "",
|
||
DateTime.Now, closePath, "", "", "", "",
|
||
"", "", "", "", "", "", "", 1
|
||
);
|
||
|
||
// 建立新的播放清單
|
||
|
||
VideoPlayerForm.publicPlaylist = new List<SongData>();
|
||
VideoPlayerForm.playingSongList = new List<SongData>();
|
||
PrimaryForm.playedSongsHistory = new List<SongData>();
|
||
// 如果有正在播放的歌曲也加進去
|
||
if (VideoPlayerForm.Instance.currentPlayingSong != null)
|
||
{
|
||
VideoPlayerForm.playingSongList.Add(VideoPlayerForm.Instance.currentPlayingSong);
|
||
}
|
||
|
||
VideoPlayerForm.publicPlaylist.Add(closeSong);
|
||
// 將 CLOSE.MPG 加入播放清單
|
||
VideoPlayerForm.playingSongList.Add(closeSong);
|
||
|
||
// 清空使用者點播清單
|
||
PrimaryForm.userRequestedSongs = new List<SongData>();
|
||
|
||
// 隱藏 Overlay 的「下一首提示」
|
||
if (IsFormReady(OverlayForm.MainForm))
|
||
{
|
||
OverlayForm.MainForm.nextSongLabel.Visible = false;
|
||
}
|
||
VideoPlayerForm.Instance.PlayNextSong();
|
||
Console.WriteLine("已設置新的播放列表,包含當前歌曲和 CLOSE.MPG");
|
||
try
|
||
{
|
||
await HttpServer.RestartServer();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine("RestartServer 發生錯誤: " + ex.Message);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"錯誤: 找不到檔案 {closePath}");
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// 更新狀態檔案為 CLOSE
|
||
UpdateStateFile(stateFilePath, "CLOSE");
|
||
continue;
|
||
}
|
||
|
||
// 指令為 "O":開啟系統,隱藏送客畫面
|
||
if (command.Trim().Equals("O", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
_ = SafeInvoke(PrimaryForm.Instance, () =>
|
||
{
|
||
PrimaryForm.Instance.HideSendOffScreen();
|
||
PrimaryForm.Instance.HideFireScreen();
|
||
string marqueeMessage= "歡迎使用網路版系統,與你共度美好時光。";
|
||
OverlayForm.MainForm.UpdateMarqueeText(marqueeMessage, OverlayForm.MarqueeStartPosition.Middle, Color.White);
|
||
});
|
||
VideoPlayerForm.publicPlaylist = new List<SongData>();
|
||
VideoPlayerForm.playingSongList = new List<SongData>();
|
||
VideoPlayerForm.Instance.PlayPublicPlaylist(); // 播放公播
|
||
// 更新狀態檔案為 OPEN
|
||
UpdateStateFile(stateFilePath, "OPEN");
|
||
continue;
|
||
}
|
||
if (command.Trim().Equals("F", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
_ = SafeInvoke(PrimaryForm.Instance, () =>
|
||
{
|
||
PrimaryForm.Instance.HideSendOffScreen();
|
||
PrimaryForm.Instance.ShowFireScreen();
|
||
VideoPlayerForm.Instance.Pause();
|
||
string marqueeMessage = "發生火災,請跟隨引導至逃生出口!!!";
|
||
OverlayForm.MainForm.UpdateMarqueeText(marqueeMessage, OverlayForm.MarqueeStartPosition.Middle, Color.Red);
|
||
});
|
||
|
||
// 更新狀態檔案(可選,若你要記錄狀態)
|
||
UpdateStateFile(stateFilePath, "PAUSE");
|
||
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// 若 Overlay Form 準備好,嘗試顯示跑馬燈文字
|
||
if (IsFormReady(OverlayForm.MainForm))
|
||
{
|
||
string message = request.Trim();
|
||
string pattern = @"^(全部|\d{4})\((白色|紅色|綠色|黑色|藍色)\)-";
|
||
Match match = Regex.Match(message, pattern);
|
||
|
||
_ = SafeInvoke(OverlayForm.MainForm, () =>
|
||
{
|
||
if (match.Success)
|
||
{
|
||
// 若符合格式,顯示主跑馬燈文字
|
||
string marqueeMessage = message.Substring(match.Value.Length).Trim();
|
||
Color textColor = GetColorFromString(match.Groups[2].Value);
|
||
OverlayForm.MainForm.UpdateMarqueeText(marqueeMessage, OverlayForm.MarqueeStartPosition.Middle, textColor);
|
||
}
|
||
else
|
||
{
|
||
// 不符合格式,顯示在第二行跑馬燈
|
||
string marqueeMessage = "系統公告: " + message;
|
||
OverlayForm.MainForm.UpdateMarqueeTextSecondLine(marqueeMessage);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 指令為 "exit":結束此連線
|
||
if (request.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
Console.WriteLine("Connection closed.");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine($"Error: {e.Message}");
|
||
}
|
||
finally
|
||
{
|
||
// 關閉 listener
|
||
listener.Stop();
|
||
}
|
||
}
|
||
|
||
|
||
private Color GetColorFromString(string colorName)
|
||
{
|
||
switch (colorName)
|
||
{
|
||
case "白色":
|
||
return Color.White;
|
||
case "紅色":
|
||
return Color.Red;
|
||
case "綠色":
|
||
return Color.LightGreen;
|
||
case "黑色":
|
||
return Color.Black;
|
||
case "藍色":
|
||
return Color.LightBlue;
|
||
default:
|
||
return Color.Black;
|
||
}
|
||
}
|
||
|
||
private string GetHostNameSuffix()
|
||
{
|
||
string hostName = Dns.GetHostName();
|
||
return hostName.Length > 3 ? hostName.Substring(hostName.Length - 3) : hostName;
|
||
}
|
||
private string ReadStateFile(string filePath)
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(filePath))
|
||
{
|
||
string state = File.ReadAllText(filePath).Trim();
|
||
Console.WriteLine($"✅ State file read: {filePath} -> {state}");
|
||
return state;
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("⚠️ State file not found. Creating new file with default state: OPEN");
|
||
UpdateStateFile(filePath, "OPEN");
|
||
return "OPEN";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"❌ Failed to read state file: {ex.Message}");
|
||
return "OPEN"; // 默認為 OPEN
|
||
}
|
||
}
|
||
private void UpdateStateFile(string filePath, string state)
|
||
{
|
||
try
|
||
{
|
||
File.WriteAllText(filePath, state);
|
||
Console.WriteLine($"✅ State file updated: {filePath} -> {state}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"❌ Failed to update state file: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
} |