From 0c66940a8e2cf64c9890519901f433b3668216b6 Mon Sep 17 00:00:00 2001
From: KEJ <kimeungju@diteam.co.kr>
Date: 수, 26 6월 2024 15:13:18 +0900
Subject: [PATCH] Ongoing80 #4942 CF AOI Review Bin File Loading 실패 개선  1. GlassRawMessenger 동기화  2. BIN파일 FindFile 제거(RawMessenger, Review 공통)  3. Unloading 시 BIN파일 제거

---
 DitGlassRawMessenger/DitGlassRawMessenger/GlassRawCPJT.cpp |  251 ++++++++++++++++++++++++++++++++-----------------
 1 files changed, 162 insertions(+), 89 deletions(-)

diff --git a/DitGlassRawMessenger/DitGlassRawMessenger/GlassRawCPJT.cpp b/DitGlassRawMessenger/DitGlassRawMessenger/GlassRawCPJT.cpp
index ef25ec9..089c488 100644
--- a/DitGlassRawMessenger/DitGlassRawMessenger/GlassRawCPJT.cpp
+++ b/DitGlassRawMessenger/DitGlassRawMessenger/GlassRawCPJT.cpp
@@ -19,10 +19,12 @@
 #define LOCAL_RAWPATH_INFO_INI_PATH	"D:\\ResultData\\"
 #define RAWINFO_FILE_NAME			"RESULT_INFO.ini"
 
+#define LOCAL_AOI_DEFECT_INFO_FILE_PATH		_T("D:\\DIT_ResultData\\DefectInfo") // 寃고븿 �긽�꽭 �젙蹂� �뙆�씪 �깮�꽦 寃쎈줈
 #define LOCAL_AOIRAWDFS_PATH		"D:\\DIT_ResultData\\Raw"
 #define LOCAL_AOIRAWBIN_PATH		"D:\\DIT_ResultData\\RawBin"
 #define NETWORK_AOIRAWDFS_PATH		"\\\\126.100.100.1\\d\\DIT_ResultData\\Raw"
 #define NETWORK_AOIRAWBIN_PATH		"\\\\126.100.100.1\\d\\DIT_ResultData\\RawBin"
+#define LOCAL_ReviewRAWBIN_PATH		"D:\\DIT_ResultData\\RawBin"
 
 #define NETWORK_AOIRAWFILE_SUB_PATH "\\\\126.100.100.1\\d\\Raw_im\\"
 #define NETWORK_AOI_IMAGE_PATH		"\\\\126.100.100.1\\d\\Image\\Defect\\" 
@@ -58,6 +60,30 @@
 		Sleep(10);
 	}
 }
+/* < KJG 20221110 - #4373 ADD Start >*/
+auto CellIDSortFunc = [](const _grmCellData* lhs, const _grmCellData* rhs) {
+	// '0A' = 0, '0B' = 1, ..., '0Z' = 25, '00' = 26, ..., '09' = 35, '1A' = 36, ...
+	const int nAlphaSet = 'Z' - 'A' + 1;
+	const int nNumSet = '9' - '0' + 1;
+	const int nAlphaNumSet = nAlphaSet + nNumSet;
+
+	CString str = lhs->m_strCellName;
+	char lhs_1st = str.Mid(str.GetLength() - 2, 1).GetAt(0);
+	char lhs_2st = str.Mid(str.GetLength() - 1, 1).GetAt(0);
+
+	str = rhs->m_strCellName;
+	char rhs_1st = str.Mid(str.GetLength() - 2, 1).GetAt(0);
+	char rhs_2st = str.Mid(str.GetLength() - 1, 1).GetAt(0);
+
+	auto GetCellValueFrom2stCellID = [&](char c) -> int {
+		if (c >= 'A' && c < 'Z') // 'A' �뒗 0遺��꽣
+			return c - 'A';
+		else if (c >= '0' && c <= '9') // '0' �� 26 遺��꽣
+			return (c - '0') + nAlphaSet;
+	};
+	return (lhs_1st - '0') * nAlphaNumSet + GetCellValueFrom2stCellID(lhs_2st) < (rhs_1st - '0') * nAlphaNumSet + GetCellValueFrom2stCellID(rhs_2st);
+};
+/* < KJG 20221110 - #4373 ADD End >*/
 
 CGlassRawCPJT::CGlassRawCPJT(void)
 {
@@ -65,6 +91,7 @@
 	CreateDirectory(LOCAL_AOIRAWDFS_PATH, NULL);
 	CreateDirectory(LOCAL_AOIRAWBIN_PATH, NULL);
 	CreateDirectory("D:\\Raw_im", NULL);
+	CreateDirectory(LOCAL_AOI_DEFECT_INFO_FILE_PATH, NULL); /* < KJG 20240620 - #4942 ADD >*/ 
 
 	/* <KMH 20220407 : #4053 ADD Start> */
 	CFileFind FileFinder;
@@ -101,9 +128,9 @@
 		
 		CString strStackFileLocalPath;
 		strStackFileLocalPath.Format("%s\\%s", NETWORK_AOISTACKLOCAL_PATH, pData->GetGlassData()->m_strGlassID);
-
+		
 		CFileFind File;
-
+		
 		if (File.FindFile(strStackFileLocalPath))
 		{
 			File.FindNextFile();
@@ -177,27 +204,30 @@
 	//210128
 	m_tmReviewEnd = m_tmReviewStart = CTime::GetCurrentTime();
 	pData->GetGlassData()->m_tmReviewEnd = m_tmReviewEnd;      // Insp End �떆 Raw �뾽濡쒕뱶濡�, E_TIME, LD_TIME �엯�젰 [ 21-03-18 KJG ]
+	pData->GetGlassData()->m_tmInspectionEND = m_tmReviewEnd;      // �떎�젣 Insp End �떆 Raw �뾽濡쒕뱶濡�, E_TIME, LD_TIME �엯�젰	//< KEJ 20240408 - #4808 ADD >
 	m_tmFileCreateTime = CTime::GetCurrentTime();
 
 	//�뿬湲곗뿉�꽌 �씪�씤蹂꾨줈 �뙆�씪紐�, �샊�� Path �쐞移� 寃곗젙�븯硫대맖. AOIServer �샊�� ReviewServer�뿉�꽌 �븞�빐�룄�맖 [源��깭�쁽2019/9/4]
-	pData->GetGlassData()->m_strFileName;
-	{
-		_grmGlassData* pGlassData = pData->GetGlassData();
-		CTime CurrTime = pGlassData->m_tmGlassLoading;
-		CString strTime=_T(""), strFileName=_T("");
-		strTime.Format(_T("%04d%02d%02d_%02d%02d%02d"),
-			
-			CurrTime.GetYear(), CurrTime.GetMonth(), CurrTime.GetDay(), CurrTime.GetHour(), CurrTime.GetMinute(), CurrTime.GetSecond());
-		strFileName.Format("%s_%s_%s.bin", pGlassData->m_strGlassID, pGlassData->m_strStepID, strTime.GetBuffer(0));
 
-		strcpy(pData->GetGlassData()->m_strFileName, strFileName.GetBuffer(0));
-	}
-	strcpy(pData->GetGlassData()->m_strPath, LOCAL_AOIRAWBIN_PATH);
-
+	//=> �꽌踰꾩뿉�꽌 �뜥以� 洹몃�濡� �깮�꽦�븯�룄濡� 蹂�寃�
+	/* < KJG 20240620 - #4942 DEL Start >*/
+// 	pData->GetGlassData()->m_strFileName;
+// 	{
+// 		_grmGlassData* pGlassData = pData->GetGlassData();
+// 		CTime CurrTime = pGlassData->m_tmGlassLoading;
+// 		CString strTime=_T(""), strFileName=_T("");
+// 		strTime.Format(_T("%04d%02d%02d_%02d%02d%02d"),
+// 			
+// 			CurrTime.GetYear(), CurrTime.GetMonth(), CurrTime.GetDay(), CurrTime.GetHour(), CurrTime.GetMinute(), CurrTime.GetSecond());
+// 		strFileName.Format("%s_%s_%s.bin", pGlassData->m_strGlassID, pGlassData->m_strStepID, strTime.GetBuffer(0));
+// 
+// 		strcpy(pData->GetGlassData()->m_strFileName, strFileName.GetBuffer(0));
+// 	}
+//	strcpy(pData->GetGlassData()->m_strPath, LOCAL_AOIRAWBIN_PATH);
+	/* < KJG 20240620 - #4942 DEL End >*/
 	
 	if(!WriteBinFile(pData))
 		return FALSE;
-	
 	
 	//if(1) ReadMuraFile(pData); 211012 KJG - QD Macro 媛쒕퀎 �뾽濡쒕뱶 �븯誘�濡� Macro �뜲�씠�꽣 �쟻�젣 �젣嫄� �슂泥�
 
@@ -237,8 +267,6 @@
 //	else
 		sprintf(pServerPath, "\\%s\\%s\\%s\\", strProcessID, strStepID, strGlassIDLevel6th);
 	/* <KYH 211129 : #3806 ADD End> */
-
-
 	BOOL bIsBDI = strcmp((pData->GetGlassData()->m_strLine), _T("BDI")) == 0 ? TRUE : FALSE;
 	if (bIsBDI)
 	{
@@ -281,69 +309,72 @@
 
 	//�뿬湲곗뿉�꽌 �씪�씤蹂꾨줈 �뙆�씪紐�, �샊�� Path �쐞移� 寃곗젙�븯硫대맖. AOIServer �샊�� ReviewServer�뿉�꽌 �븞�빐�룄�맖 [源��깭�쁽2019/9/4]
 	pData->GetGlassData()->m_strFileName;
-	strcpy(pData->GetGlassData()->m_strPath, NETWORK_AOIRAWBIN_PATH);
+	//strcpy(pData->GetGlassData()->m_strPath, NETWORK_AOIRAWBIN_PATH);
+	strcpy(pData->GetGlassData()->m_strPath, LOCAL_ReviewRAWBIN_PATH);		//< KEJ 20240621 - #4942 MOD >
 	
 	//strcpy(pData->GetGlassData()->m_strPath, LOCAL_AOIRAWBIN_PATH);
-
+	/* < KEJ 20240621 - #4942 MOD Start > */
+// 	CString strFindFile;
+// 	int nCloseTime = 600*100000;//sec
+// 	int nReTryTime = 30;
+// 	{
+// 		CTime tmReviewLoading = CTime::GetCurrentTime();
+// 		CString strWild;
+// 		
+// 		strWild.Format("%s\\%s_%s_*.bin", pData->GetGlassData()->m_strPath, pData->GetGlassData()->m_strGlassID,pData->GetGlassData()->m_strStepID);
+// 		CakFileUtil akFileFinder;
+// 		while(nReTryTime--)
+// 		{
+// 			akFileFinder.FindFile(strWild.GetBuffer(0), FALSE);
+// 			VECFINDDATA* pFindData = akFileFinder.getFindData();
+// 			int nFileNamePos = strlen(akFileFinder.getProcessPath());
+// 			std::map<LONGLONG, CString> mapSpanFileName;
+// 			for(int i=0; i<pFindData->size(); i++)
+// 			{
+// 				char* pFileName = &((*pFindData)[i]).name[nFileNamePos];
+// 				{
+// 					CakParser parser;
+// 					parser.process(pFileName, "_.");
+// 					if(parser.getTokNum() < 4) continue;
+// 
+// 					int nDataTime[8]={};
+// 					{
+// 						int nTokIndex=0;
+// 						const char* pGlassId = parser.getTokStr(nTokIndex++);
+// 						const char* pStepId = parser.getTokStr(nTokIndex++);
+// 						char* pDate = &pFileName[parser.getTokPos(nTokIndex++)];
+// 						char* pTime = &pFileName[parser.getTokPos(nTokIndex++)];
+// 
+// 						nDataTime[0] = (pDate[0]-'0')*1000 + (pDate[1]-'0')*100 + (pDate[2]-'0')*10+ (pDate[3]-'0')*1;
+// 						nDataTime[1] = (pDate[4]-'0')*10+ (pDate[5]-'0')*1;
+// 						nDataTime[2] = (pDate[6]-'0')*10+ (pDate[7]-'0')*1;
+// 						nDataTime[3] = (pTime[0]-'0')*10+ (pTime[1]-'0')*1;
+// 						nDataTime[4] = (pTime[2]-'0')*10+ (pTime[3]-'0')*1;
+// 						nDataTime[5] = (pTime[4]-'0')*10+ (pTime[5]-'0')*1;
+// 					}
+// 					
+// 					CTime tmTemp(nDataTime[0], nDataTime[1], nDataTime[2], nDataTime[3], nDataTime[4], nDataTime[5] );
+// 					CTimeSpan tmSpan = tmReviewLoading-tmTemp;
+// 					mapSpanFileName.insert(std::make_pair(tmSpan.GetTotalSeconds(), pFileName));
+// 				}
+// 			}
+// 
+// 			if(mapSpanFileName.empty() == FALSE)
+// 			{
+// 				if(mapSpanFileName.begin()->first < nCloseTime)
+// 				{
+// 					//媛��옣 理쒓렐 寃곌낵�뙆�씪 李얘린 �꽦怨� [源��깭�쁽 2019/7/17]
+// 					strFindFile = mapSpanFileName.begin()->second;
+// 					break;
+// 				}
+// 			}
+// 			akFileFinder.clear();
+// 			Sleep(100);
+// 		}
+// 	}
 	CString strFindFile;
-	int nCloseTime = 600*100000;//sec
-	int nReTryTime = 30;
-	{
-		CTime tmReviewLoading = CTime::GetCurrentTime();
-		CString strWild;
-		
-		strWild.Format("%s\\%s_%s_*.bin", pData->GetGlassData()->m_strPath, pData->GetGlassData()->m_strGlassID,pData->GetGlassData()->m_strStepID);
-		CakFileUtil akFileFinder;
-		while(nReTryTime--)
-		{
-			akFileFinder.FindFile(strWild.GetBuffer(0), FALSE);
-			VECFINDDATA* pFindData = akFileFinder.getFindData();
-			int nFileNamePos = strlen(akFileFinder.getProcessPath());
-			std::map<LONGLONG, CString> mapSpanFileName;
-			for(int i=0; i<pFindData->size(); i++)
-			{
-				char* pFileName = &((*pFindData)[i]).name[nFileNamePos];
-				{
-					CakParser parser;
-					parser.process(pFileName, "_.");
-					if(parser.getTokNum() < 4) continue;
-
-					int nDataTime[8]={};
-					{
-						int nTokIndex=0;
-						const char* pGlassId = parser.getTokStr(nTokIndex++);
-						const char* pStepId = parser.getTokStr(nTokIndex++);
-						char* pDate = &pFileName[parser.getTokPos(nTokIndex++)];
-						char* pTime = &pFileName[parser.getTokPos(nTokIndex++)];
-
-						nDataTime[0] = (pDate[0]-'0')*1000 + (pDate[1]-'0')*100 + (pDate[2]-'0')*10+ (pDate[3]-'0')*1;
-						nDataTime[1] = (pDate[4]-'0')*10+ (pDate[5]-'0')*1;
-						nDataTime[2] = (pDate[6]-'0')*10+ (pDate[7]-'0')*1;
-						nDataTime[3] = (pTime[0]-'0')*10+ (pTime[1]-'0')*1;
-						nDataTime[4] = (pTime[2]-'0')*10+ (pTime[3]-'0')*1;
-						nDataTime[5] = (pTime[4]-'0')*10+ (pTime[5]-'0')*1;
-					}
-					
-					CTime tmTemp(nDataTime[0], nDataTime[1], nDataTime[2], nDataTime[3], nDataTime[4], nDataTime[5] );
-					CTimeSpan tmSpan = tmReviewLoading-tmTemp;
-					mapSpanFileName.insert(std::make_pair(tmSpan.GetTotalSeconds(), pFileName));
-				}
-			}
-
-			if(mapSpanFileName.empty() == FALSE)
-			{
-				if(mapSpanFileName.begin()->first < nCloseTime)
-				{
-					//媛��옣 理쒓렐 寃곌낵�뙆�씪 李얘린 �꽦怨� [源��깭�쁽 2019/7/17]
-					strFindFile = mapSpanFileName.begin()->second;
-					break;
-				}
-			}
-			akFileFinder.clear();
-			Sleep(100);
-		}
-	}
-
+	strFindFile.Format("%s_%s.bin", pData->GetGlassData()->m_strGlassID, pData->GetGlassData()->m_strStepID);
+	/* < KEJ 20240621 - #4942 MOD End > */
 	if(strFindFile.IsEmpty()) return FALSE;
 
 	strcpy(pData->GetGlassData()->m_strFileName, strFindFile.GetBuffer(0));
@@ -483,7 +514,10 @@
 	
 	bResult &= MakeAOIFile(pData);
 	
-	m_GlassRawAna.MakeAnaFile(pData);
+	/* < KJG 20240620 - #4942 MOD Start >*/ 
+	//m_GlassRawAna.MakeAnaFile(pData);
+	m_GlassRawAna.MakeAnaFile(pData, LOCAL_AOI_DEFECT_INFO_FILE_PATH);
+	/* < KJG 20240620 - #4942 MOD End > */
 	//m_GlassRawRTMS.WriteAOIFile(pData);
 
 	AKLOG("WriteAOIFile Complete");
@@ -545,20 +579,32 @@
 	
 	//////////////////////////////////////////////////////////////////////////
 	//CELL & DEFECT DATA
+	/* < KJG 20221110 - #4373 ADD Start >*/ 
+	vector<_grmCellData*> vtSortCellData;
+	for (int iCell = 0; iCell < pData->GetGlassData()->m_nCellNum; iCell++)
+		vtSortCellData.push_back(pData->GetCellData(iCell));
+
+	sort(vtSortCellData.begin(), vtSortCellData.end(), CellIDSortFunc);
+	/* < KJG 20221110 - #4373 ADD End >*/
 	{
-		
 		//make
 		{
 			for (int iCell = 0; iCell < pData->GetGlassData()->m_nCellNum; iCell++)
 			{
-				_grmCellData* pCell = pData->GetCellData(iCell);
+				/* < KJG 20221110 - #4373 MOD Start >*/ 
+				//_grmCellData* pCell = pData->GetCellData(iCell);
+				_grmCellData* pCell = vtSortCellData[iCell];
+				/* < KJG 20221110 - #4373 MOD End >*/
 				makeDataCell(strLine, pData, pCell);
 				fprintf(pf, "%s\n", strLine);
 
 				for (int iDefect = 0; iDefect < pData->GetGlassData()->m_nDefectNum; iDefect++)
 				{
 					_grmDefectData* pDefect = pData->GetDefectData(iDefect);
-					if (pDefect->m_nCellIdx != iCell) continue;
+					/* < KJG 20221110 - #4373 MOD Start >*/ 
+					//if (pDefect->m_nCellIdx != iCell) continue;
+					if (pDefect->m_nCellIdx != pCell->m_nCellID) continue;
+					/* < KJG 20221110 - #4373 MOD End >*/
 					//210203 CJH - CutOff ���긽 寃곌낵�뙆�씪 �옉�꽦 �젣�쇅
 					if (pDefect->m_bDefectCutoff == TRUE) continue;
 
@@ -600,6 +646,7 @@
 	/* <LJC 20220425 : #3957 MOD End> */
 /* < KMH 20220613 : #3957 MOD End > */
 	/*< KJG 20211229 - #3846 MOD End >*/
+	strValue += _T(" B_DEFECT"); /* < KJG 20240620 - #4942 ADD >*/ 
 	strLine += strValue; strLine += "\n";
 /* < KMH 20220614 : #3957 MOD Start > */
 	/* <LJC 20220425 : #3957 MOD Start> */
@@ -856,13 +903,22 @@
 		, pGlassData->m_tmInspectionStart.GetSecond());
 	SPRINTRAW(14, "%s", (LPSTR)(LPCTSTR)strValue);
 	// 23   14  E_TIME		// Stage�뿉�꽌 寃��궗/怨꾩륫 醫낅즺�맂 �떆媛�
+	/* < KEJ 20240408 - #4808 MOD Start > */
+// 	strValue.Format("%04d%02d%02d%02d%02d%02d"			// E_TIME
+// 		, pGlassData->m_tmReviewEnd.GetYear()
+// 		, pGlassData->m_tmReviewEnd.GetMonth()
+// 		, pGlassData->m_tmReviewEnd.GetDay()
+// 		, pGlassData->m_tmReviewEnd.GetHour()
+// 		, pGlassData->m_tmReviewEnd.GetMinute()
+// 		, pGlassData->m_tmReviewEnd.GetSecond());
 	strValue.Format("%04d%02d%02d%02d%02d%02d"			// E_TIME
-		, pGlassData->m_tmReviewEnd.GetYear()
-		, pGlassData->m_tmReviewEnd.GetMonth()
-		, pGlassData->m_tmReviewEnd.GetDay()
-		, pGlassData->m_tmReviewEnd.GetHour()
-		, pGlassData->m_tmReviewEnd.GetMinute()
-		, pGlassData->m_tmReviewEnd.GetSecond());
+		, pGlassData->m_tmInspectionEND.GetYear()
+		, pGlassData->m_tmInspectionEND.GetMonth()
+		, pGlassData->m_tmInspectionEND.GetDay()
+		, pGlassData->m_tmInspectionEND.GetHour()
+		, pGlassData->m_tmInspectionEND.GetMinute()
+		, pGlassData->m_tmInspectionEND.GetSecond());
+	/* < KEJ 20240408 - #4808 MOD End > */
 	SPRINTRAW(14, "%s", (LPSTR)(LPCTSTR)strValue);
 	// 24   6   T_DEFECT		// Common Item 1 - Total Defect Count	
 	SPRINTRAW(12, "%d", pGlassData->m_nDefectNum - pGlassData->m_nCutOffDefectNum); //210203 CJH - Cutoff 寃고븿�� �쟾泥닿컻�닔�뿉�꽌 �젣�쇅�빐以��떎.
@@ -1014,6 +1070,10 @@
 		}
 	}
 	/* <LJC 20220425 : #3957 ADD End> */
+
+	// B_DEFECT(Before T_DEFECT) �븘�꽣留� �쟾 寃고븿 �닔�웾
+	SPRINTRAW(12, "%d", pGlassData->m_nDefectNum); /* < KJG 20240620 - #4942 ADD >*/ 
+
 	SPRINTRAWEND;   //以묒슂!!! �젮 留덉�留됱뿉 瑗� �엳�뼱�빞�븿!!!(�궘�젣湲덉�) [源��깭�쁽2020/9/23]
 }
 
@@ -2047,12 +2107,13 @@
 			sprintf(pServerFile, "%s", strFileName.GetBuffer(0)); 
 			sprintf(pLocalFile, "%s", strFileName.GetBuffer(0)); 
 
+			CString strTime;
 			CString strServerSubRawFilePath= NULL;
 			CString strServerSubRawFileName = NULL;
 			{
 				strServerSubRawFilePath += NETWORK_AOIRAWFILE_SUB_PATH;
 
-				CString strTime;
+				
 				CTime CurrTime = m_tmFileCreateTime;
 				strTime.Format(_T("%04d%02d%02d_%02d%02d%02d"),
 					CurrTime.GetYear(), CurrTime.GetMonth(), CurrTime.GetDay(), CurrTime.GetHour(), CurrTime.GetMinute(), CurrTime.GetSecond());
@@ -2088,6 +2149,16 @@
 				fprintf(pf, "SUMPATH= \n");
 				fclose(pf);
 			} 
+			/* < KJG 20230220 - #4403 ADD Start >*/
+			// BLUE �꽕鍮� D_COLL 誘몃낫怨� �썝�씤 遺꾩꽍�쓣 �쐞�븳 Result_info.ini �뙆�씪 諛깆뾽 �깮�꽦
+			CString strPathBackUpDir, strPathBackUpFile;
+			strPathBackUpDir.Format(_T("%s%s"), LOCAL_RAWPATH_INFO_INI_PATH, _T("ResultInfoBackUpFile"));
+			strPathBackUpFile.Format(_T("%s_%s.ini"), strGlassID, strTime);
+
+			CreateDirectory(strPathBackUpDir, NULL);
+			strPathBackUpFile.Format("%s\\%s", strPathBackUpDir, strPathBackUpFile);
+			CopyFile(strRawFilePathName, strPathBackUpFile, FALSE);
+			/* < KJG 20230220 - #4403 ADD End >*/
 		}
 		break;
 	case FTPCMD_STACK:
@@ -2228,6 +2299,8 @@
 		/* < KMH 20220311 : #3954 ADD End > */
 			case Judge_Unknown:		sStr.Format("OK");//sStr.Format("Unknown");	//Unknown�룄 �씪�떒 OK
 				break;
+			case Judge_SR:			sStr.Format("SR"); /* < KJG 20221107 - #4360 ADD >*/ 
+				break;
 			default:				sStr.Format("OK");//sStr.Format("Ets");		
 				break;
 			}

--
Gitblit v1.9.3