프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

markus / ConvertService / ServiceBase / Markus.Service.Station / ServiceStation.cs @ 42d49521

이력 | 보기 | 이력해설 | 다운로드 (19.1 KB)

1 53c9637d taeseongkim
using log4net;
2 60723dc9 taeseongkim
using Markus.EntityModel;
3 53c9637d taeseongkim
using Markus.Service.Extensions;
4
using Markus.Service.Helper;
5
using Markus.Service.IWcfService;
6
using Markus.Service.WcfService;
7
using Salaros.Configuration;
8
using System;
9
using System.Collections.Generic;
10
using System.ComponentModel;
11
using System.Data;
12
using System.Diagnostics;
13
using System.Globalization;
14
using System.IO;
15
using System.Linq;
16
using System.Runtime.InteropServices;
17
using System.ServiceModel;
18 1ae729e4 taeseongkim
using System.ServiceModel.Channels;
19
using System.ServiceModel.Dispatcher;
20 53c9637d taeseongkim
using System.ServiceProcess;
21
using System.Text;
22 d91efe5c taeseongkim
using System.Threading;
23 53c9637d taeseongkim
using System.Threading.Tasks;
24
using System.Timers;
25
using static Markus.Service.Extensions.Encrypt;
26
27
namespace Markus.Service
28
{
29
    public partial class ServiceStation : ServiceBase
30
    {
31
        protected ILog logger = LogManager.GetLogger(typeof(ServiceStation));
32
        protected ServiceHost gWcfServiceHost;
33 60723dc9 taeseongkim
        private SERVICE_PROPERTIES ServiceProperty;
34 53c9637d taeseongkim
35 0157b158 taeseongkim
        private string ServiceID;
36
        private List<SubStationServiceItem> StationServiceList;
37
        private List<string> StationServiceIDList;
38 60723dc9 taeseongkim
        private Service.WcfClient.StationServiceTask.StationServiceClient StationClient;
39 06f13e11 taeseongkim
        private bool IsStation;
40 53c9637d taeseongkim
41
        private Uri gServiceHostAddress;
42
43
        private string MarkusDBConnectionString;
44
        private string DownloadTempFolder;
45 950e6b84 taeseongkim
        private string FontsFolder;
46 53c9637d taeseongkim
        private int MultiThreadMaxPages;
47
        private int MinFontSize;
48 2091a7e5 taeseongkim
        private int UseResolution;
49 53c9637d taeseongkim
        private bool CreateProcessWindow;
50 8feb21df taeseongkim
        private long ReleaseWorkMemory;
51 53c9637d taeseongkim
52
        private int SaveStatusInterval;
53
54 43e1d368 taeseongkim
        private bool IsReleaseItems;
55
        private readonly System.Timers.Timer timer= null;
56
        
57
        private readonly object locker = new object();
58 06f13e11 taeseongkim
59 53c9637d taeseongkim
        private List<string> RunProjectList = new List<string>();
60
61
        /// <summary>
62
        /// 프로세스 카운터 자주 람다식을 사용해서 list<int>로 함.
63
        /// </summary>
64
        private List<Int64> ProcessorAffinityList;
65
66
        private string configFileName;
67
68
        public ServiceStation()
69
        {
70
            InitializeComponent();
71 43e1d368 taeseongkim
72
            timer = new System.Timers.Timer(new TimeSpan(0, 0, 0,1).TotalMilliseconds);
73
            timer.Elapsed += timerWorkMethod;
74 53c9637d taeseongkim
        }
75
76 a53dfe45 taeseongkim
        /// <summary>
77
        /// Config 파일 
78
        /// </summary>
79 53c9637d taeseongkim
        public void GetApplicationConfig()
80
        {
81
            try
82
            {
83
                ConfigParser config = null;
84
85
                try
86
                {
87
                    configFileName = $"{typeof(ServiceStation).Name}.ini";
88
                    config = ConfigHelper.AppConfig(this.configFileName);
89
                }
90 150747cb taeseongkim
                catch (Exception ex)
91 53c9637d taeseongkim
                {
92 150747cb taeseongkim
                    throw new Exception("Config Read Error.", ex);
93 53c9637d taeseongkim
                }
94
95
                if (config != null)
96
                {
97 0157b158 taeseongkim
                    // CONVERT DATABASE 연결 문자열
98
                    MarkusDBConnectionString = AESEncrypter.Decrypt(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.MARKUS_CONNECTION_STRING));
99
100 06f13e11 taeseongkim
                    IsStation = System.Convert.ToBoolean(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.IS_STATAION, "false"));
101 0157b158 taeseongkim
102 06f13e11 taeseongkim
                    ServiceID = config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.SERVICE_ID, Guid.Empty.ToString());
103 0157b158 taeseongkim
104 06f13e11 taeseongkim
                    if (IsStation)
105 0157b158 taeseongkim
                    {
106 06f13e11 taeseongkim
                        var servicetList = config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.SERVICE_LIST, "");
107 0157b158 taeseongkim
108 06f13e11 taeseongkim
                        if (!servicetList.IsNullOrEmpty())
109
                        {
110
                            StationServiceIDList = servicetList.Split(',').ToList();
111
                        }
112
                    }
113
                    else
114
                    {
115
                        var address = config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.STATAION_ADDRESS,"");
116 0157b158 taeseongkim
117 06f13e11 taeseongkim
                        if (!address.IsNullOrEmpty())
118
                        {
119
                            BasicHttpBinding myBinding = new BasicHttpBinding();
120
                            EndpointAddress myEndpoint = new EndpointAddress(UriHelper.UriCreate(address));
121 60723dc9 taeseongkim
                            StationClient = new WcfClient.StationServiceTask.StationServiceClient(myBinding, myEndpoint);
122 06f13e11 taeseongkim
                        }
123
                    }
124 53c9637d taeseongkim
125
                    CreateProcessWindow = System.Convert.ToBoolean(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.CREATE_WINDOW, "false"));
126
127
                    // PDF 임시 다운로드 폴더
128 2091a7e5 taeseongkim
                    DownloadTempFolder = config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.DOWNLOAD_TEMP_FOLDER,"C:\\temp");
129 950e6b84 taeseongkim
                    FontsFolder = config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.FONTS_FOLDER, "C:\\MarkusFonts"); 
130 2bbea412 taeseongkim
                    // pThraed를 이용한 컨버팅 설정.
131
                    //최대 페이지가 MultiThreadMaxPages설정까지 pThread를 적용하여 컨버팅함.
132 53c9637d taeseongkim
                    MultiThreadMaxPages = System.Convert.ToInt16(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.MULTI_TRHEAD_MAX_PAGE, "500"));
133
134 2bbea412 taeseongkim
                    // 서비스 상태 Log 저장간격
135 53c9637d taeseongkim
                    SaveStatusInterval = System.Convert.ToInt16(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.SAVE_STATUS_INTERVAL, "5"));
136
137 2bbea412 taeseongkim
                    //PDF에 폰트가 최소 폰트 사이즈보다 작은 사이증의 폰트가 있으면 해상도를 높여서 컨버팅함.
138 53c9637d taeseongkim
                    MinFontSize = System.Convert.ToInt16(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.MIN_FONT_SIZE, "10"));
139
140 2bbea412 taeseongkim
                    // 사용자 설정의 해상도
141
                    // MinFontSize를 -1로 하고 UseResolution를 설정하면 해당 해상도로 컨버팅됨
142 2091a7e5 taeseongkim
                    UseResolution = System.Convert.ToInt16(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.USE_RESOLUTION, "0"));
143
144 2bbea412 taeseongkim
                    // 각 프로세스의 최대 메모리 사용량
145 0157b158 taeseongkim
                    var workingMemory = System.Convert.ToDouble(config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.RELEASE_WORK_MEMORY, "1.5"));
146 8feb21df taeseongkim
147
                    ReleaseWorkMemory = MathBytes.Bytes(workingMemory, DataSizeType.GB);
148
149 2bbea412 taeseongkim
                    // 서비스 사용 프로젝트
150 2091a7e5 taeseongkim
                    var projectList = config.GetValue(CONFIG_DEFINE.SERVICE, CONFIG_DEFINE.FITER_PROCECT,"");
151 53c9637d taeseongkim
152
                    if(!projectList.IsNullOrEmpty())
153
                    {
154
                        RunProjectList = projectList.Split(',').ToList();
155
                    }
156
157
                    // 서비스 ENDPOINT
158
                    // http://localhost/
159
                    var endpointName = config.GetValue(CONFIG_DEFINE.WCF_ENDPOINT, CONFIG_DEFINE.STATION_SERVICE_NAME);
160
                    var port = config.GetValue(CONFIG_DEFINE.WCF_ENDPOINT, CONFIG_DEFINE.STATION_PORT);
161
162
                    if (!string.IsNullOrWhiteSpace(endpointName) && port.IsNumber())
163
                    {
164
                        gServiceHostAddress = UriHelper.UriCreate($"http://localhost:{port}/{endpointName}");
165
                    }
166 60723dc9 taeseongkim
167 2bbea412 taeseongkim
                    // 각  ConvertService의 원격 관리를 위한 서비스 설정 로드
168 60723dc9 taeseongkim
                    using (Markus.Service.DataBase.ConvertDatabase db = new Markus.Service.DataBase.ConvertDatabase(MarkusDBConnectionString))
169
                    {
170
                        ServiceProperty = db.GetServiceProperties(ServiceID);
171
                    }
172 53c9637d taeseongkim
                }
173
            }
174
            catch (Exception ex)
175
            {
176
                throw new Exception("ApplicationConfig ", ex);
177
            }
178
        }
179
180
        protected override void OnStart(string[] args)
181
        {
182
            try
183
            {
184 43e1d368 taeseongkim
                StartService();
185 53c9637d taeseongkim
            }
186
            catch (Exception ex)
187
            {
188
                logger.Error("ServiceStation Start Error - ", ex);
189
            }
190
        }
191 d91efe5c taeseongkim
192 06f13e11 taeseongkim
        /// <summary>
193
        /// alivequeue와 process를 비교하여 정리하고
194
        /// 대기중인 아이템을 분배한다.
195
        /// </summary>
196
        private void ReleaseItems()
197
        {
198 0157b158 taeseongkim
            try
199 53c9637d taeseongkim
            {
200 06f13e11 taeseongkim
                IsReleaseItems = true;
201
202 d91efe5c taeseongkim
                System.Diagnostics.Debug.WriteLine("CleanUpAliveQueueItems start");
203 06f13e11 taeseongkim
                CleanUpAliveQueueItems();
204 d91efe5c taeseongkim
                System.Diagnostics.Debug.WriteLine("CleanUpAliveQueueItems end");
205 06f13e11 taeseongkim
206
                if (IsStation)
207
                {
208 60723dc9 taeseongkim
                    //if (IsDataBaseWaitingList(1))
209
                    //{
210 1ae729e4 taeseongkim
                    System.Diagnostics.Debug.WriteLine("ReleaseItems start");
211 43e1d368 taeseongkim
                    var task = ReflashSubServiceAsync();
212
                    
213
                    if(task.Wait(40000))
214 1ae729e4 taeseongkim
                    {
215 43e1d368 taeseongkim
                        if (task.Result)
216
                        {
217
                            if (StationServiceList.All(x => x.IsOnline))
218
                            {
219
                                CleanUpDataBaseItems();
220
                                setDataBaseWaitingList();
221
                            }
222
                            else
223
                            {
224
                                logger.Error("Service Any Offline");
225
                            }
226
                        }
227
                        else
228
                        {
229
                            logger.Error("ReleaseItems - ReflashSubServiceAsync result false");
230
                        }
231
232 1ae729e4 taeseongkim
                    }
233
234 43e1d368 taeseongkim
235 1ae729e4 taeseongkim
                    System.Diagnostics.Debug.WriteLine("ReleaseItems end");
236 60723dc9 taeseongkim
                    //}
237 06f13e11 taeseongkim
                }
238 53c9637d taeseongkim
            }
239 0157b158 taeseongkim
            catch (Exception ex)
240 53c9637d taeseongkim
            {
241 0157b158 taeseongkim
                logger.Error("get Wating Item error", ex);
242 53c9637d taeseongkim
            }
243
244 06f13e11 taeseongkim
            IsReleaseItems = false;
245 53c9637d taeseongkim
        }
246
247 a53dfe45 taeseongkim
        /// <summary>
248
        /// System.Diagnostics.Process의 ProcessorAffinity Core 선호도를 위한 초기화
249
        /// 설정된 MultiProcessCount에 대해서 프로세스의 코어의 선호도를 지정 한다.
250
        /// 코어의 선택은 비트로 이루어 진다.
251
        /// 8코어에서 1번 코어 00000001
252
        /// 8코어에서 1번 3번 코어 00000101
253
        /// 8코어에서 1번 3번 코어 00000101
254
        /// 8코어에서 1,2,3 선택   00000111
255
        /// https://dotnetgalactics.wordpress.com/2009/10/20/how-to-set-the-processor-affinity-programatically/
256
        /// </summary>
257 53c9637d taeseongkim
        private void ProcessorAffinityInit()
258
        {
259
            ProcessorAffinityList = new List<long>();
260
261
            int processCount = Environment.ProcessorCount;
262
            int AffinityScope = 1;
263
264 60723dc9 taeseongkim
            if (processCount > ServiceProperty.PROCESS_COUNT)
265 53c9637d taeseongkim
            {
266 60723dc9 taeseongkim
                AffinityScope = processCount / ServiceProperty.PROCESS_COUNT;
267 53c9637d taeseongkim
            }
268
269
            for (int i = 0; i < processCount - AffinityScope; i += AffinityScope)
270
            {
271
                var bits = new int[processCount];
272
273
                for (int j = i; j < i + AffinityScope; j++)
274
                {
275
                    bits[j] = 1;
276
                }
277
278
                var affinity = System.Convert.ToInt64(string.Join("", bits), 2);
279
280
                ProcessorAffinityList.Add(affinity);
281
            }
282
        }
283
284 43e1d368 taeseongkim
        public void StartService()
285 53c9637d taeseongkim
        {
286 2bbea412 taeseongkim
            /// 서비스 설정 로드
287 53c9637d taeseongkim
            try
288
            {
289
                this.GetApplicationConfig();
290
                logger.Info("Read Config");
291
292 60723dc9 taeseongkim
                if (ServiceProperty != null)
293
                {
294
                    ProcessorAffinityInit();
295
                }
296
                else
297
                {
298
                    throw new Exception("StartService Error. ServiceProperty is Null");
299
                }
300 53c9637d taeseongkim
301
            }
302
            catch (Exception e)
303
            {
304 60723dc9 taeseongkim
                throw new Exception("StartService Error. ", e);
305 53c9637d taeseongkim
            }
306 60723dc9 taeseongkim
307 2bbea412 taeseongkim
            /// pThread.dll을 리소스에서 Service 실행 디렉토리로 복사.
308 43e1d368 taeseongkim
            //try
309
            //{
310
            //    // MarkusPDF.dll에서 pThread lib 사용을 위해 Insatll
311
            //    var libPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "\\convert\\libpthread.dll");
312
            //    if (!File.Exists(libPath))
313
            //    {
314
            //        Markus.Library.Installer.Install();
315
            //        logger.Info("Markus.Library.Installer Install");
316
            //    }
317
            //    else
318
            //    {
319
            //        logger.Info("Markus.Library.Installer Exists.");
320
            //    }
321
322
            //}
323
            //catch (Exception e)
324
            //{
325
            //    throw new Exception("Stop Installer Error. ", e);
326
            //}
327 53c9637d taeseongkim
328 2bbea412 taeseongkim
            /// 컨버터 프로세스가 실행중인 경우 모두 중지 시킨다.
329 53c9637d taeseongkim
            try
330
            {
331 a53dfe45 taeseongkim
                // 기존에 실행 중이던 프로세스 종료
332 53c9637d taeseongkim
                Stopprocess();
333
                logger.Info("Stopprocess");
334
            }
335
            catch (Exception e)
336
            {
337
                throw new Exception("Stop Process Error. ", e);
338
            }
339
340 2bbea412 taeseongkim
            /// wcf 서비스 실행
341 53c9637d taeseongkim
            try
342
            {
343 a53dfe45 taeseongkim
                // WCF 실행
344 53c9637d taeseongkim
                StartWcfService();
345
346
                if (gWcfServiceHost.BaseAddresses?.Count() > 0)
347
                {
348
                    gServiceHostAddress = gWcfServiceHost.BaseAddresses.First();
349
                }
350
351 43e1d368 taeseongkim
                //  각 sub서비스에 컨버터 아이템을 보내기 위한 서비스 초기화
352 06f13e11 taeseongkim
                if (IsStation)
353 0157b158 taeseongkim
                {
354
                    SetServiceList(this.StationServiceIDList);
355
                }
356
357 53c9637d taeseongkim
                logger.Info($"StartWcfService {gServiceHostAddress}");
358
            }
359
            catch (Exception e)
360
            {
361
                throw new Exception("start Wcf Service Error. ", e);
362
            }
363
364 0157b158 taeseongkim
            //try
365
            //{
366
            //    // Status가 4이하인 Convert Item을 다시 Convert 함. 
367
            //    setDataBaseWaitingList();
368
            //    logger.Info("setDataBaseWaitingList");
369
            //}
370
            //catch (Exception e)
371
            //{
372
            //    throw new Exception("Database Waiting List Error . ", e);
373
            //}
374 53c9637d taeseongkim
375
            logger.Info("Start ServiceStation");
376 43e1d368 taeseongkim
            timer.Start();
377
        }
378 d91efe5c taeseongkim
379 43e1d368 taeseongkim
        private void timerWorkMethod(object sender, ElapsedEventArgs e)
380
        {
381
            if (timer.Enabled)
382
            {
383
                var _timer = (System.Timers.Timer)sender;
384
                _timer.Stop();
385 d91efe5c taeseongkim
386 43e1d368 taeseongkim
                if (!IsStop)
387
                {
388
                    StartConvert();
389
                    
390
                    _timer.Start();
391
                }
392
                else
393
                {
394
                    StopService();
395
                }
396
            }
397
            else
398
            {
399
                logger.Error("timer disable");
400
            }
401 53c9637d taeseongkim
        }
402
403 d91efe5c taeseongkim
        private bool IsStop = false;
404 43e1d368 taeseongkim
    
405 60723dc9 taeseongkim
        DateTime logTime;
406 1ae729e4 taeseongkim
        DateTime ReleaseTime;
407 60723dc9 taeseongkim
408 43e1d368 taeseongkim
409
        private bool StartConvert()
410 60723dc9 taeseongkim
        {
411 d91efe5c taeseongkim
            //stateTimer.Change(new TimeSpan(0, 0, 0), new TimeSpan(0, 0, 0, 0, -1));
412 60723dc9 taeseongkim
            try
413
            {
414 1ae729e4 taeseongkim
               
415 d91efe5c taeseongkim
                if ((DateTime.Now - ReleaseTime) >= new TimeSpan(0, 0,1))
416 60723dc9 taeseongkim
                {
417 1ae729e4 taeseongkim
                    if (!IsReleaseItems)
418
                    {
419 43e1d368 taeseongkim
                        if (System.Environment.UserInteractive)
420
                        {
421
                            Console.WriteLine("Release Items");
422
                        }
423
                        logger.Info("Release Items");
424 1ae729e4 taeseongkim
                        ReleaseItems();
425
                    }
426 d91efe5c taeseongkim
                    else
427
                    {
428 43e1d368 taeseongkim
                        if (System.Environment.UserInteractive)
429
                        {
430
                            Console.WriteLine("pass Release Items");
431
                        }
432 d91efe5c taeseongkim
                    }
433
434 1ae729e4 taeseongkim
                    ReleaseTime = DateTime.Now;
435 60723dc9 taeseongkim
                }
436 1ae729e4 taeseongkim
              
437 150747cb taeseongkim
                if ((DateTime.Now - logTime) >= new TimeSpan(0, 5,0))
438 60723dc9 taeseongkim
                {
439
                    logTime = DateTime.Now;
440
                    logger.Info("StationService Alive Check");
441
                }
442
            }
443
            catch (Exception ex)
444
            {
445
                logger.Error("Timer Error ", ex);
446
            }
447 43e1d368 taeseongkim
448
            return true;
449 60723dc9 taeseongkim
        }
450
451 0157b158 taeseongkim
        public void SetServiceList(List<string> serviceList)
452
        {
453
            StationServiceList = new List<SubStationServiceItem>();
454
455
            using (DataBase.ConvertDatabase database = new DataBase.ConvertDatabase(MarkusDBConnectionString))
456
            {
457
                foreach (var item in serviceList)
458
                {
459
                    try
460
                    {
461
                        var prop = database.GetServiceProperties(item);
462
463
                        if (prop != null)
464
                        {
465 1ae729e4 taeseongkim
                            BasicHttpBinding httpbinding = new BasicHttpBinding();
466
                            httpbinding.CloseTimeout = new TimeSpan(0, 10, 0);
467
                            httpbinding.ReceiveTimeout = new TimeSpan(0, 10, 0);
468
                            httpbinding.SendTimeout = new TimeSpan(0, 10, 0);
469
                            httpbinding.OpenTimeout = new TimeSpan(0, 10, 0);
470
471 0157b158 taeseongkim
                            EndpointAddress myEndpoint = new EndpointAddress(UriHelper.UriCreate(prop.SERVICE_ADDRESS));
472 1ae729e4 taeseongkim
                            var StationServiceClient = new WcfClient.StationServiceTask.StationServiceClient(httpbinding, myEndpoint);
473 0157b158 taeseongkim
                        
474
                       
475
                                //var items = StationServiceClient.AliveConvertList();
476
                            
477
                                StationServiceList.Add(new SubStationServiceItem
478
                                {
479
                                    Properties = prop,
480
                                    Service = StationServiceClient
481
                                });
482
                        }
483
484
                    }
485
                    catch (Exception ex)
486
                    {
487 06f13e11 taeseongkim
                        logger.Error($"Service Properties Error  ID : { item }",ex);
488 0157b158 taeseongkim
                    }
489
                }
490
            }
491
        }
492 53c9637d taeseongkim
493
        protected override void OnStop()
494
        {
495
            try
496
            {
497 43e1d368 taeseongkim
                IsStop = true;
498 53c9637d taeseongkim
            }
499
            catch (Exception e)
500
            {
501
                logger.Error("OnStop Error . ", e);
502
            }
503
        }
504
505 d91efe5c taeseongkim
        public void StopService()
506
        {
507
            StopWcfService();
508
            Stopprocess();
509
510
            logger.Info("ServiceStation Stop");
511
        }
512
513 53c9637d taeseongkim
        #region Sleep 방지
514
        //[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
515
        //static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
516
        //[FlagsAttribute]
517
        //public enum EXECUTION_STATE : uint
518
        //{
519
        //    ES_AWAYMODE_REQUIRED = 0x00000040,
520
        //    ES_CONTINUOUS = 0x80000000,
521
        //    ES_DISPLAY_REQUIRED = 0x00000002,
522
        //    ES_SYSTEM_REQUIRED = 0x00000001
523
        //    // Legacy flag, should not be used.
524
        //    // ES_USER_PRESENT = 0x00000004
525
        //}
526
        //public static void PreventScreenAndSleep()
527
        //{
528
        //    SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS |
529
        //                            EXECUTION_STATE.ES_SYSTEM_REQUIRED |
530
        //                            EXECUTION_STATE.ES_AWAYMODE_REQUIRED |
531
        //                            EXECUTION_STATE.ES_DISPLAY_REQUIRED);
532
        //} 
533
        #endregion
534
    }
535
}
클립보드 이미지 추가 (최대 크기: 500 MB)