心跳封包
This commit is contained in:
parent
4d02d56152
commit
c623bbe26d
@ -1,39 +1,132 @@
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json; // 適用於 .NET Core 3.0+ / .NET 5/6/7/8
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
namespace HeartbeatSender{
|
||||
|
||||
namespace HeartbeatSender
|
||||
{
|
||||
|
||||
public class HeartbeatData
|
||||
{
|
||||
public string branch_name { get; set; }
|
||||
public string room_name { get; set; }
|
||||
public string room_ip { get; set; }
|
||||
public string email { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
public class heartbeatSender
|
||||
{
|
||||
private readonly UdpClient udpClient;
|
||||
private readonly IPEndPoint remoteEndPoint;
|
||||
private readonly System.Timers.Timer heartbeatTimer;
|
||||
public IPEndPoint RemoteEndPoint => remoteEndPoint;
|
||||
public heartbeatSender(string targetIp, int targetPort, int intervalMilliseconds = 3000)
|
||||
private readonly HttpClient httpClient = new HttpClient();
|
||||
private string token;
|
||||
private string heartbeatUrl;
|
||||
|
||||
public heartbeatSender(string heartbeatUrl)
|
||||
{
|
||||
udpClient = new UdpClient();
|
||||
// 設置 IP 和 PORT
|
||||
remoteEndPoint = new IPEndPoint(IPAddress.Parse(targetIp), targetPort);
|
||||
// 每3秒發送一次
|
||||
heartbeatTimer = new System.Timers.Timer(intervalMilliseconds);
|
||||
heartbeatTimer.Elapsed += SendHeartbeat;
|
||||
heartbeatTimer.AutoReset = true;
|
||||
this.heartbeatUrl = heartbeatUrl;
|
||||
}
|
||||
private void SendHeartbeat(object sender, ElapsedEventArgs e)
|
||||
public static string GetLocalIPv4()
|
||||
{
|
||||
foreach (var ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
|
||||
{
|
||||
if (ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip))
|
||||
{
|
||||
return ip.ToString();
|
||||
}
|
||||
}
|
||||
return "127.0.0.1"; // fallback
|
||||
}
|
||||
public async Task<bool> LoginAndGetTokenAsync()
|
||||
{
|
||||
var loginUrl = "http://zqd.superstar.dnsnet.cc/api/room/receiveRegister";
|
||||
string hostName = System.Net.Dns.GetHostName();
|
||||
|
||||
var loginPayload = new
|
||||
{
|
||||
branch_name = "測試",
|
||||
room_name = "PC" + hostName.Substring(Math.Max(0, hostName.Length - 3)),
|
||||
room_ip = GetLocalIPv4(),
|
||||
email = "MachineKTV@gmail.com",
|
||||
password = "aa147258-"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(loginPayload);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
string heartbeatMessage = "HEARTBEAT";
|
||||
byte[] data = Encoding.UTF8.GetBytes(heartbeatMessage);
|
||||
udpClient.Send(data, data.Length, remoteEndPoint);
|
||||
Console.WriteLine($"Heartbeat sent to {remoteEndPoint.Address}:{remoteEndPoint.Port}");
|
||||
var response = await httpClient.PostAsync(loginUrl, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseJson = await response.Content.ReadAsStringAsync();
|
||||
// Console.WriteLine("API 回傳內容:" + responseJson);
|
||||
|
||||
using var doc = JsonDocument.Parse(responseJson);
|
||||
if (doc.RootElement.TryGetProperty("data", out JsonElement dataElement) &&
|
||||
dataElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (dataElement.TryGetProperty("token", out JsonElement tokenElement))
|
||||
{
|
||||
token = tokenElement.GetString();
|
||||
}
|
||||
}
|
||||
// Console.WriteLine("登入成功,取得 token:" + token);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error sending heartbeat: {ex.Message}");
|
||||
Console.WriteLine($"登入失敗:{ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public void Start() => heartbeatTimer.Start();
|
||||
public void Stop() => heartbeatTimer.Stop();
|
||||
|
||||
public async Task SendHeartbeatAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
Console.WriteLine("請先登入取得 token");
|
||||
return;
|
||||
}
|
||||
//Console.WriteLine(GetLocalIPv4());
|
||||
string hostName = System.Net.Dns.GetHostName();
|
||||
|
||||
var heartbeatData = new
|
||||
{
|
||||
branch_name = "測試",
|
||||
hostname = "PC" + hostName.Substring(Math.Max(0, hostName.Length - 3)),
|
||||
ip = GetLocalIPv4(),
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(heartbeatData);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
heartbeatUrl = "http://zqd.superstar.dnsnet.cc/api/room/heartbeat";
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, heartbeatUrl);
|
||||
request.Content = content;
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
// Console.WriteLine("送出的 JSON:");
|
||||
// Console.WriteLine(json);
|
||||
var response = await httpClient.SendAsync(request);
|
||||
try
|
||||
{
|
||||
|
||||
// Console.WriteLine($"心跳送出狀態:{response.StatusCode}");
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseJson = await response.Content.ReadAsStringAsync();
|
||||
|
||||
using var doc = JsonDocument.Parse(responseJson);
|
||||
// Console.WriteLine("API 回傳內容:" + responseJson);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"送出心跳錯誤:{ex.Message}");
|
||||
Console.WriteLine($"後端回應內容:{errorContent}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,14 +6,50 @@ using System.Diagnostics;
|
||||
namespace DualScreenDemo{
|
||||
public partial class PrimaryForm
|
||||
{
|
||||
private static string connectionStringfortest = "";
|
||||
public static void LoadConnectionStringFromFile(string filePath)
|
||||
{
|
||||
filePath = Path.Combine(Application.StartupPath, "txt", filePath);
|
||||
try
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
string fileContent = File.ReadAllText(filePath).Trim();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(fileContent))
|
||||
{
|
||||
connectionStringfortest = fileContent;
|
||||
Console.WriteLine("連線字串載入成功。");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("檔案內容為空。");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("找不到指定的檔案:" + filePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("讀取檔案時發生錯誤:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetConnectionString()
|
||||
{
|
||||
return connectionStringfortest;
|
||||
}
|
||||
|
||||
public bool isLoggedIn = false;
|
||||
public string userPhone = string.Empty;
|
||||
public List<SongData> SearchSongs_Mysql(string query)
|
||||
{
|
||||
List<SongData> searchResults = new List<SongData>();
|
||||
Console.WriteLine(query);
|
||||
string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
|
||||
//string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
string connectionString = GetConnectionString();
|
||||
using (var connection = new MySqlConnection(connectionString))
|
||||
{
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
@ -65,8 +101,8 @@ namespace DualScreenDemo{
|
||||
public static List<Artist> SearchSingers_Mysql(string query){
|
||||
List<Artist> searchResults = new List<Artist>();
|
||||
Console.WriteLine(query);
|
||||
string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
|
||||
//string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
string connectionString = GetConnectionString();
|
||||
using (var connection = new MySqlConnection(connectionString))
|
||||
{
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
@ -100,8 +136,8 @@ namespace DualScreenDemo{
|
||||
|
||||
string query = $"INSERT INTO FavoriteSongs (userPhone,songNumber) VALUES ('{userPhone}','{songNumber}');";
|
||||
Console.WriteLine(query);
|
||||
string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
|
||||
//string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
string connectionString = GetConnectionString();
|
||||
using (var connection = new MySqlConnection(connectionString))
|
||||
{
|
||||
|
||||
@ -128,8 +164,8 @@ namespace DualScreenDemo{
|
||||
string songlist = phonenumber + "的歌單";
|
||||
string query = $"INSERT INTO FavoriteSongs (userPhone,songNumber) VALUES ('{phonenumber}','{songlist}');";
|
||||
Console.WriteLine(query);
|
||||
string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
|
||||
//string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
string connectionString = GetConnectionString();
|
||||
using (var connection = new MySqlConnection(connectionString))
|
||||
{
|
||||
|
||||
@ -163,7 +199,8 @@ namespace DualScreenDemo{
|
||||
|
||||
}
|
||||
public bool checkPhoneNumberExist(string phonenumber){
|
||||
string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
//string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
string connectionString = GetConnectionString();
|
||||
bool exists = false;
|
||||
using (var connection = new MySqlConnection(connectionString))
|
||||
{
|
||||
@ -212,8 +249,8 @@ namespace DualScreenDemo{
|
||||
public void AddSongCount(string id){
|
||||
string query = $"UPDATE songs SET song_counts = song_counts+1 WHERE id = '{id}';";
|
||||
Console.WriteLine(query);
|
||||
string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
|
||||
//string connectionString = "Server=192.168.11.4;Port=3306;Database=Karaoke-Kingpin;User=Karaoke-Kingpin;Password=ESM7yTPMnavFmbBH;";
|
||||
string connectionString = GetConnectionString();
|
||||
using (var connection = new MySqlConnection(connectionString))
|
||||
{
|
||||
|
||||
|
@ -184,31 +184,31 @@ namespace DualScreenDemo
|
||||
public PrimaryForm()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
|
||||
// 添加 DPI 感知支持
|
||||
if (Environment.OSVersion.Version.Major >= 6)
|
||||
{
|
||||
SetProcessDPIAware();
|
||||
}
|
||||
|
||||
|
||||
this.DoubleBuffered = true;
|
||||
|
||||
|
||||
InitializeProgressBar();
|
||||
|
||||
// 初始化自动刷新Timer
|
||||
autoRefreshTimer = new Timer();
|
||||
autoRefreshTimer.Interval = 1000; // 1秒
|
||||
autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
|
||||
|
||||
|
||||
// 設置窗體屬性
|
||||
this.WindowState = FormWindowState.Maximized;
|
||||
this.FormBorderStyle = FormBorderStyle.None;
|
||||
|
||||
|
||||
lightControlTimer = new Timer();
|
||||
lightControlTimer.Interval = 5;
|
||||
lightControlTimer.Interval = 5;
|
||||
lightControlTimer.Tick += LightControlTimer_Tick;
|
||||
volumeUpTimer = new Timer();
|
||||
volumeUpTimer.Interval = 100;
|
||||
volumeUpTimer.Interval = 100;
|
||||
volumeUpTimer.Tick += VolumeUpTimer_Tick;
|
||||
volumeDownTimer = new Timer();
|
||||
volumeDownTimer.Interval = 100;
|
||||
@ -216,12 +216,12 @@ namespace DualScreenDemo
|
||||
micControlTimer = new Timer();
|
||||
micControlTimer.Interval = 100;
|
||||
micControlTimer.Tick += MicControlTimer_Tick;
|
||||
|
||||
|
||||
InitializeRecording();
|
||||
InitializeMediaPlayer();
|
||||
LoadSongData();
|
||||
LoadImages();
|
||||
InitializeFormAndControls();
|
||||
LoadImages();
|
||||
InitializeFormAndControls();
|
||||
InitializeMultiPagePanel();
|
||||
OverlayQRCodeOnImage(HttpServer.randomFolderPath);
|
||||
|
||||
@ -229,16 +229,17 @@ namespace DualScreenDemo
|
||||
InitializeHandWritingForSongs();
|
||||
|
||||
InitializeSendOffPanel();
|
||||
|
||||
|
||||
InitializePromotionsAndMenuPanel();
|
||||
SaveInitialControlStates(this);
|
||||
// 包廂 + port 名稱
|
||||
this.Paint += PrimaryForm_Paint;
|
||||
this.Paint += User_Paint;
|
||||
// 註冊多頁面面板的頁碼改變事件處理
|
||||
multiPagePanel.PageIndexChanged += HandlePageChanged;
|
||||
multiPagePanel.PageIndexChanged += HandlePageChanged;
|
||||
// 添加 Load 事件處理
|
||||
this.Load += PrimaryForm_Load;
|
||||
LoadConnectionStringFromFile("test.env");
|
||||
|
||||
}
|
||||
|
||||
|
156
Program.cs
156
Program.cs
@ -2,6 +2,7 @@ using System.IO;
|
||||
using Microsoft.Win32;
|
||||
using System.Diagnostics;
|
||||
using DBObj;
|
||||
using HeartbeatSender;
|
||||
|
||||
namespace DualScreenDemo
|
||||
{
|
||||
@ -14,76 +15,101 @@ namespace DualScreenDemo
|
||||
private static PrimaryForm primaryForm; // 儲存實例的參考
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
// COM 初始化
|
||||
int hr = ComInterop.CoInitializeEx(IntPtr.Zero, ComInterop.COINIT_APARTMENTTHREADED);
|
||||
if (hr < 0)
|
||||
static void Main()
|
||||
{
|
||||
Console.WriteLine("Failed to initialize COM library.");
|
||||
return;
|
||||
}
|
||||
// 初始化管理器
|
||||
var sender = new HeartbeatSender.heartbeatSender("http://zqd.superstar.dnsnet.cc/api/room/receiveRegister");
|
||||
|
||||
songListManager = new SongListManager(); // 使用单例
|
||||
//artistManager = new ArtistManager();
|
||||
// 同步呼叫非同步登入取得 token
|
||||
bool loginSuccess = sender.LoginAndGetTokenAsync().GetAwaiter().GetResult();
|
||||
|
||||
var commandHandler = new CommandHandler(songListManager);
|
||||
serialPortManager = new SerialPortManager(commandHandler);
|
||||
serialPortManager.InitializeSerialPort();
|
||||
|
||||
// 輸出屏幕信息
|
||||
Console.WriteLine($"Virtual Screen: {SystemInformation.VirtualScreen}");
|
||||
foreach (var screen in Screen.AllScreens)
|
||||
{
|
||||
Console.WriteLine($"Screen: {screen.DeviceName} Resolution: {screen.Bounds.Width}x{screen.Bounds.Height}");
|
||||
}
|
||||
|
||||
// 啟動服務器
|
||||
Task.Run(() => HttpServerManager.StartServer());
|
||||
Task.Run(() => TCPServerManager.StartServer());
|
||||
|
||||
// 註冊事件
|
||||
Application.ApplicationExit += (sender, e) => SerialPortManager.CloseSerialPortSafely();
|
||||
SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged;
|
||||
|
||||
// 創建主窗體
|
||||
primaryForm = new PrimaryForm();
|
||||
//primaryForm.allSongs = songListManager.AllSongs;
|
||||
//primaryForm.allArtists = artistManager.AllArtists;
|
||||
primaryForm.StartPosition = FormStartPosition.Manual;
|
||||
primaryForm.Location = new Point(0, 0);
|
||||
primaryForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
|
||||
|
||||
// 在完整初始化後檢查狀態
|
||||
string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"txt","states.txt");
|
||||
bool isClosedState = File.Exists(stateFilePath) &&
|
||||
File.ReadAllText(stateFilePath).Trim().Equals("CLOSE", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
InitializeSecondaryScreen();
|
||||
|
||||
// 使用 Shown 事件來確保窗體完全加載後再顯示送客畫面
|
||||
if (isClosedState)
|
||||
{
|
||||
primaryForm.Shown += (s, e) =>
|
||||
if (loginSuccess)
|
||||
{
|
||||
primaryForm.ShowSendOffScreen();
|
||||
};
|
||||
}
|
||||
// 先送一次心跳 (同步呼叫)
|
||||
sender.SendHeartbeatAsync().GetAwaiter().GetResult();
|
||||
|
||||
primaryForm.Show();
|
||||
Application.Run(primaryForm);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLog(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
SystemEvents.DisplaySettingsChanged -= OnDisplaySettingsChanged;
|
||||
}
|
||||
// 背景持續每3秒送心跳
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await sender.SendHeartbeatAsync();
|
||||
await Task.Delay(3000); // 每3秒送一次
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("登入失敗,無法送出心跳");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// COM 初始化
|
||||
int hr = ComInterop.CoInitializeEx(IntPtr.Zero, ComInterop.COINIT_APARTMENTTHREADED);
|
||||
if (hr < 0)
|
||||
{
|
||||
Console.WriteLine("Failed to initialize COM library.");
|
||||
return;
|
||||
}
|
||||
// 初始化管理器
|
||||
|
||||
songListManager = new SongListManager(); // 使用单例
|
||||
//artistManager = new ArtistManager();
|
||||
|
||||
var commandHandler = new CommandHandler(songListManager);
|
||||
serialPortManager = new SerialPortManager(commandHandler);
|
||||
serialPortManager.InitializeSerialPort();
|
||||
|
||||
// 輸出屏幕信息
|
||||
Console.WriteLine($"Virtual Screen: {SystemInformation.VirtualScreen}");
|
||||
foreach (var screen in Screen.AllScreens)
|
||||
{
|
||||
Console.WriteLine($"Screen: {screen.DeviceName} Resolution: {screen.Bounds.Width}x{screen.Bounds.Height}");
|
||||
}
|
||||
|
||||
// 啟動服務器
|
||||
Task.Run(() => HttpServerManager.StartServer());
|
||||
Task.Run(() => TCPServerManager.StartServer());
|
||||
|
||||
// 註冊事件
|
||||
Application.ApplicationExit += (sender, e) => SerialPortManager.CloseSerialPortSafely();
|
||||
SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged;
|
||||
|
||||
// 創建主窗體
|
||||
primaryForm = new PrimaryForm();
|
||||
//primaryForm.allSongs = songListManager.AllSongs;
|
||||
//primaryForm.allArtists = artistManager.AllArtists;
|
||||
primaryForm.StartPosition = FormStartPosition.Manual;
|
||||
primaryForm.Location = new Point(0, 0);
|
||||
primaryForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
|
||||
|
||||
// 在完整初始化後檢查狀態
|
||||
string stateFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "txt", "states.txt");
|
||||
bool isClosedState = File.Exists(stateFilePath) &&
|
||||
File.ReadAllText(stateFilePath).Trim().Equals("CLOSE", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
InitializeSecondaryScreen();
|
||||
|
||||
// 使用 Shown 事件來確保窗體完全加載後再顯示送客畫面
|
||||
if (isClosedState)
|
||||
{
|
||||
primaryForm.Shown += (s, e) =>
|
||||
{
|
||||
primaryForm.ShowSendOffScreen();
|
||||
};
|
||||
}
|
||||
|
||||
primaryForm.Show();
|
||||
Application.Run(primaryForm);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLog(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
SystemEvents.DisplaySettingsChanged -= OnDisplaySettingsChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -176,9 +176,6 @@
|
||||
<Compile Update="PrimaryFormParts\SingerSearch\PrimaryForm.SingerSearch.PinyinSearch.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="PrimaryFormParts\SingerSearch\PrimaryForm.SingerSearch.StrokeCountSearch.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="PrimaryFormParts\SingerSearch\PrimaryForm.SingerSearch.WordCountSearch.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
Loading…
x
Reference in New Issue
Block a user