diff --git a/XRServer.sln b/XRServer.sln
new file mode 100644
index 0000000..a702e06
--- /dev/null
+++ b/XRServer.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35707.178 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XRServer", "XRServer\XRServer.csproj", "{A07DFA95-49EF-4586-A27B-2FB2EA2CD340}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|x86.ActiveCfg = Debug|x86
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Debug|x86.Build.0 = Debug|x86
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|x86.ActiveCfg = Release|x86
+ {A07DFA95-49EF-4586-A27B-2FB2EA2CD340}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/XRServer/App.xaml b/XRServer/App.xaml
new file mode 100644
index 0000000..01df825
--- /dev/null
+++ b/XRServer/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/XRServer/App.xaml.cs b/XRServer/App.xaml.cs
new file mode 100644
index 0000000..5424718
--- /dev/null
+++ b/XRServer/App.xaml.cs
@@ -0,0 +1,14 @@
+using System.Configuration;
+using System.Data;
+using System.Windows;
+
+namespace XRServer
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+
+}
diff --git a/XRServer/AssemblyInfo.cs b/XRServer/AssemblyInfo.cs
new file mode 100644
index 0000000..b0ec827
--- /dev/null
+++ b/XRServer/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/XRServer/BaseInfoHandler.cs b/XRServer/BaseInfoHandler.cs
new file mode 100644
index 0000000..315cc0f
--- /dev/null
+++ b/XRServer/BaseInfoHandler.cs
@@ -0,0 +1,151 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.IO;
+using System.IO.Compression;
+using System.Text;
+using System.Text.RegularExpressions;
+using uhttpsharp;
+using uhttpsharp.Headers;
+using utils;
+
+namespace XRServer
+{
+ ///
+ /// /baseinfo 요청을 처리하는 핸들러
+ ///
+ public class BaseInfoHandler : IHttpRequestHandler
+ {
+
+ private SQLiteService sqliteService;
+ public BaseInfoHandler(SQLiteService sqliteService)
+ {
+ this.sqliteService = sqliteService;
+ }
+
+ ///
+ /// /baseinfo 요청을 처리하는 메서드
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task Handle(IHttpContext context, Func next)
+ {
+ // /baseinfo/분:초(mm:ss format) 문자열을 받아 해당 시간에 대한 SelectBySecondBaseInfo 데이터 반환
+ string url = context.Request.Uri.OriginalString;
+ Logger.Debug($"BaseInfoHandler: {url} 요청 처리 중...");
+
+ //url에 mm:ss 형식이 포함되어 있는지 확인
+ Match match = Regex.Match(url, @"([0-5][0-9]):([0-5][0-9])$");
+ if (match.Success)
+ {
+ // mm:ss 형식이 맞으면 URL에서 mm:ss 부분 추출
+ string timePart = url.Substring(match.Index, 5); // URL에서 mm:ss 부분 추출
+
+ // 데이터 조회
+ List data = await sqliteService.SelectBySecondBaseInfo(timePart);
+ // 데이터가 null이 아니고, 데이터가 있는지 확인
+ if (data != null && data.Count > 0)
+ {
+ string jsonData = data[0].data;
+ Logger.Debug($"BaseInfoHandler: {data.Count}개의 데이터 조회됨. 요청 시간: {timePart}");
+
+ if (url.StartsWith("/baseinfo/", StringComparison.OrdinalIgnoreCase))
+ {
+ var json = data.Count > 0 ? jsonData : "{}";
+
+ //Logger.Debug($"BaseInfoHandler: 조회된 데이터: {json}");
+ // gzip 압축
+ byte[] compressedBytes = GzipCompress(json);
+
+ var headers = new List>
+ {
+ new KeyValuePair("Content-Type", "application/json; charset=utf-8"),
+ new KeyValuePair("Content-Encoding", "gzip"),
+ new KeyValuePair("Content-Length", compressedBytes.Length.ToString())
+ };
+
+ context.Response = new HttpResponse(
+ HttpResponseCode.Ok,
+ "application/json; charset=utf-8",
+ new MemoryStream(compressedBytes),
+ context.Request.Headers.KeepAliveConnection(),
+ headers
+ );
+
+ }
+ else
+ {
+ JObject? jsonObject = JsonConvert.DeserializeObject(jsonData);
+ if (jsonObject != null)
+ {
+ foreach (var property in jsonObject.Properties())
+ {
+ if (url.StartsWith($"/{property.Name}", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Debug($"BaseInfoHandler: {property.Name}:");
+ // 속성 값을 문자열로 직렬화
+ string propertyValue = property.Value.ToString(Formatting.None);
+ // gzip 압축
+ byte[] compressedBytes = GzipCompress(propertyValue);
+
+ var headers = new List>
+ {
+ new KeyValuePair("Content-Type", "application/json; charset=utf-8"),
+ new KeyValuePair("Content-Encoding", "gzip"),
+ new KeyValuePair("Content-Length", compressedBytes.Length.ToString())
+ };
+ context.Response = new HttpResponse(
+ HttpResponseCode.Ok,
+ "application/json; charset=utf-8",
+ new MemoryStream(compressedBytes),
+ context.Request.Headers.KeepAliveConnection(),
+ headers
+ );
+ break;
+ }
+ }
+ }
+ else
+ {
+ // JSON 데이터가 비어있는 경우
+ context.Response = new HttpResponse(HttpResponseCode.NoContent, "No Content", false);
+ }
+ }
+ }
+ else
+ { // 데이터가 없는 경우
+ context.Response = new HttpResponse(HttpResponseCode.NotFound, "No data found for the specified time.", false);
+ }
+ }
+ else
+ {
+ context.Response = new HttpResponse(HttpResponseCode.BadRequest, "Invalid time format. Use mm:ss.", false);
+ }
+ }
+
+ private byte[] GzipCompress(string content)
+ {
+ byte[] inputBytes = Encoding.UTF8.GetBytes(content);
+ using (var outputStream = new MemoryStream())
+ {
+ using (var gzipStream = new GZipStream(outputStream, CompressionLevel.Optimal, true))
+ {
+ gzipStream.Write(inputBytes, 0, inputBytes.Length);
+ }
+ return outputStream.ToArray();
+ }
+ }
+
+ private MemoryStream StringToStream(string content)
+ {
+ MemoryStream memoryStream = new MemoryStream();
+ StreamWriter streamWriter = new StreamWriter(memoryStream);
+ streamWriter.Write(content);
+ streamWriter.Flush();
+ return memoryStream;
+ }
+
+
+ }
+}
diff --git a/XRServer/DateTimeUtil.cs b/XRServer/DateTimeUtil.cs
new file mode 100644
index 0000000..7d9d5f0
--- /dev/null
+++ b/XRServer/DateTimeUtil.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace XRServer
+{
+ public class DateTimeUtil
+ {
+ //헝가리 서버에서 NQTT 시간이 UTC 시간으로 전송되서 Greenwich Standard Time 로 변경
+ private static TimeZoneInfo hungaryInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time");
+ private static TimeZoneInfo koreadInfo = TimeZoneInfo.FindSystemTimeZoneById("Korea Standard Time");//korea
+
+ ///
+ /// 헝가리 시간
+ ///
+ public static DateTime HungaryNow { get => TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.Local, hungaryInfo); }
+
+ public static string HungaryNowString { get => FormatTime(HungaryNow); }
+
+ public static DateTime UtcNow { get => DateTime.UtcNow; }
+ public static string UtcNowString { get => FormatTime(UtcNow); }
+
+
+ private static TimeSpan utcHungaryGap = TimeSpan.Zero;
+
+ ///
+ /// hungary - utc 시간 차이
+ ///
+ public static TimeSpan UtcHungaryGap { get => (utcHungaryGap == TimeSpan.Zero) ? ToHungaryDateTime(DateTime.UtcNow) - DateTime.UtcNow : utcHungaryGap; }
+
+ ///
+ /// UTC string을 UTC DateTime으로 변환. yyyy-MM-ddTHH:mm:ss.fffZ 포맷이라 제대로 변환 됨
+ ///
+ ///
+ ///
+ public static DateTime UtcParse(string s) { return DateTime.Parse(s).ToUniversalTime(); }
+
+ ///
+ /// string을 DateTime으로 변환
+ ///
+ ///
+ ///
+ ///
+ public static DateTime Parse(string s, string format) { return DateTime.ParseExact(s, format, CultureInfo.InvariantCulture); }
+
+
+ ///
+ /// UTC DateTime을 헝가리 DateTime으로 변환
+ ///
+ ///
+ ///
+ public static DateTime ToHungaryDateTime(DateTime utcDateTime)
+ {
+ return TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, hungaryInfo);
+ }
+
+ ///
+ /// UTC string을 헝가리 DateTime으로 변환
+ ///
+ ///
+ ///
+ public static DateTime UtcStringToHungaryDateTime(string s)
+ {
+ return ToHungaryDateTime(UtcParse(s));
+ }
+
+ ///
+ /// UTC string을 헝가리 Time String으로 변환
+ ///
+ ///
+ ///
+ public static string UtcStringToHungaryTimeString(string s)
+ {
+ return FormatTime(UtcStringToHungaryDateTime(s));
+ }
+
+
+ ///
+ /// UTC DateTime을 한국 DateTime으로 변환
+ ///
+ ///
+ ///
+ public static DateTime ToKoreaDateTime(DateTime utcDateTime)
+ {
+ return TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, koreadInfo);
+ }
+
+ ///
+ /// UTC string을 한국 DateTime으로 변환
+ ///
+ ///
+ ///
+ public static DateTime UtcStringToKoreaDateTime(string s)
+ {
+ return ToKoreaDateTime(UtcParse(s));
+ }
+
+
+ ///
+ /// UTC string을 한국 Time String으로 변환
+ ///
+ ///
+ ///
+ public static string UtcStringToKoreaTimeString(string s)
+ {
+ return FormatTime(UtcStringToKoreaDateTime(s));
+ }
+
+ public static string FormatTime(DateTime dateTime)
+ {
+ return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
+ }
+ }
+}
diff --git a/XRServer/INIFile.cs b/XRServer/INIFile.cs
new file mode 100644
index 0000000..5aec69c
--- /dev/null
+++ b/XRServer/INIFile.cs
@@ -0,0 +1,243 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace XRServer
+{
+ ///
+ /// INIFile
+ /// Window INI 파일을 다루기 위한 클레스
+ ///
+ /// // 사용법 예
+ /// String path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, System.Diagnostics.Process.GetCurrentProcess().ProcessName + @".ini");
+ /// FileInfo f = new FileInfo(path);
+ /// if (!f.Exists)
+ /// {
+ /// path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Application.Current.MainWindow.GetType().Assembly.GetName().Name + @".ini");
+ /// }
+ /// INIFile ini = new INIFile(path);
+ /// Config.UPDATE_FILE = ini.GetString("CONFIG", "UPDATE_FILE");
+ /// Config.IS_TEST_TOP = ini.GetBoolean("DEBUG", "IS_TEST_TOP", false);
+ ///
+ /// // ini 내부
+ /// [CONFIG]
+ /// UPDATE_URL = http://app.stickint.com/samsung/check_update.do
+ /// LOG_URL = http://app.stickint.com/samsung/upload_log.do
+ ///
+ /// [DEBUG]
+ /// IS_TEST_TOP = false
+ ///
+ ///
+ public class INIFile
+ {
+
+ //*********************************************************************************************************************
+ //
+ // properties
+ //
+ //*********************************************************************************************************************
+
+
+ ///
+ /// ini 파일명을 저장
+ ///
+ private string INIFileName;
+
+
+
+
+ ///
+ /// ini 파일을 지정하거나 가져올때 쓰는 속성
+ ///
+ public string FileName
+ {
+ get { return INIFileName; }
+ set { INIFileName = value; }
+ }
+
+ //*********************************************************************************************************************
+ //
+ // construct
+ //
+ //*********************************************************************************************************************
+ ///
+ /// 생성자 : 사용할 ini 파일을 지정
+ ///
+ /// 사용할 파일명
+ public INIFile(string FileName)
+ {
+ INIFileName = FileName;
+ }
+
+ //*********************************************************************************************************************
+ //
+ // methods
+ //
+ //*********************************************************************************************************************
+
+
+ ///
+ /// ini 파일에서 정보를 가져오기 위한 API 기초 함수
+ ///
+ [DllImport("kernel32.dll")]
+ private static extern int GetPrivateProfileString(
+ string section,
+ string key,
+ string def,
+ StringBuilder retVal,
+ int size,
+ string filePath);
+ ///
+ /// ini 파일에서 정보를 쓰기위한 위한 API 기초 함수
+ ///
+ [DllImport("kernel32.dll")]
+ private static extern long WritePrivateProfileString(
+ string section,
+ string key,
+ string val,
+ string filePath);
+ ///
+ /// ini 파일에 정보를 기록하기 위한 함수
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기록할 값
+ private void _IniWriteValue(string Section, string Key, string Value)
+ {
+ WritePrivateProfileString(Section, Key, Value, INIFileName);
+ }
+ ///
+ /// ini 파일에 정보를 가져오기 위한 함수
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 가져온 값
+ private string _IniReadValue(string Section, string Key)
+ {
+ StringBuilder temp = new StringBuilder(2000);
+ int i = GetPrivateProfileString(Section, Key, "", temp, 2000, INIFileName);
+ return temp.ToString().Trim();
+ }
+
+
+ //*********************************************************************************************************************
+ //
+ // public method
+ //
+ //*********************************************************************************************************************
+
+ ///
+ /// 문자열 타입으로 값을 기록한다
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기록 할 문자열
+ public void SetString(string Section, string Key, string Value)
+ {
+ _IniWriteValue(Section, Key, Value.Trim());
+ }
+ ///
+ /// 정수 타입으로 값을 기록한다
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기록 할 정수값
+ ///
+ public void SetInteger(string Section, string Key, int Value)
+ {
+ _IniWriteValue(Section, Key, Value.ToString().Trim());
+ }
+ ///
+ /// 소수 타입으로 값을 기록한다
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기록 할 소수값
+ ///
+ public void SetNumber(string Section, string Key, float Value)
+ {
+ _IniWriteValue(Section, Key, Value.ToString().Trim());
+ }
+
+ ///
+ /// 논리 타입으로 값을 기록 한다.
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기록 할 논리 값
+ public void SetBoolean(string Section, string Key, bool Value)
+ {
+ _IniWriteValue(Section, Key, Value ? "1" : "0");
+ }
+ ///
+ /// 논리 타입으로 값을 가져온다
+ ///
+ /// 섹션명
+ /// 키 값
+ /// 기본값
+ /// 가져온 논리값
+ public bool GetBoolean(string Section, string Key, bool def)
+ {
+ bool temp = def;
+ string stTemp = _IniReadValue(Section, Key);
+ if (stTemp == "") return def;
+ if (stTemp.Trim() == "true" || stTemp.Trim() == "True" || stTemp.Trim() == "1") return true;
+ else return false;
+ }
+ ///
+ /// 문자열로 값을 가져 온다
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 가져온 문자열
+ public string GetString(string Section, string Key)
+ {
+ return _IniReadValue(Section, Key).Trim();
+ }
+ ///
+ /// 정수 타입으로 값을 가져 온다
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기본값
+ /// 가져온 정수값
+ public int GetInteger(string Section, string Key, int def)
+ {
+ int temp = def;
+ string stTemp = _IniReadValue(Section, Key);
+ if (stTemp == "") return def;
+ try
+ {
+ temp = int.Parse(stTemp.Trim());
+ }
+ catch (Exception)
+ {
+ return def;
+ }
+ return temp;
+ }
+
+ ///
+ /// 소수 타입으로 값을 가져 온다
+ ///
+ /// 섹션명
+ /// 키 명
+ /// 기본값
+ /// 가져온 소수값
+ public float GetNumber(string Section, string Key, float def)
+ {
+ float temp = def;
+ string stTemp = _IniReadValue(Section, Key);
+ if (stTemp == "") return def;
+ try
+ {
+ temp = float.Parse(stTemp.Trim());
+ }
+ catch (Exception)
+ {
+ return def;
+ }
+ return temp;
+ }
+
+ }
+}
diff --git a/XRServer/Log4net.config b/XRServer/Log4net.config
new file mode 100644
index 0000000..4bc5b8c
--- /dev/null
+++ b/XRServer/Log4net.config
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XRServer/Logger.cs b/XRServer/Logger.cs
new file mode 100644
index 0000000..56e51db
--- /dev/null
+++ b/XRServer/Logger.cs
@@ -0,0 +1,153 @@
+using log4net;
+using log4net.Config;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+
+namespace utils
+{
+
+ public class Logger
+ {
+
+ //private static readonly log4net.ILog logger = log4net.LogManager.GetLogger("Logger");
+ private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ static Logger()
+ {
+ try
+ {
+ //log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo("log4netSqlite.config"));
+ string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log4net.config");
+ FileInfo configFile = new FileInfo(configFilePath);
+ if (configFile.Exists)
+ {
+ XmlConfigurator.ConfigureAndWatch(configFile);
+ }
+ else
+ {
+ throw new FileNotFoundException("log4net configuration file not found", configFilePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error configuring log4net: {ex.Message}");
+ throw;
+ }
+ }
+
+
+ public static void Info(string msg)
+ {
+
+ StackFrame frame = (new StackTrace(true)).GetFrame(1);
+ var method = frame.GetMethod();
+ var type = method.DeclaringType;
+ var name = method.Name;
+
+ int lineNumber = frame.GetFileLineNumber();
+
+ if (logger.IsInfoEnabled) Log(logType.Info, type.Name + "." + name + " [line " + lineNumber + "] " + msg);
+ }
+
+ public static void Debug(string msg)
+ {
+ StackFrame frame = (new StackTrace(true)).GetFrame(1);
+ var method = frame.GetMethod();
+ var type = method.DeclaringType;
+ var name = method.Name;
+ int lineNumber = frame.GetFileLineNumber();
+
+ if (logger.IsDebugEnabled) Log(logType.Debug, $"{(type.FullName == null ? type.Name : type.FullName)}.{name} [line {lineNumber}] {msg}");
+ }
+
+
+ public static void Warning(string msg, Exception ex)
+ {
+ StackFrame frame = (new StackTrace(true)).GetFrame(1);
+ var method = frame.GetMethod();
+ var type = method.DeclaringType;
+ var name = method.Name;
+ int lineNumber = frame.GetFileLineNumber();
+
+ if (logger.IsWarnEnabled) Log(logType.Warning, type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex: ex);
+ }
+
+ public static void Error(string msg, Exception ex)
+ {
+ StackFrame frame = (new StackTrace(true)).GetFrame(1);
+ var method = frame.GetMethod();
+ var type = method.DeclaringType;
+ var name = method.Name;
+ int lineNumber = frame.GetFileLineNumber();
+
+ if (logger.IsErrorEnabled) Log(logType.Error, type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex: ex);
+ }
+
+ public static void Fatal(string msg, Exception ex)
+ {
+ StackFrame frame = (new StackTrace(true)).GetFrame(1);
+ var method = frame.GetMethod();
+ var type = method.DeclaringType;
+ var name = method.Name;
+ int lineNumber = frame.GetFileLineNumber();
+
+ if (logger.IsFatalEnabled) Log(logType.Fatal, type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex: ex);
+ }
+
+ // log4net 설정 파일 로드 App.xml.cs OnStartup
+ //XmlConfigurator.Configure(new System.IO.FileInfo("log4net.config"));
+ //
+ //각 클래스 마다
+ //static ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+ //
+ //private static ILog? _log;
+ //private static ILog log
+ //{
+ // get
+ // {
+ // if (_log == null)
+ // {
+ // _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+ // }
+ // return _log;
+ // }
+ //}
+
+
+ private enum logType : ushort
+ {
+ Debug = 0x10,
+ Info = 0x01,
+ Warning = 0x02,
+ Error = 0x04,
+ Fatal = 0x08
+ }
+ private static Dictionary> logAction =
+ new Dictionary>()
+ {
+ { logType.Debug, (msg,info) => logger.Debug(msg)},
+ { logType.Info, (msg,info) => logger.Info(msg)},
+ { logType.Warning, (info,ex) => logger.Warn(info,ex)},
+ { logType.Error, (info,ex) => logger.Error(info,ex)},
+ { logType.Fatal, (info,ex) => logger.Fatal(info,ex)},
+ };
+
+ private static void Log(logType type, string msg = default(string), string info = default(string), Exception ex = default(Exception))
+ {
+ if ((logType.Error | logType.Warning | logType.Fatal).HasFlag(type))
+ //logAction[type](info, ex);
+ ThreadPool.QueueUserWorkItem(tmp => logAction[type](info, ex));
+ else
+ //logAction[type](msg, null);
+ ThreadPool.QueueUserWorkItem(tmp => logAction[type](msg, null));
+ }
+
+ //static public void Debug(string msg) => Log(logType.Debug, msg);
+ //static public void Info(string msg) => Log(logType.Info, msg);
+ //static public void Warning(string info, Exception ex) => Log(logType.Warning, info, ex: ex);
+ //static public void Error(string info, Exception ex) => Log(logType.Error, info, ex: ex);
+ //static public void Fatal(string info, Exception ex) => Log(logType.Fatal, info, ex: ex);
+
+ }
+}
\ No newline at end of file
diff --git a/XRServer/MQTTService.cs b/XRServer/MQTTService.cs
new file mode 100644
index 0000000..2573afd
--- /dev/null
+++ b/XRServer/MQTTService.cs
@@ -0,0 +1,214 @@
+using MQTTnet;
+using MQTTnet.Protocol;
+using MQTTnet.Server;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Buffers;
+using utils;
+
+namespace XRServer
+{
+ ///
+ /// MQTT 서버 초기화
+ /// mqttPort 포트에서 실행되며, newXR 토픽에 매 1초당 SQliteSevice의 SelectBySecond 메서드로 데이터를 가져와서 메세지 발송
+ /// 클라이언트가 시작 시간(00:00~59:59)을 지정할 수 있도록 함
+ /// 시작 시간이 지정되지 않은 경우 기본값은 00:00
+ /// 시작 시간부터 1초씩 증가하며, 59:59 이후에는 59:59 데이터를 계속 발송
+ ///
+ public class MQTTService
+ {
+ private MqttServerFactory mqttServerFactory = new MqttServerFactory();
+ private MqttServer mqttServer = null;
+ private System.Timers.Timer timer;
+ private int mqttPort = 1883;
+ private string topic = "newXR";
+ private string startTimeTopic = "startTime";
+
+ private SQLiteService sqliteService;
+ public MQTTService(SQLiteService sqliteService)
+ {
+ this.sqliteService = sqliteService;
+ }
+
+ public void SetConfig(int mqttPort = 1883, string topic = "newXR", string startTimeTopic = "startTime")
+ {
+ this.mqttPort = mqttPort;
+ this.topic = topic;
+ this.startTimeTopic = startTimeTopic;
+ }
+
+
+ public void Init()
+ {
+ // MQTT 서버 옵션 설정
+ var optionsBuilder = new MqttServerOptionsBuilder()
+ .WithDefaultEndpoint()
+ .WithDefaultEndpointPort(mqttPort);
+
+ // MQTT 서버 생성
+ mqttServer = mqttServerFactory.CreateMqttServer(optionsBuilder.Build());
+
+ // 클라이언트의 현재 재생 시간을 저장할 딕셔너리 (클라이언트ID -> 현재 재생 시간)
+ var clientPlaybackTimes = new Dictionary();
+
+ // 클라이언트의 마지막 업데이트 시간을 저장하는 딕셔너리 (클라이언트ID -> DateTime.Now)
+ var clientLastUpdates = new Dictionary();
+
+ // 클라이언트 연결 이벤트 처리
+ mqttServer.ClientConnectedAsync += e =>
+ {
+ Logger.Debug($"클라이언트 연결됨: {e.ClientId}");
+ // 클라이언트 연결 시 기본 시작 시간 설정 (00:00)
+ clientPlaybackTimes[e.ClientId] = TimeSpan.Zero; // 00:00
+ clientLastUpdates[e.ClientId] = DateTime.Now;
+ Logger.Debug($"클라이언트 {e.ClientId}의 기본 시작 시간 설정: 00:00");
+ return Task.CompletedTask;
+ };
+
+ // 클라이언트 메시지 수신 이벤트 처리
+ mqttServer.InterceptingPublishAsync += async e =>
+ {
+ // 클라이언트가 "startTime" 토픽으로 시작 시간을 지정할 경우
+ if (e.ApplicationMessage.Topic == startTimeTopic)
+ {
+ var startTimePayload = System.Text.Encoding.UTF8.GetString(e.ApplicationMessage.Payload.ToArray());
+
+ // 시간 형식 검증 (00:00~59:59)
+ if (TimeSpan.TryParseExact(startTimePayload, @"mm\:ss", null, out TimeSpan startTimeSpan))
+ {
+ clientPlaybackTimes[e.ClientId] = startTimeSpan;
+ clientLastUpdates[e.ClientId] = DateTime.Now;
+ Logger.Debug($"클라이언트 {e.ClientId}의 시작 시간 설정: {startTimePayload}");
+ }
+ else
+ {
+ Logger.Debug($"클라이언트 {e.ClientId}가 잘못된 시간 형식 전송: {startTimePayload}");
+ }
+ }
+ };
+
+ // 클라이언트 연결 해제 이벤트 처리
+ mqttServer.ClientDisconnectedAsync += e =>
+ {
+ clientPlaybackTimes.Remove(e.ClientId);
+ clientLastUpdates.Remove(e.ClientId);
+ Logger.Debug($"클라이언트 연결 해제: {e.ClientId}");
+ return Task.CompletedTask;
+ };
+
+
+
+ // 1초마다 데이터 발행을 위한 타이머 설정
+ timer = new System.Timers.Timer(1000);
+ timer.Elapsed += async (sender, e) =>
+ {
+ try
+ {
+ // 각 클라이언트에 대해 현재 재생 시간에 맞는 데이터 전송
+ foreach (var clientEntry in new Dictionary(clientPlaybackTimes))
+ {
+ string clientId = clientEntry.Key;
+
+ // 마지막 업데이트 시간과 현재 시간의 차이 계산 (초 단위)
+ DateTime now = DateTime.Now;
+ TimeSpan elapsedSinceUpdate = now - clientLastUpdates[clientId];
+
+ // 1초 이상 지났으면 클라이언트 재생 시간 업데이트
+ if (elapsedSinceUpdate.TotalSeconds >= 1)
+ {
+ // 재생 시간 업데이트 (1초 증가)
+ clientPlaybackTimes[clientId] += TimeSpan.FromSeconds(1);
+ clientLastUpdates[clientId] = now;
+
+ // 59:59를 넘어가면 59:59로 고정
+ if (clientPlaybackTimes[clientId] > TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59)))
+ {
+ clientPlaybackTimes[clientId] = TimeSpan.FromMinutes(59).Add(TimeSpan.FromSeconds(59));
+ }
+
+ // 현재 재생 시간으로 mm:ss 형식 문자열 생성
+ string currentPlaybackTime = clientPlaybackTimes[clientId].ToString(@"mm\:ss");
+
+ // SQLiteService를 통해 데이터 가져오기 (현재 재생 시간의 데이터, 1초 분량)
+ List data = await sqliteService.SelectBySecond(currentPlaybackTime, 1);
+
+ if (data != null && data.Count > 0)
+ {
+ //json 변환
+ JObject? jsonObject = JsonConvert.DeserializeObject(data[0].data);
+ if (jsonObject != null)
+ {
+ string topicString = "";
+ //Dictionary의 모든 키와 값을 키를 topic 값을 payload로 발송
+ foreach (var property in jsonObject.Properties())
+ {
+ topicString += property.Name + ", ";
+ // 속성 값을 문자열로 직렬화
+ string propertyValue = property.Value.ToString(Formatting.None);
+ // 클라이언트별 MQTT 메시지 생성
+ var message = new MqttApplicationMessageBuilder()
+ .WithTopic(property.Name)
+ .WithPayload(propertyValue)
+ .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
+ .WithRetainFlag(false)
+ .Build();
+
+ // 메시지 발송
+ await mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message));
+ }
+ Logger.Debug($"클라이언트 {clientId}에 MQTT 메시지 발송: 1개 항목, 토픽: {topicString} 재생시간: {currentPlaybackTime}");
+ }
+
+ var messageAll = new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(data[0].data)
+ .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
+ .WithRetainFlag(false)
+ .Build();
+ Logger.Debug($"클라이언트 {clientId}에 MQTT 메시지 발송: 1개 항목, 토픽: {topic} 재생시간: {currentPlaybackTime}");
+
+ // 메시지 발송
+ await mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(messageAll));
+ }
+ else
+ {
+ Logger.Debug($"클라이언트 {clientId}의 재생시간 {currentPlaybackTime}에 해당하는 데이터가 없습니다.");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Debug($"MQTT 메시지 발송 중 오류 발생: {ex.Message}");
+ }
+ };
+
+
+ }
+
+ public async Task Start()
+ {
+ if (mqttServer != null)
+ {
+ await mqttServer.StartAsync();
+ timer.Start();
+ Logger.Debug($"MQTT 서버가 시작되었습니다.");
+ }
+ else
+ {
+ Logger.Debug("MQTT 서버가 초기화되지 않았습니다.");
+ }
+ }
+
+ public async Task Stop()
+ {
+ timer?.Stop();
+ if (mqttServer != null)
+ {
+ await mqttServer.StopAsync();
+ }
+ Logger.Debug("MQTT 서버가 중지되었습니다.");
+ }
+
+ }
+}
diff --git a/XRServer/MainWindow.xaml b/XRServer/MainWindow.xaml
new file mode 100644
index 0000000..800e7a5
--- /dev/null
+++ b/XRServer/MainWindow.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ http://localhost:8888/baseinfo/00:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mqtt://localhost:1883
+
+
+
+
+
+
+
diff --git a/XRServer/MainWindow.xaml.cs b/XRServer/MainWindow.xaml.cs
new file mode 100644
index 0000000..01c3d97
--- /dev/null
+++ b/XRServer/MainWindow.xaml.cs
@@ -0,0 +1,322 @@
+using MQTTnet;
+using MQTTnet.Protocol;
+using MQTTnet.Server;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text.Json;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Shapes;
+using uhttpsharp;
+using uhttpsharp.Handlers;
+using uhttpsharp.Listeners;
+using uhttpsharp.RequestProviders;
+using utils;
+
+namespace XRServer
+{
+ ///
+ ///
+ ///
+ public partial class MainWindow : Window
+ {
+
+ private bool isRunning = false;
+ private SQLiteService sqliteService = new SQLiteService();
+ // HTTP 서버 설정
+ private HttpServer httpServer = new HttpServer(new HttpRequestProvider());
+ private int httpPort = 8888; // HTTP 서버 포트
+
+ private MQTTService mqttService;
+
+ private INIFile ini;
+
+ private int mqttPort = 1883; // MQTT 서버 포트
+
+ private string sqliteDbPath = string.Empty; // SQLite 데이터베이스 경로
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ LoadIniFile();
+ InitHttpServer();
+ mqttService = new MQTTService(sqliteService);
+ mqttService.Init();
+
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ /*httpLinkTxtBlock.ContentEnd.InsertLineBreak();
+ httpLinkTxtBlock.ContentEnd.InsertLineBreak();
+ httpLinkTxtBlock.ContentEnd.InsertTextInRun($"1");
+ httpLinkTxtBlock.ContentEnd.InsertLineBreak();
+ httpLinkTxtBlock.ContentEnd.InsertTextInRun($"2");
+ httpLinkTxtBlock.ContentEnd.InsertLineBreak();
+ httpLinkTxtBlock.ContentEnd.InsertTextInRun($"3");*/
+ }
+
+ private void LoadIniFile()
+ {
+ // INI 파일 경로 설정
+ String iniFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, System.Diagnostics.Process.GetCurrentProcess().ProcessName + @".ini");
+ FileInfo f = new FileInfo(iniFilePath);
+ if (!f.Exists)
+ {
+ iniFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Application.Current.MainWindow.GetType().Assembly.GetName().Name + @".ini");
+ }
+ ini = new INIFile(iniFilePath);
+ // INI 파일에서 설정 값 읽기
+ sqliteDbPath = ini.GetString("CONFIG", "DB_PATH").Trim();
+ dbPathTxt.Text = sqliteDbPath;
+
+ if(dbPathTxt.Text == string.Empty || !File.Exists(dbPathTxt.Text))
+ {
+ startBtn.IsEnabled = false;
+ }
+ }
+
+
+
+
+ private void startBtn_Click(object sender, RoutedEventArgs e)
+ {
+ StartStop();
+ }
+
+ private async void StartStop()
+ {
+ if(sqliteDbPath == string.Empty || !File.Exists(sqliteDbPath))
+ {
+ Logger.Debug("SQLite DB 경로가 설정되지 않았습니다. DB File 경로를 확인하세요.");
+ MessageBox.Show("SQLite DB 경로가 설정되지 않았습니다. DB File 경로를 확인하세요.", "오류", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+
+ if (!sqliteService.Connected)
+ {
+ sqliteService.Connect(sqliteDbPath);
+ AppendHttpPathLinkAsync();
+ AppendMQTTTopicLinkAsync();
+ }
+
+ if (!isRunning)
+ {
+ httpPort = int.Parse(httpPortTxt.Text);
+ mqttPort = int.Parse(mqttPortTxt.Text);
+ Logger.Debug($"Start Server httpPort:{httpPort} mqttPort:{mqttPort}");
+ httpServer.Start();
+
+ // MQTT 서버 시작
+ mqttService.SetConfig(mqttPort, mqttTopicTxt.Text, mqttTopicTxt2.Text);
+ await mqttService.Start();
+
+ startBtn.Content = "Stop";
+ httpPortTxt.IsEnabled = false;
+ mqttPortTxt.IsEnabled = false;
+ httpLink.IsEnabled = true;
+ mqttLink.IsEnabled = true;
+ mqttTopicTxt.IsEnabled = false;
+ mqttTopicTxt2.IsEnabled = false;
+ }
+ else
+ {
+ Logger.Debug("Stop Server");
+ httpServer.Dispose();
+ // MQTT 서버 정지
+
+ await mqttService.Stop();
+ Logger.Debug($"MQTT 서버가 포트 {mqttPort}에서 정지되었습니다.");
+ startBtn.Content = "Start";
+ httpPortTxt.IsEnabled = true;
+ mqttPortTxt.IsEnabled = true;
+ httpLink.IsEnabled = false;
+ mqttLink.IsEnabled = false;
+ mqttTopicTxt.IsEnabled = true;
+ mqttTopicTxt2.IsEnabled = true;
+ }
+
+ isRunning = !isRunning;
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ if (isRunning)
+ {
+ Logger.Debug("Stopping server on window close.");
+ httpServer.Dispose();
+ mqttService.Stop().Wait();
+ }
+ sqliteService.CloseDB();
+ }
+
+
+ private void InitHttpServer()
+ {
+ httpServer.Use(new TcpListenerAdapter(new TcpListener(IPAddress.Loopback, httpPort)));
+ httpServer.Use(new BaseInfoHandler(sqliteService));
+ }
+
+ private void dbFileLoadBtn_Click(object sender, RoutedEventArgs e)
+ {
+ // SQLite 데이터베이스 파일 선택 대화상자 열기
+ var dialog = new Microsoft.Win32.OpenFileDialog();
+ dialog.FileName = "SQLite"; // Default file name
+ dialog.DefaultExt = ".txt"; // Default file extension
+ dialog.Filter = "SQLite file (.sqlite)|*.sqlite|(.db)|*.db"; // Filter files by extension
+
+ // Show open file dialog box
+ bool? result = dialog.ShowDialog();
+
+ // Process open file dialog box results
+ if (result == true)
+ {
+ // Open document
+ sqliteDbPath = dialog.FileName.Trim();
+ dbPathTxt.Text = sqliteDbPath;
+ ini.SetString("CONFIG", "DB_PATH", sqliteDbPath);
+ startBtn.IsEnabled = dbPathTxt.Text != string.Empty && File.Exists(dbPathTxt.Text);
+ }
+ }
+
+ private void httpLink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ {
+ // 기본 브라우저로 HTTP 링크 열기
+ var psi = new ProcessStartInfo(e.Uri.AbsoluteUri)
+ {
+ UseShellExecute = true
+ };
+ Process.Start(psi);
+ e.Handled = true;
+ }
+
+ private void httpPortTxt_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
+ {
+ if (httpPortTxt.Text == string.Empty || httpLink == null) return;
+ string url = $"http://localhost:{httpPortTxt.Text}/baseinfo/00:00";
+ try
+ {
+ httpLink.NavigateUri = new Uri(url);
+ httpLink.Inlines.Clear();
+ httpLink.Inlines.Add(url);
+ }
+ catch (UriFormatException ex)
+ {
+ Logger.Debug($"Invalid URL format: {ex.Message}");
+ httpLink.NavigateUri = null;
+ httpLink.Inlines.Clear();
+ httpLink.Inlines.Add("Invalid URL format");
+ }
+ }
+
+ private void mqttLink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ {
+ // 기본 브라우저로 HTTP 링크 열기
+ var psi = new ProcessStartInfo(e.Uri.AbsoluteUri)
+ {
+ UseShellExecute = true
+ };
+ Process.Start(psi);
+ e.Handled = true;
+ }
+
+ private void mqttPortTxt_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
+ {
+ if (mqttPortTxt.Text == string.Empty || mqttLink == null) return;
+ string url = $"mqtt://localhost:{mqttPortTxt.Text}";
+ try
+ {
+ mqttLink.NavigateUri = new Uri(url);
+ mqttLink.Inlines.Clear();
+ mqttLink.Inlines.Add(url);
+ }
+ catch (UriFormatException ex)
+ {
+ Logger.Debug($"Invalid URL format: {ex.Message}");
+ mqttLink.NavigateUri = null;
+ mqttLink.Inlines.Clear();
+ mqttLink.Inlines.Add("Invalid URL format");
+ }
+ }
+
+ ///
+ /// sqlite에서 baseinfo 데이터를 조회하여 json 첫번째 항목을 확인해서 httpLinkTxtBlock에 추가 표시
+ ///
+ ///
+ private async Task AppendHttpPathLinkAsync()
+ {
+ // 데이터 조회
+ List data = await sqliteService.SelectBySecondBaseInfo("00:00");
+ // 데이터가 null이 아니고, 데이터가 있는지 확인
+ if (data != null && data.Count > 0)
+ {
+ try
+ {
+ // Newtonsoft.Json을 사용하여 1-depth 키만 추출
+ using (StringReader stringReader = new StringReader(data[0].data))
+ using (JsonTextReader reader = new JsonTextReader(stringReader))
+ {
+ JObject jsonObject = JObject.Load(reader);
+ foreach (var property in jsonObject.Properties())
+ {
+ httpPara.Inlines.Add(new LineBreak());
+ string linkString = $"http://localhost:{httpPort}/{property.Name}/00:00";
+ Hyperlink hyperLink = new Hyperlink()
+ {
+ NavigateUri = new Uri(linkString)
+ };
+ hyperLink.Inlines.Add(linkString);
+ hyperLink.RequestNavigate += httpLink_RequestNavigate;
+ httpPara.Inlines.Add(hyperLink);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Debug($"JSON 파싱 오류: {ex.Message}");
+ }
+ }
+ }
+
+ private async Task AppendMQTTTopicLinkAsync()
+ {
+ // 데이터 조회
+ List data = await sqliteService.SelectBySecond("00:00", 1);
+ // 데이터가 null이 아니고, 데이터가 있는지 확인
+ if (data != null && data.Count > 0)
+ {
+ try
+ {
+ // Newtonsoft.Json을 사용하여 1-depth 키만 추출
+ using (StringReader stringReader = new StringReader(data[0].data))
+ using (JsonTextReader reader = new JsonTextReader(stringReader))
+ {
+ JObject jsonObject = JObject.Load(reader);
+ mqttPara.Inlines.Add(new LineBreak());
+ mqttPara.Inlines.Add(new Run($"제공하는 Topic명 ------"));
+ foreach (var property in jsonObject.Properties())
+ {
+ mqttPara.Inlines.Add(new LineBreak());
+ mqttPara.Inlines.Add(new Run(property.Name));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Debug($"JSON 파싱 오류: {ex.Message}");
+ }
+ }
+ }
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/XRServer/SQLiteService.cs b/XRServer/SQLiteService.cs
new file mode 100644
index 0000000..0ef0422
--- /dev/null
+++ b/XRServer/SQLiteService.cs
@@ -0,0 +1,191 @@
+using Microsoft.VisualBasic;
+using SQLite;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using utils;
+
+namespace XRServer
+{
+ public class SQLiteService
+ {
+ //#region Singleton
+ //private static readonly SQLiteService instance = new SQLiteService();
+ //public static SQLiteService Instance => instance;
+ //static SQLiteService() { }
+ //#endregion
+
+
+ private SQLiteConnection? dbConnection;
+
+ public bool Connected { get => dbConnection != null; }
+
+ public void Connect(string dbFilePath)
+ {
+ var _dbPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbFilePath);
+ var str = new SQLiteConnectionString(_dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex, false);
+ dbConnection = new SQLiteConnection(str);
+ }
+
+ public void CloseDB()
+ {
+ dbConnection?.Close();
+ dbConnection = null;
+ }
+
+ ///
+ /// 추가하기
+ ///
+ ///
+ /// yyyy-MM-ddTHH:mm:ss.fffZ format string
+ ///
+ /// 데이터베이스에서 추가된 행 수
+ public int Insert(string data, string timeStamp, string temp = null)
+ {
+ var query = $"INSERT INTO realTime (data, timestamp, temp) VALUES ('{data}', '{timeStamp}', " + (temp == null ? "null" : "'" + temp + "'") + ");";
+ int changedRowLen = dbConnection!.Execute(query);
+ return changedRowLen;
+ }
+
+ private string yyyyMMddHH = "";
+
+ readonly string[] queryParts =
+ {
+ "SELECT * FROM realTime WHERE ",
+ "timestamp >= '",
+ "' AND timestamp < '",
+ "timestamp <= '",
+ "' AND timestamp > '",
+ " ORDER BY timestamp ",
+ " LIMIT ",
+ };
+
+ ///
+ /// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
+ /// second > 0 : selectTime <= data < selectTime + second
+ /// second < 0 : selectTime + second < data <= selectTime
+ ///
+ /// mm:ss format string
+ ///
+ /// true: 오래된 시간이 먼저, false: 최근 시간이 먼저
+ ///
+ ///
+ public async Task> SelectBySecond(string selectTime, int second, bool orderAsc = true, int limit = 0)
+ {
+ if (yyyyMMddHH.Length == 0)
+ {
+ List one = await Task.Run(() =>
+ {
+ return dbConnection!.Query("SELECT * FROM realTime LIMIT 1");
+ });
+ if (one.Count > 0)
+ {
+ yyyyMMddHH = one[0].timestamp.Substring(0, 14);
+ }
+ }
+ selectTime = yyyyMMddHH + selectTime + ".000Z";
+
+ StringBuilder queryBuilder = new();
+ DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
+ string targetTime = DateTimeUtil.FormatTime(date);
+ //Debug.Log($"SelectBySecondBaseInfo {selectTime} {second} {targetTime} {date}");
+ queryBuilder.Append(queryParts[0]);
+ //second가 selectTime 보다 더 미래면
+ if (second > 0)
+ {
+ queryBuilder.Append($"{queryParts[1]}{selectTime}{queryParts[2]}{targetTime}'");
+ }
+ else
+ {
+ //second가 selectTime 보다 더 과거면
+ queryBuilder.Append($"{queryParts[3]}{selectTime}{queryParts[4]}{targetTime}'");
+ }
+ queryBuilder.Append($"{queryParts[5]}{(orderAsc ? "asc" : "desc")}");
+ if (limit > 0)
+ queryBuilder.Append($"{queryParts[6]}{limit}");
+
+ queryBuilder.Append(";");
+ //Debug.Log($"SelectBySecond {query}");
+ List result = await Task.Run(() =>
+ {
+ var query = queryBuilder.ToString();
+ queryBuilder.Clear();
+ return dbConnection!.Query(query);
+ });
+ return result;
+ }
+
+
+ private string yyyyMMddHHBaseInfo = "";
+
+ ///
+ /// selectTime보다 +- second 사이의 데이터 요청. selectTime, second 포함
+ /// second > 0 : selectTime <= data < selectTime + second
+ /// second < 0 : selectTime + second < data <= selectTime
+ ///
+ /// mm:ss format string
+ ///
+ /// true: 오래된 시간이 먼저, false: 최근 시간이 먼저
+ ///
+ ///
+ public async Task> SelectBySecondBaseInfo(string selectTime, int second = 59, bool orderAsc = false, int limit = 1)
+ {
+ if(yyyyMMddHHBaseInfo.Length == 0)
+ {
+ List one = await Task.Run(() =>
+ {
+ return dbConnection.Query("SELECT * FROM baseInfo LIMIT 1;");
+ });
+ if(one.Count > 0)
+ {
+ yyyyMMddHHBaseInfo = one[0].timestamp.Substring(0, 14);
+ }
+ }
+ Logger.Debug($"yyyyMMddHHBaseInfo1: {yyyyMMddHHBaseInfo}");
+ selectTime = yyyyMMddHHBaseInfo + selectTime + ".000Z";
+ Logger.Debug($"yyyyMMddHHBaseInfo2: {yyyyMMddHHBaseInfo}");
+ StringBuilder queryBuilder = new();
+ DateTime date = DateTimeUtil.UtcParse(selectTime).AddSeconds(second);
+ string targetTime = DateTimeUtil.FormatTime(date);
+ queryBuilder.Append($"SELECT * FROM baseInfo WHERE ");
+ //second가 selectTime 보다 더 미래면
+ if (second > 0)
+ {
+ queryBuilder.Append($"timestamp >= '{selectTime}' AND timestamp < '{targetTime}'");
+ }
+ else
+ {
+ //second가 selectTime 보다 더 과거면
+ queryBuilder.Append($"timestamp <= '{selectTime}' AND timestamp > '{targetTime}'");
+ }
+ queryBuilder.Append($" ORDER BY timestamp {(orderAsc ? "asc" : "desc")}");
+ if (limit > 0)
+ queryBuilder.Append($" LIMIT {limit}");
+ queryBuilder.Append(";");
+
+ List result = await Task.Run(() =>
+ {
+ var query = queryBuilder.ToString();
+ Logger.Debug($"SelectBySecondBaseInfo {query}");
+ queryBuilder.Clear();
+ return dbConnection.Query(query);
+ });
+
+ return result;
+ }
+ }
+
+ [System.Serializable]
+ public class SQLiteDataEntity
+ {
+ public string data { get; set; }
+ [PrimaryKey]
+ public string timestamp { get; set; }
+ public DateTime timestampHungary { get => DateTimeUtil.UtcStringToHungaryDateTime(timestamp); }
+ public string temp { get; set; }
+ }
+}
diff --git a/XRServer/XRServer.csproj b/XRServer/XRServer.csproj
new file mode 100644
index 0000000..7ed5df9
--- /dev/null
+++ b/XRServer/XRServer.csproj
@@ -0,0 +1,32 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+ AnyCPU;x86
+ icon.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/XRServer/XRServer.ini b/XRServer/XRServer.ini
new file mode 100644
index 0000000..1d2a722
--- /dev/null
+++ b/XRServer/XRServer.ini
@@ -0,0 +1,2 @@
+[CONFIG]
+DB_PATH=
\ No newline at end of file
diff --git a/XRServer/icon.ico b/XRServer/icon.ico
new file mode 100644
index 0000000..b21b0c2
Binary files /dev/null and b/XRServer/icon.ico differ