--- src/main/java/com/munjaon/client/config/DataSourceConfig.java
+++ src/main/java/com/munjaon/client/config/DataSourceConfig.java
... | ... | @@ -11,7 +11,6 @@ |
| 11 | 11 |
import org.mybatis.spring.annotation.MapperScan; |
| 12 | 12 |
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; |
| 13 | 13 |
import org.springframework.beans.factory.annotation.Autowired; |
| 14 |
-import org.springframework.boot.jdbc.DataSourceBuilder; |
|
| 15 | 14 |
import org.springframework.context.ApplicationContext; |
| 16 | 15 |
import org.springframework.context.annotation.Bean; |
| 17 | 16 |
import org.springframework.context.annotation.Configuration; |
... | ... | @@ -27,7 +26,6 @@ |
| 27 | 26 |
@EnableTransactionManagement |
| 28 | 27 |
@MapperScan(basePackages= "com.munjaon.client.**.mapper") |
| 29 | 28 |
public class DataSourceConfig {
|
| 30 |
- //public class DataSourceConfig implements BeanDefinitionRegistryPostProcessor {
|
|
| 31 | 29 |
@Autowired |
| 32 | 30 |
private ApplicationContext applicationContext; |
| 33 | 31 |
private final ServerConfig serverConfig; |
... | ... | @@ -56,22 +54,6 @@ |
| 56 | 54 |
return new HikariDataSource(hikariConfig); |
| 57 | 55 |
} |
| 58 | 56 |
|
| 59 |
-// @Primary |
|
| 60 |
-// @Bean(name = "datasource") |
|
| 61 |
-// public DataSource dataSource() throws ConfigurationException {
|
|
| 62 |
-// String dbms = serverConfig.getString("DB.DBMS");
|
|
| 63 |
-// System.out.println("MARIADB.DRIVER : " + serverConfig.getString(dbms + ".DRIVER"));
|
|
| 64 |
-// System.out.println("MARIADB.URL : " + serverConfig.getString(dbms + ".URL"));
|
|
| 65 |
-// System.out.println("MARIADB.USER : " + serverConfig.getString(dbms + ".USER"));
|
|
| 66 |
-// System.out.println("MARIADB.PASSWORD : " + serverConfig.getString(dbms + ".PASSWORD"));
|
|
| 67 |
-// return DataSourceBuilder.create().type(HikariDataSource.class) |
|
| 68 |
-// .driverClassName(serverConfig.getString(dbms + ".DRIVER")) |
|
| 69 |
-// .url(serverConfig.getString(dbms + ".URL")) |
|
| 70 |
-// .username(serverConfig.getString(dbms + ".USER")) |
|
| 71 |
-// .password(serverConfig.getString(dbms + ".PASSWORD")) |
|
| 72 |
-// .build(); |
|
| 73 |
-// } |
|
| 74 |
- |
|
| 75 | 57 |
@Primary |
| 76 | 58 |
@Bean(name = "factory") |
| 77 | 59 |
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
|
... | ... | @@ -83,7 +65,6 @@ |
| 83 | 65 |
sqlSessionFactory.setTypeAliasesPackage("com.munjaon.client.**.dto");
|
| 84 | 66 |
sqlSessionFactory.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
|
| 85 | 67 |
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/sqlmap/**/*.xml"));
|
| 86 |
-// sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/sqlmap/" + dbms.toLowerCase() + "/*.xml"));
|
|
| 87 | 68 |
return sqlSessionFactory.getObject(); |
| 88 | 69 |
} |
| 89 | 70 |
|
... | ... | @@ -92,22 +73,4 @@ |
| 92 | 73 |
public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) {
|
| 93 | 74 |
return new SqlSessionTemplate(sqlSessionFactory); |
| 94 | 75 |
} |
| 95 |
- |
|
| 96 |
-// @Override |
|
| 97 |
-// public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
|
| 98 |
-// String dbms = null; |
|
| 99 |
-// try {
|
|
| 100 |
-// dbms = serverConfig.getString("DB.DBMS");
|
|
| 101 |
-// } catch (ConfigurationException e) {
|
|
| 102 |
-// throw new RuntimeException(e); |
|
| 103 |
-// } |
|
| 104 |
-// AbstractBeanDefinition mapperScannerConfigurer = BeanDefinitionBuilder |
|
| 105 |
-// .genericBeanDefinition(MapperScannerConfigurer.class) |
|
| 106 |
-// .addPropertyValue("sqlSessionFactoryBeanName", "factory")
|
|
| 107 |
-// .addPropertyValue("basePackage", "com.munjaon.client.**.dto")
|
|
| 108 |
-// .addPropertyValue("annotationClass", MapperType.value(dbms).getClass())
|
|
| 109 |
-// .getBeanDefinition(); |
|
| 110 |
-// |
|
| 111 |
-// registry.registerBeanDefinition("MapperScanner", mapperScannerConfigurer);
|
|
| 112 |
-// } |
|
| 113 | 76 |
} |
--- src/main/java/com/munjaon/client/config/RunnerConfiguration.java
+++ src/main/java/com/munjaon/client/config/RunnerConfiguration.java
... | ... | @@ -12,12 +12,21 @@ |
| 12 | 12 |
import org.springframework.context.annotation.Configuration; |
| 13 | 13 |
import org.springframework.core.annotation.Order; |
| 14 | 14 |
|
| 15 |
+/** |
|
| 16 |
+ * RunnerConfiguration |
|
| 17 |
+ * Agent Service 실행 |
|
| 18 |
+ */ |
|
| 15 | 19 |
@Slf4j |
| 16 | 20 |
@Configuration |
| 17 | 21 |
@RequiredArgsConstructor |
| 18 | 22 |
public class RunnerConfiguration {
|
| 19 | 23 |
private final ServerConfig serverConfig; |
| 20 | 24 |
|
| 25 |
+ /** |
|
| 26 |
+ * 설정파일 로드 |
|
| 27 |
+ * @return |
|
| 28 |
+ * @throws ConfigurationException |
|
| 29 |
+ */ |
|
| 21 | 30 |
@Bean |
| 22 | 31 |
@Order(1) |
| 23 | 32 |
public CommandLineRunner getRunnerBeanForProperty() throws ConfigurationException {
|
... | ... | @@ -26,9 +35,13 @@ |
| 26 | 35 |
System.setProperty("DBMS", serverConfig.getString("DB.DBMS"));
|
| 27 | 36 |
PropertyLoader.load(); |
| 28 | 37 |
|
| 29 |
- return args -> System.out.println("Runner Bean #1 : " + serverConfig.getServerProperyFile());
|
|
| 38 |
+ return args -> System.out.println("MunjaonAgent Config Property : " + serverConfig.getServerProperyFile());
|
|
| 30 | 39 |
} |
| 31 | 40 |
|
| 41 |
+ /** |
|
| 42 |
+ * SMS 서비스 실행 |
|
| 43 |
+ * @return |
|
| 44 |
+ */ |
|
| 32 | 45 |
@Bean |
| 33 | 46 |
@Order(2) |
| 34 | 47 |
public CommandLineRunner getRunnerBeanForSms() {
|
... | ... | @@ -41,9 +54,13 @@ |
| 41 | 54 |
throw new RuntimeException(e); |
| 42 | 55 |
} |
| 43 | 56 |
|
| 44 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 57 |
+ return args -> System.out.println("SMS Service Started");
|
|
| 45 | 58 |
} |
| 46 | 59 |
|
| 60 |
+ /** |
|
| 61 |
+ * LMS 서비스 실행 |
|
| 62 |
+ * @return |
|
| 63 |
+ */ |
|
| 47 | 64 |
@Bean |
| 48 | 65 |
@Order(2) |
| 49 | 66 |
public CommandLineRunner getRunnerBeanForLms() {
|
... | ... | @@ -56,9 +73,13 @@ |
| 56 | 73 |
throw new RuntimeException(e); |
| 57 | 74 |
} |
| 58 | 75 |
|
| 59 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 76 |
+ return args -> System.out.println("LMS Service Started");
|
|
| 60 | 77 |
} |
| 61 | 78 |
|
| 79 |
+ /** |
|
| 80 |
+ * MMS 서비스 실행 |
|
| 81 |
+ * @return |
|
| 82 |
+ */ |
|
| 62 | 83 |
@Bean |
| 63 | 84 |
@Order(2) |
| 64 | 85 |
public CommandLineRunner getRunnerBeanForMms() {
|
... | ... | @@ -71,9 +92,13 @@ |
| 71 | 92 |
throw new RuntimeException(e); |
| 72 | 93 |
} |
| 73 | 94 |
|
| 74 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 95 |
+ return args -> System.out.println("MMS Service Started");
|
|
| 75 | 96 |
} |
| 76 | 97 |
|
| 98 |
+ /** |
|
| 99 |
+ * 카카오 알림톡 서비스 실행 |
|
| 100 |
+ * @return |
|
| 101 |
+ */ |
|
| 77 | 102 |
@Bean |
| 78 | 103 |
@Order(2) |
| 79 | 104 |
public CommandLineRunner getRunnerBeanForKat() {
|
... | ... | @@ -86,9 +111,13 @@ |
| 86 | 111 |
throw new RuntimeException(e); |
| 87 | 112 |
} |
| 88 | 113 |
|
| 89 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 114 |
+ return args -> System.out.println("KAT Service Started");
|
|
| 90 | 115 |
} |
| 91 | 116 |
|
| 117 |
+ /** |
|
| 118 |
+ * 카카오 친구톡 서비스 실행 |
|
| 119 |
+ * @return |
|
| 120 |
+ */ |
|
| 92 | 121 |
@Bean |
| 93 | 122 |
@Order(2) |
| 94 | 123 |
public CommandLineRunner getRunnerBeanForKft() {
|
... | ... | @@ -101,9 +130,13 @@ |
| 101 | 130 |
throw new RuntimeException(e); |
| 102 | 131 |
} |
| 103 | 132 |
|
| 104 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 133 |
+ return args -> System.out.println("KFT Service Started");
|
|
| 105 | 134 |
} |
| 106 | 135 |
|
| 136 |
+ /** |
|
| 137 |
+ * 리포트 서비스 실행 |
|
| 138 |
+ * @return |
|
| 139 |
+ */ |
|
| 107 | 140 |
@Bean |
| 108 | 141 |
@Order(2) |
| 109 | 142 |
public CommandLineRunner getRunnerBeanForReport() {
|
... | ... | @@ -115,9 +148,13 @@ |
| 115 | 148 |
throw new RuntimeException(e); |
| 116 | 149 |
} |
| 117 | 150 |
|
| 118 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 151 |
+ return args -> System.out.println("REPORT Service Started");
|
|
| 119 | 152 |
} |
| 120 | 153 |
|
| 154 |
+ /** |
|
| 155 |
+ * 로그이동 서비스 실행 |
|
| 156 |
+ * @return |
|
| 157 |
+ */ |
|
| 121 | 158 |
@Bean |
| 122 | 159 |
@Order(3) |
| 123 | 160 |
public CommandLineRunner getRunnerBeanForMove() {
|
... | ... | @@ -129,6 +166,6 @@ |
| 129 | 166 |
throw new RuntimeException(e); |
| 130 | 167 |
} |
| 131 | 168 |
|
| 132 |
- return args -> System.out.println("Runner Bean #2");
|
|
| 169 |
+ return args -> System.out.println("LOG MOVE Service Started");
|
|
| 133 | 170 |
} |
| 134 | 171 |
} |
--- src/main/java/com/munjaon/client/config/ServerConfig.java
+++ src/main/java/com/munjaon/client/config/ServerConfig.java
... | ... | @@ -21,7 +21,6 @@ |
| 21 | 21 |
@Component |
| 22 | 22 |
public class ServerConfig {
|
| 23 | 23 |
@Getter |
| 24 |
-// @Value("${agent.server-property-file}")
|
|
| 25 | 24 |
private String serverProperyFile; |
| 26 | 25 |
|
| 27 | 26 |
@Getter |
... | ... | @@ -32,7 +31,6 @@ |
| 32 | 31 |
|
| 33 | 32 |
@PostConstruct |
| 34 | 33 |
void init() throws ConfigurationException {
|
| 35 |
-// this.serverRootPath = System.getProperty("SERVICE_HOME");
|
|
| 36 | 34 |
this.serverProperyFile = serverRootPath + File.separator + "config" + File.separator + "munjaonAgent.conf"; |
| 37 | 35 |
builder = new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class).configure(new Parameters().fileBased().setFile(new File(serverProperyFile))); |
| 38 | 36 |
|
--- src/main/java/com/munjaon/client/server/packet/Bind.java
+++ src/main/java/com/munjaon/client/server/packet/Bind.java
... | ... | @@ -2,7 +2,6 @@ |
| 2 | 2 |
|
| 3 | 3 |
import java.io.UnsupportedEncodingException; |
| 4 | 4 |
import java.nio.ByteBuffer; |
| 5 |
-import java.util.Arrays; |
|
| 6 | 5 |
|
| 7 | 6 |
public final class Bind {
|
| 8 | 7 |
public static final int BIND_BODY_LENGTH = 41; |
--- src/main/java/com/munjaon/client/server/service/CollectClientService.java
+++ src/main/java/com/munjaon/client/server/service/CollectClientService.java
... | ... | @@ -17,16 +17,19 @@ |
| 17 | 17 |
import java.util.List; |
| 18 | 18 |
|
| 19 | 19 |
public class CollectClientService extends Service {
|
| 20 |
+ /* 서비스 타입 |
|
| 21 |
+ * RunnerConfiguration에서 서비스 타입 지정 |
|
| 22 |
+ * SMS, LMS, MMS, KAT, KFT |
|
| 23 |
+ * */ |
|
| 20 | 24 |
private final String serviceType; |
| 21 |
- private DatabaseTypeWorker worker; |
|
| 22 |
- private SocketChannel socketChannel; |
|
| 23 |
- private long lastPacketSendTime = 0; |
|
| 24 |
- private String address; |
|
| 25 |
- private int port; |
|
| 26 |
- private String id; |
|
| 27 |
- private String pwd; |
|
| 25 |
+ private DatabaseTypeWorker worker; // 서비스 타입별 DAO 호출 |
|
| 26 |
+ private SocketChannel socketChannel; // 클라이언트 SocketChannel |
|
| 27 |
+ private long lastPacketSendTime = 0; // 마지막 패킷을 전송 및 응답 시간 |
|
| 28 |
+ private String address; // 서비스별 서버 주소 |
|
| 29 |
+ private int port; // 서비스별 포트 정보 |
|
| 30 |
+ private String id; // 접속 아이디 |
|
| 31 |
+ private String pwd; // 접속 비밀번호 |
|
| 28 | 32 |
|
| 29 |
- private StringBuilder deliverBuilder; |
|
| 30 | 33 |
private List<String> deliverList = null; |
| 31 | 34 |
|
| 32 | 35 |
public CollectClientService(String serviceName, String serviceType) {
|
... | ... | @@ -187,7 +190,6 @@ |
| 187 | 190 |
|
| 188 | 191 |
private void messageService() {
|
| 189 | 192 |
List<MunjaonMsg> list = selectToDeliver(); |
| 190 |
- this.deliverBuilder = new StringBuilder(); |
|
| 191 | 193 |
if (list == null || list.isEmpty()) {
|
| 192 | 194 |
try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}
|
| 193 | 195 |
return; |
--- src/main/java/com/munjaon/client/server/service/PropertyLoader.java
+++ src/main/java/com/munjaon/client/server/service/PropertyLoader.java
... | ... | @@ -33,7 +33,7 @@ |
| 33 | 33 |
value = value.replaceAll("\\$SERVICE_HOME", System.getProperty("SERVICE_HOME"));
|
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 |
- return value; |
|
| 36 |
+ return value == null ? null : value.trim(); |
|
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 | 39 |
public synchronized static Properties load() {
|
--- src/main/java/com/munjaon/client/server/service/Service.java
+++ src/main/java/com/munjaon/client/server/service/Service.java
... | ... | @@ -4,12 +4,10 @@ |
| 4 | 4 |
import com.munjaon.client.util.LogUtil; |
| 5 | 5 |
import org.json.simple.JSONObject; |
| 6 | 6 |
|
| 7 |
-import java.text.SimpleDateFormat; |
|
| 8 | 7 |
import java.time.LocalDateTime; |
| 9 | 8 |
import java.time.format.DateTimeFormatter; |
| 10 | 9 |
|
| 11 | 10 |
public abstract class Service extends Thread {
|
| 12 |
- public static final SimpleDateFormat sdf = new SimpleDateFormat("[MM-dd HH:mm:ss]");
|
|
| 13 | 11 |
public static final String LOG_DATE_FORMAT = "[MM-dd HH:mm:ss]"; |
| 14 | 12 |
|
| 15 | 13 |
private String LOG_FILE; |
... | ... | @@ -17,9 +15,9 @@ |
| 17 | 15 |
private Long LAST_PROPERTY_LOAD_TIME = 0L; |
| 18 | 16 |
|
| 19 | 17 |
protected boolean IS_SERVER_RUN; // 서버가 구동중인지 여부 |
| 20 |
- protected boolean IS_READY_YN; // 서비스 구동준비가 완료되었는지 체크 |
|
| 21 |
- protected boolean IS_RUN_YN; |
|
| 22 |
- protected boolean IS_STOP_YN; |
|
| 18 |
+ protected boolean IS_READY_YN; // 서비스 구동준비가 완료되었는지 체크 |
|
| 19 |
+ protected boolean IS_RUN_YN; // 서비스 실행여부 |
|
| 20 |
+ protected boolean IS_STOP_YN; // 서비스 Stop여부 |
|
| 23 | 21 |
|
| 24 | 22 |
public Service() {}
|
| 25 | 23 |
public Service(String serviceName) {
|
... | ... | @@ -27,22 +25,42 @@ |
| 27 | 25 |
LOG_FILE = System.getProperty("ROOTPATH") + getProp("LOG_FILE");
|
| 28 | 26 |
} |
| 29 | 27 |
|
| 28 |
+ /** |
|
| 29 |
+ * config property load |
|
| 30 |
+ * @param name |
|
| 31 |
+ * @return |
|
| 32 |
+ */ |
|
| 30 | 33 |
protected String getProp(String name) {
|
| 31 | 34 |
return getProp(getName(), name); |
| 32 | 35 |
} |
| 33 | 36 |
|
| 37 |
+ /** |
|
| 38 |
+ * config property load |
|
| 39 |
+ * @param svc |
|
| 40 |
+ * @param name |
|
| 41 |
+ * @return |
|
| 42 |
+ */ |
|
| 34 | 43 |
public static String getProp(String svc, String name) {
|
| 35 | 44 |
return PropertyLoader.getProp(svc, name); |
| 36 | 45 |
} |
| 37 | 46 |
|
| 47 |
+ /** |
|
| 48 |
+ * 서비스 실행 여부 config property 조회 |
|
| 49 |
+ */ |
|
| 38 | 50 |
protected void checkRun() {
|
| 39 | 51 |
this.IS_RUN_YN = getProp("RUN_FLAG") != null && "Y".equals(getProp("RUN_FLAG"));
|
| 40 | 52 |
} |
| 41 | 53 |
|
| 54 |
+ /** |
|
| 55 |
+ * 클라이언트 실행여부 config property 조회 |
|
| 56 |
+ */ |
|
| 42 | 57 |
protected void checkClientRun() {
|
| 43 | 58 |
this.IS_SERVER_RUN = getProp("client", "run") != null && "Y".equals(getProp("client", "run"));
|
| 44 | 59 |
} |
| 45 | 60 |
|
| 61 |
+ /** |
|
| 62 |
+ * 서비스 및 클라이언트 실행여부 조회 |
|
| 63 |
+ */ |
|
| 46 | 64 |
public void reloadCheckRun() {
|
| 47 | 65 |
if ((System.currentTimeMillis() - this.LAST_PROPERTY_LOAD_TIME) > ServerConfig.INTERVAL_PROPERTY_RELOAD_TIME) {
|
| 48 | 66 |
checkRun(); |
... | ... | @@ -51,14 +69,26 @@ |
| 51 | 69 |
} |
| 52 | 70 |
} |
| 53 | 71 |
|
| 72 |
+ /** |
|
| 73 |
+ * 서비스 실행 조건 조회 |
|
| 74 |
+ * @return |
|
| 75 |
+ */ |
|
| 54 | 76 |
public boolean isRun() {
|
| 55 | 77 |
return IS_SERVER_RUN && IS_RUN_YN && !IS_STOP_YN; |
| 56 | 78 |
} |
| 57 | 79 |
|
| 80 |
+ /** |
|
| 81 |
+ * 서비스가 실행준비가 되었는지 조회 |
|
| 82 |
+ * @return |
|
| 83 |
+ */ |
|
| 58 | 84 |
public boolean isReady() {
|
| 59 | 85 |
return IS_READY_YN; |
| 60 | 86 |
} |
| 61 | 87 |
|
| 88 |
+ /** |
|
| 89 |
+ * 서비스별 로그파일 초기화 |
|
| 90 |
+ * @param sLogFile |
|
| 91 |
+ */ |
|
| 62 | 92 |
protected void setLogFile(String sLogFile) {
|
| 63 | 93 |
if ( logger != null ) {
|
| 64 | 94 |
logger.close(); |
... | ... | @@ -68,14 +98,27 @@ |
| 68 | 98 |
logger = new LogUtil( sLogFile ); |
| 69 | 99 |
} |
| 70 | 100 |
|
| 101 |
+ /** |
|
| 102 |
+ * 실행로그 화면 출력 및 저장 |
|
| 103 |
+ * @param obj |
|
| 104 |
+ */ |
|
| 71 | 105 |
protected void saveSystemLog(Object obj) {
|
| 72 | 106 |
saveLog(obj, true); |
| 73 | 107 |
} |
| 74 | 108 |
|
| 109 |
+ /** |
|
| 110 |
+ * 실행로그 저장 |
|
| 111 |
+ * @param obj |
|
| 112 |
+ */ |
|
| 75 | 113 |
protected void saveLog(Object obj) {
|
| 76 | 114 |
saveLog(obj, false); |
| 77 | 115 |
} |
| 78 | 116 |
|
| 117 |
+ /** |
|
| 118 |
+ * 실행로그 저장 |
|
| 119 |
+ * @param obj |
|
| 120 |
+ * @param isConsoleOutput |
|
| 121 |
+ */ |
|
| 79 | 122 |
protected void saveLog(Object obj, boolean isConsoleOutput) {
|
| 80 | 123 |
if(isConsoleOutput) {
|
| 81 | 124 |
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern(LOG_DATE_FORMAT)) + " {{"+ getName() +"}} "+obj);
|
... | ... | @@ -91,6 +134,9 @@ |
| 91 | 134 |
} |
| 92 | 135 |
} |
| 93 | 136 |
|
| 137 |
+ /** |
|
| 138 |
+ * 로그파일 초기화 |
|
| 139 |
+ */ |
|
| 94 | 140 |
protected void initLogFile() {
|
| 95 | 141 |
LOG_FILE = System.getProperty("ROOTPATH") + getProp("LOG_FILE");
|
| 96 | 142 |
System.out.println("LOG_FILE: " + LOG_FILE);
|
... | ... | @@ -98,6 +144,9 @@ |
| 98 | 144 |
saveSystemLog("Service Log Initializing ... ...");
|
| 99 | 145 |
} |
| 100 | 146 |
|
| 147 |
+ /** |
|
| 148 |
+ * 서비스 실행 |
|
| 149 |
+ */ |
|
| 101 | 150 |
@Override |
| 102 | 151 |
public void run() {
|
| 103 | 152 |
while (true) {
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?